# Debugging

Tracing HTTP traffic through the SDK is opt-in via the
`ApiDebugConfig` callback interface. Wire it at client
construction time and the SDK's HTTP interceptor will log
every request + response through your callback.

**Safety first**: enabling debug logging without redaction
will leak credentials in the request bodies the SDK sends to
the platform. The `ApiDebugConfig.redactStrings` array is the
load-bearing safety feature — pass every active secret literal
you've installed BEFORE the SDK fires its first request.

## Wiring the debug interceptor

```typescript
import {
  createIsolatedPlatformApi,
  type ApiDebugConfig,
} from '@alvera-ai/platform-sdk'

const debugConfig: ApiDebugConfig = {
  log: (msg: string) => process.stderr.write(msg + '\n'),
  redactStrings: getActiveSecretLiterals(),
  // e.g. ['AKIA...real-key', 'twilio-real-token', ...]
}

const api = createIsolatedPlatformApi({
  baseUrl,
  sessionToken,
  apiKey,          // the tenant's publishable key (X-API-Key)
  debug: debugConfig,
})
```

When `debug` is wired:
- The SDK's HTTP interceptor logs each request + response
  through the `log` callback. Consumer chooses the sink:
  stderr, Pino, OpenTelemetry, etc.
- BEFORE logging, the SDK substitutes every string in
  `redactStrings` with a fixed-length placeholder
  (`********`).

When `debug` is omitted (the default), the SDK doesn't log
HTTP traffic at all — nothing to redact.

## Safety: the load-bearing piece

The SDK never sees secret REFERENCES (`<%= name %>` Eta
syntax is a consumer-side convention used by some — CLI
manifests, for instance). By the time a request body reaches the SDK,
credentials are already substituted to their literal values.
If you enable debug logging without populating
`redactStrings`, those literals appear in plaintext in your
log output — including production logs if `debug` is wired
in non-dev environments.

The redaction is fixed-length (`********`), NOT length-
preserving. A length-preserving mask would still leak
length-as-signal (an AWS access key is 20 characters;
a 20-char placeholder says "this WAS a 20-char string").

## The ApiDebugConfig contract

```typescript
type ApiDebugConfig = {
  log: (message: string) => void
  redactStrings?: readonly string[]
}
```

- `log` is the sink. The SDK emits formatted log messages
  (request method + URL + body, response status + URL + body)
  — one call per request and one per response, so TWO calls
  per HTTP exchange, each a separate string.
- `redactStrings` is an array of LITERAL values the SDK
  should scrub from log output. Empty / absent = no
  redaction (use only when no secrets are in the request
  bodies — rare).

The SDK's log output is plain strings, not structured. For
structured logging (per-field JSON, OTel spans, etc.),
consumers wrap the `log` callback with their own emitter.

## What the SDK does NOT redact

- **Exception bodies thrown by the SDK** (the 422 envelope,
  etc.) are NOT filtered through `redactStrings`. If your
  error-rendering path includes the offending request body,
  redact it yourself before rendering. See `errors.md` for
  the throw mechanism.
- **Secrets the server MINTS that you never installed**.
  Response bodies ARE filtered through `redactStrings` — the
  same scrub the request gets — so any literal in your redact
  list is masked whichever direction it travels. But a
  credential the server generates and returns (rare but
  possible) is by definition not in `redactStrings`, so it
  passes through un-masked; the consumer is responsible for
  not logging it.
- **Anything OUTSIDE the SDK's HTTP path**. `redactStrings`
  only filters what the SDK itself writes to `log`. Your own
  log statements that touch request/response data must
  redact themselves.

## Defense in depth

`redactStrings` is one layer. Belt-and-suspenders practice:
- Avoid log statements that interpolate request bodies
  OUTSIDE the SDK's debug pipeline.
- Use fixed-length placeholders in your own surfaces (CLI
  tables, browser consoles, error pages, exception traces).
- Treat exception bodies as untrusted for log purposes —
  parse what you need, don't dump the whole thing.
- Don't ship `debug` enabled in production unless the
  redact list is provably complete.

## Pattern: snapshotting the redact list

`redactStrings` is read at client construction time
(`createIsolatedPlatformApi(...)`). If your consumer's
secret store mutates during the process's lifetime (e.g. a
new credential gets installed mid-run), you need to
construct a NEW client to pick up the new redaction string —
the SDK doesn't observe the source array after construction.

Consumers that hold secrets in a mutable store typically
build the client lazily (per-request or per-handler) to
guarantee a fresh snapshot.

## What's in the log line

The SDK fires `log` TWICE per exchange — once for the request
(prefixed `→`), once for the response (prefixed `←`) — not once
per round-trip. Each is a separate string. The request line
carries method + URL; a body, if present, follows on the next
lines indented four spaces. The response line carries status +
URL, then its body the same way. A pair looks roughly like:

```
→ POST https://demo.example.com/api/v1/tenants/acme/datalakes/main/tools
    {
      "name": "...",
      "intent": "data_exchange",
      "body": {
        "tool_body_type": "s3",
        "access_key_id": "********",
        "secret_access_key": "********"
      }
    }
← 201 https://demo.example.com/api/v1/tenants/acme/datalakes/main/tools
    {
      "id": "...",
      "slug": "...",
      "name": "..."
    }
```

The URL is **absolute** — origin included — by design, not
path-only. The origin is the only thing distinguishing one
environment from another: a path-only line reads identically
whether the call hit localhost or a demo server, which defeats
the question the log is most often asked, *where did this
actually go?*. The query string is deliberately omitted (it
carries filter values, not routing; the body is where those
are read). Only the request/response BODY is logged — never
headers, so the `Authorization: Bearer …` and `X-API-Key`
credentials (see `AGENTS.md` for the two-credentials model)
never reach the log at all.

(Format is plain-string; the `→`/`←` prefixes and field
ordering are SDK-internal and may evolve.)
