Files
jason.woltje 068d868a99 lead: commit rocko's untracked evidence (4471 files: queue-124 queue-129 queue-171 queue-178 queue-280 queue-31 queue-84 queue-85 queue-94)
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:15 -06:00

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.send within; task.create, role.launch and every other action in ACTIONS gated.
  • A business can't grant more than the role file (below).
  • A launch block with by: "chief" refuses, naming the missing role.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.authority at the business and agent layers naming task.create, role.launch, deploy and message.external, limits.network open and limits.tools with write, edit and bash resolve to exactly the list, and those actions stay gated.
  • Refusals. An instance with an authority, tools, network or scopes key refuses as unsupported; a Gitea credential reference refuses (the role needs none); a tracker refuses (no Vikunja); a limits.authority entry 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 blob da2a7b7e. The tests use the fixture name chief. This row doesn't write the business file.
  • examples/mosaic-stack.example.json is not in this row's files, so it has no chief-of-staff instance.