Files
jason.woltje 4ff4f44c98 lead: commit researcher's untracked evidence (1406 files: queue-101 queue-105 queue-128 queue-134 queue-143 queue-148 queue-183 queue-228)
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.
2026-11-02 15:05:14 -06:00

7.3 KiB
Raw Permalink Blame History

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.human does not refuse it, and the same #checkAuthority(s, "organization.change") runs before the read. A caller whose role lacks the grant is refused exactly as organization.set refuses it (not-holder, read-only, tester-refused, agent-required for the human).
  • keys(args, []) rejects any argument (invalid-request).
  • The result is built as { revision: <limits read>.revision }, so the rest of the limits read never reaches the caller.
  • The grant is consumed only for organization.set (role && !revisionRead). A read writes no action.allowed event and spends nothing.
  • limits, limits.set, plugins, instances, routes stay human-only and organization.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-required on 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.