Repository navigation
CONTRACT: feat(ui-server): run governed and strict Claude turns on the turn egress proxy on managed storage - #2645
Merged
schlessera merged 9 commits intoOct 22, 2026
Conversation
…call The turn proxy cannot tell which process opened a connection, so G5's exception follows the tool calls in progress: between a call's outward classification and its result. A connection to an outward host skips its card only while every classified call in progress is a visible read or an approved outward action naming that host. Any unknown or opaque call in progress withholds the exception. The backend receives a bridge that feeds these observations to the proxy. A host without a governance recorder still enforces the turn's egress policy; only the records are absent. Refs #1665 Co-Authored-By: Claude Opus 5.5 <[email protected]>
…e turn egress proxy The gate allows default-on when each failing path's gap is stated in the capabilities note. A governed or strict Claude turn now gets its own turn proxy and the host inference relay. Each connection is a durable governance event attributed to the turn, and a recording failure ends the turn. The capabilities note states recorded egress, and the remaining reachable-socket gap (#2633), only when the backend actually routed the worker through the proxy. API-key Claude profiles and Pi keep their current network and note. The internal WorkerNoteFacts gains optional egress; the package-api contract component records the automatic server selection. Moved run-session and worker-sandbox citations follow their anchors. Refs #1665 Co-Authored-By: Claude Opus 5.5 <[email protected]>
schlessera
added this pull request to stack #2646
October 22, 2026 12:19
This was referenced Oct 22, 2026
…gress-activation-044
…tom Claude origins Per the maintainer's 2026-10-22 rulings on #1665: - Governed and strict Claude turns run on the turn egress proxy only when the native manager acquired their startup view with its stage operations. Elsewhere they keep today's network. `runtime.workerHost.governed.egress` (`{ active, detail }`) reports which applies. - The Claude inference relay forwards to the server-selected route (profile baseUrl, else ANTHROPIC_BASE_URL) and releases the host login to a custom origin only when BRAIN_UI_CLAUDE_OAUTH_ORIGINS lists it exactly. An unlisted origin refuses at the prompt gate and again per dispatch, before the credential is read. Refs #1665 Co-Authored-By: Claude Opus 5.5 <[email protected]>
…ss status - Model-discovery cache entries live in the brain, which a governed worker can write. Read back only the fields discovery writes, so a cached entry can no longer supply a profile's baseUrl, credential variable names, aliases or tools. - Compare native settings with the server's route as a canonical origin and base path, so an equivalent spelling is accepted and another path refused. - Worker-host egress status names subscription Claude turns as routed and API-key Claude profiles and Pi as not yet routed. - Regenerate the API reports for the new internal exports and fields. Refs #1665 Co-Authored-By: Claude Opus 5.5 <[email protected]>
…e env descriptor test Refs #1665 Co-Authored-By: Claude Opus 5.5 <[email protected]>
schlessera
marked this pull request as ready for review
October 22, 2026 17:21
schlessera
marked this pull request as draft
October 22, 2026 17:22
…gress-activation-044 # Conflicts: # docs/decisions/governed-writable-workers.md # packages/ui-backend-claude/src/turn-runner.ts
…gress-activation-044 The parent's socket walk now passes with all of /var masked (#2657), so the capabilities note, changeset, hosting doc and decision record drop the reachable-socket gap; Pi inference remains the stated gap. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…gress-activation-044 # Conflicts: # docs/decisions/governed-writable-workers.md
schlessera
marked this pull request as ready for review
October 22, 2026 21:22
This was referenced Oct 22, 2026
7 of 8 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #1665
Top layer of the #1665 stack. Parent: #2644 (
feat/1665-egress-measurement-044). Bottom: #2399.Round 2 applies the maintainer's 2026-10-22 rulings on #1665 (comment), which removes the two regressions that blocked round 1:
ws-scope-capspasses again.BRAIN_UI_CLAUDE_OAUTH_ORIGINSlists it. On copied-fixture hostssubscription-loginkeeps today's behaviour. On managed hosts an operator must list the origin.What changed
egress_connectionevent, and a recording failure ends the turn. A connection to an outward host skips its approval card only while every classified call in progress is a visible read, or an approved outward action naming that host. Round 1 built this part; it is kept unchanged.GovernedTurnHold.managedStoragecomes fromisManagedWorkerStartupView, the same attestation the relay's route guard needs.run-sessioncomposes the proxy only when it is true.runtime.workerHost.governed.egressis{ active, detail }. It isactive: trueon a Btrfs subvolume root namedbrain.detailnames the routed turns (subscription Claude) and the unrouted ones (API-key Claude, Pi). This is an additive contract change, documented indocs/integration-contract/http.md.baseUrlfromBRAIN_UI_CLAUDE_PROFILES, else the server'sANTHROPIC_BASE_URL, else first-party. The base path is kept, and the worker's base URL ishttp://localhost<basePath>. Onlyhttps://api.anthropic.comand the exact origins inBRAIN_UI_CLAUDE_OAUTH_ORIGINSmay receive the login. Anything else is refused twice before the credential is read: at the prompt gate, and again on every dispatch. Native settings may repeat the server's route in an equivalent spelling (same canonical origin and base path), but never move it. Redirects stay refused.baseUrl, credential variable names, aliases or tools. An independent review found this; without the fix, brain content could pick the route within the authorized origins.Why this shape
Ruling 1 limits default-on to managed storage, and leaves the relay-without-storage work to a follow-up: #2654. In production
createApprefuses to boot without the native manager, so every hosted turn gets the proxy. The no-storage path is what copied-fixture hosts and directWsHostcompositions use.Ruling 2's decision record requires an explicit authorization "in trusted host configuration for this credential", but no record named the shape. The coordinator chose a server environment variable (recorded in
claude-code-runtime.md; the maintainer may override it). Brain content cannot reach the server environment. The host has one Claude login, so the list is per credential.Migration
An operator whose subscription profile
baseUrlorANTHROPIC_BASE_URLpoints Claude at a custom origin must add that exact origin toBRAIN_UI_CLAUDE_OAUTH_ORIGINS(for examplehttps://gateway.ithaca.invalid). Otherwise those turns are refused before their prompt is sent. The changeset anddocs/hosting/agent-workers.mdsay so.Proof
Head
06c3cec79, parent547795928(#2644, refreshed ontoorigin/main4ba50d824).Round 3 refresh. The coordinator's stack merge hit conflicts against main. This layer took the refreshed parent in three merges:
2165d2a8c:turn-runner.tsnow imports both main'sambientExecConfig(CONTRACT: fix(backends): use the host app exec wrapper #2397) and this layer'sresolveEnv. ThehostSpawncitation ingoverned-writable-workers.mdis re-pointed.75d884f48: fix(ui-sdk): mask all host /var in worker profiles #2657 masks all of/var, and the parent's socket walk now passes. The capabilities note, changeset, hosting doc and decision record therefore drop the reachable-socket gap; Pi inference remains the stated gap. TheEGRESS_NOTE_STATES_SOCKET_GAPexpectation went with that sentence.06c3cec79: after fix(ui-server): keep host Git credentials out of workers #2671, thehostSpawncitation is re-pointed again (host-boundary.ts:447-458).Stack squash simulation. In a scratch worktree at
origin/main4ba50d824, each layer's own diff (parent head → layer head) was squashed on in order: #23998406e0062, #2644547795928, #264506c3cec79. All three applied without conflict, and the final tree equals this head's tree. Each head contains4ba50d824and merges cleanly withorigin/mainon its own.Result on
06c3cec79: passed (exit 0). Lint, metadata and the fast phase pass (763 tests / 36 files), and import-graph feedback passes 3801 tests across 330 files. An earlier round 3 run on this head was killed from outside and produced no result.Round 2's result on
acc8e1ac1: passed (exit 0), 744 fast and 3724 import-graph tests. An earlier run on this head failed two tests while another preflight held the shared per-UID maintenance ledger (Maintenance temporary storage is busy, and scratch left read-only). Both files pass in isolation:sync-mirrorandnative-worker-startup-order, 15/15. The run before that found the env-descriptor list missing the new variable, which is fixed inacc8e1ac1.bun scripts/prove-native-host.ts --backend=claudepassed on head06c3cec79for app uid 0 and 1000 (2/2 each, exit 0). Both boot receipts reportgoverned.egress.active: true.c5c0191af(the gate commit; the later commits change only the route comparison, cache reading, status wording, API reports and tests). On a real managed brain, the boot receipt reportsgoverned.egress.active: true, and governed turns are admitted and complete. The proof does not yet assert per-connection records from that harness.native-route-qualification(13, real native CLI offline in bwrap),turn-egress-composition(3),worker-host-status,ws-scope-caps(4),subscription-login(2),model-discovery,env,env-parity,api-surfaceanddecision-citations(44). Types and lint are clean.1db46b91a,ws-scope-capsfails both Claude cases withNative output requires a manager-acquired immutable startup view and configured stage operations.On the pre-change runner source, the new custom-origin cases fail:CUSTOM_ORIGIN_LISTED_RECEIVES_HOST_LOGIN, andCUSTOM_ORIGIN_UNLISTED_REFUSED_AT_THE_GATEin both unlisted cases.run-session: both Claudews-scope-capscases fail with the native-output error, andTURN_EGRESS_UNMANAGED_STORAGE_KEEPS_NETWORKfails.TURN_EGRESS_STATUS_UNMANAGED_INACTIVEfails.CUSTOM_ORIGIN_UNLISTED_REFUSED_AT_THE_GATEfails for unlisted and other-port. The per-dispatch check still holds, so no credential is forwarded.CUSTOM_ORIGIN_UNLISTED_NO_CREDENTIAL_FORWARDINGfails, because the login is forwarded to the unlisted gateway.CUSTOM_ORIGIN_LISTED_RECEIVES_HOST_LOGINfails.CUSTOM_ORIGIN_UNLISTED_NO_CREDENTIAL_FORWARDINGfails for the other-port case.CUSTOM_ORIGIN_PROJECT_SAME_ROUTE_ALLOWEDfails.CUSTOM_ORIGIN_PROJECT_OTHER_ROUTE_NO_FORWARDINGfails.MODEL_CACHE_CARRIES_NO_ROUTE_OR_CREDENTIALfails.c5c0191afadversarially. It found four issues, all fixed ine159ab1bf/acc8e1ac1: the brain-writable model cache supplying a route, raw-string route comparison, status overstating API-key turns, and stale API reports. Base-path handling and redirect/refresh recipients were verified with no findings.turn-runner.ts,run-session.ts,governed-turn.ts,host-boundary.ts,protocol.ts,config/env.tsandworker-startup-view.ts. Every citation under them is re-anchored, and all 44 citation checks pass.Checklist
docs/reflect the change, or nothing documented moved.docs/decisions/provider-history.md): the provider-history guard passes, or nothing on a turn's path changed.Anything left open
Merging this layer turns egress on by default for every hosted governed or strict subscription Claude turn on managed storage, which is every production
createApphost.Remaining #1665 work, not delivered by this stack:
brain-hosting-templateshould listBRAIN_UI_CLAUDE_OAUTH_ORIGINSin its environment contract once this ships.🤖 Generated with Claude Code