Files
jason.woltjeandClaude Opus 5.5 c83ee5d82b agents: Dewey row 139 round 2, Mos's REQ-ROLE-2 sentences (#1633)
PRD 1.6 REQ-ROLE-2 gains Mos's two sentences (sage-0545): the chief ends
the assistant with no decision, and any revoke of it is a hard wait.
Checked against broker.mjs; the code already does both. New candidate:
patch a5f21ead, manifest a4c5657f. Both PRD-reading tests pass; repro OK.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-16 14:16:13 -05:00

23 KiB

Row 139 (#1633): decisions that cite the spec go to an agent; only hard waits reach Jason

Builder: Dewey. Reviewer: Filbert. Gate owner: Sage. Brief: docs/plans/2026-10-15_q167-rows-r3.md at b71268b8e (blob 6805904555, rev 1565), section "Decisions that cite the spec go to an agent; only hard waits reach Jason". Round 1 built against 04c572003 (blob c22ce67cd). Requirements: PRD 1.5 REQ-DEC-2 and REQ-WAIT-1 to REQ-WAIT-3, and PRD 1.6 (this row) for Jason's ruling Q169. Calls from sage-0502, 0504, 0512, 0514, 0516, 0528, 0533 and 0539. sage-0523 (Mos's ruling) is superseded by Q169.

Round 2 follows Jason's ruling Q169 (sage-0528), which replaces Mos's sage-0523 ruling that the first round 2 build followed. See "Round 2" below. Round 1's gate and mutant outputs stay in gate/ and mutants/. Round 2's are in r2/. Round 1's candidate is build.patch and candidate-manifest.sha256 at commit 62f354746. The ones at the packet root are round 2's, with Mos's REQ-ROLE-2 sentences (sage-0545): build.patch sha256 a5f21ead8195382fbf21da0ee8f66b794329c4b7f8737fa942ccd6c219524e3f, candidate-manifest.sha256 sha256 a4c5657ff69ef0665285e4c2095858346f2d325d454c6fa99edd999633dcd0f0. The round 2 candidate before those sentences (patch 7a29d911…, manifest 7a1fcae1…) is at commit c740f56ee and differs only in docs/prd/mosaic-stack.md. PRD-1.6.md holds the 1.6 text. Mos accepted it with the added sentences, and readings 1 to 4 (sage-0545). See "PRD sentence (sage-0545)" below.

Base: 5661bd233b (base.txt, origin/refactor after row 135). The candidate is build.patch on that base. candidate-manifest.sha256 lists every changed path with its sha256. repro.sh applies the patch to a fresh detached worktree and checks the digests.

