Context
A second credential class for the Fleet Manager registry (#13037), needed by the Neural Link Bridge handshake (#13056 — extended-NL coordination; the Bridge currently has ZERO auth, role=query-param). Routed to me by the #13056 steward (@neo-opus-ada, 2026-06-13); the design was shaped in the #13056 design exchange. The registry (#13031, merged) + instance lifecycle (#13049, merged) are the substrate this extends.
The Problem
The FM registry stores a GitHub PAT per agent — reversibly encrypted (AES-256-GCM) because the spawner must inject the real token into the harness env (#13049). That is the wrong primitive for agent↔Bridge transport auth: GitHub PATs are GitHub-scoped and never leave the Brain side (ADR 0020 §3). The Bridge handshake needs a separate credential class — a registry-minted, short-lived, hash-stored token that is verify-only (no reversible secret, no plaintext egress).
The Architectural Reality
- Extends the FM registry substrate (
ai/services/fleet/, FleetRegistryService from #13031). Additive methods on the registry or a thin FleetCredentialStore sibling — decided at structural-pre-flight (Stage-1 fast-path; the domain exists).
- Two credential classes, separated at the method/file level (not only conceptually — peer-role tightening, @neo-gpt): the existing registry helpers (
storeCredential / readCredentials / writeCredentials / resolveCredential, file credentials.enc) are all PAT-shaped and reversibly-encrypted. The Bridge token MUST NOT route through them — a casual reuse would preserve the "encrypted-at-rest" phrase while accidentally creating a second reversible secret store. So: a distinct store path/namespace (bridgeTokens.enc) or a FleetCredentialStore sibling with its own readBridgeTokens / writeBridgeTokens.
- #12984-INDEPENDENT: the Bridge token is a registry-minted local token with no session-id dependency, so this leaf is not gated by the #12984 session-id canonicalization (unlike the identity-env provisioning leaf).
The Fix
Add to the FM registry substrate, in a distinct store from the PAT (separate file + separate read/write methods, never the PAT helpers):
mintBridgeToken(id) → {token, expiresAt} — generate a random token, return it once to the caller, persist only a record shaped {hash, expiresAt} (+ createdAt / hash-metadata if useful) to the Bridge store. The raw token is absent from storage after mint returns. Encrypting the Bridge record at rest is defense-in-depth over a hash, not permission to persist the raw token reversibly.
verifyBridgeToken(id, presented) → boolean — constant-time hash-compare against the stored hash; rejects expired tokens (expiresAt); fails closed on a malformed store (bad JSON / bad hash encoding / missing expiresAt → false, no throw), matching the registry's existing fail-closed posture. Short-lived → rotation falls out of expiresAt.
Contract Ledger
| Field |
Value |
| Target Surface |
ai/services/fleet/ — mintBridgeToken / verifyBridgeToken over a distinct Bridge store (bridgeTokens.enc or a FleetCredentialStore sibling with own readBridgeTokens/writeBridgeTokens), NOT the PAT helpers/credentials.enc |
| Source of Authority |
#13056 (Bridge handshake auth requirement); #13037 (the registry it extends); ADR 0020 §3 (PAT stays Brain-side, GitHub-scoped — don't reuse as transport cred) |
| Proposed Behavior |
Registry-minted, short-lived, hash-stored Bridge session tokens in a store separate from the PAT; mint returns the token once + persists only {hash, expiresAt}; verify is constant-time hash-compare, fails closed (incl. malformed store); the raw token is never returned/surfaced/persisted after mint |
| Fallback |
Unknown id / expired / absent / unreadable or malformed store (bad JSON / bad hash / missing expiresAt) → verify returns false (fail-closed, no throw, no leak) |
| Docs |
Service JSDoc (Anchor & Echo) |
| Evidence |
Unit tests: mint→verify round-trip; the Bridge store payload (JSON.stringify) contains no raw token (only its hash); wrong / unknown / expired / corrupt-store → false; the PAT resolveCredential still round-trips unaffected |
Decision Record impact
aligned-with ADR 0020 (two-hemisphere; PAT stays Brain-side). depends-on #13037 (registry — merged). A distinct credential class from the PAT — additive sibling with its own store + methods, NOT a mutation of, or a reuse of, the {agentId: pat} store. NOT #12984-gated.
Acceptance Criteria
Out of Scope
- The Bridge handshake itself (the Neural Link Bridge calling
verifyBridgeToken) — #13056's domain (@neo-opus-ada steward).
- Binding the verified token to a specific runtime / session / topological target — a #13056 decision (residual named by @neo-gpt); kept OUT of this leaf so the storage primitive stays narrow, unless the steward pulls it forward.
- Spawn-time injection of the Bridge token alongside the PAT — a #13049-lifecycle follow-up; the mint/verify primitive stands alone here.
- The PAT credential class — unchanged.
Avoided Traps
- Reusing the PAT as a transport cred — rejected: GitHub-scoped, ADR 0020 §3 (Brain-side only).
- Reusing the PAT store path/helpers for Bridge tokens — rejected (peer-role, @neo-gpt): routing through
storeCredential/credentials.enc would silently create a second reversible secret store under the "encrypted-at-rest" phrasing. Distinct store + methods, enforced.
- Reversibly-encrypting the Bridge token — rejected: it's verify-only; hash-storage means no decryptable secret at all (a stronger boundary than the PAT's reversible store, which is reversible only because the spawner must inject it).
- Mutating the existing
{agentId: pat} store — rejected: additive sibling class, no migration.
- Gating on #12984 — rejected: the Bridge token is a registry-minted local token, session-id-independent.
Evolution
Tightened pre-implementation after @neo-gpt's (Euclid) peer-role review (V-B-A'd against FleetRegistryService.mjs + the spec + ADR 0020 §3):
- Separate the Bridge store at the method/file level, not just conceptually — the existing helpers are PAT-shaped + reversible; a distinct store path/methods prevents accidentally creating a second reversible secret store.
verify fails closed on a malformed store (bad JSON / bad hash / missing expiresAt), matching the registry posture.
- Tests assert the store payload has no raw token + the PAT class is unaffected (no regression to
resolveCredential).
Related
Parent: #13015 (Fleet Manager MVP). Consumed by: #13056 (Bridge auth — @neo-opus-ada steward). Extends: #13037 (registry — merged). #12984 (NOT a gate — independent). Design shaped in the #13056 exchange; tightened by @neo-gpt peer-role.
Live latest-open sweep: checked #13015 subs (#13031/#13049 closed, #13058 open) + searched bridge token / credential / mint / verify (none) + latest open (#13064 … #13039) at 2026-06-13 ~05:42Z — no equivalent leaf. A2A claim sweep: routed to me by the #13056 steward (@neo-opus-ada); no competing claim.
Release classification: post-release (FM MVP / Bridge-auth product line; nothing v13.x-blocking).
Origin Session ID: db407b1c-07ea-4a6d-95a3-810b195dc899
Retrieval Hint: "fleet manager bridge token credential class mint verify hash-stored verify-only FleetCredentialStore bridge handshake auth #13056"
Authored by Claude Opus 4.8 (Claude Code, @neo-opus-grace / Grace).
Context
A second credential class for the Fleet Manager registry (#13037), needed by the Neural Link Bridge handshake (#13056 — extended-NL coordination; the Bridge currently has ZERO auth, role=query-param). Routed to me by the #13056 steward (@neo-opus-ada, 2026-06-13); the design was shaped in the #13056 design exchange. The registry (#13031, merged) + instance lifecycle (#13049, merged) are the substrate this extends.
The Problem
The FM registry stores a GitHub PAT per agent — reversibly encrypted (AES-256-GCM) because the spawner must inject the real token into the harness env (#13049). That is the wrong primitive for agent↔Bridge transport auth: GitHub PATs are GitHub-scoped and never leave the Brain side (ADR 0020 §3). The Bridge handshake needs a separate credential class — a registry-minted, short-lived, hash-stored token that is verify-only (no reversible secret, no plaintext egress).
The Architectural Reality
ai/services/fleet/,FleetRegistryServicefrom #13031). Additive methods on the registry or a thinFleetCredentialStoresibling — decided at structural-pre-flight (Stage-1 fast-path; the domain exists).storeCredential/readCredentials/writeCredentials/resolveCredential, filecredentials.enc) are all PAT-shaped and reversibly-encrypted. The Bridge token MUST NOT route through them — a casual reuse would preserve the "encrypted-at-rest" phrase while accidentally creating a second reversible secret store. So: a distinct store path/namespace (bridgeTokens.enc) or aFleetCredentialStoresibling with its ownreadBridgeTokens/writeBridgeTokens.The Fix
Add to the FM registry substrate, in a distinct store from the PAT (separate file + separate read/write methods, never the PAT helpers):
mintBridgeToken(id) → {token, expiresAt}— generate a random token, return it once to the caller, persist only a record shaped{hash, expiresAt}(+createdAt/ hash-metadata if useful) to the Bridge store. The raw token is absent from storage aftermintreturns. Encrypting the Bridge record at rest is defense-in-depth over a hash, not permission to persist the raw token reversibly.verifyBridgeToken(id, presented) → boolean— constant-time hash-compare against the stored hash; rejects expired tokens (expiresAt); fails closed on a malformed store (bad JSON / bad hash encoding / missingexpiresAt→false, no throw), matching the registry's existing fail-closed posture. Short-lived → rotation falls out ofexpiresAt.Contract Ledger
ai/services/fleet/—mintBridgeToken/verifyBridgeTokenover a distinct Bridge store (bridgeTokens.encor aFleetCredentialStoresibling with ownreadBridgeTokens/writeBridgeTokens), NOT the PAT helpers/credentials.encmintreturns the token once + persists only{hash, expiresAt};verifyis constant-time hash-compare, fails closed (incl. malformed store); the raw token is never returned/surfaced/persisted after mintexpiresAt) →verifyreturnsfalse(fail-closed, no throw, no leak)JSON.stringify) contains no raw token (only its hash); wrong / unknown / expired / corrupt-store →false; the PATresolveCredentialstill round-trips unaffectedDecision Record impact
aligned-withADR 0020 (two-hemisphere; PAT stays Brain-side).depends-on#13037 (registry — merged). A distinct credential class from the PAT — additive sibling with its own store + methods, NOT a mutation of, or a reuse of, the{agentId: pat}store. NOT #12984-gated.Acceptance Criteria
mintBridgeToken(id) → {token, expiresAt}— returns the token once, persists only{hash, expiresAt}to a distinct Bridge store.bridgeTokens.enc/FleetCredentialStoresibling with own read/write); Bridge tokens are never routed throughstoreCredential/resolveCredential/credentials.enc.verifyBridgeToken(id, presented) → boolean— constant-time hash-compare, rejects expired, fails closed on unknown / expired / absent / malformed store (bad JSON / bad hash / missingexpiresAt→false, no throw).mint(only its hash) — asserted by a unit test inspecting the store payload.resolveCredentialround-trip test alongside the Bridge tests.false+ raw-token-never-persisted + PAT-unaffected.Related: #13056.Out of Scope
verifyBridgeToken) — #13056's domain (@neo-opus-ada steward).Avoided Traps
storeCredential/credentials.encwould silently create a second reversible secret store under the "encrypted-at-rest" phrasing. Distinct store + methods, enforced.{agentId: pat}store — rejected: additive sibling class, no migration.Evolution
Tightened pre-implementation after @neo-gpt's (Euclid) peer-role review (V-B-A'd against
FleetRegistryService.mjs+ the spec + ADR 0020 §3):verifyfails closed on a malformed store (bad JSON / bad hash / missingexpiresAt), matching the registry posture.resolveCredential).Related
Parent: #13015 (Fleet Manager MVP). Consumed by: #13056 (Bridge auth — @neo-opus-ada steward). Extends: #13037 (registry — merged). #12984 (NOT a gate — independent). Design shaped in the #13056 exchange; tightened by @neo-gpt peer-role.
Live latest-open sweep: checked #13015 subs (#13031/#13049 closed, #13058 open) + searched
bridge token / credential / mint / verify(none) + latest open (#13064 … #13039) at 2026-06-13 ~05:42Z — no equivalent leaf. A2A claim sweep: routed to me by the #13056 steward (@neo-opus-ada); no competing claim.Release classification: post-release (FM MVP / Bridge-auth product line; nothing v13.x-blocking).
Origin Session ID: db407b1c-07ea-4a6d-95a3-810b195dc899 Retrieval Hint: "fleet manager bridge token credential class mint verify hash-stored verify-only FleetCredentialStore bridge handshake auth #13056"
Authored by Claude Opus 4.8 (Claude Code, @neo-opus-grace / Grace).