Unclaimed files left in the canonical checkout. Inventory: canonical-cleanup-inventory.md (commit-as-evidence groups). detect-secrets scan clean on 2026-11-02: hits are sha256 pins, git object ids, test sentinels already in tracked tests and unit-file lines.
7.3 KiB
Row 134 (#1669): organization revision read for a role holding organization.change
Seat: Researcher (owner). Reviewer: Filbert. Gate owner: Sage.
Brief: docs/plans/2026-10-15_organization-revision-read.md, blob f50ef7b05f (rev 1988, Files owned amended at 2cef8cd35).
Requirements: REQ-COS-1, REQ-LIMIT-1.
Base: d58c9c5cd (row 170 landed there and touched packages/bus/src/broker.mjs, outside this branch). The candidate was first built
on 7319bd867 and re-applied on d58c9c5cd with git apply (no fuzz); row134-at-7319bd867.patch and candidate-manifest-at-7319bd867.sha256
are the superseded first freeze and can be ignored. row134.patch is the candidate, 11 files, +181 −35.
candidate-manifest.sha256 lists the 11 files with file sha256; digest of the manifest file
8977d90723db6745eef85b72658129f393036d8aa7390b8d49697b0939a7ad60. Patch sha256 843fdee91ccc220715e2e8d681fb888b71d8d8eda66c8eb1a61475d0d923aa24.
Nothing is committed, nothing is pushed. No credential, live store, unit or ~/.mosaic file was touched. No schema, roles/*.json,
runtime.mjs or who-may-call change, so nothing in the brief's Out of scope applies.
Mechanism: a read verb, organization.revision (candidate A)
A role session whose file lists organization.change sends organization.revision with no arguments and gets {revision}:
the organization file's current revision, and nothing else the human's limits read returns (no limits, models, instances, tokens).
How it is built (packages/bus/src/broker.mjs, the limits / organization.set branch only):
- The verb joins the same early branch. It is a role verb like
organization.set, so!role && !s.humandoes not refuse it, and the same#checkAuthority(s, "organization.change")runs before the read. A caller whose role lacks the grant is refused exactly asorganization.setrefuses it (not-holder,read-only,tester-refused,agent-requiredfor the human). keys(args, [])rejects any argument (invalid-request).- The result is built as
{ revision: <limits read>.revision }, so the rest of thelimitsread never reaches the caller. - The grant is consumed only for
organization.set(role && !revisionRead). A read writes noaction.allowedevent and spends nothing. limits,limits.set,plugins,instances,routesstay human-only andorganization.set's keys and refusals are unchanged.
Harness (packages/harness/src/tools.mjs): an organization_revision tool, no parameters, verb organization.revision, gated on
held.has("organization.change") like change_organization. change_organization's description now says the revision comes from
organization_revision (it said "as the human gave it to you"). The chief's bundle lists the new tool as typed, untyped stays [].
Why not B (revision returned with revision-conflict)
BusError carries a code only, so B would add a payload to an error and change the error shape for every caller and client.
It would also show the revision only after a failed write, so a first change always costs one refused attempt and one action.refused
event. A is a new read with the existing authority check and no change to an existing result.
Limitation (documented in the bus README, Sage agreed in sage-1034)
The read takes no decision argument. A role whose file lists organization.change only as cross-role classification gets
decision-required on the read, as it does on organization.set without a decision. No shipped role is cross-role; the chief of
staff is within-role. A later row would have to give the read a decision argument if one ever is.
Changed paths (11)
| Path | Change |
|---|---|
packages/bus/src/broker.mjs |
the verb, authority, revision-only result, no grant spend |
packages/harness/src/tools.mjs |
organization_revision tool, change_organization description |
packages/bus/tests/chief-of-staff.test.mjs |
3 tests: stub revision-only and no spend; refusals for non-holders; real writer read → set → stale refusal → re-read → set |
packages/harness/tests/tools.test.mjs |
tool name list, new tool's verb/action/parameters, description, grant filter |
packages/harness/tests/bundle.test.mjs |
pinned typed-tool lists include the tool |
packages/harness/tests/runner.test.mjs |
the tool reaches the broker as limits for the chief and answers {revision}, unknown key refused |
packages/bus/README.md |
organization.revision row; the organization.set row no longer says no verb gives the revision |
packages/harness/README.md |
organization_revision bullet; change_organization paragraph |
packages/business/README.md |
the "revision comes from the human" sentence (:419-420) |
roles/chief-of-staff.md |
:39 now tells the chief to read the revision with organization_revision before each change |
docs/TOOLS.md |
:688-689 names the tool |
The last three paths are the sentences Sage added to Files owned at 2cef8cd35. Path grep: no test reads docs/TOOLS.md or the three
READMEs, and no test pins a 64-hex hash of broker.mjs, tools.mjs or roles/chief-of-staff.md (channel-proof.test.mjs pins the
webui/cli files, none touched). packages/business/tests/chief-of-staff.test.mjs:138 reads roles/chief-of-staff.md and was rerun singly.
Results (gate in a worktree at d58c9c5cd with the patch applied, 2026-10-19T19:17:25Z to 19:20:11Z, slot sage-1041)
Single files (suites/singles/), 133 tests, 0 failed: bus chief-of-staff 18, routes-verb 4, instances-verb 2; business chief-of-staff 10,
workflow-roles 7, role 14; harness tools 22, bundle 22, runner 34.
Node suites (suites/{business,bus,harness}.txt, the summed ℹ tests lines), 697 tests, 697 pass, 0 fail, 0 skipped:
business 148, bus 426, harness 123.
scripts/test-*.sh (suites/test-*.txt), 422 passed, 0 failed, all rc=0: auth 15, conductor 17, config 24, discord 78,
extension-package 18, foundation 44, land-work 86, queue 28, release 14, task 98.
Docker: networks and containers recorded before and after; the run created wt2_default, the script removed it, 0 new containers left.
The manifest verified (sha256sum -c) after the gate and after the mutants.
Mutants (mutants/, scripts and per-mutant logs; all restored, broker.mjs and tools.mjs sha256 checked OK afterward)
All seven killed (mutants/mutants2.out; mutants/logs/ holds the failing tests of each):
| # | Mutant | Killed by |
|---|---|---|
| M1 | the read skips #checkAuthority |
refusals for non-holders; real-writer round trip |
| M2 | the read consumes the grant | revision-only / no-spend test |
| M3 | the read returns the whole limits result |
revision-only test; real-writer round trip |
| M4 | organization.revision dropped from the no-argument list |
revision-only, refusals, round trip |
| M5 | the verb is not a role verb (human-required) | all three bus tests |
| M6 | the tool sends limits instead of organization.revision |
tools.test.mjs |
| M7 | change_organization's description reverted |
tools.test.mjs |
The first run (mutants/mutants.out) gave the same seven results; the second only added the failing-test logs.
Not covered
- A cross-role-only role (no shipped role is one) gets
decision-requiredon the read. Described above; no test of a cross-role role fixture was added. - No live bus, Console or re-pin: this row reaches a running stack only through a copy and re-pin, which is Sage's and Mos's.