feat(bus): copy launch gate CB3, verified bound copies only (#1873) #1947

Merged
jason.woltje merged 2 commits from org-cb3 into refactor 2026-11-12 19:44:15 +00:00
Owner

Copy credential binding CB3 (#1873): a copy launches only on a binding this bus verified at boot, and every use rechecks it.

  • New packages/bus/src/copy-launch.mjs (copyLaunchGate). Boot re-resolves every binding and proves it live against the service with a token proof. Only proved copies materialize; a binding approved after boot answers restart-required. Verification that fails, a partial binding or a stale slot file refuses.
  • Two fences, the A3 revision and a launch authority digest over the binding facts, are carried through prepare, final admission (launcher.mjs) and bindLaunch (broker.mjs, process.mjs). A reissue or an organization file change between prepare and bind refuses, and no credential call is made.
  • A pending revoke blocks admission, and a fence is never released on an uncertain outcome.
  • Credential calls (credentials.mjs, runtime.mjs) check session, incarnation, scopes, generation and liveness, take the slot from the session and never from the caller, go only to the approved origin (a redirect refuses copy-redirect-refused and is not followed) and recheck the generation at completion. No cached-token fallback. No raw token or path enters a sandbox.
  • Repository reads need a repository scope. Organization lifecycle (archived, deleting, reserved) is read live.
  • Proof requests run inside a time budget (prepare 8 s, boot 20 s), so one silent service cannot outlast the host's 10 s request timeout.
  • The launcher maps a host refusal from copyLaunch to its own code instead of launch-failed. promoteCopies promotes only proved copies. New refusal codes have refusal-table entries. No changes in roles/ or contracts/, no new human gates.

Tests: new bus copy-launch and copy-launch-docker (real Gitea 1.27.1, including a real 301 redirect and a minted-token leak check); cli and organization agent-copies extended. Each is red on BASE 6d0e9f89b. Fix-round mutants 10/10 killed; build-round 15/16 (M13 is covered by an earlier check). Lane gate 551 files, 5601/3 plus one timeout, all four load flakes untouched by the change and green alone. Rebased on #1946 with no conflicts; range-diff identical. Lead check: 309 files on 0b8fe202b (touched tests, every reader of the eight touched bus, cli and organization sources, every refusal-table, redaction and last-values file), 3475/1, 1 skip (federation acceptance runs only under its script); the fail is conversation reader CHAT-01, red on refactor 666f4d686 too: the host Python has no jsonschema module.

Review: build (Opus) approve, P3s. Security (daybreak-blue) changes 3 (one P1 on live organization lifecycle, two P2 on repository scope and revision to bind), all fixed with tests; re-check approve, no new findings.

Deferred: CB4 stops running sessions on revoke (CB3 denies new work only). No agent verb exposes copyCall yet. A service that is down reports credential-scope-unverified. bindingFacts is duplicated between the gate and the organization package. The launcher logs a bind refusal twice as launch-failed (pre-existing).

Copy credential binding CB3 (#1873): a copy launches only on a binding this bus verified at boot, and every use rechecks it. - New `packages/bus/src/copy-launch.mjs` (`copyLaunchGate`). Boot re-resolves every binding and proves it live against the service with a token proof. Only proved copies materialize; a binding approved after boot answers `restart-required`. Verification that fails, a partial binding or a stale slot file refuses. - Two fences, the A3 revision and a launch authority digest over the binding facts, are carried through prepare, final admission (`launcher.mjs`) and `bindLaunch` (`broker.mjs`, `process.mjs`). A reissue or an organization file change between prepare and bind refuses, and no credential call is made. - A pending revoke blocks admission, and a fence is never released on an uncertain outcome. - Credential calls (`credentials.mjs`, `runtime.mjs`) check session, incarnation, scopes, generation and liveness, take the slot from the session and never from the caller, go only to the approved origin (a redirect refuses `copy-redirect-refused` and is not followed) and recheck the generation at completion. No cached-token fallback. No raw token or path enters a sandbox. - Repository reads need a repository scope. Organization lifecycle (archived, deleting, reserved) is read live. - Proof requests run inside a time budget (prepare 8 s, boot 20 s), so one silent service cannot outlast the host's 10 s request timeout. - The launcher maps a host refusal from `copyLaunch` to its own code instead of `launch-failed`. `promoteCopies` promotes only proved copies. New refusal codes have refusal-table entries. No changes in `roles/` or `contracts/`, no new human gates. Tests: new bus copy-launch and copy-launch-docker (real Gitea 1.27.1, including a real 301 redirect and a minted-token leak check); cli and organization agent-copies extended. Each is red on BASE 6d0e9f89b. Fix-round mutants 10/10 killed; build-round 15/16 (M13 is covered by an earlier check). Lane gate 551 files, 5601/3 plus one timeout, all four load flakes untouched by the change and green alone. Rebased on #1946 with no conflicts; range-diff identical. Lead check: 309 files on 0b8fe202b (touched tests, every reader of the eight touched bus, cli and organization sources, every refusal-table, redaction and last-values file), 3475/1, 1 skip (federation acceptance runs only under its script); the fail is conversation reader CHAT-01, red on refactor 666f4d686 too: the host Python has no jsonschema module. Review: build (Opus) approve, P3s. Security (daybreak-blue) changes 3 (one P1 on live organization lifecycle, two P2 on repository scope and revision to bind), all fixed with tests; re-check approve, no new findings. Deferred: CB4 stops running sessions on revoke (CB3 denies new work only). No agent verb exposes `copyCall` yet. A service that is down reports `credential-scope-unverified`. `bindingFacts` is duplicated between the gate and the organization package. The launcher logs a bind refusal twice as `launch-failed` (pre-existing).
jason.woltje added 2 commits 2026-11-12 19:44:07 +00:00
Materialize only verified bound copies: a binding approved after boot
answers restart-required. Boot and every launch re-resolve the binding
and prove the slot live against its service; unavailable verification,
a partial binding or a stale file refuses. The A3 revision fence and
the launch authority digest ride through prepare, final admission and
bindLaunch. Pending revocation blocks admission and no fence is
released on an uncertain outcome.

Every copy credential call checks session, incarnation, scopes,
generation and liveness, takes the slot from the session, goes only to
the approved origin with redirects refused, and rechecks the generation
at completion. Prepare and boot proofs run inside a time budget so a
silent service cannot stall the host channel. The launcher maps a
copyLaunch refusal to its own code instead of launch-failed.

Tests: copy-launch unit suite, CLI launch and race cases (prepare and
bind races assert no repo call, empty project manifest included), and a
docker acceptance on gitea 1.27.1.
- The copy launch gate reads the organization registry entry on every
  check for a verified copy. A state other than active, or a new
  incarnation, refuses credential-revoked; an unreadable registry
  refuses credential-unavailable.
- Repository reads need read or write repository scope, like writes
  need write scope.
- The admitted organization revision travels from the launcher through
  bindLaunch. prepare checks it before and after the live proof, and
  admit checks it after the launch authority; a change refuses
  organization-changed.
- Tests for the completion authority recheck, the broker session
  authority compare and an incarnation change; the copyRefusal comment
  now names where restart-required is answered.
jason.woltje merged commit 0b8fe202bc into refactor 2026-11-12 19:44:15 +00:00
Sign in to join this conversation.