# Plano: `auth: { type: "jwt" }` no Direct mode

Decidido em 2026-09-24 (sessão de grilling). Termos em [CONTEXT.md](../CONTEXT.md).

## Contrato

```ts
type DirectModeAuth =
  | { type: "none" }
  | { type: "api_key"; key: string; transport?: "subprotocol" | "query" } // inalterado
  | { type: "jwt"; token: string };                                        // novo
```

- Só **Direct mode**. **Discovery mode** não muda.
- Uma **Auth strategy** por sessão: `api_key` e `jwt` são exclusivos.
- `protocols` e `api_key` mantêm exatamente o comportamento de hoje.

## Comportamento de `type: "jwt"`

| Onde | O que o SDK envia |
|---|---|
| WebSocket (**Stream session**) | `[token, ...protocols]`: o JWT na 1ª posição, então o servidor o ecoa |
| POST do batch, batch reprocess, audit ingestion | `Authorization: Bearer <token>` |
| **Final upload** | nada: continua sem credencial, como hoje |

- **O SDK adiciona o `Bearer ` automaticamente.** O integrador passa o JWT puro. Isso tem destaque no README (callout na seção `auth`) e na docstring do tipo.
- Um `Authorization` definido em `headers` continua vencendo, como hoje.
- Público-alvo: STT com auth desligado ou com um gateway que valida o JWT. O STT com auth ligado exige `api_key`. O audit sem `x-api-key` continua sendo pulado com o warning `missing_api_key`. Tudo isso fica documentado.
- Nada muda no backend: o STT já ecoa o primeiro subprotocol.

## Validação (antes de conectar, `SofyaAuthError`)

| Caso | Código | Mensagem |
|---|---|---|
| `token` vazio ou não-string | `INVALID_JWT` | `auth.token must be a non-empty string when auth.type is "jwt".` |
| `token` começa com `Bearer ` (sem diferenciar maiúsculas) | `INVALID_JWT` | `auth.token must be the raw JWT, without the "Bearer " prefix; the SDK adds it.` |
| **Stream session** com um caractere fora do RFC 6455 | `INVALID_JWT_CHARACTERS` | explica que o navegador recusaria o subprotocol |

- O SDK nunca altera o token (não remove o prefixo em silêncio).
- **Batch session** não valida caracteres: o JWT vai só no header.

## Compatibilidade

- `config.token` continua funcionando como hoje: vira Bearer nos POSTs com qualquer `auth`.
  - Marcado `@deprecated` no tipo, com um `console.warn` por instância quando usado: "será removido na 1.0.0".
  - Se `auth.token` e `config.token` vierem juntos, vale o `auth.token`.
- Um JWT colocado à mão em `protocols` junto com `type: "jwt"` vai duplicado. Não tem dedup. Uma nota nas seções `protocols` e `auth` do README orienta a não repetir.

## Logs

- `redactAuthConfig` mascara `auth.token` (`[redacted]`) quando `type` é `jwt`.
- O vazamento que já existe (`config.token`, JWT em `protocols`, `headers.Authorization`) fica fora deste escopo: Azure Boards **#3937**.

## Testes

Unitários (jest):
- [x] `protocols` com o JWT em 1º, com e sem `protocols` do integrador
- [x] Bearer no batch, no reprocess e no audit; final upload sem credencial
- [x] `INVALID_JWT` (vazio, prefixo `Bearer `) e `INVALID_JWT_CHARACTERS` (só stream)
- [x] batch aceita JWT com caracteres fora do RFC 6455
- [x] precedência `auth.token` > `config.token`; `headers.Authorization` vence os dois
- [x] `auth.token` sai `[redacted]` no log
- [x] `console.warn` de descontinuação do `config.token`, uma vez por instância

Regressão:
- [x] Suíte existente do SDK inteira (jest + e2e Playwright) verde, sem alterar nenhum teste antigo.
- [x] Caderno E2E do STT (`/caderno`, foco SDK) contra `scribe.sofya.health` (PRD LTS) **antes** da implementação, com o SDK atual (0.3.1): relatório de referência.
- [x] A mesma rodada **depois**, com o SDK novo, contra o mesmo alvo. O backend não muda entre as duas, então qualquer diferença é regressão do SDK. O critério é nenhuma diferença.

## Documentação

- [x] README: seção `auth` com `jwt`, callout do `Bearer ` automático, nota de duplicação em `protocols` e `auth`, público-alvo (auth do STT desligado ou com gateway)
- [x] Docstrings de `DirectModeAuth` e `config.token` (`@deprecated`)
- [x] `docs/CONNECTION_MODE.md`: tabela da estratégia por canal
- [x] CHANGELOG
