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.
5.5 KiB
Row 84 (#1578): M1 chief-of-staff role file, contract and tests, candidate packet, round 1
Author: Rocko. Reviewers: Darkwing and Filbert. Gate: Sage. Brief:
docs/plans/2026-10-12_m1-chief-of-staff-role.md (blob da2a7b7e),
section "M1 chief-of-staff role file, contract and tests" (lead decision
104). Base: 4918d410 (base.txt). The packet is uncommitted. I made
no commits, pushes or Gitea calls, and read no token. Every test uses a
fixture business in a temporary directory; nothing touched Jason's
business file or anything under ~/.config/mosaic-dev.
Files (files.txt, candidate-manifest.sha256)
| File | Change |
|---|---|
roles/chief-of-staff.json |
new, roleVersion 2, decision 104's list |
roles/chief-of-staff.md |
new, the contract, in the style of roles/pm.md |
packages/business/tests/chief-of-staff.test.mjs |
new, 5 tests |
packages/business/README.md |
new subsection "Chief of staff instance" after "Business file" |
agents/rocko/work/queue-84/mutants.py |
the mutant rerun: python3 mutants.py <tree> |
No source file changes. Nothing in the repository lists roles/ by
directory, so the new file changes no other suite.
The role file
Exactly the brief's list: name chief-of-staff, title "Chief of staff",
contract chief-of-staff.md; tools read, grep, find, ls; network
api-only; authority.withinRole ["message.send"]; crossRole [];
credentials [].
The contract
It names the role, not the persona. It says the holder sends the human's
requests to the PM as messages and asks the PM for work; relays rulings
verbatim and asks the human when one is unclear; never resolves a
decision for the human; never holds or imitates human authority; never
routes around a refusal. One duty goes beyond the brief's four points and
comes from the approved draft ("raises decisions to Jason"): bringing
decisions that need the human to the human and carrying the answer back.
The protocol section says the holder never holds a token and sends
messages through the bus, as pm.md does.
Tests
node --test packages/business/tests/chief-of-staff.test.mjs: 5 pass.
Each test writes the fixture business (businessDoc plus an instance
chief with holder mos) mode 600 under a temp config/businesses/
and loads it with loadBusiness and the repository's roles/.
- The role file holds exactly the list (
loadRole). - The instance loads and resolves; the resolved tools, network, authority and credentials equal the list; tracker and launch are null.
classify:message.sendwithin;task.create,role.launchand every other action inACTIONSgated.- A business can't grant more than the role file (below).
- A
launchblock withby: "chief"refuses, naming the missingrole.launch.
One departure from the brief's wording
The brief asks for "a business that grants the instance a gated action
through its limits is refused". In the code, limits.authority is an
allowlist that only narrows (packages/business/src/resolve.mjs:66-69,
vars.mjs:117-125): naming task.create there is accepted and has no
effect, so it can't refuse. The test covers both sides:
- No widening.
limits.authorityat the business and agent layers namingtask.create,role.launch,deployandmessage.external,limits.networkopenandlimits.toolswithwrite,editandbashresolve to exactly the list, and those actions stay gated. - Refusals. An instance with an
authority,tools,networkorscopeskey refuses as unsupported; a Gitea credential reference refuses (the role needs none); atrackerrefuses (no Vikunja); alimits.authorityentry outside the vocabulary refuses.
Making a gated action in limits refuse would be a change to
packages/business/src, outside the files this row owns. If the
reviewers want it, it's a follow-up.
Mutants (mutants.py, out/mutants.txt)
13 mutants, each applied alone to the candidate tree, all killed: the
role file with write added, bash added, ls dropped, network open,
network none, task.create within, role.launch within, withinRole
empty, a crossRole action, a Gitea credential; and in business.mjs, an
instance allowed an authority key, the launch.by check removed, the
credential map check removed.
Gate (out/gate/, out/gate.sh)
Sequential, in the scratch worktree at 4918d410 with the candidate and
node_modules linked, full output kept per suite:
| Suite | Result |
|---|---|
business node (packages/business/tests/*.test.mjs) |
65 pass, 0 fail |
| test-auth | 15 passed, 0 failed |
| test-conductor | 17 passed, 0 failed |
| test-config | 24 passed, 0 failed |
| test-discord | 66 passed, 0 failed |
| test-extension-package | 18 passed, 0 failed |
| test-foundation | 44 passed, 0 failed |
| test-queue | 27 passed, 0 failed (the canonical-root checks skip in a worktree, as always) |
| test-release | 14 passed, 0 failed |
| test-task | 98 passed, 0 failed |
No ✖ or not ok in any output file. rocko-r84_default was removed
after the Docker suites. The gate ran before the README entry changed
from a placeholder to mos (below); no suite reads the README, and the
business node suite was rerun after the change (out/node-business-after-readme.txt).
For the reviewers
- The README entry names the instance
mos: Jason's Q120, lead decision 116, brief re-pinned at blobda2a7b7e. The tests use the fixture namechief. This row doesn't write the business file. examples/mosaic-stack.example.jsonis not in this row's files, so it has no chief-of-staff instance.