Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
173 commits
Select commit Hold shift + click to select a range
013cbb9
docs(devlog): open the protocol-first-class plan unit
lidge-jun Sep 24, 2026
c217422
feat(protocols): add the shared protocol vocabulary
lidge-jun Sep 24, 2026
361430b
feat(protocols): declare per-hop feature dispositions
lidge-jun Sep 24, 2026
43bb741
feat(protocols): pin the 18-cell ingress-by-upstream baseline
lidge-jun Sep 24, 2026
9d884f0
feat(protocols): fix the plan and trace wire shapes
lidge-jun Sep 24, 2026
258648e
test(protocols): pin vocabulary mappings and the leaf-module boundary
lidge-jun Sep 24, 2026
abc03a3
feat(config): declare apiSurfaces and protocols keys
lidge-jun Sep 24, 2026
575b73f
feat(protocols): resolve surface and protocol settings fail-closed
lidge-jun Sep 24, 2026
47f79bd
test: register the protocol contract tests in the layout
lidge-jun Sep 24, 2026
31a2af1
docs(structure): add Protocol Paths and claim src/protocols
lidge-jun Sep 24, 2026
caef012
refactor(chat): name the reason native Chat declines a route
lidge-jun Sep 24, 2026
7cee784
docs(structure): note the native Chat decline reasons
lidge-jun Sep 24, 2026
d64b8b9
feat(protocols): derive request paths from the ingress lane
lidge-jun Sep 25, 2026
0de990c
refactor(request-log): move the /api/logs filters into request-log-fi…
lidge-jun Sep 25, 2026
b140d55
feat(protocols): derive the observed protocol trace from entry and at…
lidge-jun Sep 25, 2026
a8a0bae
test(protocols): cover trace derivation and register it in the layout
lidge-jun Sep 25, 2026
a28afbf
feat(request-log): record and persist the observed protocol trace
lidge-jun Sep 25, 2026
b8101ce
feat(request-log): filter /api/logs by protocolMode
lidge-jun Sep 25, 2026
359de56
test(request-log): cover protocol trace persistence and the protocolM…
lidge-jun Sep 25, 2026
070e16c
feat(chat): mark the protocol lane a Chat Completions request takes
lidge-jun Sep 25, 2026
688f38b
feat(claude): mark the protocol lane a Messages request takes
lidge-jun Sep 25, 2026
db7ed13
feat(gui): add protocol path copy to every locale
lidge-jun Sep 25, 2026
836fd83
feat(gui): add the protocol path badge and trace panel
lidge-jun Sep 25, 2026
c874390
feat(gui): filter Logs by protocol delivery mode
lidge-jun Sep 25, 2026
97b1015
feat(gui): show the protocol path on Logs rows and in the detail dialog
lidge-jun Sep 25, 2026
58d5f5c
test(gui): cover the protocol badge, trace panel and mode filter
lidge-jun Sep 25, 2026
0fb43a2
docs(structure): document the observed protocol trace
lidge-jun Sep 25, 2026
c4677d2
feat(protocols): add the pure protocol planner
lidge-jun Sep 25, 2026
23d9bef
feat(protocols): build plan snapshots from config without side effects
lidge-jun Sep 25, 2026
7ca15b8
feat(management): serve protocol vocabulary and request-path previews
lidge-jun Sep 25, 2026
e92cc4f
feat(gui): add a validated client for protocol path previews
lidge-jun Sep 25, 2026
542780d
feat(gui): preview the request path on the API page
lidge-jun Sep 25, 2026
4f07bcb
docs: describe the protocol planner, preview routes and API page panel
lidge-jun Sep 25, 2026
68c62c4
fix(protocols): report native-lane declines only where a native lane …
lidge-jun Sep 25, 2026
6828b9a
fix(gui): translate the French route-kind labels
lidge-jun Sep 25, 2026
6bbf9da
fix(gui): key plan candidates by provider and model
lidge-jun Sep 25, 2026
1279960
refactor(gui): name the preview model list planModels
lidge-jun Sep 25, 2026
21cc171
test(cli): account for /api/protocols in the headless parity map
lidge-jun Sep 25, 2026
64b4de2
refactor(inference): add createInferenceSendBudget
lidge-jun Sep 25, 2026
1e98719
refactor(inference): add a finish-once final request log
lidge-jun Sep 25, 2026
175edca
refactor(inference): add beginInferenceAttempt
lidge-jun Sep 25, 2026
7ae30d7
refactor(inference): add the client-wire response marker
lidge-jun Sep 25, 2026
6b94522
refactor(responses): build the ingress send budget through inference/…
lidge-jun Sep 25, 2026
dbdaec1
refactor(chat): finish the bridged Chat log through createFinalReques…
lidge-jun Sep 25, 2026
c381d70
refactor(claude): finish Messages logs through createFinalRequestLog
lidge-jun Sep 25, 2026
82cdb60
refactor(chat-native): open the attempt through beginInferenceAttempt
lidge-jun Sep 25, 2026
a8811a7
refactor(chat-native): claim the final log through createFinalRequestLog
lidge-jun Sep 25, 2026
75e237b
refactor(chat-native): split runNativeChatAttempt from the ingress wr…
lidge-jun Sep 25, 2026
efd1e5a
refactor(responses): group core.ts compatibility re-exports by owner
lidge-jun Sep 25, 2026
f662151
docs(structure): describe the shared inference primitives and native …
lidge-jun Sep 25, 2026
4d47158
feat(claude): gate Messages exposure through resolveApiSurfaceSettings
lidge-jun Sep 25, 2026
b3e1c9e
feat(management): report API surfaces with their source in API access…
lidge-jun Sep 25, 2026
7142caf
refactor(claude): install claudeCode blocks through one sentinel-stam…
lidge-jun Sep 25, 2026
d2ef09b
feat(protocols): validate and apply protocol settings patches
lidge-jun Sep 25, 2026
e927c8f
feat(management): add PATCH /api/protocols/settings
lidge-jun Sep 25, 2026
ac2211a
test(management): cover PATCH /api/protocols/settings and surface met…
lidge-jun Sep 25, 2026
f0885c7
test(claude): record the Messages surface upgrade/rollback matrix
lidge-jun Sep 25, 2026
ce7eca3
feat(gui): parse API surfaces and add the protocol settings client
lidge-jun Sep 25, 2026
9de268c
feat(gui): add API surface card copy to every locale
lidge-jun Sep 25, 2026
0746c1a
feat(gui): show Responses, Chat Completions and Messages as API cards
lidge-jun Sep 25, 2026
8efa12d
feat(gui): feed API surfaces and the Messages toggle into the API page
lidge-jun Sep 25, 2026
30ab0e9
test(gui): cover API surface cards, parsing and the settings client
lidge-jun Sep 25, 2026
9e30c81
docs(structure): describe the Messages surface reader, settings write…
lidge-jun Sep 25, 2026
ab82e7f
docs: document apiSurfaces and PATCH /api/protocols/settings
lidge-jun Sep 25, 2026
24c37d6
feat(protocols): add the request source envelope
lidge-jun Sep 25, 2026
2a9a8fd
feat(protocols): add the unrepresentable-feature guard
lidge-jun Sep 25, 2026
9ffd5c0
feat(protocols): name codec entry points over the existing translators
lidge-jun Sep 25, 2026
0b20754
refactor(ingress): call the Chat and Messages bridge through the code…
lidge-jun Sep 25, 2026
115207a
feat(protocols): phrase unrepresentable refusals from feature keys only
lidge-jun Sep 25, 2026
8f672a4
feat(chat): refuse unrepresentable features at ingress under the reje…
lidge-jun Sep 25, 2026
52ad310
feat(claude): refuse unrepresentable Messages features under the reje…
lidge-jun Sep 25, 2026
a7eb15c
test(protocols): pin the source envelope and the unrepresentable guard
lidge-jun Sep 25, 2026
b17152f
test(ingress): pin reject-policy refusals on Chat and Messages
lidge-jun Sep 25, 2026
1801162
docs(structure): describe the source envelope, codecs and ingress guard
lidge-jun Sep 25, 2026
00feb2e
refactor(outbound): export the Chat and Messages terminal mapping hel…
lidge-jun Sep 25, 2026
517a06d
feat(protocols): encode AdapterEvent streams directly as Chat Complet…
lidge-jun Sep 25, 2026
639cd9f
feat(protocols): encode AdapterEvent streams directly as Anthropic Me…
lidge-jun Sep 25, 2026
f2c2d6f
feat(responses): add the clientEncoder option for direct Chat and Mes…
lidge-jun Sep 25, 2026
99e0fa6
feat(inference): log client-wire responses from reported facts
lidge-jun Sep 25, 2026
cda345e
refactor(bridge): let a caller fold events without recording buffered…
lidge-jun Sep 25, 2026
ff3e3ae
feat(responses): deliver adapter events in the client's wire when asked
lidge-jun Sep 25, 2026
7013d8c
feat(ingress): request direct encoding and pass client-wire responses…
lidge-jun Sep 25, 2026
3df5584
feat(protocols): trace directly encoded attempts as upstream-ir-clien…
lidge-jun Sep 25, 2026
8183a2b
test(protocols): pin direct encoder parity with the bridge and conver…
lidge-jun Sep 25, 2026
c804622
fix(protocols): key the argument repair on any mapped namespace, as t…
lidge-jun Sep 25, 2026
1b9b390
docs(structure): describe the direct Chat and Messages encoders
lidge-jun Sep 25, 2026
8b590f7
test(protocols): count only tool-call delta frames in the Chat frame-…
lidge-jun Sep 25, 2026
2153fe1
feat(chat-native): let a caller hand the attempt a send budget and hooks
lidge-jun Sep 25, 2026
10214e6
feat(protocols): append a reason to a request's entry trace mark
lidge-jun Sep 25, 2026
3cf911c
feat(responses): send eligible Chat combo candidates on the native lane
lidge-jun Sep 25, 2026
2217b9d
feat(chat): hand Chat combos a native source behind nativeChatCombos
lidge-jun Sep 25, 2026
3fb6a1b
test(chat): pin native Chat candidates, failover and skips in combos
lidge-jun Sep 25, 2026
b252c01
docs(structure): describe native Chat candidates in combos
lidge-jun Sep 25, 2026
4cb30da
docs(devlog): record what PF-07 leaves on the bridge
lidge-jun Sep 25, 2026
8f359ef
feat(protocols): preview a Chat combo's candidates as the combo now s…
lidge-jun Sep 25, 2026
0ccac36
refactor(responses): keep the combo send-scope comment beside its code
lidge-jun Sep 25, 2026
685385f
refactor(anthropic): share the Messages URL, version and key-auth hea…
lidge-jun Sep 25, 2026
f8ba968
feat(anthropic): build a managed native Messages request from the sou…
lidge-jun Sep 25, 2026
23fd503
feat(claude): name why a Messages route declines the managed native lane
lidge-jun Sep 25, 2026
7024b5b
feat(claude): send eligible managed-key Messages natively
lidge-jun Sep 25, 2026
8b6f97d
feat(claude): route eligible managed-key Messages to the native lane
lidge-jun Sep 25, 2026
716c117
feat(claude): count the native Messages body in count_tokens
lidge-jun Sep 25, 2026
f0ec1db
feat(protocols): preview managed native Messages candidates
lidge-jun Sep 25, 2026
ed858b5
test(claude): pin the native Messages builder, decline rule and preview
lidge-jun Sep 25, 2026
5218c52
test(claude): exercise managed native Messages against a fake upstream
lidge-jun Sep 25, 2026
97cfd0c
docs(structure): describe the managed native Messages lane
lidge-jun Sep 25, 2026
d328dc7
feat(protocols): name operator policy that only the bridge applies
lidge-jun Sep 25, 2026
6ece218
feat(claude): tell whether translation would elide a blocked skill
lidge-jun Sep 25, 2026
a2a827d
feat(claude): keep Messages on the bridge when bridge-only policy app…
lidge-jun Sep 25, 2026
109db7e
feat(claude): judge native Messages with the bridge's selector and Cl…
lidge-jun Sep 25, 2026
629be51
feat(claude): record why a Messages route declined the native lane
lidge-jun Sep 25, 2026
384d785
feat(protocols): preview the bridge-only decline for Messages candidates
lidge-jun Sep 25, 2026
eb8a625
feat(claude): echo the requested model on native Messages responses
lidge-jun Sep 25, 2026
a11c3d4
test(claude): expect the requested model on native Messages responses
lidge-jun Sep 25, 2026
fa673ec
test(claude): pin bridge-only-policy declines and the decline trace
lidge-jun Sep 25, 2026
e182700
docs(structure): record bridge-only declines, the decline trace and m…
lidge-jun Sep 25, 2026
eff984a
feat(protocols): add the provider wire summary DTO and validator
lidge-jun Sep 25, 2026
cc28b2d
feat(protocols): answer GET /api/protocols?provider with the resolved…
lidge-jun Sep 25, 2026
336623b
feat(protocols): list exact-id hard pins among provider model overrides
lidge-jun Sep 25, 2026
8bbc935
test(protocols): pin the ?provider summary bounds, 404 and source map…
lidge-jun Sep 25, 2026
1ba483b
feat(gui): fetch the provider wire summary from GET /api/protocols?pr…
lidge-jun Sep 25, 2026
eef484a
feat(gui): let providers and compatibility hashes carry a query
lidge-jun Sep 25, 2026
86ad004
feat(gui): add protocol deep-link hash helpers
lidge-jun Sep 25, 2026
87658b3
feat(gui): add the provider upstream wire panel
lidge-jun Sep 25, 2026
96c924e
feat(gui): show the upstream wire panel under the provider adapter field
lidge-jun Sep 25, 2026
7a020bb
feat(gui): map Lab subject identities onto protocol pair filters
lidge-jun Sep 25, 2026
fc7bd0f
feat(gui): add compatibility protocol pair filters and status
lidge-jun Sep 25, 2026
3e32ab5
feat(gui): filter the compatibility matrix by inbound and upstream pr…
lidge-jun Sep 25, 2026
58778a0
feat(gui): link a traced log row to the compatibility matrix for its …
lidge-jun Sep 25, 2026
a11c831
feat(gui): link plan candidates to provider settings and Lab evidence
lidge-jun Sep 25, 2026
ab32557
feat(gui): open a provider's settings from #providers?provider=<name>
lidge-jun Sep 25, 2026
5af82a2
feat(gui): add the combo candidate path preview
lidge-jun Sep 25, 2026
be126f3
feat(gui): show candidate paths in the saved combo detail
lidge-jun Sep 25, 2026
9ba61db
refactor(gui): move subject pair resolution out of the filter components
lidge-jun Sep 25, 2026
aac16ff
refactor(gui): apply a provider deep link while rendering, not in an …
lidge-jun Sep 25, 2026
64d822f
test(gui): pin the provider wire panel labels, no-control rule and ol…
lidge-jun Sep 25, 2026
a85a253
test(gui): pin protocol pair filters and unverified absent evidence
lidge-jun Sep 25, 2026
5de4101
test(gui): pin protocol deep-link hashes and their Back/Forward behavior
lidge-jun Sep 25, 2026
eb1dffa
test(gui): pin the on-demand combo candidate path preview
lidge-jun Sep 25, 2026
062dd50
docs(structure): describe the provider wire summary and protocol evid…
lidge-jun Sep 25, 2026
1d0a1ec
docs(api): document GET /api/protocols?provider in the management ref…
lidge-jun Sep 25, 2026
9062f69
fix(gui): load the provider wire panel and combo paths on the same-or…
lidge-jun Sep 25, 2026
5ad42bf
feat(protocols): name OAuth pools, dropped betas and stripped opaque …
lidge-jun Sep 25, 2026
04321a6
feat(anthropic): allowlist caller betas for the native Messages lane
lidge-jun Sep 25, 2026
bc84839
feat(protocols): keep opaque thinking state inside Anthropic's own do…
lidge-jun Sep 25, 2026
17a7fbf
refactor(anthropic): export the OAuth header placement the adapter ap…
lidge-jun Sep 25, 2026
a3cb8b8
feat(anthropic): build native Messages for OAuth, allowlisted betas a…
lidge-jun Sep 25, 2026
e3bda7a
feat(messages): admit unpooled Anthropic OAuth routes to the native lane
lidge-jun Sep 25, 2026
49fefb6
feat(messages): resolve the Anthropic OAuth account for a native send
lidge-jun Sep 25, 2026
2457ee9
feat(messages): send native Messages with OAuth, allowlisted betas an…
lidge-jun Sep 25, 2026
78361a1
feat(messages): hand the native lane its beta header and guard opaque…
lidge-jun Sep 25, 2026
f6a2c75
docs(protocols): say how the plan preview judges Anthropic OAuth
lidge-jun Sep 25, 2026
9013fdb
test(anthropic): pin the native beta allowlist and the OAuth request …
lidge-jun Sep 25, 2026
7846532
test(protocols): pin opaque-state domains and OAuth native eligibility
lidge-jun Sep 25, 2026
9114758
test(messages): drive the native lane with OAuth, stripped opaque sta…
lidge-jun Sep 25, 2026
383a18e
docs(structure): describe the native Messages beta allowlist, opaque …
lidge-jun Sep 25, 2026
0e57cc4
test(messages): build credential-shaped fixtures at runtime for the p…
lidge-jun Sep 25, 2026
f9e721e
feat(protocols): compare a recorded dispatch plan with the observed t…
lidge-jun Sep 25, 2026
02f4db3
feat(protocols): record the shadow plan input at the Chat and Message…
lidge-jun Sep 25, 2026
bf33f68
test(protocols): pin shadow plan match, mismatch, switch-off and thro…
lidge-jun Sep 25, 2026
97cbbd6
feat(cli): add ocx api protocols, explain and policy
lidge-jun Sep 25, 2026
34ddd3b
feat(cli): declare the api capabilities and retire the PF-12 deferred…
lidge-jun Sep 25, 2026
316b545
test(cli): pin the ocx api verbs' requests, usage errors and route pa…
lidge-jun Sep 25, 2026
62f02a4
docs(guides): add a Protocol paths guide and register it in the sidebar
lidge-jun Sep 25, 2026
4334ece
docs(config): document the protocols keys and their conservative defa…
lidge-jun Sep 25, 2026
f07f31a
docs(cli): document ocx api protocols, explain and policy
lidge-jun Sep 25, 2026
f5e2738
docs(devlog): record PF-12 acceptance status and refresh the not-migr…
lidge-jun Sep 25, 2026
cbfa812
docs(structure): describe the shadow plan comparison and the ocx api …
lidge-jun Sep 25, 2026
617c996
docs(protocols): describe the OAuth switch and beta allowlist as PF-1…
lidge-jun Sep 25, 2026
24ad5aa
test(cli): map /api/protocols to the ocx api verbs in the parity map
lidge-jun Sep 25, 2026
f35739e
test(cli): compare the ocx api protocols GET by method, URL and body
lidge-jun Sep 25, 2026
7322804
fix(gui): wrap the Logs protocol badge instead of clipping it
lidge-jun Sep 25, 2026
3df0d8f
fix(anthropic): honour the opaque-state strip flag when replaying thi…
lidge-jun Sep 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions devlog/_plan/260924_protocol_first_class/000_plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# 260924 Protocol first class — plan

