Context
#16694 (operator friction→gold, HIGH) delivered transport self-supply for the Electron shell via #16708 / PR #16696: npm start spawns its own fleet child and the renderer receives the bearer through the shell IPC seam. The browser one-command flow (npm run cockpit, buildScripts/devCockpit.mjs) composes both processes and hands the launcher-generated bearer to the fleet child via env — but the page still cannot receive it without an agent: apps/agentos/app.mjs:47-51 documents that only "the Electron main process, the Neural Link, or a test init-script" may place globalThis.AgentOS.fleet.bearerToken before app start. The 2026-08-09 live roster runs (receipts on #16694) each required a Neural-Link wireFleetBridge injection — an agent in the loop of a flow whose whole point is zero coordination.
The sunset runbook on #16694 (comment IC_kwDODSospM8AAAABN-PPsA) names this exact gap as finish-line A: "a one-time loopback handshake endpoint the launcher serves and the page fetches" — never a URL param, never persisted.
The Problem
After npm run cockpit, the cockpit page boots fail-closed (installFleetBridge with bearerToken: null → every call rejects locally) even though a healthy authenticated transport is listening one port away with a bearer generated by the very launcher that opened the page. A human cannot hand-carry the 43-char secret (the #16694 premise); an agent injection is coordination by another name. The one-command flow therefore cannot deliver the live roster to an operator without a maintainer agent present.
The Architectural Reality
buildScripts/devCockpit.mjs:200-231 — the launcher already owns the bearer custody decision (generates in memory, hands to the fleet child via env, "never via URL, log, or file").
ai/services/fleet/fleetIngressAuth.mjs:57-99 — one pure admission guard: Host allowlist → exact-Origin policy (absent Origin admissible because the bearer still gates) → constant-time bearer check. Preflights admitted on Host+Origin alone.
ai/services/fleet/fleetBridgeServer.mjs:114-171 — routes after admission: GET /fleet/probe (bearer-gated), POST /fleet. Exact-origin CORS echo with Vary: Origin already implemented.
apps/agentos/app.mjs:38-77 — onStart is sync and reads the designed pre-boot slot globalThis.AgentOS.fleet.bearerToken; src/worker/App.mjs:691 calls module.onStart() after importApp — a dynamic import, so top-level await in app.mjs completes before onStart runs: a pre-boot async redemption can fill the designed slot without touching the boot contract.
apps/agentos/fleet/installFleetBridge.mjs — bearer format gate (FLEET_BEARER_PATTERN), refuses credential-shaped URL params; the slot consumer stays unchanged.
ai/configBase.mjs:272-350 — the fleet config subtree (ADR 0019 SSOT); cockpitOrigins is the existing exact-origin authority the handshake reuses.
The Fix
- Config leaf (
ai/configBase.mjs, fleet subtree): bearerHandshake: leaf(false, 'NEO_FLEET_BEARER_HANDSHAKE', 'boolean') — opt-in, default off, so a standalone npm run ai:fleet-server exposes zero new surface. Boolean leaf, not plane-anchored → no planeMember decision required.
- Admission (
fleetIngressAuth.mjs): guard option bearerHandshake; when armed, GET /fleet/handshake admits on Host + present-and-allowlisted Origin (stricter than preflight: absent Origin REFUSED — this endpoint serves browsers only; the boot probe never calls it), returning {admitted: true, handshake: true, corsOrigin} before the bearer check it exists to bootstrap. Guard stays pure/stateless.
- Route (
fleetBridgeServer.mjs): on decision.handshake, respond 200 {ok: true, result: {bearerToken}} with the exact-origin echo, Vary: Origin, Cache-Control: no-store, Connection: close; log the redemption (origin + timestamp — identity facts, never the bearer). Unarmed servers 404 the path (fail-closed default unchanged).
- Arming (
devCockpit.mjs): set NEO_FLEET_BEARER_HANDSHAKE: '1' in the fleet child env alongside NEO_FLEET_BEARER — arming is the launcher's custody decision, exactly where the bearer already lives. Reuse path (endpointStatus: 'fleet'): protocol identity is COMPATIBILITY, never adoption authority — reuse requires the AUTHENTICATED probeExistingFleetServer proof ("same token, same viewer", driven by a pinned NEO_FLEET_BEARER + the resolved identity claim); an incumbent this launcher cannot verify is REFUSED with the remediation named, before any credential-redeeming page opens. (Corrected per PR review cycle 1 — the original sentence claimed honest arming inheritance while silently adopting an unverified incumbent's credential and server-bound viewer; disclosure in the PR #16912 response comment.)
- Redemption module (NEW
apps/agentos/fleet/redeemFleetBearerHandshake.mjs, sibling of installFleetBridge.mjs, worker-realm, Neo-free): derive <fleet-origin>/fleet/handshake from the resolved fleet URL, bounded GET (AbortSignal.timeout), validate the bearer format, return token or null — fail-closed, never throws, response only ever held in memory.
- Pre-boot fill (
app.mjs): module-level top-level await — browser mode only, only when the designed slot is empty — places a successful redemption into globalThis.AgentOS.fleet.bearerToken; onStart stays byte-identical in behavior.
- Cockpit launch entry (
.claude/launch.json): add a cockpit configuration so the in-app browser pane can front the one-command flow.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
fleet.bearerHandshake leaf |
ADR 0019 (ai/configBase.mjs fleet subtree) |
opt-in boolean, env NEO_FLEET_BEARER_HANDSHAKE |
default false = today's surface exactly |
leaf JSDoc |
unit spec |
GET /fleet/handshake |
fleetIngressAuth guard + fleetBridgeServer route |
armed: bearer to exact-allowlisted browser Origins; unarmed: 404 |
fail-closed 403/404 |
module JSDoc threat-delta section |
fleetBridgeServer.spec.mjs + fleetIngressAuth cases |
redeemFleetBearerHandshake() |
NEW worker-realm module |
token or null, bounded, never throws |
null → existing fail-closed boot |
module JSDoc |
new spec (mirror tree) |
globalThis.AgentOS.fleet.bearerToken |
existing designed slot (app.mjs:47-51) |
one more launch path fills it pre-boot |
unchanged |
comment update names the handshake path |
installFleetBridge.spec.mjs untouched (slot contract identical) |
devCockpit child env |
buildScripts/devCockpit.mjs launch contract |
+ NEO_FLEET_BEARER_HANDSHAKE: '1' |
— |
file JSDoc |
devCockpit.spec.mjs |
The custody decision, named
While armed, any local process able to forge an Origin header on a loopback GET can redeem the bearer — armed dev-mode custody collapses browser-caller auth to exact-Origin (browsers cannot forge Origin; non-browser local processes can). Before: a local attacker needed process-memory/env access. This is a deliberate, opt-in, dev-only widening: the browser one-command topology is the transitional Option-B surface (app.mjs:66-68), the packaged Electron product path keeps strict custody, and the default-off leaf keeps every existing deployment at today's posture. Redemptions stay observable in the transport log. Multi-redemption while armed is chosen over one-shot deliberately: a page reload (F5) is a normal cockpit operation and must not strand the operator on a fail-closed page.
Decision Record impact
aligned-with ADR 0019 (declarative leaf; entrypoint reads at use site and injects at the named startFleetBridgeServer bootstrap boundary — the existing bearerToken/allowedOrigins shape). No ADR challenged.
Acceptance Criteria
Out of Scope
- Electron/shell topology (delivered,
#16708) and the packaged-product profile.
- The composed plane fleet-server (C2 landed the wire contract; its admission is PAT-based, not this bearer).
- Plane env ergonomics (
NEO_FLEET_PLANE_BASE/NEO_FLEET_PLANE_BEARER are one-process deployment declarations, not two-process coordination — the #16694 defect class this leaf closes).
- Any bearer-prompt UX.
Avoided Traps
- Bearer via URL param / persisted file — forbidden by the launch contract (
installFleetBridge refuses credential-shaped params; history/log persistence).
- Webpack dev-server middleware or proxy serving the secret — widens custody into the webpack process and couples generic build substrate to fleet secrets.
- One-shot redemption — strands every page reload; fail-closed-after-F5 punishes the normal operator action while stopping only the attacker class (forged-Origin local) that trivially wins the race anyway.
- Arming by default — silently changes the standalone fleet server's threat posture; the flag rides the launcher, where bearer custody already lives.
onStart made async / bridge re-install dance — src/worker/App.mjs:691 deletes Neo.bootingWindowId synchronously after onStart(); top-level await in the app module is the seam that needs no framework change.
Related
#16694 (umbrella, remaining-AC receipt) · #16708 (Electron sibling, delivered) · #16743 (C2 wire contract, closed) · #16787 (presence axis, merged 55219f40d8) · #14560 (Lane B epic).
Live latest-open sweep: latest 20 open issues checked at 2026-08-10T19:44Z — no equivalent (nearest: #16834 banner vocabulary, #16824 scoped-empty roster; both different surfaces). A2A sweep (last 15, all read-states): no overlapping claim.
Origin Session ID: ff94e740-acb8-4f25-a94b-b614bdd91ea1
Retrieval Hint: "browser cockpit bearer handshake redemption exact-origin armed devCockpit"
Context
#16694(operator friction→gold, HIGH) delivered transport self-supply for the Electron shell via#16708/ PR #16696:npm startspawns its own fleet child and the renderer receives the bearer through the shell IPC seam. The browser one-command flow (npm run cockpit,buildScripts/devCockpit.mjs) composes both processes and hands the launcher-generated bearer to the fleet child via env — but the page still cannot receive it without an agent:apps/agentos/app.mjs:47-51documents that only "the Electron main process, the Neural Link, or a test init-script" may placeglobalThis.AgentOS.fleet.bearerTokenbefore app start. The 2026-08-09 live roster runs (receipts on#16694) each required a Neural-LinkwireFleetBridgeinjection — an agent in the loop of a flow whose whole point is zero coordination.The sunset runbook on
#16694(commentIC_kwDODSospM8AAAABN-PPsA) names this exact gap as finish-line A: "a one-time loopback handshake endpoint the launcher serves and the page fetches" — never a URL param, never persisted.The Problem
After
npm run cockpit, the cockpit page boots fail-closed (installFleetBridgewithbearerToken: null→ every call rejects locally) even though a healthy authenticated transport is listening one port away with a bearer generated by the very launcher that opened the page. A human cannot hand-carry the 43-char secret (the#16694premise); an agent injection is coordination by another name. The one-command flow therefore cannot deliver the live roster to an operator without a maintainer agent present.The Architectural Reality
buildScripts/devCockpit.mjs:200-231— the launcher already owns the bearer custody decision (generates in memory, hands to the fleet child via env, "never via URL, log, or file").ai/services/fleet/fleetIngressAuth.mjs:57-99— one pure admission guard: Host allowlist → exact-Origin policy (absent Origin admissible because the bearer still gates) → constant-time bearer check. Preflights admitted on Host+Origin alone.ai/services/fleet/fleetBridgeServer.mjs:114-171— routes after admission:GET /fleet/probe(bearer-gated),POST /fleet. Exact-origin CORS echo withVary: Originalready implemented.apps/agentos/app.mjs:38-77—onStartis sync and reads the designed pre-boot slotglobalThis.AgentOS.fleet.bearerToken;src/worker/App.mjs:691callsmodule.onStart()afterimportApp— a dynamic import, so top-level await in app.mjs completes beforeonStartruns: a pre-boot async redemption can fill the designed slot without touching the boot contract.apps/agentos/fleet/installFleetBridge.mjs— bearer format gate (FLEET_BEARER_PATTERN), refuses credential-shaped URL params; the slot consumer stays unchanged.ai/configBase.mjs:272-350— thefleetconfig subtree (ADR 0019 SSOT);cockpitOriginsis the existing exact-origin authority the handshake reuses.The Fix
ai/configBase.mjs, fleet subtree):bearerHandshake: leaf(false, 'NEO_FLEET_BEARER_HANDSHAKE', 'boolean')— opt-in, default off, so a standalonenpm run ai:fleet-serverexposes zero new surface. Boolean leaf, not plane-anchored → noplaneMemberdecision required.fleetIngressAuth.mjs): guard optionbearerHandshake; when armed,GET /fleet/handshakeadmits on Host + present-and-allowlisted Origin (stricter than preflight: absent Origin REFUSED — this endpoint serves browsers only; the boot probe never calls it), returning{admitted: true, handshake: true, corsOrigin}before the bearer check it exists to bootstrap. Guard stays pure/stateless.fleetBridgeServer.mjs): ondecision.handshake, respond 200{ok: true, result: {bearerToken}}with the exact-origin echo,Vary: Origin,Cache-Control: no-store,Connection: close; log the redemption (origin + timestamp — identity facts, never the bearer). Unarmed servers 404 the path (fail-closed default unchanged).devCockpit.mjs): setNEO_FLEET_BEARER_HANDSHAKE: '1'in the fleet child env alongsideNEO_FLEET_BEARER— arming is the launcher's custody decision, exactly where the bearer already lives. Reuse path (endpointStatus: 'fleet'): protocol identity is COMPATIBILITY, never adoption authority — reuse requires the AUTHENTICATEDprobeExistingFleetServerproof ("same token, same viewer", driven by a pinnedNEO_FLEET_BEARER+ the resolved identity claim); an incumbent this launcher cannot verify is REFUSED with the remediation named, before any credential-redeeming page opens. (Corrected per PR review cycle 1 — the original sentence claimed honest arming inheritance while silently adopting an unverified incumbent's credential and server-bound viewer; disclosure in the PR #16912 response comment.)apps/agentos/fleet/redeemFleetBearerHandshake.mjs, sibling ofinstallFleetBridge.mjs, worker-realm, Neo-free): derive<fleet-origin>/fleet/handshakefrom the resolved fleet URL, bounded GET (AbortSignal.timeout), validate the bearer format, return token ornull— fail-closed, never throws, response only ever held in memory.app.mjs): module-level top-level await — browser mode only, only when the designed slot is empty — places a successful redemption intoglobalThis.AgentOS.fleet.bearerToken;onStartstays byte-identical in behavior..claude/launch.json): add acockpitconfiguration so the in-app browser pane can front the one-command flow.Contract Ledger Matrix
fleet.bearerHandshakeleafai/configBase.mjsfleet subtree)NEO_FLEET_BEARER_HANDSHAKEfalse= today's surface exactlyGET /fleet/handshakefleetIngressAuthguard +fleetBridgeServerroutefleetBridgeServer.spec.mjs+fleetIngressAuthcasesredeemFleetBearerHandshake()null, bounded, never throwsnull→ existing fail-closed bootglobalThis.AgentOS.fleet.bearerTokenapp.mjs:47-51)installFleetBridge.spec.mjsuntouched (slot contract identical)devCockpitchild envbuildScripts/devCockpit.mjslaunch contractNEO_FLEET_BEARER_HANDSHAKE: '1'devCockpit.spec.mjsThe custody decision, named
While armed, any local process able to forge an Origin header on a loopback GET can redeem the bearer — armed dev-mode custody collapses browser-caller auth to exact-Origin (browsers cannot forge Origin; non-browser local processes can). Before: a local attacker needed process-memory/env access. This is a deliberate, opt-in, dev-only widening: the browser one-command topology is the transitional Option-B surface (
app.mjs:66-68), the packaged Electron product path keeps strict custody, and the default-off leaf keeps every existing deployment at today's posture. Redemptions stay observable in the transport log. Multi-redemption while armed is chosen over one-shot deliberately: a page reload (F5) is a normal cockpit operation and must not strand the operator on a fail-closed page.Decision Record impact
aligned-with ADR 0019 (declarative leaf; entrypoint reads at use site and injects at the named
startFleetBridgeServerbootstrap boundary — the existingbearerToken/allowedOriginsshape). No ADR challenged.Acceptance Criteria
GET /fleet/handshakeis 404; guard/server specs prove no new admitted surface.no-store; armed + absent/foreign/nullOrigin: refused (403), no CORS grant.redeemFleetBearerHandshakespec: success, refusal, timeout, malformed-body, malformed-token →nullon every non-success path.devCockpitspec: spawned fleet child env carries the arm flag; plan/probe semantics unchanged.npm run cockpit(plane env exported) → cockpit renders the live roster with presence bands, zero agent involvement in the bearer hand-off — posted to#16694as its remaining-AC receipt.Out of Scope
#16708) and the packaged-product profile.NEO_FLEET_PLANE_BASE/NEO_FLEET_PLANE_BEARERare one-process deployment declarations, not two-process coordination — the#16694defect class this leaf closes).Avoided Traps
installFleetBridgerefuses credential-shaped params; history/log persistence).onStartmade async / bridge re-install dance —src/worker/App.mjs:691deletesNeo.bootingWindowIdsynchronously afteronStart(); top-level await in the app module is the seam that needs no framework change.Related
#16694(umbrella, remaining-AC receipt) ·#16708(Electron sibling, delivered) ·#16743(C2 wire contract, closed) ·#16787(presence axis, merged55219f40d8) ·#14560(Lane B epic).Live latest-open sweep: latest 20 open issues checked at 2026-08-10T19:44Z — no equivalent (nearest:
#16834banner vocabulary,#16824scoped-empty roster; both different surfaces). A2A sweep (last 15, all read-states): no overlapping claim.Origin Session ID: ff94e740-acb8-4f25-a94b-b614bdd91ea1
Retrieval Hint: "browser cockpit bearer handshake redemption exact-origin armed devCockpit"