feat(bus,webui,cli): copy binding status and next steps, two-org acceptance (CB5, #1873) #1958

Merged
jason.woltje merged 6 commits from org-cb5 into refactor 2026-11-13 02:04:25 +00:00
Owner

Credential binding slice CB5 (#1873, design PR #1923): two-org acceptance, and the operator flow that shows each copy's binding status and its next step.

  • Two-org acceptance (test only, cli/tests/agent-copies.test.mjs): acme and beta each bind the shipped research copy with their own owner-approved, issued and restart-verified binding. Real buses, fake harness and fake Gitea. Each org's thread runs and reopens. A revokes, archives and is deleted while B keeps working. A's binding or grant replayed in B is refused.
  • Status: organization/src/copy-binding-status.mjs (new) is the design's status table as fixed text, {status, next} per code. An unknown code reads credential-unavailable. copy-launch status() runs the launch gate's own checks and returns nothing it read. The owner's instances read gains binding (never in a tester session), and mosaic org instances prints <name> (copy): <status>. <next>. A copy whose role needs a service a copy cannot bind yet (vikunja) reads copy-service-unbindable, unless a revoke is running or failed.
  • Console: a binding decision's confirmation returns only {instance, change}, and an approved resolve adds the fixed next step. The decision page names the copy and whether it binds or unbinds. The Agents grid shows "can't launch" with the next step for a copy whose binding is not ready. serve.mjs rebuilds each copy's binding from the fixed table, so the page never shows text the bus supplied.
  • Reads: the inbox preview, the hire.raise answer and the trail read reduce a binding proposal to {instance, change}.
  • CB4 deferred tests P3-1, P3-2 and P3-3 are added. They needed no source change.
  • packages/webui/tests/browser.mjs now passes --disable-extensions to every system Chromium launch. packages/business/src/copy-binding-status.mjs is the old-path re-export the rename guard requires.

No new refusal codes, no schema change, nothing in roles/ or contracts/, no right or role change.

Tests: each acceptance item has a test that is red on BASE or under a guard-removal mutant (notes in the lane report). Lane gate on this head: 8249/2 over 760 files, plus test-config.sh and test-task.sh rc 0. The 2 fails are the CHAT-01 jsonschema base red. Lead check under env -i on this head (392 files: touched tests, importers of the touched sources, refusal, redact and rename tests, every webui browser test): 4129 pass, 1 fail (CHAT-01), 1 skipped.

Reviews (fresh Opus 5.5 delegates): the build review asked for 3 changes and the security review for 4. Six were fixed, each with a mutant-checked test. One needed no change, and the re-check agreed. The security re-check found 2 P3s, both fixed. One of those fixes changes code: copy-launch status() now lets a running or failed revoke take precedence over copy-service-unbindable. No reviewer read that change. The Lead read it: it uses the same fenced-or-closed test the gate already exports as revoking(), it changes only the status shown, and check() still denies the launch as before.

Lead rulings on the lane's open questions:

  • If an owner issues org A's token to B's bound copy, B's copy is deactivated before the token is refused. B then reads issue-required, and recovery is a new verified issue and a restart. That fails closed on an owner error, so it stays. The two-org test pins it.
  • Tester and reader sessions still get the console preview for ordinary hires (name, role, scope lines). That predates CB5 and is a follow-up.

Deferred: the 60 s sweep interval has no test; socket path length fails closed on a long data root; credentialLines reach the Console unfiltered (existing); status() does one gate check per bound copy per instances read.

Credential binding slice CB5 (#1873, design PR #1923): two-org acceptance, and the operator flow that shows each copy's binding status and its next step. - Two-org acceptance (test only, `cli/tests/agent-copies.test.mjs`): acme and beta each bind the shipped research copy with their own owner-approved, issued and restart-verified binding. Real buses, fake harness and fake Gitea. Each org's thread runs and reopens. A revokes, archives and is deleted while B keeps working. A's binding or grant replayed in B is refused. - Status: `organization/src/copy-binding-status.mjs` (new) is the design's status table as fixed text, `{status, next}` per code. An unknown code reads credential-unavailable. `copy-launch` `status()` runs the launch gate's own checks and returns nothing it read. The owner's instances read gains `binding` (never in a tester session), and `mosaic org instances` prints `<name> (copy): <status>. <next>`. A copy whose role needs a service a copy cannot bind yet (vikunja) reads `copy-service-unbindable`, unless a revoke is running or failed. - Console: a binding decision's confirmation returns only `{instance, change}`, and an approved resolve adds the fixed next step. The decision page names the copy and whether it binds or unbinds. The Agents grid shows "can't launch" with the next step for a copy whose binding is not ready. `serve.mjs` rebuilds each copy's binding from the fixed table, so the page never shows text the bus supplied. - Reads: the inbox preview, the hire.raise answer and the trail read reduce a binding proposal to `{instance, change}`. - CB4 deferred tests P3-1, P3-2 and P3-3 are added. They needed no source change. - `packages/webui/tests/browser.mjs` now passes `--disable-extensions` to every system Chromium launch. `packages/business/src/copy-binding-status.mjs` is the old-path re-export the rename guard requires. No new refusal codes, no schema change, nothing in `roles/` or `contracts/`, no right or role change. Tests: each acceptance item has a test that is red on BASE or under a guard-removal mutant (notes in the lane report). Lane gate on this head: 8249/2 over 760 files, plus `test-config.sh` and `test-task.sh` rc 0. The 2 fails are the CHAT-01 jsonschema base red. Lead check under `env -i` on this head (392 files: touched tests, importers of the touched sources, refusal, redact and rename tests, every webui browser test): 4129 pass, 1 fail (CHAT-01), 1 skipped. Reviews (fresh Opus 5.5 delegates): the build review asked for 3 changes and the security review for 4. Six were fixed, each with a mutant-checked test. One needed no change, and the re-check agreed. The security re-check found 2 P3s, both fixed. One of those fixes changes code: `copy-launch` `status()` now lets a running or failed revoke take precedence over `copy-service-unbindable`. No reviewer read that change. The Lead read it: it uses the same fenced-or-closed test the gate already exports as `revoking()`, it changes only the status shown, and `check()` still denies the launch as before. Lead rulings on the lane's open questions: - If an owner issues org A's token to B's bound copy, B's copy is deactivated before the token is refused. B then reads issue-required, and recovery is a new verified issue and a restart. That fails closed on an owner error, so it stays. The two-org test pins it. - Tester and reader sessions still get the console preview for ordinary hires (name, role, scope lines). That predates CB5 and is a follow-up. Deferred: the 60 s sweep interval has no test; socket path length fails closed on a long data root; `credentialLines` reach the Console unfiltered (existing); `status()` does one gate check per bound copy per instances read.
jason.woltje added 6 commits 2026-11-13 02:04:22 +00:00
The two-org test boots real buses for two organizations with a shipped
Gitea-requiring thread role, binds each copy through its own owner
decision and verified issue, and checks identity separation, record
isolation, token isolation, B through A's revoke, archive and delete,
and refusal of a replayed raw A binding or A token in B.

P3-1 shadowed cleanup, P3-2 runtime revoke wiring, P3-3 ordinary-run
stop. Each passes on BASE; guard-removal mutants recorded.
The launch gate's status() runs the same checks a launch runs, without the loss report, and
answers a fixed { status, next } from organization copy-binding-status.mjs (design section 6
texts plus issue-required and ready). A tester's read never carries it.
The bus instances read adds a copy's binding status only when the copy was
bound at boot or is unbound for want of a bindable service, never to a
tester. decision-http returns an allowlisted binding (instance and change)
on a binding confirmation and fixed next-step text on an approved resolve
and on a binding hire start. The decision page names the copy and the
change; the receipt shows the next step; the Agents grid shows can't launch
with the binding's next step. Every new string is fixed text.
- broker: the hire.raise answer, the inbox console preview and the trail read carry a
  binding as {instance, change} only; the raise event keeps the full proposal the resolve reads.
- copy-launch status(): a copy whose role needs a service a copy cannot bind reads
  copy-service-unbindable (new fixed status text) instead of a gitea binding step.
- cli instances: an unbound copy's line names its missing services.
- webui serve: the instances verb rebuilds each copy binding from the fixed status table.
- bus.js reads the binding change field only.
- browser tests: system Chromium starts with --disable-extensions; the A3 booted-bus test passes it.
- copy-launch status(): a fenced or closed copy falls through to the gate check, so the owner
  sees credential-revoking or credential-revoke-failed for a binding issued to a copy whose role
  also needs a service a copy cannot bind.
- copy-bindings: an unbind raise reads change 'unbind' in the raise answer, every inbox and trail.
Every packages/organization/src module has a one-line re-export at its old packages/business/src
path; the CB5 status table had none, so rename-b-identity refused it.
jason.woltje merged commit a9d72b3cd1 into refactor 2026-11-13 02:04:25 +00:00
Sign in to join this conversation.