Model proxy: a run's bearer buys model calls through the bot, and no container ever holds a key
The credential boundary (record 0016): no Worker and no execution container holds a model key; the bot process does. A harness that runs inside an execution container and calls the provider itself (record 0032) must therefore believe it holds a key while holding a token the bot minted for that run alone. The model proxy is that seam: three routes on the bot's HTTP server shaped like the three provider APIs a harness speaks natively, a run bearer minted per run and presented as the API key, and a meter that records every proxied call as the run's own model.turn span with the attrs the native runner sets — so the run page, the friction analyzer and the costs page keep one vocabulary and never learn the turn happened in another process. Its first client is the pi harness (harness-pi.md): a run whose preset is on pi hands pi the bearer as its provider key; every other run is unchanged.
- Code:
src/core/harness/toolRecovery.ts,src/core/providerRetry.ts(providerRetryDelay— bounded attempts, jitter and provider delay advice),src/core/dispatch/runLoop.ts(original run publication and model-turn closure),src/core/modelProxy/runBearers.ts(RunBearerStore—mint,leaseStarted,markLoopEnded,markTurn,clearTurn,marksOf,issue,adopt,rotate,verify,consumeTurn,reparent,revoke,grantOf,sweep;bearerHashOf;RunBearerGrant,RunBearerFacts,RotateVerdict),src/core/modelProxy/providerFailureAuth.tsandsrc/core/modelProxy/terminalEvidence.ts(authenticateProxyProviderFailure,proxyProviderFailureIsAuthenticated),src/core/providerToolSchemas.ts(shapeToolSchemasForWire, the pure per-wire copy-on-change shaper),src/core/budgets.ts(bearerExpiresAt,provisionalBearerExpiresAt,ALLOWANCES.bearerGrace),src/channels/githubDoor.ts,src/channels/githubDoorApiScope.ts,src/channels/githubDoorPaths.ts,src/channels/gitPushPolicy.ts,src/core/modelProxy/gitBindings.ts,src/core/publicationEffect.ts(the runner-owned typed push),src/execution/githubApp.ts,src/core/trace/attrs.ts(usageComplete, partial consumed usage),src/channels/responsesConsumer.ts(bounded validator client),src/channels/responsesConsumerWorker.ts(bounded neutral JSON decoding),src/channels/responsesValidationCapacity.ts(ephemeral validator capacity),src/channels/responsesResources.ts(iterative graph and exclusive full-value IPC guards),deploy/cloudflare/wrangler.template.jsonc(single bot container capacity),src/channels/modelProxyResponses.ts(ResponsesFailureBoundary, typed streamed failures),src/channels/modelProxy.ts(decideDoorthe header-only door,handleAdmittedthe call,handleModelProxyRequestboth,createModelProxyHandlerthe node:http adapter;pinRequest,shapeTools,upstreamFor,SseMeter,meterAnthropicMessage,meterOpenAiCompletion,meterResponses,turnAttrs,refusalResponse; the paths and forwarded-header lists),src/core/modelProxy/usage.ts(usageFromAnthropic,usageFromOpenAI,usageFromResponses),src/channels/adminModelProxy.ts(POST /admin/model-proxy/bearer, the operator's probe bearer),src/core/provider.ts(item 11: the block's declaration —WIRES/Wire,WIRE_ALIASES,ProviderModelOverride,wireOf,vendorOf; item 12b:ProviderFailure,classifyProviderFailure,providerFailureParks,renderProviderFailure),src/core/modelCard.ts(item 11:resolveModelCard,decideControls,ModelCard,CardRegistry),src/core/modelRegistry.ts(item 11: the catalog seam's types, Node-free for the Workers' typechecks),src/core/installedModelRegistry.ts(item 11: the installed pi registry as the catalog — the bot's Node reader),src/config/validate.ts(validateProviders,validateModelOverride),src/core/testing/providerConformance.ts(item 11: the matrix scaffold — rows, drivers, the lint, the printer),src/core/dispatch/resolve.ts(item 11: the card resolved inresolveTarget, the refused control thrown),src/core/dispatch/provision.ts(item 11:run_meta.card, thecontrol_degradednotes;mintRunBearer,ProvisionDeps.runBearers),src/core/dispatcher.ts(the mint before the attach, the revoke in the outer finally),src/core/runEnding.ts(RunEndingDeps.onFinished— the revoke at the run's finish),src/core/trace/workerTrace.ts(shimRoute: themodel-proxyroute word),src/index.ts(the store, the three routes and the mint route wired beside/ingressand/mcp),src/core/commands/providers.ts(item 12: the comparison, the command, the drift gate),scripts/registry-drift.ts(item 12: thecheck:registry-driftgate over the example config),src/core/harness/pi/harness.ts(item 12a: the typed-cause held turn and its reissue),src/core/harness/pi/bridge.ts(item 12b: verifies the proxy's failure marker before trusting its cause),src/core/harness/piAi.ts(the same shaper before direct structured Responses calls),src/core/plane/decide.ts(item 12a:reissueSteerSentence,isReissueSteerText). - Tests:
src/core/providerRetry.test.ts,src/core/modelProxy/providerFailureAuth.test.ts(run-bound local budget authentication, item 8),src/channels/modelProxyBudget.test.ts(local turn refusal through the real pi adapter, item 8),src/core/dispatch/runLoop.test.ts,src/core/modelProxy/runBearers.test.ts,src/core/modelCard.test.ts(item 11:vendorOf, the three layers with provenance,decideControls),src/core/modelRegistry.test.ts(item 11: the installed registry as a catalog),src/core/providerConformance.test.ts(item 11: the matrix rows over pi and OpenCode, the lint, the printer, the one-ref-parser scan),src/channels/githubDoor.test.ts,src/channels/githubDoorIntegration.test.ts,src/channels/gitPushPolicy.test.ts,src/core/modelProxy/gitBindings.test.ts,src/core/publicationEffect.test.ts,src/channels/modelProxy.test.ts,src/channels/responsesConsumer.test.ts,src/channels/responsesResources.test.ts,src/channels/responsesValidationCapacity.test.ts,src/channels/testing/responsesFixtureProtocol.ts,src/channels/modelProxyResponses.test.ts,src/channels/adminModelProxy.test.ts,src/core/dispatch/provision.test.ts(mintRunBearer),src/core/dispatcher.test.ts(the bearer through a dispatch),src/core/runEnding.test.ts(the finish hook),src/core/trace/workerTrace.test.ts(the route word),src/core/commands/providers.test.ts(item 12: the comparison, the command, the drift gate),deploy/cloudflare/modelProxyForwarding.test.ts(the shim forwards the paths blind; the worker-bot project),src/core/harness/pi/harness.test.ts(item 12a: the held turn and the reissue steer),src/core/harness/pi/bridge.test.ts(item 12b: marked and forged typed envelopes),src/core/dispatch/operatorResponses.test.ts(the direct operator's Responses serialization over the full authorized catalogue). - Docs: record 0032 (the proxy, the run bearer and the meter), record 0016, http-ingress.md item 10 (the shim's part), tracing.md item 17 (the
model.turnemitters), harness-pi.md item 15 (the turn guard the proxy enforces, derived from the wall clock), How-to: operate production (the probe).
Behavior
- Three routes, the shapes a harness speaks natively.
POST /v1/messagesis Anthropic-shaped (anthropic-messages),POST /v1/chat/completionsChat-Completions-shaped (openai-chat) andPOST /v1/responsesResponses-shaped (openai-responses) — the same words aproviders:block'swiredeclares (item 11; the legacytypewords load as their aliases), so a run's route is its provider's wire and nothing is ever translated between shapes: a run whose provider speaks another shape is refused400 wrong_shapenaming the path it should have used. A preset on OpenCode with a Responses block is refused at dispatch by name (resolveTarget) until OpenCode's bundled@ai-sdk/openaiis measured against the logging fake — the conformance matrix's declaredcannotcarries the same reason — so the Responses route's clients are pi's runs. The shim Worker forwards the three paths to the container the way it forwards/ingress— it answers none itself, reads nothing, and its route table names them with the one wordmodel-proxy— and the Access application does not cover them; the run bearer is the whole door, decided in the bot. Transparent on successful calls means: the proxy touches exactly two fields of the request body (item 4) and re-serializes the rest as it came —cache_controlmarkers,tool_choice,thinking,output_config.effort,reasoning.effort,stop_sequences,metadata,streamall reach the provider byte-for-byte; tool schemas do too except that per-wire schema shaping removes a construct measured as refused by that wire and records onecontrol_degradednote per tool and keyword. The direct operator'sproviderStructuredModel→PiAiProviderResponses path applies that same pure shaper before context normalization and after adapter serialization; canonical tool definitions and other wires remain untouched — and the proxy hands a successful provider answer back with its status,content-typeand request id, preserving every stream chunk in order while adding only the SSE keepalive comments item 6 names during silence; a failed answer follows item 12b and keeps only status, request id, typed cause and rendered sentence. Responses function tools that omitstrictare forwarded withstrict: false, so the provider cannot turn optional arguments into required fields. Explicitstrict: trueandstrict: falsestay unchanged, as do custom tools and other wires. Canonical tool schemas are not changed. - The run bearer. One is minted before workspace attach (
mintRunBearer) so the Git door can authenticate provisioning, bound to the run's id and pinned to the resolved<provider>/<model>, the preset'smaxTokensandmaxTurns, expiring provisionally at the provisioning allowance plus the run's effective lease plus the grace (provisionalBearerExpiresAt, decision 0046); the moment the harness starts the lease it moves the expiry to the lease's end plus the grace of one minute (leaseStarted,bearerExpiresAt,ALLOWANCES.bearerGrace) — the write-up and the post-step run inside the lease (harness-pi.md item 15), so a minute is all a late call needs, and the grace is measured from the lease, never from the attach. The token issbr_<runId>.<secret>— the run named in the clear, 32 random bytes as the secret — so the proxy tells an unknown run from a wrong secret without a second lookup, and secrets compare in constant time. It is revoked the moment the run is reported finished (RunEnding.finishedrunsonFinishedbefore the seal) and again by the dispatch's outer finally for a run that never reached its loop; a revoked entry stays until its expiry so a late call is answeredrevoked, then is swept. The store is in-process: a bot restart drops every bearer with the runs that held them, and the generation that resumes a run mints its own. A second mint for the same run replaces the first — except that a pi which outlived the bot still holds the bearer the previous generation revealed to it (harness-pi.md item 8): the run's harness facts carry that bearer's secret hash (bearerHashOf, SHA-256 hex — the store keeps hashes, never secrets, and a hash buys no call), and the generation that re-attaches adopts it onto the run's fresh entry (adopt) so pi's calls verify under this generation's grant and turn counter beside the new bearer; a row without a hash cannot make that pi's calls verify, so the harness ends it and restarts pi with the new bearer instead. Without a store (the CLI, tests) nothing is minted and the run is byte-identical to before. The token returns to the caller alone: never logged, never on the record, never in a file. Rotation. A process relaunched under a living bot — a replaced container, the ceiling of record 0038's survival clause over harness-pi.md item 16's floor — gets a new secret on the run's own entry (rotate), never a second mint: the turns spent, the expiry and the span stay exactly what they were, wheremintwould reset them and hand each relaunch a fresh budget. The order is for a bot death: the new hash joins the entry and is handed to the caller's write of the run's row BEFORE every earlier hash — the mint's, an operator's, an adopted one — is dropped, so a generation that dies between the two steps leaves a row naming the secret the relaunch was minted and never the orphan's; a row write that throws propagates with both secrets verifying and nothing dropped. The write is issued synchronously insiderotate(the run'ssaveFacts, queued on the ledger's write-through, which flushes in order): what the store orders is that write's place in the queue, so no facts write may land between the callback androtate's return. A re-attach adopts and never rotates: a live process cannot be handed a new secret.rotaterefuses by name the runsissuemints nothing for — unknown, ended, expired — and hands the row nothing then;grantOfcounts the secrets that buy the run's calls (bearers): the mint's, one more perissueandadopt, exactly one once a rotation completes. The rotation's one caller is the run loop's relaunch (prepareRelaunch, harness.md item 6): the row's facts — the new hash and the relaunch count — are written insiderecord, the harness is opened on the rotated bearer only afterrotatereturned, and a rotation refused by name refuses the relaunch, so no process ever starts on a secret the row does not name. - The door, from the headers alone, in order. A path that is not one of the three is
404; a non-POST405; no bearer401 missing_bearer(the bearer ridesAuthorization: Bearer <bearer>, the OpenAI shapes' header, orx-api-key: <bearer>, the Anthropic SDK's — either is accepted on every route); a token that is not a run bearer401 malformed_bearer; a run this bot never minted404 unknown_run; the right run with a wrong secret401 unknown_bearer; a bearer past its expiry403 expired; one whose run ended403 revoked. The node adapter decides this before it reads a byte of the body and destroys a refused request. Past the door: a body over the cap (32 MB, the Messages API's own) is413; a body that is not a JSON object400 invalid_body. Every refusal is written in the route's shape so the client's SDK reads it as the provider's own error:{ type: "error", error: { type: <code>, message } }on the Anthropic route,{ error: { type: <code>, message } }on the two OpenAI ones. A refusal never echoes what the caller sent: an unknown path is refused without repeating it, and the wrong-shape message names the route hit from the proxy's own table. - Pinning and the upstream.
modelon the wire is the grant's bare model id whatever the body named; the output cap is the grant'smaxTokens— asmax_tokens, on the chat shape asmax_completion_tokenswhen the body capped with that key (a straymax_tokensbeside it is dropped, so no second cap survives), on the Responses shape asmax_output_tokens, never below 16 (the API's own floor: a smaller pin would be refused upstream). The upstream is the run'sproviders:entry: the Anthropic shape goes to<baseUrl ?? https://api.anthropic.com>/v1/messageswith the real key asx-api-keyand the client'santhropic-version(the SDK's default when the client sent none) andanthropic-beta; the chat shape to<baseUrl>/chat/completionsand the Responses shape to<baseUrl>/responses, each withAuthorization: Bearer <key>when the entry names anapiKeyEnv, and no authorization header at all when it does not (a local endpoint). Onlyaccept,anthropic-versionandanthropic-betaare forwarded from the request; the run bearer's own headers never are. A provider the config does not name, or one without abaseUrlon an OpenAI shape, is a typedpermanent503; a key variable that is unset is a typedkey-absent503 — both decided before a turn is spent, logged under their internal proxy codes, and rendered through the one sentence without exposing configuration detail. The key is revealed into the upstream request and nowhere else. Theproviders:block is read at each call, so a config reload reaches the proxy as it reaches the mint. - The turn guard is enforced here, as a typed run event. The grant's
maxTurnsis the preset's runaway guard —runawayTurnCap(maxMinutes), six turns a minute over the wall clock (harness-pi.md item 15; 540 for a coding run) — not a budget a working run reaches. A turn is counted before the call is forwarded (so concurrent calls cannot overrun) and the call past it is refused403 turn_budget_exhausted, forwarding nothing and opening no span; the refusal publishes onerun_noteof kindturn_budget_exhaustedon the run's stream naming the guard and the counts (model proxy refused a call past the run's 540-turn guard (540 turns used); the response body saysthe run is past its 270-turn guard (270 turns used)) — the same kind the harness notes when it ends the run on the guard (harness-pi.md item 6), so the run page and the friction analyzer read one vocabulary. A403rather than a429: a rate-limit status is one an SDK retries, and this is a refusal. An upstream failure spends the turn. A run that ended between the door and the turn — its bearer verified a moment ago, the run revoked since — is403 revokedwith no note: the store answersended, never a zero-turn budget. - The meter: one
model.turnper proxied call, the loop's attrs. Every forwarded call opens onemodel.turnspan under the span the grant carries — the request root at the mint, the harness'srun.agentoncereparentmoved it there (harness-pi.md item 5), so a proxied turn lands where the native loop's did — withmodel(the<provider>/<model>ref, asrun_metacarries it) and the tools the request offered at start —tools, their count;toolNames, sorted and comma-joined;toolChoice, the request's word asauto(the default when tools came),none(no tool came — whatever the choice said, an explicitautoincluded — or the request said so),any(the OpenAI dialects'required) ortool(a named tool — the chat shape's function object, the Responses shape's flat{type: "function", name}; the Responses tool table is flat too, its names read at the top like Anthropic's) — so a record says whether a write-up turn still carried the workspace tools (harness-pi.md item 15, decision 0046) and a call that never answers still says what it offered. The harness's marks shape what goes upstream (shapeTools, besidepinRequest; the record's amendment): the harness marks the run's bearer entry when it steers the write-up (markLoopEnded), and every request after it goes upstream withtool_choice: none— the model is shown its tools and may call none, so the checkpoint is a text turn, the cached tool and system prefix stand, and only the conversation's cached blocks are re-read for that turn; a follow-up turn on the session (harness-pi.md item 14) is marked for its duration with the tools it may call (markTurn, cleared byclearTurn): the request's tool list goes upstream trimmed to them with the choice left to the model (the description turn reads the pull request and the change and submits; the verdict turn reads the head and submits), or, marked without a list, as it came (the re-review turn). Nothing is refused for the tools it carries;toolChoiceon the span is the word that went upstream,toolsandtoolNameswhat the run offered. The marks live on the run's entry with its bearer, so a rotation or an adopt keeps them; and ends it with the attrs the native runner set:stopReasonmapped as the deleted adapters mapped it (Anthropicend_turnandstop_sequence→end_turn,tool_use,max_tokens, anything elseother; chatstop→end_turn,tool_calls→tool_use,length→max_tokens; the Responses shape has no finish reason, so its status is mapped —completedwith afunction_callitem in the output →tool_use,completedotherwise →end_turn,incompleteatmax_output_tokens→max_tokens, anything elseother),inputTokens,outputTokens,cacheReadTokensandcacheWriteTokenswhen the provider reported them (usageFromAnthropic,usageFromOpenAI,usageFromResponsesinsrc/core/modelProxy/usage.ts— the Responses usage shape isinput_tokens,output_tokens,input_tokens_details.cached_tokensandcache_write_tokensas the cache counts, withoutput_tokens_details.reasoning_tokensriding inside the output count, never a fifth counter), andttftMs(the first chunk's arrival) on a streamed answer. The meter row (record 0052): everymodel.turnalso says who bills it and what it cost —biller(the block the call was forwarded through, the grant's provider name) andvendor(the card's, when the grant carries one) from the span's start, and at its endusd,priceSourceand, on a BYOK turn,feeUsd(priceTurninsrc/core/modelPricing.ts, over the operator'scosts.pricesand the card the grant carries). The precedence isprovider>operator>registry>none: a provider-reported cost is stored as reported — the meter reads it beside the counters off the same usage object (reportedCostOf: OpenRouter's final chunk'scost,is_byokandcost_details.upstream_inference_cost; on BYOKusdis the fee plus the upstream cost with the fee beside asfeeUsd; a BYOK chunk without the upstream charge keeps the fee asfeeUsdbeside the layer below that prices the tokens, never asusd) — else the operator's layer prices the counted tokens (costs.pricesfor the exact ref, costs.md item 4b, or the block's ownmodels.<id>.price), else the registry layer (the card's rate, its long-context tiers by pi's rule — the whole request re-rates at the highest tier whoseinputTokensAbovethe input side, input plus cache reads plus cache writes, exceeds — or the Anthropic family list, folded into this layer), else the turn isnone: unpriced, never $0, and a stream broken before its final chunk is unmetered andnone. A streamed answer preserves every provider chunk in order and the span ends only after the last one; while the provider is silent the proxy emits an SSE comment everyMODEL_STREAM_HEARTBEAT_MSso the public container hop and pi's between-byte timer stay open through a reasoning pause — the comment is not a model event, is not metered and does not set TTFT; theSseMeterreads the counts as the provider bytes pass — Anthropic'smessage_startcarries the input and cache counts,message_deltathe stop reason and the final output count; a chat stream'sfinish_reasonand itsusageframe when the client asked for one (stream_options.include_usage); a Responses stream's closingresponse.completed/response.incomplete/response.failedevent, which carries the whole response with its usage and status — adata:line may span chunks and CRLF framing is accepted. A buffered JSON answer is metered whole. An upstream status outside 2xx keeps its status and request-id header but its body is replaced by the typed cause plus renderer sentence, and ends the spanerrorwithhttpStatus; an upstream that cannot be reached is a typed transient 502; a stream the upstream breaks ends the spanerrorand errors the forwarded stream. The span's parent is the request root, so it streams onto the run's record through the same sink every setup span uses and is dropped, like any late span, once the run is sealed. - What the proxy never says. One ordinary completion log line per call —
[model-proxy] run=<id> turn=<n>/<max> <shape> → <status> in=<bytes> out=<bytes> <ms>ms— and one per refusal naming the status, the code and the run id. A refusal before body admission logs the run, wire shape and closed interruption kind without inventing a turn or usage; diagnostic failure cannot replace its authenticated local refusal. A signed unknown ending from validation may add one interruption line with the run, turn, wire shape and closed kind (aborted,capacity,worker-error,worker-exit,protocol,frame-bytes,stream-bytes,fields,output-bytes,graph,ipc-bytes,storage); an upstream source error or idle cleanup does not invent a validation interruption. A response close beforewritableFinishedmay add at most one unexpected-close diagnostic line, bounded to the run and internally owned request ids, clock-derived close and elapsed times, request/response/socket booleans, the witnessed abort source and an allowlisted transport code. Ordinary completion and refusal lines carry no body, header, prompt, bearer, secret, key or raw error text. A provider failure also records bounded redacted diagnostics as described in item 12b, and the diagnostic does not infer the root closer or an unwitnessed cancellation path. The bearer store's verdicts carry a reason and a run id, never token material. - The operator's probe bearer.
POST /admin/model-proxy/bearerwith{ "runId": "<id>" }mints one more bearer on a live run's own entry — same expiry, same turn counter, same pinned model — and answers201 { ok, runId, bearer, expiresAt, model, path, turns: { used, max } }, the bearer written to that response once and never to a log (the line names the operator and the run). The door is the restart's and the crash's: an ingress bearer whosehttp:<subject>actor holdsdeploy:write— the operator who may stop the bot may mint a probe for one of its runs —401without a bearer,403without the grant naming it,503without a token map,405for a non-POST, all from the headers. A run this bot never minted is404 unknown_run; a run that ended or whose bearer expired409 run_ended; a body that is not{ runId }with a run id in the run-id alphabet400. Its calls are the run's: they spend its turns and land on its record asmodel.turnspans, and they stop at its end. - The node adapter. The door from the headers, then the call; a buffered answer is written whole; a streamed one has its headers flushed first and is written chunk by chunk under back-pressure, then ended; a caller that goes away mid-stream aborts the upstream call through the request's signal. Every response's content type is one of three literals chosen by kind — the provider's JSON or event stream as such, anything else as
text/plain— withX-Content-Type-Options: nosniff, so nothing this route writes, a refusal or a provider's error page, is ever rendered by a browser as a document; the other forwarded headers ride as they came. - Every run is the proxy's client. The run's pi is handed the bearer as its provider key through the process environment (harness-pi.md item 4), and every model call it makes is one proxied turn here — pinned, guarded and metered as above; no code path in the bot calls a provider with the deployment's key for a run's turn. The bearer exists on every run — minted and revoked around each — and buys nothing unless presented.
- The card: one description of
<block>/<model>per run, decided before the first call (record 0052;src/core/modelCard.ts,src/core/modelRegistry.ts,src/core/installedModelRegistry.ts). Theproviders:block declares what the card is resolved from (src/core/provider.ts):wire—anthropic-messages,openai-chatoropenai-responses— with the legacytype(anthropic,openai-compatible) loading as its alias for one release, a block declaring both refused by name;vendor— a name, ormodelfor an aggregator whose vendor is the model id's first segment;catalog— the pi registry file the block's cards are read from, defaulting to the block's own name when such a file exists andnonefor no catalog;models.<id>— the operator's override (levels,capField,window,inputs,cache,price, andanswers— the answer shapes the card takes,tooland/ortext, read by the intake gate's load-time check, routing-and-config.md item 27);passthrough— extra body fields merged into every request, never a control.vendorOfis the one vendor parse besideparseModelRef(catalognever enters it: a block named unlike its catalog still serves the vendor the declaration or the id names), and the validator refuses a malformed declaration by name (validateProviders: acatalogthe installed library does not ship, avendor: modelblock whose ids carry no prefix, amodels.<id>.levelsword outside our tiers ornull).resolveModelCardlayers the operator's override over the registry card over the wire's structural defaults — the cap field each wire spells the cap with (max_tokens,max_completion_tokens,max_output_tokens), pi's 128000 window when no layer names one, the vendor-keyed cache table — carrying per field the layer that named it. A registry card on the selectedanthropic-messagesoropenai-responsesAPI also names that protocol's fixed cap field without a redundantcompat.maxTokensField; unknown cards, absent or mismatched APIs, and genericopenai-chatcards without an explicit cap field retain the unvouched wire fallback. An exact public OpenRouter card uses the gateway contract described below. Operator cap overrides still win over registry facts; the registry is the installed@earendil-works/pi-ai'sproviders/data/*.json, read once per process, a card looked up under the block's wire first and then under any wire, ouropenai-chatspelled pi'sopenai-completions.decideControlsthen answers native, degraded or refused for each control the request asks about: the effort tier (a tier the card names is native under the card's own wire word, whatever its spelling; a tier the card marksnullis refused;xhigh/maxa card does not name degrade to the highest named tier below, and only that fallback degrades; a card naming no levels sends the asked tier unvouched), the output cap and the window (always decided — every run sends one and compacts somewhere), images and documents (a document degrades to a text stub until a harness carries files), and the cache rule. The dispatcher resolves the card inresolveTargetwhere it checks the block, before any ack card or span: a refused control ends the run with the reply naming the model and what it takes (Model "<ref>" refuses effort "max": <model> does not take effort "max", the refusal codemodel_card_refused), the same shape asUnknown provider, and never a call that fails mid-run; the card rides the record asrun_meta.cardand each degraded control publishes onerun_noteof kindcontrol_degraded(control,asked,applied,vouched,why) before the first turn. The harnesses run on the card: it ridesHarnessRun.cardinto pi'smodels.jsonand OpenCode's configuration (harness-pi.md item 4, harness.md item 12), so the word on the wire is the card's, never one a harness chose; the harness write also names the biller's own provider for the block (record 0052's amendment): OpenCode'spackageis a table on the wire and the biller (openCodeProviderPackage;billerHarnessProvider,src/core/provider.ts) —@ai-sdk/anthropicon the Anthropic wire,@ai-sdk/openaion Responses, the aggregator's own@openrouter/ai-sdk-providerfor a biller the table names (it asks forusage.coston the final chunk and spells reasoningreasoning: { effort }in the tier variants), and@ai-sdk/openai-compatibleonly for a block that is generic in fact — amarkerscache rule on a biller the table names caches the aggregator's own way: OpenCode's write retains the workaround for the openrouter route's per-blockcache_controlplacement exemption measured on 2.0.3 (re-verified on the current 2.0.18 pin by the real-binary suite; item below) and carries OpenRouter's top-levelcache_control: { type: "ephemeral" }(its "automatic caching") as the provider'ssettings.extraBody, merged into every request body — and amarkersrule on a chat-wire biller with no harness-side provider is decideddegradedwith acontrol_degradednote true on both harnesses ("no harness-side provider vouches for the markers"), never a vouchednative(decideControls); the meter still records four counters and the proxy serves the wire's route (item 1);modelPricing's list fallback still drops the provider prefix alone throughparseModelRef(costs.md item 4b — an aggregator's vendor-prefixed ref stays a list miss, reported unpriced), so a ref is split in one place. The matrix that proves the card is a scaffold (src/core/testing/providerConformance.ts): unknown-card rows use a fictional model id so a catalog upgrade learning a real model cannot remove their coverage; rows are controls × outcomes, columns are harnesses, each cell a card resolution and control decision asserted against the record the card produces and the request body derived from that harness's own configuration write (piCapturedBodyreadingpiModelsJson,openCodeCapturedBodyreadingopenCodeConfig), so a green cell proves the file carries the wire's word; the lint names a wire, an example-config block or a control with no row, and a row a harness cannot pass is declaredcannotwith the reason — one remains on OpenCode: the Responses block, refused at dispatch until@ai-sdk/openaiis measured (the aggregator's marker payload has historical real-binary evidence through the real proxy: the then-pinned 2.0.3 exempted the openrouter route from per-block cache placement and no configuration key reached the request's owncacheoption, so the markers rode as the top-levelcache_controlthroughsettings.extraBody, measured beside the usage ask and the reasoning spelling inopencode/testing/realDriver.test.ts; the real-binary suite also verifies that payload on the current 2.0.18 pin). 12a. The provider level and bounded recovery (record 0064, amended). The proxy observes provider health through the optional plane seam:levelreportsupon a relayed success,downfor a typed transient or rate-limited failure, andparkwaits onprovider_up(provider). Billing/credit/hard-quota, key, model, request-rejected, permanent and unknown failures end without automatic retry or a provider-wide recovery wait. Every proxied request makes exactly one upstream call; the harness owns retries. Pi allows at most two retry prompts after the original failed call, with 5s and 15s base delays plus up to 20% positive jitter, within one minute from the first failure and the original run or follow-up lease. Signed diagnosticRetry-Afterseconds or HTTP-date advice is a minimum delay; advice beyond the remaining budget ends recovery. Local cancellation/stream EOF uses the same attempt and time bounds without claiming provider health. Provider-up reissues spend the same attempt budget and cannot bypass a rate-limit wait. Actual model success resets the recovery budget; a prompt acknowledgement does not. Retry reservations and their original deadlines are checkpointed inPiHarnessFacts.retryRecoverybefore the prompt can be sent. A restart continuation consumes another attempt and cannot replenish a spent budget or shorten the saved wait. Unknown send outcomes consume their reserved attempt. Provider-up control rows remain durable and deduplicated: the plane commits a typed inbox row and steer offer, push/heartbeat/reclaim deliver one release under verified ownership, and the harness consumes buffered sequences after the release echo, never forwarding them as ordinary model input. A refused release prompt retains the hold. Both initial and post-loop turns apply this policy. OpenCode currently ends on its execution failure and has no typed harness recovery; its existing gap remains explicit in harness.md.
12b. One typed provider-failure seam for every model call. ProviderFailure.cause is the closed set transient | rate-limited | credit-or-quota-exhausted | key-absent | key-invalid | model-unknown | request-rejected | permanent. classifyProviderFailure alone reads a wire status, structured body or adapter transport error; mandatory provider facts win before untrusted body signals (402 → credit-or-quota-exhausted, 5xx/network → transient); a 429 with structured credit or quota evidence remains credit-or-quota-exhausted, and other 429 responses are rate-limited, and a body's cause field is ignored unless the model proxy's provider_failure envelope carries its response-specific HMAC marker and the pi bridge verifies that marker out of band; merely passing through the bearer-authenticated proxy does not authenticate provider-controlled content from a successful stream. The proxy signs the typed cause, safe sentence, provider diagnostics and any schema-rejection evidence it classified (one offered tool plus one keyword the provider named). At pi's terminal boundary (PiTerminalFailure), the wire's typed policy stop is a provider refusal, a verified proxy failure envelope is a provider failure, pi's exact abort is local cancellation, an incomplete pi-local stream is a local retryable failure, and an unclassified stop reason (other included), missing result or untrusted fallback is unknown; unsigned status or transport prose stays unknown; none is promoted into another class. The harness persists and renders those facts only: unknown does not retry or claim refusal, local cancellation and incomplete streams use only the lease-bounded in-run recovery, permanent provider failure does not render as refusal, and provider refusal keeps its own safe sentence. The proxy, pi harness, intake gate and memory reflection consume the cause rather than matching provider prose. The conformance matrix declares the disposition beside every cause: transient and rate-limited park only where a live run lease can recover them; credit-or-quota-exhausted, key-absent, key-invalid, model-unknown, request-rejected and permanent end the request. A one-shot CLI ask has no lease, so a door-level provider refusal prints the safe refusal and exits 1. renderProviderFailure maps each cause to one calm sentence for each disposition surface: only parked promises that kept work will continue under the live lease; ended names the cause and says the request did not start on no-lease intake and operator doors. A missing configured key remains a typed key-absent failure whose thrown/operator diagnostic and proxy log name the provider and variable (Provider "<name>": <VARIABLE> is not set), while requester surfaces receive only the calm sentence; neither form includes provider payload or URL. A structured-answer wrapper is transparent to this seam: providerFailureOf unwraps a nested typed cause before any fallback classification, so a malformed answer followed by a 402 remains credit/quota exhaustion. The proxy replaces every failed provider body with that cause, sentence, bounded redacted diagnostics and opaque authentication marker before the answer crosses into a harness. Diagnostics preserve error class, type, code, parameter, message, request ID, retry/rate-limit headers and additional error fields, but exclude request content and credentials. They are diagnostic data, not schema-repair or retry authority, and remain on provider-failure notes and the operational log. The live backlog and final record protect the newest notes within a 64 KiB diagnostic budget independently of the contiguous setup head; normal history truncation flags and event counts still report omitted events. Native streamed errors and unclassified Responses errors are recorded without granting new recovery authority. Structured errors without an explicit message do not echo raw JSON as a message. Signed diagnostic subtrees are data, even when a provider names its error provider_failure; modified signatures and multiple control envelopes remain rejected. Direct model calls retain the HTTP request ID and safe response headers even when an HTTP 200 stream fails, without changing its recovery classification. They redact the configured key before JSON encoding and any diagnostic truncation on every failure, including HTTP 200 streamed errors, and retain their HTTP diagnostics before the SDK flattens them; intake receipts and operator records retain those details too. The intake gate stores the typed cause and posts the no-lease sentence once instead of silently dropping the reply. The operator stores and renders the same no-lease typed provider refusal instead of converting a failed call to non_decision and falling through to general; the 06:36Z OpenAI schema 400 is the request-rejected fixture. Reflection logs the rendered cause and writes nothing. Requester replies use the cause-owned sentence; diagnostic surfaces retain the redacted details. Unrecognized request rejection does not claim that the request shape was malformed.
providers checkand the registry drift gate (record 0052;src/core/commands/providers.ts,scripts/registry-drift.ts). The card's live half:providers.check, a typed command on every surface (actionproviders:read— a browser session's read baseline, a grant everywhere else; gated on no capability), reads the provider's ownGET <baseUrl>/models/<vendor>/<model>/endpointsfor each AGGREGATOR model the configuration names — a block declaringvendor: model, the refs fromconfiguredModelRefs(the defaults' per-agent models and every block'smodels.<id>override key) — through the deps' injectable fetch, never a global one.compareCardToEndpointsis the pure comparison over the card the dispatcher would resolve (item 11) and the endpoint's facts (endpointsFromJson:architecture.input_modalities, each endpoint'scontext_lengthandsupported_parameters): it reports where the two disagree, each drift with themodels.<id>override that would pin the endpoint's fact — a window differing from the card's (a wire default is called unvouched) pinswindow: <n>; a modality list contradicting or unvouched by the card pinsinputs: {image: …}; a parameter list without the card's cap field pins the wires' spelling it does list (capField: max_tokens); areasoningparameter against a card naming no levels is reported as "levels not stated" — the endpoints API lists parameter names, never effort values — with the pin naming the wire's own words for the tiers pi's rule would otherwise refuse, confirmed against the vendor's documentation before pinning; a card naming levels against a parameter list withoutreasoningpins the refusal. A failing endpoints read is that model's own error line, never the whole check's failure; a configuration naming no aggregator model answers that plainly. The drift gate is the same module's offline half:scripts/registry-drift.ts(npm run check:registry-drift, undercheck:consistency) runsregistryDriftover the example config's refs and the installed pi registry, naming every model whose block reads a catalog the registry ships but whose card the registry no longer carries — a pi bump that drops a card failsverifyby name instead of surfacing on a run; a block without a catalog has no registry to disagree with and is never drift.
12c. Wire failure, transport usage and model semantics are separate. The gateway signs only the exact provider failure allowlist for a function-only request, with the original hosted-effect and conversation guards. A first context_length_exceeded error, including the provider's nested invalid_request_error form, carries a fixed context-overflow diagnostic so Pi can use its native compaction. The harness does not issue a second prompt for this condition. Only a later accepted model turn clears the pending overflow; compaction alone cannot turn it into success. A later retryable failure keeps its own bounded recovery while overflow evidence remains pending. Provider prose, malformed frames and local resource interruptions grant no provider recovery or replay. It forwards bounded neutral JSON without a second SDK output or tool-argument parser. The harness SDK alone interprets model semantics and owns the task result. Only wire events admitted for forwarding enter the usage meter. A completed permitted terminal plus clean EOF and no earlier wire/resource fatal or caller abort establishes provider availability, not SDK acceptance or a review verdict. After a fatal, prior consumed usage is partial and unread/conflicting later usage is absent. Legacy authenticated SDK-consume diagnostics remain readable, but this gateway no longer produces them.
- The same bearer buys bounded GitHub operations through a separate door. The grant carries the run's effective GitHub identity and dispatch-authorized repository, with an optional resolved ref. A repository-less run cannot use the door. A missing, expired or revoked bearer buys no Git or API request. The door mints read-only, repository-pinned App tokens for API and upload-pack reads when a repository is bound, and repository-pinned write tokens limited to Contents, Workflows and Metadata only for receive-pack. API reads require a bound repository: REST paths must stay under its
/repos/<owner>/<name>route, and GraphQL must be a single repository-rooted read for that owner and name; a related fork or owner reached from that root exposes metadata only, and foreign or unconstrained query shapes are refused before token mint or forwarding. REST writes are refused. Git receive-pack is parsed before forwarding; non-branch, deletion, default-branch, foreign repository/ref, malformed and push-options requests are refused. An authenticated exactly four-byte0000probe with confirmed EOF is a protocol no-op: it neither claims nor forwards a write, spends no one-use authorization, mints no write token, and does not bind a branch; any trailing bytes are refused. On write-harness runs, the model's bearer cannot consume a Door slot, even while a runner-owned push is pending: the slot requires a separate effect credential hash beside the exact source commit, destination and (for an existing PR) old head. Pi servespublish_branchonly with an authorized repository, Git Door and bearer store, and requires its bridge to observe the exact typed call; the effect checks its clean checked-out source, endpoint and immutable commit, leases the old head, and checks the Door's durable accepted transition and remote head before crediting a receipt. Its model shell, including a literal owned push, has no grant; opaque executables may run locally but cannot publish with the model bearer. OpenCode has no typed effect yet: its shell cannot publish. Runner-owned checkpoints receive their own effect credential. The Door's pending intent and outcome remain the durable publication evidence, not shell text. Dispatch's repository binding and the first branch reservation are persisted on the run ledger. An unbound branch stays create-only until Git confirms its creation, including after a failed push or resume. The model sees only its run bearer. The main dashboard/api/*Access gate remains on the main host; hostedghuses the door's separate custom domain. Execution item 5 defines each backend's environment and resident repositories items 8 and 20 define thread credential removal. The explicit model-turn closure retires model request generations and tool push authority; it does not clear a real authorization block. A trusted deterministic post-step can use only the same original recorder for an exact full old/next ref transition after fresh canonical run, owner, stop, grant and audience checks both before and after pending admission. Missing recorders, authorization blocks and changed original owners refuse; no generic bearer is restored.
An explicit providers.<block>.models.<wire-id>.catalogModel associates the unchanged wire alias with an exact id in the block's configured catalog and endpoint listing. No alias, vendor, cap field or cache-control rule is guessed from its spelling. Fields present in the selected catalog card retain registry provenance; missing facts stay unvouched unless an operator pins them individually. A provider check offers an explicit pin even when the supported cap spelling equals an unvouched wire default. See wire aliases and catalog identities.
On the selected public OpenRouter Chat Completions endpoint, an undeclared bare model name with exactly one catalog suffix match is refused before workspace setup or a model call, naming the exact ref without rewriting the request. Review retains its earlier repository/PR preflight, including the closed-PR refusal. Explicit model overrides retain alias behavior. A catalog card on openai-completions whose baseUrl is that same public endpoint vouches the gateway's max_completion_tokens with registry provenance; the gateway translates upstream spellings. Operator and explicit registry cap fields retain precedence. Unknown cards, different APIs and custom endpoints keep the unvouched fallback.
12d. Bound the whole transport and decode once. Keep 32 active exchanges and 128 queued unread header requests in FIFO order. A queue wait ends after 30 seconds or caller cancellation; excess requests retain their authenticated403 before body/upstream/turn debit. Use a separate one-permit FIFO for short parsing, request-preparation and frame-processing operations, acquired before temporary allocations and released after completion or actual failed-worker exit. Keep neutral JSON/frame decoding in the existing built-in worker with the original heap, graph, byte, output and global512MiB storage policy. Delete only duplicate SDK output/argument state, SDK shadow charges and consume/close IPC. Preserve exact whole-value prepared/serialize identity and graph checks, actual worker-exit release, original source cancellation ownership, HTTP write/backpressure/finish/close and reserved authenticated failure endings. Native stream cancellation stops queued and active parsing without waiting for flush; an uncertain or pending source/worker/HTTP disposition remains held. All resource, grant, run-time and model-turn guards remain unchanged. Queued calls show a capacity-wait span; sanitized admission and actual-release counts identify occupancy without bodies or credentials. No model replay, semantic parser or client ACK is introduced. See decision0098.
Validation criteria
Provider failures retain the original error class and type, code, parameter, message, problem title and detail, body status, request ID, safe retry/rate-limit headers and bounded redacted response beside Switchboard's disposition. The body status is diagnostic evidence, separate from the classified HTTP status. The proxy signs these diagnostic fields and records them on a protected provider_failure note. Diagnostic prose never grants retry authority. Anthropic billing/usage-limit 400s and OpenAI insufficient_quota 429s classify as credit/quota exhaustion. Ordinary 400s report request rejection without claiming a malformed shape.
| Criterion | Proof |
|---|---|
| A bound model source check retains its allowed or refused outcome on the saved run. A thrown history check records its stage and a closed error-class discriminator, with no private exception text or name. Source checks continue to refuse the same calls. | [unit] src/core/dispatch/runLoop.test.ts::runLoop — the model turn and everything that rides on it::retains the failed model source check without copying private exception text: %s |
| 12b: Claude, Chat Completions and Responses preserve redacted provider diagnostics through the signed reply and recorded note; billing 400 and quota 429 retain their actual category | [unit] src/core/providerConformance.test.ts::ProviderFailure — the closed failure matrix::classifies Anthropic billing 400 and OpenAI quota 429 without calling them shape or rate errors, ::keeps the provider error class and details through signed and serialized boundaries, ::keeps the signed HTTP status when provider diagnostic prose contains a different status, ::retains SDK class and safe headers while redacting credentials and request content with explicit bounds, ::retains a transport exception's native class and nested cause without its request; src/channels/modelProxy.test.ts::upstream failures::%s keeps complete redacted error details in the signed reply and run record, ::%s records streamed error details without granting new recovery authority, ::redacts the configured credential from native transport failures, ::records a buffered Responses failure without reporting successful provider availability; src/core/harness/piAi.test.ts::PiAiProviders — the provider table config.yaml names, built on pi's model library::retains redacted diagnostics when the direct SDK fetch fails before an HTTP response; src/core/runRecord.test.ts::stored operator failures retain provider diagnostics after serialization; src/core/intake.test.ts::decideIntake — the verdict from one forced tool call (routing-and-config item 27)::a provider failure carries its typed cause and one safe sentence, never the provider payload or URL; src/core/dispatch/operator.test.ts::runOperator — the loop over a scripted model::a provider-rejected request without explicit schema evidence is one typed refusal, never a re-ask or non_decision, src/core/runRegistry/backlog.test.ts::provider diagnostic retention::*, src/core/runRecord.test.ts::fitRecordToBudget::retains provider diagnostics in the middle when fitting the final record budget, ::keeps the record byte bound when a caller reuses the same provider note object, src/core/providerConformance.test.ts::ProviderFailure — the closed failure matrix::redacts configured keys before encoding structured diagnostic responses |
| 8: a refused call past the original turn cap carries signed local budget evidence bound to the same run and exact used/maximum counts; the real Responses, Chat and Anthropic adapters, pi bridge and full harness preserve that local cause into the dispatcher's user-visible terminal report without another upstream call, provider failure or recovery grant; foreign or unbound signed budget evidence remains unknown even during an abort | [unit] src/channels/modelProxyBudget.test.ts::local model-call budget::the real pi adapter preserves the proxy's exhausted budget without another upstream call, src/core/modelProxy/providerFailureAuth.test.ts::unknown terminal authentication::binds local turn-cap evidence to exact run and counts without provider authority, src/core/harness/pi/bridge.test.ts::turns, narration and the answer — the loop's rules::local budget evidence requires an authenticated matching run and exact counts |
| 12c/d: the gateway transports bounded wire facts and usage independently of SDK task acceptance; the stock harness owns exact tool arguments and decoder errors | [unit] src/channels/modelProxy.test.ts::createModelProxyHandler — the node adapter::assigns SDK argument rejection to the harness while keeping gateway wire usage independent; src/channels/modelProxy.test.ts::createModelProxyHandler — the node adapter::checks latched neutral worker failure before EOF availability…, ::settles concurrent stock-SDK transport without later terminal credit… |
| 12d: five engineers with four simultaneous threads reach the provider without waiting or capacity refusal; completed calls return every transport and storage permit | [unit] src/channels/modelProxy.test.ts::Responses transport admission::admits five engineers with four simultaneous threads without waiting or rejecting… |
| 12d: parsing wait cancellation prevents worker startup and preserves later admission | [unit] src/channels/responsesConsumer.test.ts::Responses validation isolation::cancels queued parsing before worker startup and returns its permit for a later call |
| 12d: queue waiting is bounded without releasing unfinished active work; authenticated admission capacity is named without provider recovery or replay | [unit] src/channels/responsesValidationCapacity.test.ts::Responses transport reservation::bounds queue waiting without freeing the active owner's unfinished work, src/core/harness/pi/harness.test.ts::*::names authenticated local admission capacity without provider recovery or replay |
| 12d: source failure interrupts queued, starting and busy validation independently of its write, preserves the original error and byte/backpressure ownership, prevents a late enqueue during asynchronous local cleanup, and keeps caller abort distinct from upstream provider failure | [unit] src/channels/modelProxyResponses.test.ts::Responses validator lifecycle::*, src/channels/modelProxy.test.ts::Responses stream failure boundary::preserves upstream source-error authority without replay or treating caller abort as provider-down |
| 12d: bot build and registry profiles render the smallest predefined container qualified for the finite retained-memory validation workload, without changing the single container's ownership; the managed policy and V8 limits are not RSS guarantees | [unit] src/deploy/wranglerTemplate.test.ts::bot container validation capacity::* |
| 12d: two synchronized complete2MiB stock SDK streams preserve full tool arguments and complete usage while retaining actual request, raw backing inside the existing512MiB policy | [unit] src/channels/modelProxy.test.ts::Responses stream failure boundary::admits two complete2MiB harness SDK streams without gateway output shadow state |
| 12d: neutral NativeJSON/full-value IPC preserves full values, applies graph and whole-backing limits, verifies prepared reply identity and retains failed/aborted graph credit through actual worker exit | [unit] src/channels/responsesResources.test.ts::Responses full-value resource protocol::*, src/channels/responsesConsumer.test.ts::Responses validation isolation::* |
| 12d: aggregate storage admission, emergency closure and live permit bookkeeping remain bounded through open delivery, deferred callbacks, backpressure and disconnect | [unit] src/channels/responsesValidationCapacity.test.ts::Responses transport reservation::*, src/channels/modelProxyResponses.test.ts::Responses byte budgets::retires small frame and output permits while the transport remains open, ::delivers its reserved authenticated ending when the global storage policy is full, ::preserves a first fatal whose retained usage exceeds the emergency ending permit…, ::preserves the full first fatal when another owner fills storage after frame parsing, ::refuses complete-frame output admission before parsing or accepting a fatal under initial pressure, ::refuses a maximal finite frame before wire or usage acceptance and holds its prepared graph through exit, src/channels/modelProxy.test.ts::createModelProxyHandler — the node adapter::keeps encoded Responses output charged until write completion…, ::retires encoded output once when HTTP…, ::retires backpressured output at drain before a deferred write callback |
| 7, 12d: a validation capacity refusal produces one sanitized typed interruption line without provider-down or replay; original source failure remains separate and emits no spurious validation interruption | [unit] src/channels/modelProxy.test.ts::Responses stream failure boundary::logs one sanitized header-admission refusal without an upstream call or turn debit, ::preserves upstream source-error authority without replay or treating caller abort as provider-down, src/channels/modelProxyResponses.test.ts::Responses validator lifecycle::* |
| 12a: buffered Responses provider-up is published only after resource/graph validation on the successful-return path; a local validation refusal returns authenticated unknown without releasing parked turns | [unit] src/channels/modelProxy.test.ts::Responses transport admission::publishes buffered provider-up only after graph admission… |
| 12d: raw-backing refusal cancels the original reader once and retains source ownership through actual cancellation settlement and validator exit, across initial/later SSE, partial usage, first fatal, buffered success, one upstream error attempt (nested-proxy second attempts are retired) and known nonretryable status; HTTP finish/close and nonHTTP delivery cannot settle an uncancelled source | [unit] src/channels/modelProxy.test.ts::Responses raw storage refusal::*, src/channels/modelProxy.test.ts::createModelProxyHandler — the node adapter::keeps a raw-refused source through HTTP … |
| 7, 12d: actual queued header admission or interruption logs elapsed wait before any body, upstream or model turn; immediate admission stays distinct and diagnostic failures cannot alter original outcomes or ownership | [unit] src/channels/modelProxy.test.ts::Responses transport admission::observes queued header wait for …, ::does not report queue time for immediate header admission, src/channels/responsesValidationCapacity.test.ts::Responses transport reservation::observes only actual queue entry without changing immediate admission or full-queue refusal, ::keeps a throwing queue observer outside the original cancellation and ownership |
| 12d: verified Responses headers reserve before body reads for both streaming and buffered clients; queued and refused bodies stay unread without upstream, debit or usage; linear ownership waits for processing, source cancellation, worker exit and real HTTP finish, including backpressure and disconnect | [unit] src/channels/modelProxy.test.ts::Responses transport admission::*, src/channels/responsesValidationCapacity.test.ts::Responses transport reservation::*, src/channels/modelProxy.test.ts::createModelProxyHandler — the node adapter::delivers a real HTTP pre-body refusal before destroying the unread request, ::keeps Responses reservation until HTTP finish for streamed=…, ::holds a backpressured Responses reservation until drain and real HTTP finish, ::cancels a backpressured Responses transport and removes its drain listeners, ::cancels a queued HTTP request without reading its body or starting an upstream, ::holds disconnected partial body processing until the owned iterator actually settles, ::holds a disconnected unframed source until its real cancellation settles, with no late validation, src/core/modelProxy/providerFailureAuth.test.ts::unknown terminal authentication::authenticates a pre-body JSON refusal as local unknown without provider or turn-debit authority |
| 12d: exact raw UTF8 retained/cumulative/field/output limits reject before decoder/large allocation, preserve accepted bytes and first fatal, keep usage partial, and count actual response attempts once with bounded cancellation and Fetch-equivalent document decoding | [unit] src/channels/modelProxyResponses.test.ts::Responses byte budgets::*, src/channels/modelProxy.test.ts::Responses transport admission::bounds buffered Responses documents at16MiB…, ::stops an oversized error document at status…, ::a new request after a temporary failure has its own response reservation, ::holds an oversized document reservation until its actual source cancellation finishes, ::keeps consumed usage partial and prevents positive finish after a later frame-byte stop, ::keeps Fetch BOM-stripping semantics for split buffered Responses bytes and usage, ::retains bounded error-body classifier evidence with the same BOM bytes, ::retains Fetch invalid-UTF8 replacement for admitted buffered bytes, src/channels/responsesValidationCapacity.test.ts::Responses transport reservation::retains exact cumulative bytes and latches a local overflow across response attempts |
12c: an authenticated unknown Responses ending may retain only closed rejection phase/kind: admission, request validation, response validation with a bounded interruption kind, or a legacy SDK-consume observation with rejected that remains readable but is no longer produced by this gateway. The exact pair is signed; absent carrier preserves legacy authentication. Tampered, malformed or foreign fields grant no classification. The saved Pi diagnostic distinguishes source stages without provider-attempt, ending or retry authority; unknown remains unknown and no raw request/frame/error body is retained. | [unit] src/channels/modelProxy.test.ts::Responses stream failure boundary::distinguishes authenticated*; src/core/modelProxy/providerFailureAuth.test.ts::unknown terminal authentication::*; src/core/harness/pi/harness.test.ts::runPiHarness — a run on pi from the first file to the answer::unknown terminal diagnostics reach the run record*; src/core/dispatch/runLoop.test.ts::runLoop — the model turn and everything that rides on it::saves authenticated proxy rejection provenance* |
| 12c: structured stream errors survive the pinned pi adapter and bridge as authenticated causes; no immediate replay, provider prose or hosted tools authorize retries; failure is recorded and unknown remains unknown | [unit] src/channels/modelProxy.test.ts::Responses stream failure boundary::typed failures cross the real pi adapter without replay or provider prose, ::unknown codes and hosted tools remain unsigned even when prose claims a retryable error, ::fragmented ordinary frames retain their bytes and only completed evidence reports up, ::a caller closing after a failed frame cannot turn unknown or permanent failure into provider-down, ::multiline SSE failures retain typed evidence through the real pi adapter, ::SDK errors and CR-only frames cannot replay old signed failures, ::malformed completed status and earlier hosted output cannot authorize completion or recovery, ::an actual pi abort race retains permanent and unknown stream failure evidence, ::provider conversation state and approval inputs leave stream recovery unverified, ::malformed completed output cannot report provider-up or a successful model turn, ::malformed output discriminators cannot manufacture provider-down or recovery, ::SDK BOM lines cannot bypass hosted-effect tracking or error sanitation…, ::ordinary BOM frames keep their bytes and retain valid completion and truncation, ::EOF residue, DONE and thread wrappers confer no terminal or health authority, ::only dispatched Responses events enter the turn meter, ::BOM-only EOF lines dispatch and sanitize exactly as the SDK does, ::actual wire fatal events prevent later terminal and usage credit, ::usage consumed before a fatal event is explicitly partial and unread usage is absent |
| 13: the door uses a trusted-side read token for API calls, refuses API writes, scopes reads to a bound repository, and revocation stops further calls | [unit] src/channels/githubDoor.test.ts::GitHub run-bearer door::forwards read API with a trusted-side token and refuses API writes, ::refuses REST reads outside the run's repository before any upstream call, ::forwards only a GraphQL read rooted in the bound repository, ::keeps paginated API reads on the door instead of sending its bearer to GitHub, ::refuses Git discovery without a repository authorized by dispatch |
| 13: authenticated CLI API diagnostics distinguish the local scope refusal with zero upstream calls from an upstream 403 with one actual attempt and a bounded request ID; token and transport failures retain safe classification without exception contents, and measurement does not retry or widen access | [unit] src/channels/githubDoor.test.ts::GitHub run-bearer door::records a scoped CLI refusal on the original run with zero upstream attempts and no request content, ::distinguishes an upstream 403 from a local refusal and retains only a safe request ID without retry, ::records token and transport failures at their original attempt count without retaining exception text |
| 13: a receive-pack revoked during metadata lookup or write-token mint is refused before forwarding; a replacement registration has a different request generation | [unit] src/channels/githubDoor.test.ts::GitHub run-bearer door::refuses an in-flight receive-pack after its run binding and bearer are revoked, ::refuses receive-pack revoked while its write token is being minted, src/core/modelProxy/gitBindings.test.ts::GitBindings::gives a replacement registration a new request generation |
| 13: the pi write harness refuses every model-shell push; its typed publication effect binds a separate one-use credential to the checked source commit, destination and old head, and a model bearer cannot borrow that slot during a concurrent receive-pack; runner-owned checkpoints carry their own credential and fail closed on a moved checkout | [unit] src/core/modelProxy/gitBindings.test.ts::GitBindings::consumes one typed harness push authorization bound to source, destination and old head, ::preserves the first-branch ref binding while refusing foreign receive-pack destinations, src/channels/githubDoor.test.ts::GitHub run-bearer door::requires a source-bound harness authorization before forwarding even an owned receive-pack, src/core/dispatch/runLoop.test.ts::runLoop — the model turn and everything that rides on it::refuses model-shell publication without opening a Door slot, including a literal owned push, src/core/publicationEffect.test.ts::publish_branch — a runner-owned Git Door effect::*, src/core/modelProxy/gitBindings.test.ts::GitBindings::does not let a model shell bearer consume the runner's execution-bound slot, src/channels/githubDoor.test.ts::GitHub run-bearer door::refuses a concurrent model bearer even when a typed effect owns the same one-use ref/source slot, src/core/codingPrPostStep.test.ts::salvageBudgetPush — a ship coding child pushes what it has at the budget (push-before-abort, agent-ship.md item 8)::binds a runner-owned checkpoint to its checked-out source commit before Git can push |
| 13: an existing PR's Git door starts blocked, accepts only fresh trusted publication authority for its bound ref, and rejects a stale or malformed old-head command before forwarding | [unit] src/core/modelProxy/gitBindings.test.ts::GitBindings::starts an existing PR blocked and invalidates in-flight requests when its authority changes, src/channels/gitPushPolicy.test.ts::receive-pack command boundary::refuses an existing PR update unless its Git command names the exact authorized old head, src/channels/githubDoor.test.ts::GitHub run-bearer door::holds an existing PR behind fresh authority and refuses a stale old head at the HTTP door |
| 13: a direct coding run resolved to an existing PR also starts with Git writes blocked; a mere PR URL/ref/head does not grant trusted publication authority | [unit] src/core/dispatcher.test.ts::coding PR post-step (docs/reference/specs/pr-description.md)::a direct coding run on an existing PR begins with Git writes blocked until trusted publication authority exists |
| 13: an existing-PR Git write has one durable pending intent before forwarding; an accepted report advances the head only after its attributed receipt and branch-to-PR mapping commit together, known no-write outcomes restore only the pre-attempt completeness, while a missing recorder or failed outcome commit blocks further writes | [unit] src/core/modelProxy/gitBindings.test.ts::GitBindings::allows one durable existing-PR write intent and advances only after its accepted outcome commits, ::blocks later writes when a trusted outcome fails to commit or authority is revoked in flight, src/channels/githubDoor.test.ts::GitHub run-bearer door::refuses an existing PR push without a trusted durable receipt writer, ::withholds an accepted existing-PR push when its durable outcome fails and blocks another write, src/core/dispatch/runLoop.test.ts::runLoop — the model turn and everything that rides on it::commits a Git-door existing-PR intent before forwarding and its accepted head before reporting success… |
| 13: an existing-PR write forwarded before revocation holds one exact-old-head durable intent and a five-minute request bound; finalization waits for the report, records an accepted head before seal, or preserves the exact owner/ref transition as uncertain after an unknown result or bounded timeout, with no new write authorized | [unit] src/channels/githubDoor.test.ts::GitHub run-bearer door::attributes a one-use forwarded push after revocation under a bounded request, src/channels/githubDoorIntegration.test.ts::GitHub door against a real Git smart HTTP peer::attributes an existing-PR push accepted by Git before its run is revoked, src/core/modelProxy/gitBindings.test.ts::GitBindings::blocks later writes when a trusted outcome fails to commit or authority is revoked in flight, ::times out a forwarded claim without authorizing another write, src/core/dispatch/runLoop.test.ts::runLoop — the model turn and everything that rides on it::keeps a forwarded Git-door accepted outcome in the final record after the model ends, ::keeps a forwarded Git-door unknown outcome in the final record after the model ends, src/core/runRecord.test.ts::isRunRecord::keeps an uncertain Git publication owner and ref transition on a finished record |
| 13: a first or subsequent branch receive-pack write commits a durable pending intent before forwarding; the run waits for its report before sealing, retains an uncertain ref transition on unknown outcome, skips PR lookup/edit/open while the write is in flight or unresolved, and only a committed accepted receipt adds a pushed head | [unit] src/channels/githubDoor.test.ts::GitHub run-bearer door::refuses a branch push without a durable receipt writer, src/core/modelProxy/gitBindings.test.ts::GitBindings::holds every branch write behind one durable intent and keeps an unknown outcome blocked, ::ends branch admission before waiting while a forwarded claim may still settle, src/core/dispatch/runLoop.test.ts::runLoop — the model turn and everything that rides on it::keeps a forwarded branch Git-door accepted outcome in the final record after the model ends, ::keeps a forwarded branch Git-door unknown outcome in the final record after the model ends, src/core/dispatch/runLoop.test.ts::*::a resumed child keeps an earlier uncertain branch write on its finished record, src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::refuses original-unit recovery for an unresolved branch write before a PR exists, src/core/runRecord.test.ts::isRunRecord::keeps an uncertain first-branch transition before a pull request exists |
| 13: the last model turn closes new Git-door write admission before PR lookup, edit or open; a forwarded write first settles, and an accepted head that the final workspace and remote read cannot confirm leaves the durable receipt intact but blocks the PR post-step | [unit] src/core/dispatch/runLoop.test.ts::*::keeps one successful unit PR and its base when private chat refusal is…, ::runLoop — the model turn and everything that rides on it::keeps a forwarded branch Git-door accepted outcome in the final record after the model ends, ::keeps a forwarded Git-door accepted outcome in the final record after the model ends |
| 13: a first-branch receive-pack forward has the same request bound; an aborted response does not confirm branch creation and leaves the original bound ref create-only | [unit] src/channels/githubDoor.test.ts::GitHub run-bearer door::bounds a first-branch receive-pack forward without changing its branch identity, ::leaves a first branch create-only when its bounded receive-pack forward aborts |
| 13, live: the bounded one-use existing-PR write and durable attribution work on the original unit after exact deployment | [gap] The local Git smart HTTP peer does not prove a live original-unit receipt or GitHub's abort behavior; keep publication held until production proof. |
| 13: a real Git smart HTTP peer accepts clone and a bound non-default push, while default-branch push fails without moving the ref | [unit] src/channels/githubDoorIntegration.test.ts::GitHub door against a real Git smart HTTP peer::clones and pushes only the bound non-default branch with a run bearer |
| 13: an exact EOF flush probe leaves write authority untouched for a large initial or existing-ref push; malformed or unauthorized probes fail closed, and a lost real write response remains uncertain without replay | [unit] src/channels/githubDoorIntegration.test.ts::GitHub door against a real Git smart HTTP peer::accepts a large new branch push after an empty probe without spending its one-use authority, ::accepts a large existing PR push after an empty probe without spending its one-use authority, src/channels/githubDoor.test.ts::GitHub run-bearer door::treats only an authorized exact EOF flush as a no-op and leaves command fences intact |
| 13: a coding unit pins its first push to the durable unit branch before a PR exists; the coordinator tag carries that branch through a restart | [unit] src/core/dispatcher.test.ts::coding PR post-step (docs/reference/specs/pr-description.md)::pins a coding unit's first push to its contract branch before any PR exists; src/core/dispatch/provision.test.ts::registerRun — the run's row on every surface before the attach::publishes the coordinator admission tag before attachment and never duplicates it on restart; src/core/dispatch/reattach.test.ts::carriedCoordinatorTag: the tag a resumed run carries forward::rebuilds the tag from the row's meta with the base the coordinator_tag event carried |
| 13: receive-pack parser refuses malformed, foreign, deletion and push-options commands; dispatch supplies the repository; a first ref reservation is durable and remains create-only until Git confirms creation; a resume cannot change the target | [unit] src/channels/gitPushPolicy.test.ts::*, src/core/modelProxy/gitBindings.test.ts::*, src/channels/githubDoor.test.ts::GitHub run-bearer door::keeps a rejected first branch creation create-only until Git confirms it |
| 13 live: an authorized child has no App token in its environment, helper or files, can read and push its exact bound ref, and cannot push a foreign/default ref or act after revocation | [agent] After deploy, use an existing authorized run; record the command outcomes and binding without printing bearer values. |
| 2: a mint names its run and verifies to the grant with zero turns; secrets never collide and verify only for their own run; a malformed token, an unknown run and a wrong secret are refused by reason, garbage never throws; a second mint replaces the first | [unit] src/core/modelProxy/runBearers.test.ts::RunBearerStore — mint and verify::* |
6: the marks on the run's entry — the loop's end read back, a turn with tools, a turn without, the turn cleared; they survive rotate and adopt; a run never minted or one that ended takes none | [unit] src/core/modelProxy/runBearers.test.ts::RunBearerStore — the loop's end and a turn's tools, marked by the harness for the proxy (model-proxy item 6)::* |
2: the lease's start resets the expiry to the lease's end plus the grace, replacing the mint's provisional expiry; issue hands out the new one; a run never minted or one that ended takes no lease | [unit] src/core/modelProxy/runBearers.test.ts::RunBearerStore — expiry and revocation::the lease's start resets the expiry to the lease's end plus the grace… |
2: valid to the instant before expiry and expired from it; revoke ends every bearer of the run and a later call is revoked, not unknown; a revoked entry is swept past its expiry and becomes an unknown run; the sweep runs on every mint | [unit] src/core/modelProxy/runBearers.test.ts::RunBearerStore — expiry and revocation::* |
2: a pi that outlived the bot keeps the previous generation's bearer: bearerHashOf names a token's secret as 64 hex without being one; adopt lets that bearer verify on the next generation's store under its grant and turn counter beside the new bearer; the hash presented as a secret buys nothing; adopt refuses an unminted run, one past its expiry, an ended run and a hash of another shape | [unit] src/core/modelProxy/runBearers.test.ts::RunBearerStore — a bearer across generations (docs/reference/specs/harness-pi.md item 8)::* |
2: rotate mints a new bearer on the run's own entry and drops the old — the old answers unknown_bearer, the new verifies to the same grant with the same turns, expiry and span, and the row is handed the new secret's hash once; an unknown, ended or expired run is refused by name with nothing handed to the row; a row write that fails leaves both secrets verifying and the old never dropped; grantOf counts the run's secrets | [unit] src/core/modelProxy/runBearers.test.ts::RunBearerStore — rotate: a relaunch's new bearer on the run's own meter::* |
| 2: the relaunch rotates before the harness rebuilds — the row written once inside the rotation with the new hash and the count, the old bearer refused, the turns kept, the new bearer in the relaunched process's environment; a rotation refused by name refuses the relaunch with nothing written | [unit] src/core/dispatch/relaunch.test.ts::prepareRelaunch — the relaunch decided and prepared::relaunches: the bearer rotated on the run's own meter…, ::refuses by name, writing nothing…, src/core/dispatch/runLoop.test.ts::the pi harness — the container replaced under a living bot: the relaunch ceiling::a living bot, the typed error from the log read… |
2, 5, 8: consumeTurn counts to maxTurns and refuses past it naming the counts, never past the cap; unknown and revoked runs are refused as ended; issue mints another bearer on a live entry with the same expiry and counter and nothing for an unknown, revoked or expired run; grantOf reads the facts and never a secret | [unit] src/core/modelProxy/runBearers.test.ts::RunBearerStore — turns and the operator's extra bearer::* |
| 1: the three paths name the three shapes and nothing else | [unit] src/channels/modelProxy.test.ts::the model proxy's paths::names the three shapes a harness speaks natively, and nothing else |
| 1: a preset on OpenCode with a Responses block is refused at dispatch by name; the same block on pi resolves | [unit] src/core/dispatch/resolve.test.ts::resolveTarget — the provider checked, and the target repo/ref/PR started::a preset on OpenCode with a Responses block is refused at dispatch by name… |
| 3: the door — no or malformed bearer 401 with the body unread; unknown run 404, wrong secret 401; expired and revoked 403 by name; non-POST 405 and a non-proxy path 404 before any bearer; either header spelling; wrong shape 400 naming the right path, the Responses route both ways; the same refusals on the Responses path in the OpenAI error shape — none forwards; a refusal never echoes the path | [unit] src/channels/modelProxy.test.ts::the door — decided from the headers, before the body is read::* |
| 3: a body over the cap 413, non-JSON 400, an array 400 — none forwarded, no turn spent | [unit] src/channels/modelProxy.test.ts::the body — bounded, JSON, an object::* |
1, 4: the Anthropic shape — the preset's model and max_tokens on the wire, every other field byte-for-byte, the real key as x-api-key with the client's version and beta headers, the bearer nowhere in the upstream request; the version defaulted and the config's baseUrl honoured; pinRequest is pure | [unit] src/channels/modelProxy.test.ts::pinning and pass-through — the Anthropic shape::* |
1, 4: the OpenAI shape — the provider's baseUrl and bearer, model and cap pinned, the rest kept; max_completion_tokens pinned on its own key with no second cap; a keyless provider sent no authorization; an unset key variable a typed key-absent 503 and an unnamed provider a typed permanent 503, both before a turn is spent | [unit] src/channels/modelProxy.test.ts::pinning and pass-through — the OpenAI shape::* |
1, 4: the Responses shape — <baseUrl>/responses with the provider's bearer, model and max_output_tokens pinned at or above the floor of 16, reasoning.effort and native fields byte-for-byte; pinRequest is pure | [unit] src/channels/modelProxy.test.ts::pinning and pass-through — the Responses shape::* |
| 1: each wire receives the operator catalogue clean of every construct known to be refused there; Responses removes lookaround from the four measured tools and records each degradation, while the other wires keep it; the direct operator's real structured-model/provider serialization does the same over the full authorized catalogue, binds review with an explicit repository, preserves its reasoning cap, and leaves canonical and non-Responses schemas unchanged. Its credential-free fake reconstructs the known rejection and is not upstream evidence | [unit] src/channels/modelProxy.test.ts::tool-schema conformance on each wire::the operator catalogue is clean of every construct known to be refused on each wire, src/core/dispatch/operatorResponses.test.ts::the direct operator on the Responses wire::* |
| 1: Responses function tools use explicit non-strict sampling by default; explicit strict choices, custom tools and canonical input schemas remain unchanged | [unit] src/channels/modelProxy.test.ts::tool-schema conformance on each wire::keeps omitted strict sampling off for Responses functions and preserves explicit choices; src/tools/reviewRead.test.ts::review_read::keeps first-page inputs optional on the Responses wire and enforces pinned continuations |
6: a streamed Anthropic answer preserves every provider chunk in order with its headers, the span open until the last chunk and ended with model, stop reason, the four token counts and TTFT; a 60 s provider silence stays open through unmetered SSE heartbeat comments; the tools offered — count, sorted names and the tool_choice word on both dialects, none when no tools came — on the span from its start; after markLoopEnded the checkpoint request goes upstream with tool_choice: none on both dialects with its tools untouched, a turn marked with tools goes upstream trimmed to them, a turn marked without tools goes as it came, an unmarked run is untouched, and the span's toolChoice is the upstream word; a buffered answer metered whole with the adapter's stop-reason mapping; a streamed OpenAI answer's tool_calls, cached tokens and the usage-less case; the SSE meter across chunk boundaries and CRLF | [unit] src/channels/modelProxy.test.ts::the meter — one model.turn span per proxied call, the runner's attrs::a streamed Anthropic reply is forwarded chunk for chunk in order with its headers, and the span ends only after the last chunk with model, stop reason, the four token counts and the time to first token, ::a 60 s reasoning pause inside the turn bound stays open through SSE heartbeats, src/channels/modelProxy.test.ts::the meter — one model.turn span per proxied call, the runner's attrs::* |
6: the Responses route metered — a coding preset on pi offers tools with reasoning.effort and gets a tool call back from the fake, the stream forwarded chunk for chunk and the span ended tool_use with the usage shape's four counts and the ttft; a buffered answer metered whole with the status mapping; a tool_choice in the flat Responses shape recorded as the span's word; the marks shape the flat table as they shape the other two dialects' | [unit] src/channels/modelProxy.test.ts::the meter — the Responses route (model-proxy item 6)::*, src/core/modelProxy/usage.test.ts::usageFromResponses (token usage → TokenUsage)::* |
5: the call past maxTurns is 403 turn_budget_exhausted, publishes the run_note naming the guard and the counts, the body says the run is past its guard, forwards nothing and opens no span; a run that ended between the door and the turn is 403 revoked with no note | [unit] src/channels/modelProxy.test.ts::the turn guard — a refusal is a typed run event::*, ::the turn guard — a run that ended between the door and the turn::* |
6, 12b: an upstream error keeps its status and request id but crosses as provider_failure with the typed cause, safe sentence and verifiable proxy marker; a schema error naming one offered tool and keyword carries that signed evidence even for an unmeasured construct; the span ends error with httpStatus and the turn is spent; an unreachable upstream is a typed transient 502; a broken stream errors the forwarded stream and the span, unmetered and priceSource: none | [unit] src/channels/modelProxy.test.ts::upstream failures::*, src/channels/modelProxy.test.ts::tool-schema conformance on each wire::carries a provider-named unmeasured schema rejection as authenticated tool and keyword evidence |
12b: a provider_failure object or rate-limit/status prose forged inside a 200 provider stream cannot forge the proxy marker and remains unknown rather than adopting a claimed cause; a proxy-classified envelope whose marker verifies may name a park-capable cause; changing or removing any signed field invalidates the marker | [unit] src/channels/modelProxy.test.ts::upstream failures::a 200 provider stream cannot forge the proxy's provider_failure authentication marker, src/core/harness/pi/bridge.test.ts::turns, narration and the answer — the loop's rules::does not trust a provider_failure object forged inside a 200 provider stream, ::turns, narration and the answer — the loop's rules::does not promote untrusted rate-limit or status prose into a provider failure, ::turns, narration and the answer — the loop's rules::trusts a proxy-classified provider failure only when its authentication marker verifies, src/core/providerConformance.test.ts::ProviderFailure — the closed failure matrix::authenticates the proxy's exact typed envelope and rejects an absent or tampered marker |
12b: the failure matrix covers every closed cause with an explicit park/end disposition, including the incident's 402 limit_source (end) and the OpenAI schema 400 as request-rejected (end); 402 and 5xx statuses override a contradicting untrusted body cause; a 429 retains a structured insufficient-quota cause before the generic rate-limit fallback, while a body cause alone is ignored; one renderer maps each cause to a safe requester sentence with no payload, provider URL or instruction; only leased temporary recovery promises continuation, and billing names the account block; a missing configured key's thrown/operator diagnostic and proxy log name provider + variable while requester surfaces receive only the safe sentence; a structured-answer wrapper preserves a later typed 402; the proxy ends a 402 call without a provider-down wait and relays only the account-blocked sentence; the intake gate carries and renders the same typed cause | [unit] src/core/providerConformance.test.ts::ProviderFailure — the closed failure matrix::*, src/channels/modelProxy.test.ts::the provider level and the park (record 0064)::a credit limit ends immediately with a safe sentence and no provider recovery wait, src/channels/modelProxy.test.ts::pinning and pass-through — the OpenAI shape::a missing configured key logs its provider and variable while the requester gets only the safe sentence, src/core/intake.test.ts::decideIntake — the verdict from one forced tool call (routing-and-config item 27)::a provider failure carries its typed cause and one safe sentence…, ::an AbortError wrapper preserves its typed provider cause, ::a malformed answer followed by a 402 keeps the nested credit failure non-retryable, src/core/dispatch/operator.test.ts::runOperator — the loop over a scripted model::a park-capable failure at the no-lease operator door says the request did not start, src/core/harness/piAi.test.ts::complete — one request through pi's own adapter, on the wire::an OpenAI schema 400 crosses the adapter as request-rejected without its payload, src/core/dispatch/operator.test.ts::runOperator — the loop over a scripted model::a provider-rejected request without explicit schema evidence is one typed refusal, never a re-ask or non_decision, src/core/dispatcher.test.ts::the operator behind routing.operator (record 0057; routing-and-config item 29)::on: an operator schema 400 renders one typed sentence and never falls through to general, src/channels/slack.test.ts::receiveSlackMessage — the intake gate (docs/reference/specs/slack-channel.md item 15)::an intake provider failure is not silent…, deploy/cloudflare-memory/runLedger.test.ts::the plane's checkpoint steers and the provider condition — the heartbeat body, /plane/park, /plane/level provider (record 0064)::a provider down and parked live run recover atomically… |
12a, 12b: the harness half — transient and rate-limited causes hold for at most two retries inside one minute and the run lease; authenticated credit/quota exhaustion ends immediately; post-loop follow-up turns use the same typed distinction and recovery. A pi-local abort or stream EOF uses lease-bounded backoff without provider-up release; intentional aborts keep their endings; other or missing evidence stays unknown, fails without retry and never renders provider refusal; typed refusal, permanent provider failure and transient provider failure remain separate. A transient provider hold can instead be released early by the plane's reissue steer within the same retry budget; a rate-limit wait cannot be shortened, exactly once as a prompt (two steers in one drain are one prompt, and another arriving while that prompt is in flight is checkpointed with them before restart); a reissue drained before the hold exists or settles is buffered, and a refused release keeps the hold; a stream cut uses the same local hold even when no park was reported | [unit] src/core/harness/pi/bridge.test.ts::turns, narration and the answer — the loop's rules::keeps local aborts and unknown terminal results outside the provider-failure types, ::turns, narration and the answer — the loop's rules::keeps proven provider refusal, permanent failure and transient failure as distinct terminal types, ::classifies incomplete OpenAI Responses streams as local retries without provider-down evidence, src/core/harness/pi/harness.test.ts::runPiHarness — a run on pi from the first file to the answer::an OpenAI stream ending without finish_reason is retried and the child survives, ::an abort while a model stream is open is retried as the same turn, ::an unknown terminal model result fails honestly without retrying or claiming a provider refusal, ::a parked provider failure on a park-capable run holds the turn and retries on backoff without waiting for another run; a plane reissue can still release it early, ::a temporary failure during a follow-up turn parks and resumes on provider-up without ending the run, …::a reissue steer drained before the hold exists is buffered, never an ordinary steer…, …::a reissue steer drained between the errored message_end and pi's settle waits for the settle…, …::a refused reissue prompt is a failed release…, …::a provider-up effect pushed into the owning live inbox reissues the parked turn immediately and acks done before any heartbeat or reclaim, …::a stream cut after a relayed success keeps the retry ladder even on a park-capable run…, …::a typed billing failure ends the run without another model prompt, …::the harness's park decision is the typed cause set, not a second prose classifier |
12a: a transport failure without a proxy retry reports the provider down and parks the run; a 200 gateway HTML document is sanitized rather than relayed; a relayed success reports up once per change; the plane's up atomically persists and offers each release, live-pushes it only after an atomic effect/owner fence and lease renewal, preserves it on failure/defer or a stale-generation ack, and deduplicates by inbox sequence | [unit] src/channels/modelProxy.test.ts::the provider level and the park (record 0064)::*, deploy/cloudflare-memory/runLedger.test.ts::the plane's checkpoint steers and the provider condition — the heartbeat body, /plane/park, /plane/level provider (record 0064)::a provider down and parked live run recover atomically as one durable row and one pushed offered steer; repeat up writes nothing, ::fences a pushed steer and renews its owner's lease atomically before registry delivery, ::a stale generation cannot close a steer offer after reclaim; the durable owner heartbeat receives and closes it, ::a failed provider-up transaction writes neither the durable row nor its steer effect, ::a failed live push leaves both the durable row and steer offer for an owner heartbeat; sealing first removes the park and produces no steer, src/channels/planeEffects.test.ts::POST /plane/effects — the push transport (record 0064)::the stale generation defers a pushed steer, then the durable owner heartbeat delivers and acks it, ::renews the durable owner fence before registry delivery, so reclaim cannot cross the local wake, ::rechecks drain after the durable fence returns, so handoff cannot wake stale registry state, src/core/runLedger/writeThrough.test.ts::events, state, heartbeat::an owner heartbeat delivers a live steer once; duplicate heartbeat and reclaim copies of its durable sequence are skipped |
6: the meter row on the span — a provider-priced turn stores the figure as reported with priceSource: provider, the biller and the card's vendor; a BYOK turn sums the fee and the upstream cost with feeUsd beside (without the upstream charge the fee rides beside the layer that prices the tokens, never as the turn's dollars); a turn with no wire cost and an operator entry prices operator; a registry card's tier above 272,000 input tokens prices the whole request at the tier | [unit] src/channels/modelProxy.test.ts::the meter row — biller, vendor, usd, priceSource (model-proxy item 6)::* |
6: the precedence as a pure function — provider over operator over registry over none, the BYOK sum, the tier rule counting cache reads and writes on the input side, the Anthropic family list as the registry layer's fallback, no usage → none whatever the card says | [unit] src/core/modelPricing.test.ts::priceTurn — the meter row's precedence (model-proxy item 6)::* |
6: the cost fields read beside the counters — OpenRouter's cost, is_byok and cost_details.upstream_inference_cost; a usage without a finite cost reports none | [unit] src/core/modelProxy/usage.test.ts::reportedCostOf (the wire's cost fields beside the counters)::* |
| 7: no log line carries the bearer, the key or any request or reply text | [unit] src/channels/modelProxy.test.ts::what the proxy never says::* |
9: the adapter refuses from the headers with the body unread and the request destroyed, pipes a stream chunk by chunk after flushing the headers, and a caller's close aborts the upstream call; every content type from the closed literal table with nosniff, an HTML-claiming upstream body written as plain text | [unit] src/channels/modelProxy.test.ts::createModelProxyHandler — the node adapter::* |
9, diagnostic: a clean response finish emits no unexpected-close observation; a close before writableFinished aborts upstream once and emits at most one safe log carrying only the run and local request ids, provided-clock close/elapsed time, request/response/socket booleans (or unknown), the witnessed response_close abort source and an allowlisted transport code when present; duplicate close emits once, missing or throwing transport-state getters stay unknown without escaping the listener, and timing, headers, bodies, prompts, credentials and raw errors neither enter the observation nor infer timeout, explicit stop or the root closer | [unit] src/channels/modelProxy.test.ts::createModelProxyHandler — the node adapter::a client that goes away mid-stream aborts the upstream call, src/channels/modelProxy.test.ts::createModelProxyHandler — the node adapter::unexpected response-close diagnostics::* |
| 8: the probe bearer — 201 with the bearer, expiry, model, path and turns for a live run and a log that names the operator and the run but never the bearer; unknown run 404, ended or expired 409; the door's 401 / 403 naming the grant / 503 / 405 from the headers; a malformed body 400; nothing minted on a refusal | [unit] src/channels/adminModelProxy.test.ts::POST /admin/model-proxy/bearer — an operator's bearer for a live run::* |
2: mintRunBearer pins the resolved provider and model and the preset's caps, expires at the profile's minutes plus the margin, hangs under the request root and publishes on the run's stream. Private/source-bound dispatch requires trusted transient proof before attach and binds it in the original run loop; all three model routes refuse missing, changed or throwing proof locally before forwarding or turn debit, recheck the same entry, revocation and expiry after async proof, and preserve the proof through issue/adopt/rotate/reparent; nothing without a store or for an unnamed provider | [unit] src/core/dispatch/provision.test.ts::mintRunBearer — the run's model-proxy bearer::*; src/channels/modelProxy.test.ts::current model source guard::*; src/core/dispatch/runLoop.test.ts::runLoop — the model turn and everything that rides on it::checks the final private destination after model-source validation: %s; src/channels/browserHomeInstallation.test.ts::browser installation::guards the next original bearer model request after plain-text deletion: %s |
2, 10: through dispatch() the bearer exists and verifies during the model turn with no proxied turn counted, carries the resolved model and the preset's caps, and is revoked once the run finishes; a refused dispatch mints nothing and a process without a store runs as before | [unit] src/core/dispatcher.test.ts::the model proxy's run bearer through dispatch()::* |
2: RunEnding.finished runs onFinished at once, before any seal, once per finish; a throwing hook is logged with the run id | [unit] src/core/runEnding.test.ts::createRunEnding — seal after the reply, records after the seal::finished(id) runs the onFinished hook at once… |
1: the shim's route table names the three paths model-proxy and no other /v1 path | [unit] src/core/trace/workerTrace.test.ts::shimRoute::names the model proxy's three paths as one route word, and no other /v1 path… |
| 1: the shim answers three paths itself and forwards every other to the container, knows nothing of the proxy's paths, and holds the model keys only to hand them into the container's environment — never in a header, a URL or a fetch | [unit] deploy/cloudflare/modelProxyForwarding.test.ts::the shim forwards the model proxy's paths to the container blind::* |
12: the record's trace — an unknown aggregator model's three unknowns (window, image input, levels) reported with their pins beside the cap field's; an endpoint lacking max_completion_tokens reported with capField: max_tokens; an endpoint naming reasoning without values reported as levels not stated; agreement is no drift; a card naming levels against no reasoning parameter pins the refusal | [unit] src/core/commands/providers.test.ts::compareCardToEndpoints — the pure comparison over the card and the endpoint::*, src/core/commands/providers.test.ts::endpointsFromJson — the provider's answer parsed to the fields the comparison reads::* |
| 12: the command reads each aggregator model the configuration names through the injectable fetch — never a direct block's — and reports the drift with its pins; a failing read is that model's error line; no aggregator model is answered plainly | [unit] src/core/commands/providers.test.ts::providers.check — the command over the registry with an injectable fetch::* |
12: the drift gate names a model the bumped registry dropped, by ref; a block without a catalog is never drift; configuredModelRefs collects the defaults' models and every block's override keys once each | [unit] src/core/commands/providers.test.ts::registryDrift — the drift gate between the pinned registry and the config's models::* |
12, live: providers check against the example config's aggregator models, its output posted on the tracking issue | [agent] Run npm run cli -- providers check on a checkout whose config/config.yaml names an aggregator block (or ask providers check in chat with the grant) and post the drift lines with their pins. |
11: an operator's exact models.<wire-id>.catalogModel selects catalog metadata without rewriting the wire id or inferring its vendor; absent or missing mappings retain unknown notices; registry fields carry registry provenance and unsupported effort remains refused; cap and cache still require separate facts when the catalog has none; malformed identities fail validation by name | [unit] src/core/modelCard.test.ts::resolveModelCard — operator over registry over wire::uses only an explicit exact catalog identity for a wire alias, preserving unknown vendor and unvouched cap and cache, src/config/validateModelOverride.test.ts::model catalog identity::accepts exact catalog ids and rejects malformed identities by name |
| 11: the exact public OpenRouter Codex ref resolves high effort, a 400000 window, automatic cache and the gateway cap without fallback notes; unknown cards and custom endpoints retain unvouched caps; both harness writes carry the exact model and output cap | [unit] src/core/modelCard.test.ts::resolveModelCard — operator over registry over wire::vouches all requested controls for the exact public OpenRouter Codex card, src/core/providerConformance.test.ts::provider conformance — pi::cap: the exact public gateway card vouches its cap across upstream spellings, src/core/providerConformance.test.ts::provider conformance — opencode::cap: the exact public gateway card vouches its cap across upstream spellings |
| 11: an undeclared public OpenRouter short name is refused before workspace resolution with its exact catalog ref; explicit aliases retain their wire identity | [unit] src/core/dispatch/resolve.test.ts::resolveTarget — the provider checked, and the target repo/ref/PR started::refuses a public OpenRouter short name with its exact ref before resolving a workspace, preserving declared aliases |
| 12: provider checks read the explicitly selected catalog identity, retain the wire alias in their results, and report a supported but unvouched cap with the explicit pin instead of silently treating it as vouched | [unit] src/core/commands/providers.test.ts::providers.check — the command over the registry with an injectable fetch::reads an explicitly mapped metadata id without rewriting the wire alias and reports supported but unvouched caps |
11: vendorOf names a direct block's vendor off the block name, an aggregator's off the id's first segment and a block named unlike its catalog by its declaration or its id — never its catalog; type: openai-compatible loads as wire: openai-chat; a malformed models.<id>.levels is refused at load by name, as is a models.<id>.answers that is not a list or names a word outside tool and text; the card layers operator over registry over wire with provenance per field, decideControls answers native/degraded/refused, and the record's trace rows pass on both drivers with the request body rendered from each harness's own write — the harness-write rows green, the write naming the biller's own provider (the aggregator package with usage.include, reasoning: { effort } and the top-level cache_control via settings.extraBody, the generic package only for a biller with no harness-side provider, whose markers card degrades with a note true on both harnesses), OpenCode's Responses row the declared cannot; no by-hand split of a ref survives outside parseModelRef and vendorOf | [unit] src/core/modelCard.test.ts::vendorOf — the one vendor parse::*, ::resolveModelCard — operator over registry over wire::*, ::decideControls — native, degraded or refused before the first call::*, src/core/modelRegistry.test.ts::the pi registry as a catalog::*, src/core/providerConformance.test.ts::the provider table::*, src/config/validate.test.ts::validateProviders — the block's declaration::* |
| 11: a registry card whose API matches the selected Anthropic Messages or OpenAI Responses wire vouches for that protocol's fixed cap field without a compatibility override; unknown models, absent or mismatched APIs, and generic Chat Completions without an explicit cap retain the unvouched decision; explicit operator and registry cap fields retain precedence | [unit] src/core/modelCard.test.ts::decideControls — native, degraded or refused before the first call::vouches for…, ::keeps an unspecified cap unvouched…, ::an unknown card sends the asked effort unvouched, the cap unvouched, the window degraded and the document as a stub, ::explicit registry and operator cap fields take precedence over a fixed protocol default |
| 11: an operator model price can include typed whole-request long-context tiers; load accepts complete finite nonnegative rates with a positive integer threshold and refuses malformed or duplicate tiers by name, so no declared tier is silently dropped | [unit] src/config/validate.test.ts::validateProviders — the block's declaration::accepts a complete long-context price tier and refuses malformed tiers by name |
| 1, 6, 8, live: a bearer minted for a real run buys one model call through the deployed bot, metered on that run's page | [agent] (needs an ingress bearer whose subject holds deploy:write.) Start any run and read its id from the card's Live run link or runs list. POST $SWITCHBOARD_BASE_URL/admin/model-proxy/bearer with authorization: Bearer $SWITCHBOARD_INGRESS_TOKEN and {"runId":"<id>"} → 201 with bearer, expiresAt, model, path. POST $SWITCHBOARD_BASE_URL<path> with x-api-key: <bearer> (the Anthropic shape; authorization: Bearer <bearer> on the OpenAI one) and {"model":"ignored","max_tokens":1,"stream":true,"messages":[{"role":"user","content":"Say OK."}]} → the provider's event stream, and the run page shows one more model turn on the preset's model with its token counts; the same request without the header → 401; with the bearer after the run has ended → 403 revoked; the bot's log carries [model-proxy] run=<id> turn=… and no body. |
| Criterion | Proof |
|---|---|
| Model-turn closure prevents model writes, including a delayed pending ACK, while an exact trusted same-run post-step retains the original recorder. A real authorization block or missing recorder never becomes post-step permission. | [unit] src/core/modelProxy/gitBindings.test.ts::model publication closure and original post-step claims::* |
| Identity ref publication uses the original durable recorder and preserves accepted, unknown or stopped native outcomes under the same run without a replacement owner or speculative CAS receipt. | [unit] src/core/dispatch/runLoop.test.ts::a resume with the answer in hand …::identity ref publication uses the original run's durable Door recorder: … |
The explanatory data-flow diagram is How model calls use capacity.
| 12a: billing makes one upstream request without a provider recovery wait; temporary failures have bounded attempts, delays, and saved restart reservations | [unit] src/channels/modelProxy.test.ts::the provider level and the park (record 0064)::billing failures stop after one upstream request without a provider recovery wait, src/core/providerRetry.test.ts::provider retry policy::*, src/core/harness/pi/harness.test.ts::runPiHarness — a run on pi from the first file to the answer::repeated temporary failures stop after three model attempts in initial and follow-up turns, ::rate limits honor Retry-After even when provider-up arrives before the wait in initial and follow-up turns, ::a tracked retry waits for the durable reservation acknowledgement before another model prompt, ::a refused retry reservation ends before another model prompt, src/core/harness/pi/harness.test.ts::*::a restart cannot replenish a spent model retry budget or issue a continuation |
| 12a: a replacement producer's first saved facts retain the original retry reservation; a restart continuation rechecks expiry after its durable acknowledgement and again when preceding FIFO controls finish, before model admission | [unit] src/core/harness/pi/harness.test.ts::runPiHarness — after a bot restart::the first replacement Pi facts retain a spent retry reservation before any continuation, ::a restart continuation cannot start after its recovery deadline passes during acknowledgement, ::a restart continuation cannot spend its recovery deadline behind queued … | | 12a: saved recovery deadlines belong to the active loop or post-loop turn; a valid post-loop retry is not rejected against the initial loop's spent deadline | [unit] src/core/harness/pi/harness.test.ts::runPiHarness — a run on pi from the first file to the answer::a post-loop turn can recover inside its own lease after the initial loop deadline |
| Criterion | Proof |
|---|---|
| The existing turn-admission gate joins pending tool-recovery checkpoints and refuses every provider wire after recovery stops, without consuming a turn or calling upstream. Model admission reads the run-owned controller, never an error string supplied by the model. | [unit] src/channels/modelProxyBudget.test.ts::tool recovery at model admission::joins pending recovery checkpoints and refuses every provider wire without spending a turn |
| Native result observation cannot race the next model request: the gate waits for the bridges' existing pending native calls and their outcomes before charging another turn. A confirmed OpenCode cut retains its unsettled record but does not block the write-up; a posted interrupt or a fresh live call still waits. | [unit] src/channels/modelProxyBudget.test.ts::tool recovery at model admission::waits for a gated native result before admitting another charged model call; ::admits the write-up after a confirmed OpenCode cut while preserving its unsettled result |
| Both recovery admission refusals use the existing proxy logger with the run ID and closed stop reason. Header admission keeps the body unread; turn admission still charges no model call. Logs contain no request body, arguments or bearer. | [unit] src/channels/modelProxyBudget.test.ts::tool recovery at model admission::logs stopped recovery at both admission boundaries without request data or model spend |
| Registry modality prefixes do not change public model IDs; only chat cards enter the run catalog. | [unit] src/core/modelRegistry.test.ts::the pi registry as a catalog::exposes public chat IDs without registry modality prefixes |
| 12c: a verified context overflow reaches the pinned adapter's native compaction detector without provider prose; hosted failures and prose alone grant no compaction authority. Initial and follow-up turns accept the later model answer without a second harness prompt; an unresolved overflow remains failed. A later transient provider error or local EOF still uses bounded recovery in either turn. | [unit] src/channels/modelProxyResponses.test.ts::Responses unknown terminal diagnostics::preserves a verified context overflow for native compaction; ::keeps overflow prose and hosted failures outside compaction authority; src/core/harness/pi/harness.test.ts::runPiHarness — a run on pi from the first file to the answer::lets native compaction recover a verified overflow without a second harness prompt; ::keeps a verified overflow failed when compaction produces no later accepted turn; ::recovers a later… |