Responses stays the first-class feature surface. What changes is that Chat Completions and
Anthropic Messages stop needing the public Responses JSON/SSE as a mandatory intermediate to
reach the shared execution policy. Same-wire requests keep their source representation;
cross-wire requests convert through the adapter-neutral IR; one execution owner keeps account
selection, affinity, send budget, retry, cancellation and logging.

## Outcome

```text
Responses / Chat / Messages request
-> source body kept + lazily parsed intent
-> shared admission, routing, execution policy
-> final provider / model / credential settled
-> per-attempt protocol plan
same wire : native builder from the source body
different wire : codec -> IR -> target builder
not migrated yet : legacy bridge (internal Responses), labelled as such
not expressible : refused before any send (when policy = reject)
-> upstream

same wire : upstream -> safe relay + observation -> client wire
different wire: upstream -> AdapterEvent -> client encoder
```

## Non-goals

- Files, Batches and Responses CRUD APIs; every beta feature; lossless behavior for arbitrary
custom providers; emulating every Responses-only feature on Chat or Messages.
- A second or third execution engine. Native lanes reuse the shared attempt, budget, cancel
and log owners; they do not copy them.
- Renaming the Responses schema and calling it a neutral IR.
- Forwarding arbitrary headers or body fields to every provider unchecked.
- Guessing native support from a provider name in the GUI.
- A shadow mode that sends two inferences. Shadow compares plans only.
- Presenting one successful connection as protocol verification.

