Verdict comment 26765, queue rev 140. R1: message.send and role.revoke verbs use gated decisions without consuming them. R2: a cross-role decision authorizes without limit once policy makes the action gated. Two rulings for Sage on cross-role reuse and class drift. Co-Authored-By: Claude Opus 5.5 <[email protected]>
1.9 KiB
Row 43, S2b, round 1: request changes (Darkwing)
Candidate candidate-manifest.sha256 1b4b2f20…, three files under
packages/bus/. Full record:
agents/darkwing/work/slice1-s2b-review/review-r1.md.
The patch applies at 65d78d12 and the manifest checks 3/3. Tests pass
48/48 on Node 26 and on Node 24. Rocko's S1 contract script passes, and
the within-role route_to assertion is in. Six of seven mutants are
killed; the survivor is the R2 fix, which no test covers yet.
The authorize change holds. The check and the event share one
BEGIN IMMEDIATE transaction, and the writer lock allows one Store
per data root, so callers serialize. The decision.raise exclusion
can't be used to skip the check, because no agent path writes that
operation for an existing decision. A gated decision stays consumed after
policy makes its action cross-role. The README's decision 62 note claims
nothing the decision doesn't.
Required:
- R1: the
message.sendandrole.revokeverbs use gated decisions without consuming them. A gatedmessage.sendapproval sends any number of times, andauthorizegrants it afterwards. Put the check and event in one helper thatauthorizeand both verbs call, and test each verb. - R2: a decision raised cross-role authorizes without limit once policy makes the action gated, because the check keys only on the recorded class. Check consumption when either class is gated, and test it.
For Sage, two rulings, either of which settles R2:
- Cross-role reuse matters. One cto approval of a coder's scope change on a task authorizes every later scope change on it in that run. I recommend every decision-backed authorization be single-use.
- Pre-existing from S2: once policy makes an action gated, an arbiter's earlier approval still authorizes it without Jason. I recommend refusing when the decision's recorded class differs from the current class.