# ADR 0002: One Auth Strategy per Direct-Mode Session

## Status

Accepted (2026-09-24)

## Context

Direct mode already had `auth: { type: "api_key" }`. Integrators who
authenticate with a user JWT had no `auth` option: they placed the JWT in
`protocols` by hand for the WebSocket and in `config.token` for the SDK's HTTP
requests. We wanted a single `auth` entry for the JWT.

The alternatives were:

- a `token` field on `auth`, combinable with `api_key`;
- a list of strategies (`auth: [{ type: "api_key" }, { type: "jwt" }]`);
- a new exclusive variant, `{ type: "jwt", token }`.

## Decision

`auth` stays a discriminated union and gains `{ type: "jwt"; token: string }`.
A session has exactly one **Auth strategy** (see `CONTEXT.md`), and every
request the SDK makes to Sofya follows it:

| strategy | WebSocket | batch, batch reprocess, audit ingestion |
|---|---|---|
| `api_key` | unchanged (`x-api-key.<key>` subprotocol, or query) | `x-api-key` header |
| `jwt` | JWT first in the subprotocol list | `Authorization: Bearer <token>`, prefix added by the SDK |

Final upload targets an integrator-owned endpoint and carries no credential.

The combinable field was rejected because it makes the same `auth` object mean
two credentials at once. The list was rejected because it would need rules for
duplicates, `none` and ordering, all to cover a single case. STT authentication
is optional per client, so a JWT-only session is a normal case, not a gap.

## Consequences

- `api_key` and `jwt` cannot be combined through `auth`. Do not "fix" this by
  adding a field without revisiting this ADR.
- With `jwt`, an STT that requires an api-key rejects the connection, and audit
  ingestion is skipped with `missing_api_key`. The `jwt` strategy is for STTs
  with authentication off or behind a gateway that validates the JWT.
- `config.token` stays as a deprecated alias (removal in 1.0.0). While it
  exists, it still produces a Bearer header alongside any `auth`.