## Invariants every work packet keeps

1. The planner never selects a provider. It consumes the route the router settled
(`ResolvedModelPolicy` precedence: hard-pin, explicit override, ingress-scoped registry
default, provider default) and decides only the wire within that route.
2. No native lane may bypass admission scope, send budget, affinity, key failover, cancellation,
request logging or spend accounting. A native lane that lands before its safety wiring is
not acceptable in any order.
3. Every fallback candidate builds its request from the source envelope. An earlier candidate's
deleted fields or injected headers are never the next candidate's input.
4. Plan and trace records carry only fixed vocabulary (`src/protocols/contract.ts`) and
identifiers the server already exposes. No prompt, tool argument, token, signature or key.
5. `native` (a delivery mode) and `VERIFIED` (a Lab evidence verdict) are different axes and
are never merged into one badge.
6. Every rollout switch defaults off and changes nothing while off
(`resolveProtocolSettings`, `src/protocols/settings.ts`).
7. The core request path stays free of Lab imports
(`tests/lab/core-lab-boundary.test.ts`).
8. Files at their file-size cap (`tests/fixtures/file-size-baseline.json`) do not grow; code
moves out first. `src/server/request-log.ts` sits at 1996 of a 2000-line threshold.

## Work packets and stack order

Each packet is one pull request, stacked on the previous one. PF numbers are work ids, not
GitHub numbers.

