devFleetServer ignores fleet.planeBearerFile and then refuses plane mode with the wrong remediation
Context
Found during the #17271 intake (recorded there as adjacent debt). The fleet.planeBearer leaf's own JSDoc (ai/configBase.mjs) promises the two-home custody split — direct value wins, else NEO_FLEET_PLANE_BEARER_FILE names a secret file, "Read at the use site by the Fleet entry, never at import." The composed Fleet server honors it through resolveFleetPlaneBearer (ai/services/fleet/fleetServer.mjs:640). The DEV entry does not: devFleetServer.mjs constructs the plane mailbox client with credential: AiConfig.fleet.planeBearer — the direct leaf only.
The Problem
A deployment on the file custody class (the canonical Compose shape — "env literals are the wrong custody class for credentials", per the leaf's own comment) boots npm run ai:fleet-server or npm run cockpit with NEO_FLEET_PLANE_BEARER_FILE set and the direct value empty. The dev entry reads the empty direct leaf, the plane client sends no Authorization header, plane admission refuses, and the boot dies with fix fleet.planeBase / fleet.planeBearer — a remediation that names the WRONG fix while the configured file sits unread. The file indirection the leaf documents is silently inert on the dev journey.
The Architectural Reality
devFleetServer.mjs already imports from fleetServer.mjs (assertFleetPlaneAdmissionBearerClass, assertFleetViewerMcAuthorizationClass) — the resolver sits beside them, so the fix is an import + a swap, zero new machinery.
resolveFleetPlaneBearer({aiConfig}) is the sanctioned use-site read (ADR-0019): direct wins, else the file, else '' — and the dev entry's existing empty-credential behavior (tokenless planes decide admission themselves) is preserved by the '' fall-through.
- The wake-stream admission credential (
assertFleetPlaneAdmissionBearerClass) already resolves both homes via resolveFleetPlaneAdmissionBearer — only the MCP mailbox credential has the gap.
The Fix
devFleetServer.mjs builds the plane client credential through resolveFleetPlaneBearer() instead of reading the direct leaf. Unit witness: a FILE-only boot resolves the file's value into the client credential (the resolver's injection seam keeps the spec hermetic); the direct-wins precedence is pinned against regression.
Decision Record impact
aligned-with ADR 0019 — completes the leaf's documented contract at the dev entry; no new leaves, no re-derivation (the resolver IS the use-site read).
Acceptance Criteria
Out of Scope
The launcher-side file materialization shipped in #17276 stays as-is (it materializes the file into the child's direct env — compatible with this fix, and the launcher's custody story remains its own) · the admission-bearer and viewer-MC chains (already two-home).
Related
#17271 (the intake that surfaced it) · #17276 / PR #17277 (the journey whose launcher papers over it for the composed path) · ADR-0019.
Live latest-open sweep: latest 20 open checked 2026-08-17T08:05Z, no equivalent; A2A herd-window sweep clean.
Origin Session ID: 0c5a1cf3-093b-4e9d-a7ba-74137e4d4f23
Retrieval Hint: query_raw_memories("devFleetServer planeBearerFile file custody resolveFleetPlaneBearer misleading refusal")
devFleetServerignoresfleet.planeBearerFileand then refuses plane mode with the wrong remediationContext
Found during the #17271 intake (recorded there as adjacent debt). The
fleet.planeBearerleaf's own JSDoc (ai/configBase.mjs) promises the two-home custody split — direct value wins, elseNEO_FLEET_PLANE_BEARER_FILEnames a secret file, "Read at the use site by the Fleet entry, never at import." The composed Fleet server honors it throughresolveFleetPlaneBearer(ai/services/fleet/fleetServer.mjs:640). The DEV entry does not:devFleetServer.mjsconstructs the plane mailbox client withcredential: AiConfig.fleet.planeBearer— the direct leaf only.The Problem
A deployment on the file custody class (the canonical Compose shape — "env literals are the wrong custody class for credentials", per the leaf's own comment) boots
npm run ai:fleet-serverornpm run cockpitwithNEO_FLEET_PLANE_BEARER_FILEset and the direct value empty. The dev entry reads the empty direct leaf, the plane client sends no Authorization header, plane admission refuses, and the boot dies withfix fleet.planeBase / fleet.planeBearer— a remediation that names the WRONG fix while the configured file sits unread. The file indirection the leaf documents is silently inert on the dev journey.The Architectural Reality
devFleetServer.mjsalready imports fromfleetServer.mjs(assertFleetPlaneAdmissionBearerClass,assertFleetViewerMcAuthorizationClass) — the resolver sits beside them, so the fix is an import + a swap, zero new machinery.resolveFleetPlaneBearer({aiConfig})is the sanctioned use-site read (ADR-0019): direct wins, else the file, else''— and the dev entry's existing empty-credential behavior (tokenless planes decide admission themselves) is preserved by the''fall-through.assertFleetPlaneAdmissionBearerClass) already resolves both homes viaresolveFleetPlaneAdmissionBearer— only the MCP mailbox credential has the gap.The Fix
devFleetServer.mjsbuilds the plane client credential throughresolveFleetPlaneBearer()instead of reading the direct leaf. Unit witness: a FILE-only boot resolves the file's value into the client credential (the resolver's injection seam keeps the spec hermetic); the direct-wins precedence is pinned against regression.Decision Record impact
aligned-with ADR 0019— completes the leaf's documented contract at the dev entry; no new leaves, no re-derivation (the resolver IS the use-site read).Acceptance Criteria
NEO_FLEET_PLANE_BEARER_FILE-only boot of the dev fleet entry resolves the file's value as the plane credential (witnessed).Out of Scope
The launcher-side file materialization shipped in #17276 stays as-is (it materializes the file into the child's direct env — compatible with this fix, and the launcher's custody story remains its own) · the admission-bearer and viewer-MC chains (already two-home).
Related
#17271 (the intake that surfaced it) · #17276 / PR #17277 (the journey whose launcher papers over it for the composed path) · ADR-0019.
Live latest-open sweep: latest 20 open checked 2026-08-17T08:05Z, no equivalent; A2A herd-window sweep clean.
Origin Session ID: 0c5a1cf3-093b-4e9d-a7ba-74137e4d4f23
Retrieval Hint:
query_raw_memories("devFleetServer planeBearerFile file custody resolveFleetPlaneBearer misleading refusal")