LearnNewsExamplesServices
Frontmatter
id15801
titleSeat identity: request-time subject binding via the window-identity spine pattern
stateClosed
labels
enhancementaiarchitecturesecurity
assigneesneo-fable-clio
createdAtJul 24, 2026, 1:15 PM
updatedAtJul 24, 2026, 11:11 PM
githubUrlhttps://github.com/neomjs/neo/issues/15801
authorneo-fable
commentsCount2
parentIssue15798
subIssues
15851 Restore the plane-member count pin in BaseServer.spec (census tripwire)
subIssuesCompleted1
subIssuesTotal1
contentTrust
projected
quarantined0
signals[]
blockedBy[x] 15799 Plane identity: opaque planeId as a paired AiConfig leaf + pure-defaults twin
blocking[x] 15805 Per-seat parity opt-in via the seat-config generator (stdio preserved)
closedAtJul 24, 2026, 11:11 PM

Seat identity: request-time subject binding via the window-identity spine pattern

Closed Backlog/active-chunk-9 enhancementaiarchitecturesecurity
neo-fable
neo-fable commented on Jul 24, 2026, 1:15 PM

Context

Phase-1 leaf of epic #15798 (Discussion #15595, OQ1 contract 2 of the four-contract decomposition from fold 2). When KB/MC become long-running shared HTTP servers locally, per-seat identity moves from process-possession to request-time proof — the fold-2 finding: local-bearer is possession-only with no token→AgentIdentity map, "the crown-jewel gap for shared local servers." The multi-clone reality (N seats → 1 shared plane) makes this multi-client-one-stack auth, not per-seat config.

The Problem

Contract 2 (request-time subject binding) has no mechanism today. Contracts 1/3 (credential lifecycle, identity lifecycle) have the wake-subscription analog per fold 3; contract 4 (authorization separation) is genuinely new design and explicitly deferred (Out of Scope). The #14388 auth≠admission lesson binds: authenticating a transport is not admitting a subject.

The Architectural Reality

The in-tree precedent is the trusted window-identity spine (#15514 / PR #15529, merged): a one-time capability minted by the owner, consumed exactly once at the handshake, bound to exactly one runtime identity, generation-invalidated on reload/replacement, fail-closed for everything stale. Precedent transfer per the fold-6 ported analysis: seat-config generator mints → seat consumes at boot handshake → rotation invalidates the generation. Plane scoping: tokens bind subject-to-planeId (from #15799) so a seat admitted to an overlay plane cannot present against the durable plane.

The Fix (one PR)

Implement contract 2 for the parity HTTP path: mint (generator-side), bind-once-per-generation admission (server-side — the consume-once transfer, PR #15832 Delta 1), token→AgentIdentity subject binding, generation invalidation, fail-closed stale handling; bind admission to the declared planeId. Exemplar coverage on Memory Core's HTTP transport; KB follows the same seam.

Contract Ledger

(Derived at intake 2026-07-24 by the claimer with Surface-Anchor V-B-A — the body predated the ledger gate; anchors verified against current dev: AuthService.mjs:66-68 documents local-bearer as "possession-only: no PRM, discovery, identity lookup, or provisioning" — the premise, at source; the precedent's private-route shape from PR #15529's shipped contract.)

(Row 1 + AC1 restated 2026-07-24 per PR #15832 review PRR_kwDODSospM8AAAABHK9rpg §5.2 "satisfy or restate": consume-once → bind-once-per-generation, the PR's argued Delta 1; restatement recorded in the ticket comment thread, confirmation requested from the ticket author.)

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
MC HTTP auth path (AuthService mode surface + auth.* config leaves) this ticket + #15514/PR #15529 precedent + ADR 0019 (any new leaf: read-gate + leaf shape) request-time subject binding: a seat token bound once per config generation resolves to its minted AgentIdentity (consume-once transferred as bind-once-per-generation — PR #15832 Delta 1); possession-only local-bearer remains the loopback-only dev mode, never the parity path malformed-token / wrong-plane / stale-generation / unknown-token → named fail-closed rejection (401-class; no consumed class — nothing is consumed under the generation bind) auth section of the server docs + OpenAPI auth notes the five precedent properties as specs (mint-once / bind-once-per-generation / bind-one / generation-invalidate / fail-closed)
Seat-config generator (mint side) #15805 consumer contract (the generator is the single authoring surface) mints ONE one-time token per seat per config generation, generator-private (never in public config surfaces — the opaqueHandleKey discipline); regeneration invalidates prior generations seat config without a token = stdio wiring untouched generator docs mint + generation-invalidation specs
Token→AgentIdentity binding + RequestContextService propagation #14388 auth≠admission lesson the resolved subject propagates as the caller identity through the existing context chain; admission is plane-scoped (subject×plane.id, the #15799 leaf) cross-plane presentation → named fail-closed error ADR 0019 §10.6-adjacent note binding + cross-plane rejection specs
stdio transport path epic non-goal (explicit) untouched n/a body note existing stdio specs stay green

Acceptance Criteria

  • One-time mint / bind-once-per-generation / bind-to-one-identity / generation-invalidate / fail-closed — each property has a test (the #15514 property set, transferred; consume-once carries over as bind-once-per-generation: per-request bearer re-presentation is the HTTP reality, so the bind is one token ⇔ one subject ⇔ one config generation, invalidated wholesale on regeneration — the WindowProxy analog is the seat-config generation, not the TCP request. Argued in PR #15832 Delta 1; the stricter one-live-transport-session-per-token bind is #15805's hardening rider).
  • Request-time subject resolution to AgentIdentity, never possession-only, on the parity HTTP path.
  • Admission is plane-scoped (subject×planeId); cross-plane presentation fails closed with a named error.
  • auth≠admission separation explicit in the seam (contract-4 hooks stubbed as named extension points, not implemented).
  • stdio path untouched (non-goal preserved).

Out of Scope

Contract 4 (authorization separation — later sub); contracts 1/3 beyond reusing their existing lifecycle shapes; any Fleet read-model changes (OQ2's submatrix stays in its own lane).

Related

Epic #15798 · blocked_by #15799 · precedent #15514 / PR #15529 · fold-2 OQ1 decomposition + #14388 lesson (D#15595) · borrowed-authority caveat as in #15799.

Origin Session ID: 758f110e-a429-4597-beb0-5fd3ec7d1bfc Retrieval Hint: query_raw_memories("OQ1 contract 2 request-time subject binding window identity spine transfer")