What ships

  1. The hard waits. The broker's GATED and the business's GATED_ONLY are now exactly these: spend, data.delete, network.expose, account.create, credential.mint, message.external, git.push.protected and deploy. data.delete, network.expose and account.create are new actions in both ACTIONS lists, placed after deploy.
  2. The actions that left. git.merge.protected, policy.change, prd.approve and role.revoke are no longer on either list. A role file may grant them, and a raise routes them under REQ-DEC-2.
  3. REQ-WAIT-3 by construction. Neither list is written out by hand. Each side holds an explicit ROUTED list of 21 actions: ROUTED exported from vocabulary.mjs, and a Set in broker.mjs. The hard waits are ACTIONS minus ROUTED, so an action added to ACTIONS is a hard wait until a reviewed commit adds it to ROUTED. The bus imports no business code, so the two lists stay separate. packages/bus/tests/hard-waits.test.mjs pins them equal: HARD_WAITS equals GATED_ONLY, and the broker's ACTIONS equal the vocabulary's.
  4. Routing (broker.mjs:1206-1211). arbitrated = cls === 'cross-role' || (cls === 'gated' && spec !== null && !GATED.has(action)). An arbitrated raise goes to arbiters[domain]. Otherwise a gated raise goes to the human and a within-role raise to the raiser. If the arbiter would be the raiser, the decision goes to the human, as cross-role routing already did. spec is non-null only when the raise cited a spec and readSpec accepted it. readSpec refuses a stale citation (stale-spec) and a spec whose last commit isn't the human's (spec-unconfirmed) before any decision is stored.
  5. REQ-WAIT-2. The validator's refusal (role.mjs:41, "which is always gated") and the broker's invalid-authority already applied to every GATED_ONLY/GATED entry at both classes. They now cover the new list. No code change was needed there; the tests cover each hard wait at each class.
  6. roles/*.json. No change. No shipped role grants any of the four actions that left, and PRD 1.5 requires no new grant from this row. merge-gate's cross-role git.merge.protected grant is row 149's.

Round 2: Jason's ruling Q169

Q169, verbatim: "The only agent that should be able to remove the assistant role is the chief of staff. The COS and the user should have the ability to (re)configure the assistant role and seat name. The user via WebUI or CLI, the COS via the proper tooling."

  • The hard wait (kept from the first round 2). #hardWait(s, action, target) (broker.mjs) is true for a GATED action, or for a role.revoke whose target's definition is in END_PROTECTED. #classification returns gated when it holds, and decision.raise's arbitrated check uses it in place of round 1's GATED.has. A raise to revoke the assistant goes to the human at every class, cited or not, so no arbiter resolves one. A role granted role.revoke cross-role or within-role can't route it to the arbiter or approve its own.
  • The protection, new. #protectedEnd(s, target, r) refuses role-end-refused for an END_PROTECTED target unless one of two things holds:
    • the caller's definition is chief-of-staff (STAFF_TO);
    • the cited resolution was recorded via 'cli'. decision.resolve records cli only for a human session that passed #human(s). It runs in two places:
    • on the role.end reason path, after the existing refusal of the caller's own run, which stays;
    • in #checkAuthority for role.revoke, after the decision-not-approved check. So a revoke citing a decision an arbiter resolved is refused. One raised before the hard wait existed, or by a build that routed it to the arbiter, is refused the same way.
  • Execution is row 155's. The assistant's runs hold thread claims (row 121). The chief's end finds no role claim and refuses not-held. A cited revoke refuses decision-mismatch, because decision.raise records holderRun null and #checkAuthority reads undefined. Per sage-0528, 139 doesn't fix that. The tests assert only that the protection lets these through.
  • Every other cited revoke routes by REQ-DEC-2 and executes, unchanged from round 1.
  • Configuration (the second half of Q169) is row 155's. PRD 1.6 records what it covers.
  • PRD 1.6. It adds:
    • the status line;
    • a 1.6 source block quoting Q169;
    • text in REQ-ROLE-2, REQ-ROLE-4 and REQ-WAIT-1;
    • a Notes item. The Mos note from the first round 2 is gone. The text is recorded as Jason's ruling Q169. PRD-1.6.md has the text and four readings for Mos to confirm.
  • Filbert's note 1 (a within-role role.revoke grant lets a role approve its own revoke of a non-assistant role) stays a PRD question (sage-0523).
  • business classify (resolve.mjs) isn't changed. It takes no target, and its only caller is the WebUI's per-action authority view (packages/webui/src/reads.mjs:154), which routes nothing.

Files outside the brief's list (accepted, sage-0539)

Each follows from Q169 and PRD 1.6:

  1. packages/tasks/requirements.json and packages/tasks/tests/requirements.test.mjs. The test checks the PRD status line's version against requirements.json, so both go to 1.6.
  2. packages/harness/tests/runner.test.mjs and packages/harness/README.md. The chief's end_run of the assistant now gets past the protection and refuses not-held. Before, it refused role-end-refused.
  3. packages/bus/tests/chief-of-staff.test.mjs. It changes for the same reason: the chief ending itself stays role-end-refused, and ending the assistant is not-held.

The end_run description in packages/harness/src/tools.mjs still says the assistant can't be ended. It's stale, and row 155 owns the fix (sage-0539).

Tests

  • packages/bus/tests/hard-waits.test.mjs (new, 12 tests: 7 from round 1, 5 for Q169):
    • the lists match each other and the vocabulary;
    • the Broker refuses each hard wait at either class;
    • each hard wait goes to the human uncited, cited, and cited with a domain, and the arbiter's resolve gets human-required;
    • each action that left goes uncited to the human, cited to the technical or delivery arbiter;
    • an arbiter-approved cited role.revoke authorizes the raiser, and the human's resolve gets agent-required;
    • merge-gate's cited merge goes to the technical arbiter (cross-role, resolved via 'broker'); a coder's cited merge goes to the arbiter as gated, and uncited to the human;
    • a stale or unconfirmed citation is refused with no decision stored; an arbiter raising a cited policy.change of its own goes to the human.
    • Q169 (sage-0528's four, then one kept from the first round 2). The first and third assert the protection only, because execution is row 155's:
      1. The chief's role.end of the assistant refuses not-held, not role-end-refused. The chief ending itself, and lead (granted role.end) ending the assistant, refuse role-end-refused. No action.allowed event is written.
      2. A cited revoke of the assistant goes gated → human. For a decision an arbiter resolved, the test uses a cited revoke of pm, which went to reviewer, who resolved it yes. A second Broker on the same store then changes pm's definition to assistant. The coder's revoke citing that decision refuses role-end-refused. The decisions table is append-only, so the test changes the business, not the row.
      3. The human resolves a cited revoke of the assistant; the arbiter's resolve first gets human-required. The coder's revoke citing it gets past the protection and refuses decision-mismatch (row 155).
      4. A cited revoke of pm goes gated → reviewer, the reviewer resolves it, and the revoke returns { revoked: true }.
      5. A role granted role.revoke cross-role, and one granted it within-role, still send a revoke of the assistant to the human. The within-role one doesn't resolve itself, and its revoke gets decision-not-approved, not decision-mismatch. Their revokes of pm go cross-role to the arbiter and within-role to themselves.
  • packages/bus/tests/chief-of-staff.test.mjs: REQ-COS-1 now expects not-held for the chief's end of the assistant. Its own run stays role-end-refused.
  • packages/harness/tests/runner.test.mjs: end_run's second refusal is not-held.
  • packages/tasks/tests/requirements.test.mjs: the version literal is 1.6.
  • packages/business/tests/hard-waits.test.mjs (new, 3 tests): GATED_ONLY is the list; every action is routed or a hard wait, never both; the validator refuses each hard wait at either class and accepts the actions that left.
  • packages/bus/tests/spec-fixtures.mjs: fixture() takes arbiters and extra roles, and returns agent. Existing callers are unchanged.
  • scripts/test-land-work.sh: two cases, not-human:arbiter-cross (merge-gate raised cross-role, cto resolved via broker) and not-human:arbiter-cited (coder raised gated with a citation, cto resolved). Both exit 2 not-human. scripts/land-work/read.mjs is unchanged (sage-0516).

Left out, as the brief and sage-0512/0516 say

  1. REQ-LAND-1's refusal of a gated merge decision, and its coder test (coder raises a cited merge, the arbiter approves, the land refuses). This ships with whichever of 137 and 139 lands second. On 2026-10-16 at 18:31Z, git log 5661bd233..origin/refactor is empty: row 137 hasn't landed, so 139 doesn't carry it. If 137 lands first, 139 needs a round 2 that adds it.
  2. The host land op accepting an arbiter-resolved merge decision, in 137's shared module. This also ships with whichever of 137 and 139 lands second. The module isn't on this base.
  3. The chief-of-staff grant to set instance names (row 141, queued). Whichever of 139 and 141 lands second adds it. Until then, names are set only by the human through the CLI.

For the reviewer

  • role.revoke can now be granted within-role. A within-role raise resolves itself, so a role file granting it would let that role approve its own revoke. REQ-DEC-2 and REQ-WAIT-1 allow this, and REQ-ROLE-2's "resolved decision for a revoke" still holds. No shipped role grants it. A file that adds it is a reviewed commit.
  • role.launch where launch.by isn't the caller. It still classifies gated, but it isn't a hard wait, so a cited raise now goes to the arbiter. REQ-DEC-2 reads that way. I didn't add a carve-out.
  • "A change to the hard-waits list, or a grant of a hard-wait action, is itself a hard wait" (REQ-WAIT-2). The list lives in code and changes only by a reviewed commit to vocabulary.mjs and broker.mjs. The validator and the broker refuse any role file that grants a hard wait. No decision can change either, so the broker has no decision action to classify for them. policy.change is routed. A policy.change decision can't edit the list, because the list isn't policy data.
  • git.push.protected and force pushes. REQ-WAIT-1 says git.push.protected covers any force push or history rewrite on any branch. I grepped packages/cli/src/git-socket.mjs and broker.mjs's push path for a non-fast-forward check and found none. I didn't trace the whole push path. It isn't in row 139's brief or files, so it's a follow-up question for Sage, not a change here. The PRD's notes answer part of it: the host refuses a non-fast-forward push to the working repository (packages/seat/src/working.mjs).

Follow-ups (not row 139's files)

  • A role.revoke or chief's end of the assistant can't execute. Row 155 (#1649, Darkwing, after 139 and 141) fixes it; sage-0528 keeps it out of 139. Since row 121 the assistant's run holds a thread claim, not a role claim. decision.raise records holderRun as null, and #checkAuthority compares that with #holder(...)?.holder_run, which is undefined, so a cited revoke refuses decision-mismatch, then not-held. The chief's end refuses not-held.
  • The end_run tool text is stale. packages/harness/src/tools.mjs:156 (comment) and :158 (description) say the assistant can't be ended. Since Q169 the chief can, and the broker refuses not-held until row 155. Row 155 owns the text (sage-0539).
  • The harness raise_decision tool has no domain parameter (packages/harness/src/tools.mjs:195-208), and decision.raise defaults to delivery. No live agent reaches the technical arbiter, which REQ-ROLE-5's merge-gate raise needs. Row 149 (Dewey) takes it; Sage adds it to row 149's brief at its next amendment (sage-0528).
  • role.revoke with no target. The holder lookup throws a non-BusError, which surfaces as storage-refused instead of a validation refusal. This predates 139; the bus test passes a target.
  • land-work's fixture (scripts/land-work/fixture.mjs) can't set spec_path/spec_commit, so the arbiter-cited case has no citation columns. land-work doesn't read them, so the case still tests what it should.
  • Contract text. roles/pm.md:42-43 and roles/chief-of-staff.md:41-46 still say anything ungranted "is gated" and goes to the human, and the chief text lists policy and PRD changes among them. Under REQ-DEC-2 a cited raise goes to the arbiter. That text is row 149's (proactive contracts).
  • The harness raise_decision description (packages/harness/src/tools.mjs:195) doesn't mention that a spec citation changes the route. Harness text isn't in row 139's files.

PRD sentence (sage-0545)

Mos approved the 1.6 text with two sentences added to REQ-ROLE-2 after "the chief of staff is the only agent that removes it.": "The chief of staff removes it by ending its runs, and that end needs no decision. A revoke of the assistant, the chief's included, is a hard wait under REQ-WAIT-1." They went in verbatim, because the candidate's broker.mjs already does both:

  • The chief's end needs no decision. A role.revoke with reason (line 1168) consumes role.end (line 1171). role.end is in ROUTED (line 55), so it isn't in HARD_WAITS or GATED (lines 62-63), and #hardWait only adds role.revoke (line 494). For a chief granted role.end within-role (REQ-COS-1), #checkAuthority returns with no decision (line 659). #protectedEnd (line 1173) passes for the chief's definition (line 502). Past that, the end refuses not-held until row 155.
  • The chief's revoke is a hard wait. #hardWait (line 494) reads the target's definition, not the raiser, so #classification returns gated for anyone's raise (line 508). Line 1232 keeps a hard wait from the arbiter, cited or not, and line 1233 routes it to the human.
  • A nuance, not a mismatch. In #checkAuthority (line 681), #protectedEnd passes the chief whatever recorded the resolution. A revoke decision the chief raised while the target's definition wasn't protected, and an arbiter resolved, would pass the protection for the chief after the definition changed. Another role's would be refused (Q169 test 2). Changing a definition is configuration, which is row 155's.
  • Not tested directly. No test raises a revoke of the assistant as the chief. Q169 tests 2 and 5 raise it as coder, lead and boss, and the path has no raiser check to differ on. Sage scoped this round to the PRD (sage-0545), so no test was added.

Reruns on the edited candidate, each file singly, 2026-10-16T19:15:11Z, outputs in r2/prd/:

  • packages/bus/tests/requirements.test.mjs: 3 pass, 0 fail.
  • packages/tasks/tests/requirements.test.mjs: 3 pass, 0 fail.
  • The candidate's new tests (both hard-waits files) don't read docs/prd/mosaic-stack.md, so there was nothing else to rerun. A grep of packages//tests and scripts/test-.sh found only those two.

repro.sh on the new patch, in a fresh worktree at base.txt: DIGESTS_OK. The worktree was removed.

Gate, round 2 (Q169)

Run under Sage's slot (sage-0533, sage-0539; the slot was Dewey's from 19:06Z), from the worktree at the round 2 candidate, with COMPOSE_PROJECT_NAME=dewey-r139. The script is r2/gate.sh, and the outputs are in r2/gate/. r2/gate/summary.txt has each run's exit code and times.

  • 2026-10-16T19:06:10Z to 19:08:58Z: all 14 runs rc=0, with no flakes.
    • scripts/test-land-work.sh: 86 passed, 0 failed, including OK arbiter-cross: exit 2, not-human and OK arbiter-cited: exit 2, not-human.
    • bus 306/306, business 112/112, harness 105/105 and tasks 97/97, 0 skipped.
    • test-task selftest 98 passed, 0 failed.
    • auth, conductor, config, discord, extension-package, foundation, queue and release: rc=0.
  • dewey-r139_default was removed with 0 containers on it.

Checks, round 2

  • Before the gate, as built (19:02:52Z to 19:03:20Z), each file run singly with 0 skipped:
    • bus: agents, broker, chief-of-staff (13), approval-binding, decisions, hard-waits (12), project-runtime, requirements, single-use, task-page, tester, replies, message-citation, threads, transport, spec-socket, decision-route, decision-close, messaging and spec.
  • Two runs outside the slot, before sage-0533 gave it: the tasks suite (97 pass) and packages/harness/tests/runner.test.mjs (24 pass). Sage was told in dewey-0534. Neither used Docker. Nothing collided: Darkwing had cleared, and Researcher was idle (sage-0539).
  • The busy check, by /proc cwd links only:
    • At 19:04:08Z, no node, mutant or gate processes from another seat. There were three bash shells, with no node, in ~/filbert-scratch/r137-r1.
    • In a watch from 19:04:25Z to 19:05:42Z (30 scans), none.
    • No dewey-r139 containers or networks.
  • An overlap in the slot (sage-0542). From about 19:06:14Z to 19:06:50Z, Filbert ran single test files in ~/filbert-scratch/r137-r1: seat working.test and cli git-socket.test, with probes. It wasn't a suite, a gate or Docker. Sage's single-file allowance in sage-0537 and its late correction caused it, and Filbert has stopped. My watch had ended at 19:05:42Z, so it didn't see the overlap. These gate steps ran in that window:
    • land-work, to 19:06:22Z;
    • bus, 19:06:22Z to 19:06:36Z;
    • business, 19:06:36Z to 19:06:38Z;
    • harness, 19:06:38Z to 19:06:51Z. All of them passed, and the gate runs no seat or cli tests, so there was nothing to rerun.

Mutants, round 2 (Q169)

r2/mutants/summary.txt; the script is r2/mutants.py. They ran from 2026-10-16T19:09:11Z to 19:10:46Z, under the same rules as round 1:

  • each mutant ran in its own process group, killed as a group on a 600-second timeout;
  • the file was restored and its hash checked after each one.

m1 to m5 are round 1's, with m1 and m2 retargeted at #hardWait. All nine were killed:

Mutant Change Suite Tests that failed
m1 drop spec !== null bus (13 fail) both REQ-DEC-2 citation tests, and older tests that expect an uncited gated raise at the human
m2 drop !this.#hardWait(...): a cited hard wait goes to the arbiter bus (5 fail) REQ-DEC-2 hard waits; three Q169 tests; spec.test's current-citation test
m3 add account.create to the broker's ROUTED bus (3 fail) REQ-WAIT-1, REQ-WAIT-2, REQ-DEC-2 hard waits
m4 add spend to the vocabulary's ROUTED business (2 fail) REQ-WAIT-1, REQ-WAIT-2
m5 disable role.mjs:41's refusal of a hard-wait grant business (3 fail) REQ-WAIT-2, plus two older role tests
m6 #hardWait drops the assistant check bus (3 fail) Q169 tests 2, 3 and 5
m7 #protectedEnd drops the chief exemption bus (2 fail) Q169 test 1; REQ-COS-1
m8 #protectedEnd drops the human (via 'cli') exemption bus (2 fail) Q169 test 3; REQ-ROLE-2 REQ-THREAD-1's approved-revoke test
m9 #checkAuthority no longer calls #protectedEnd for role.revoke bus (1 fail) Q169 test 2

Gate, round 1

Run under Sage's slot (sage-0518), from the worktree at the round 1 candidate, with COMPOSE_PROJECT_NAME=dewey-r139. gate-summary.txt has each run's exit code and times.

  • 2026-10-16T18:28:41Z to 18:31:11Z: all 12 runs rc=0, with no flakes.
    • scripts/test-land-work.sh: the two new cases print OK arbiter-cross: exit 2, not-human and OK arbiter-cited: exit 2, not-human.
    • bus 301/301 and business 112/112, 0 skipped.
    • test-task selftest 98 passed, 0 failed.
    • auth, conductor, config, discord, extension-package, foundation, queue and release: rc=0.
  • The 19 affected test files also passed run singly, 0 skipped (18:25:46Z to 18:26:03Z):
    • bus 10;
    • business 7;
    • harness bundle and tools, since both read ACTIONS.
  • dewey-r139_default was removed with 0 containers on it.

Mutants, round 1

mutants-summary.txt. Each mutant changed one file and reran its owning suite in its own process group. The file was restored and its hash checked before the next mutant. All five were killed:

Mutant Change Suite New tests that failed
m1 drop spec !== null: an uncited gated raise goes to the arbiter bus (14 fail) both REQ-DEC-2 citation tests, and 12 older tests that expect an uncited gated raise at the human
m2 drop !GATED.has(...): a cited hard wait goes to the arbiter bus (2 fail) REQ-DEC-2 hard waits; spec.test's current-citation test
m3 add account.create to the broker's ROUTED bus (3 fail) REQ-WAIT-1, REQ-WAIT-2, REQ-DEC-2 hard waits
m4 add spend to the vocabulary's ROUTED business (2 fail) REQ-WAIT-1, REQ-WAIT-2
m5 disable role.mjs:41's refusal of a hard-wait grant business (3 fail) REQ-WAIT-2, plus two older role tests