diff --git a/docs/plans/2026-10-05_s2b-single-use-approvals.md b/docs/plans/2026-10-05_s2b-single-use-approvals.md new file mode 100644 index 00000000..d0394012 --- /dev/null +++ b/docs/plans/2026-10-05_s2b-single-use-approvals.md @@ -0,0 +1,60 @@ +# Slice 1 S2b: gated approvals are single-use (2026-10-05) + +Status: written by Sage, lead, under lead decision 63. It follows +`docs/plans/BRIEF-TEMPLATE.md`, and it amends no section of the slice 1 +brief. + +## S2b: gated approvals are single-use (authorize records consumption) + +### Problem + +In S2 round 2 (`agents/rocko/work/slice1-s2-r2/`, candidate manifest +61519059…), `broker.authorize(cap, action, {decision, target})` checks +that the decision is resolved and approved, then appends an +`action.allowed` event. Nothing stops a second call with the same +decision. Darkwing's probe P3 (`agents/darkwing/work/slice1-s2-review/`) +deploys three times on one approval. A gated decision is Jason saying yes +to one action, so one approval must authorize one action. + +### Owner and reviewer + +- Owner: rocko. +- Reviewer: darkwing. + +### Files owned + +- `packages/bus/src/broker.mjs` and `packages/bus/src/store.mjs`, for the + check. +- `packages/bus/tests/broker.test.mjs` and, if a new test file is + cleaner, one new file under `packages/bus/tests/`. +- `packages/bus/README.md`, the paragraph on `authorize`. + +### What ships + +- For a gated decision, `authorize` refuses with `decision-consumed` if + an `action.allowed` event already names that decision. The check and + the new event run in the same transaction, so two concurrent calls + can't both pass. +- Routine, within-role and cross-role decisions keep their current + behavior unless Darkwing's review shows the same hole matters there. + If it does, the review names it and I rule on it. +- No schema change is expected, because the `action.allowed` event + already carries the decision. If one is needed, Darkwing writes it as + schema v3c and S2b pins it. +- Tests: the second use refuses, a use in another run refuses, a use + after the broker restarts refuses, and a fresh approval of the same + action works. Add a mutant that drops the check. +- Suites: `packages/bus` with the glob form on Node 24 and 26, plus + every `scripts/test-*.sh`. + +### Out of scope + +- Who calls `authorize` and when. That's S3's tasks and S6's launches. +- Expiring an approval that is never used. + +### Gate + +Darkwing approves on the row's issue. Sage runs the integration gate +(every suite, live-provider cases included) on an index export before +committing. This has to land before S3 or S6 calls `authorize` for a +gated action.