| Packet | Branch | Scope | Doc |
|---|---|---|---|
| PF-01 | `feat/pf01-protocol-contract` | vocabulary, feature dispositions, 18-cell baseline, plan/trace DTOs, settings keys | [010](010_contract_and_baseline.md) |
| PF-02 | `feat/pf02-protocol-trace` | observed path trace on request/attempt rows, persisted; Logs badge, detail, filter | [030](030_gui_and_management_api.md#pf-02-observed-path-trace) |
| PF-03 | `feat/pf03-protocol-plan` | pure planner, `GET /api/protocols`, `POST /api/protocols/plan`, API page preview | [030](030_gui_and_management_api.md#pf-03-planner-and-preview) |
| PF-05 | `feat/pf05-inference-primitives` | shared execution context, attempt and delivery primitives, client-wire marker | [020](020_engine_and_codecs.md#pf-05-shared-inference-primitives) |
| PF-04 | `feat/pf04-api-surfaces` | Messages exposure split from Claude integration, settings PATCH, API cards | [030](030_gui_and_management_api.md#pf-04-api-surface-settings) |
| PF-06 | `feat/pf06-source-envelope` | source envelope, codecs, unrepresentable guard | [020](020_engine_and_codecs.md#pf-06-source-envelope-and-guard) |
| PF-09 | `feat/pf09-direct-encoders` | AdapterEvent to Chat/Messages encoders behind `directEncoders` | [020](020_engine_and_codecs.md#pf-09-direct-client-encoders) |
| PF-07 | `feat/pf07-native-chat-combos` | eligible Chat candidates in combos/policy send natively | [020](020_engine_and_codecs.md#pf-07-native-chat-candidates-in-combos) |
| PF-08 | `feat/pf08-managed-messages-native` | key-auth Anthropic targets receive `/v1/messages` natively | [020](020_engine_and_codecs.md#pf-08-managed-native-messages) |
| PF-11 | `feat/pf11-protocol-evidence-gui` | provider protocol panel, compatibility pair filters, combo guarantees, deep links | [030](030_gui_and_management_api.md#pf-11-evidence-combo-and-provider-views) |
| PF-10 | `feat/pf10-auth-opaque-state` | beta allowlist, OAuth native Messages, cross-domain opaque state guard | [020](020_engine_and_codecs.md#pf-10-auth-and-opaque-state) |
| PF-12 | `feat/pf12-protocol-rollout` | shadow plan comparison, docs, not-migrated inventory | [040](040_acceptance_and_rollout.md) |

PF-05 lands before PF-04 because PF-06 through PF-09 build on its primitives and PF-04 does
not; the dependency order, not the id order, decides the stack.

## Verification policy for this unit

Each pull request records exactly what ran. Unit tests are written beside each change and
registered in the test layout; whether they were executed is stated per PR, never implied.
Default flips for rollout switches are out of scope until the acceptance scenarios in
[040](040_acceptance_and_rollout.md) have recorded evidence.
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# 010 — PF-01 contract and baseline

## Modules

| File | Role | Import rule |
|---|---|---|
| `src/protocols/contract.ts` | protocols, upstream wires, hops, delivery modes, reason codes, name mappings | leaf |
| `src/protocols/features.ts` | feature keys, sources, cross-wire hop dispositions, body extraction, path effects | leaf (+ type from `src/compatibility/manifest.ts`) |
| `src/protocols/baseline.ts` | 18-cell current/target matrix | leaf |
| `src/protocols/dto.ts` | `ProtocolPlanV1`, `ProtocolTraceV1`, validators, limits | leaf |
| `src/protocols/settings.ts` | only reader of `apiSurfaces` and `protocols` config | type-only config import |

"Leaf" means importable from `gui/src/*`: no server, router, provider or Lab imports, not even
as types. `tests/responses/protocol-contract.test.ts` enforces it by reading import specifiers.

## Vocabulary

- **Protocol**: `responses | chat | messages` — the public API a client speaks.
- **Upstream wire**: a protocol or `other` (Gemini, Kiro, Cursor, ...). Nothing is claimed about
`other`; absent feature dispositions there mean unknown.
- **Hop**: a wire name, `ir` (`OcxParsedRequest` / `AdapterEvent`), or `responses-internal`
(Responses JSON/SSE produced only as an internal bridge).
- **Delivery mode**: `native` (same wire, source body is the wire source), `translated`
(cross-wire through the IR or the target wire only), `legacy-bridge` (path contains
`responses-internal`), `blocked` (refused before any send).
- **Fidelity**: `preserved`, `degraded`, `unknown`.

Internal spellings keep their names and map explicitly: routing `InboundWire` "anthropic" is
`messages`; adapters `openai-responses`/`openai-chat`/`anthropic` are the three protocol wires;
Lab identities `openai-responses`/`openai-chat`/`anthropic-messages` map one to one. Persisted
rows are not rewritten.

## Feature dispositions and their evidence

Same-wire hops are passthrough by definition. Cross-wire claims about current code:

| Claim | Evidence |
|---|---|
| Chat `n`, `logprobs`/`top_logprobs`, `logit_bias`, `seed`, `audio`/`modalities`, `prediction` are unsupported on `chat>responses` | `chatCompletionsToResponsesBody` in `src/chat/inbound.ts` builds the body from an explicit field list without them |
| Chat tools, sampling, stop, user, parallel tool calls, service tier, prompt cache key, metadata, reasoning effort, response format are translated on `chat>responses` | same function, explicit field copies |
| Messages `top_k` is unsupported on `messages>responses` | `src/claude/inbound.ts` header: accepted and dropped |
| Messages `thinking.budget_tokens` is degraded on `messages>responses` | maps to an effort tier, never forwarded raw |
| Messages `cache_control` is degraded on `messages>responses` | block-level cache hints are not carried into the Responses body; caching is re-derived downstream |
| Responses hosted tools are degraded on `responses>chat` and `responses>messages` | only web search and image generation have sidecar bridges |
| Responses `previous_response_id` and compaction are translated off-wire | expanded from proxy-side state before the adapter runs |
| Responses `store` is degraded and `background` unsupported off-wire | only proxy-side state exists for non-Responses upstreams |

The `chat>messages` and `messages>chat` rows describe the target direct codecs (PF-06). Today
those pairs travel through `responses-internal`, so their effective disposition is the
composition of the two Responses hops, which `featureEffectsForPath` computes from the path.

## Baseline (eligible single-provider route)

| inbound → upstream | current | target |
|---|---|---|
| responses → responses | native `responses,responses` | native |
| responses → chat / messages | translated `responses,ir,X` | translated |
| chat → chat | native `chat,chat` (JSON when `stream:false`) | native |
| chat → responses | translated `chat,responses` | translated |
| chat → messages | legacy-bridge `chat,responses-internal,ir,messages` | translated `chat,ir,messages` |
| messages → messages | legacy-bridge for managed keys (native only for caller-forwarded Anthropic credentials) | native |
| messages → responses | translated `messages,responses` | translated |
| messages → chat | legacy-bridge | translated `messages,ir,chat` |

Every routed (non-native) Chat and Messages path streams internally and folds for a
non-streaming client (`sse-folded`). The stream axis doubles the table to 18 cells in
`src/protocols/baseline.ts`.

Out of scope for the baseline, and described per request by the planner instead: combos and
policy routes (legacy bridge today), OAuth/forward credentials, synthetic effort rows,
vision-preprocessed images, tool-result images, and Responses-only features on Chat.

## DTOs

`ProtocolTraceV1` is the observed record for one request (`v: 1`), persisted with the request
log row; `attempts[]` records each physical attempt's upstream, mode and request path. A blocked
request has empty paths. `ProtocolPlanV1` is the predicted record (`schemaVersion: 1`) with one
candidate per route target, `guaranteedFeatures` (preserved by every eligible candidate) and
`partialFeatures`. Both validators reject anything that is not exactly v1, oversize, or outside
the vocabulary. Limits: 6 hops, 8 reason codes, 24 feature effects, 16 candidates, 16 attempts,
200-character identifiers without control characters.

## Settings

```jsonc
{
"apiSurfaces": { "messages": { "enabled": false } }, // absent => inherit claudeCode.enabled
"protocols": {
"unrepresentable": "legacy", // or "reject"
"rollout": {
"nativeChatCombos": false,
"managedMessagesNative": false,
"managedMessagesNativeOAuth": false, // effective only with the key-auth switch
"directEncoders": false,
"shadowPlan": false
}
}
}
```

`apiSurfaces` is kept raw by the config schema and parsed fail-closed: a present malformed value
closes the surface rather than inheriting. `protocols` is schema-validated and drops to defaults
when malformed, because every default is the conservative one. PF-01 adds the keys and the
resolver only; no request path reads them until PF-04.
Loading
Loading