# Row 132 (#1628): harness tools for ending a run and changing the organization file Seat: Researcher. Reviewer: Filbert (broker authority, no sandbox change). Gate owner: Sage. Requirements: REQ-ROLE-2, REQ-COS-1, REQ-LIMIT-1. Brief: `docs/plans/2026-10-14_prd-1.2-build.md`, section "Harness tools for ending a run and changing the organization file". **Base: b6a588cd2** (detached worktree). Rows 119, 120, 122 and 123 are in it. No path under `packages/harness`, `packages/bus` or `packages/business` changed between b6a588cd2 and canonical HEAD 4a34fb0d6 (checked with `git diff --name-only`; later commits are docs and queue only). `row132.patch` is a diff of my changes only: 8 files, +162 −19. It applies with `git apply --cached --check` on b6a588cd2 and round-trips to the candidate tree `85056b71653ac02634d1742d2e72e4f3588bcbbd`. `candidate-manifest.sha256` lists the 8 files (`packages/bus/src/broker.mjs` among them), file sha256 `ba644f8d97cfa88175fe0157d9f77523cee7e5821ddbd676b29aae46fa73cf07`. Nothing is committed in the worktree, nothing is pushed, and no live store, unit, credential or `~/.mosaic` file was touched. ## For the 3d re-pin window: the chief's bundle manifest **The chief of staff's bundle manifest lists both tools in `typed` and `untyped` is `[]`, for both harnesses.** Test: `packages/harness/tests/bundle.test.mjs:85`, "a chief-of-staff bundle lists end_run and change_organization as typed and nothing untyped, for either harness". It builds a bundle for the `chief` instance (definition `chief-of-staff`) with `buildBundle` and asserts, per harness: | Harness | `manifest.typed` | `manifest.untyped` | |---|---|---| | `pi` | `send_message, launch, end_run, change_organization, raise_decision, list_agents, inbox, trail, list_tasks, get_task` | `[]` | | `claude-code` | the same ten, each as `mcp__mosaic__` (so `mcp__mosaic__end_run`, `mcp__mosaic__change_organization`) | `[]` | The same test checks the prompt's `Bus tools:` line and that the "Actions you hold that have no bus tool yet" line is absent. `tools.test.mjs:33` asserts `untypedActions(chief, tools)` is `[]` (it was `["role.end", "organization.change"]` after row 123). Controls and mutants m3b and m4b (below) fail this test if either tool goes missing from `typed`. ## What the row does 1. **`end_run`** (`packages/harness/src/tools.mjs:141`): a session gets it when its resolved `limits.authority` holds `role.end`. Verb `role.revoke`, parameters `role` and `reason` (1 to 500 characters), **no `decision`**. The broker already refuses the caller's own run and the assistant's with `role-end-refused`; the runner passes that code through (`refused: role-end-refused`). 2. **`change_organization`** (`tools.mjs:147`): a session gets it when it holds `organization.change`. Verb `organization.set`, parameters `revision` (64 hex characters, required), `limits`, `models`, `instances`. A stale revision fails with `revision-conflict`. 3. **Key list in one place, with a failing test.** `ORGANIZATION_KEYS` is exported from `tools.mjs` for the harness; the broker's own list at `packages/bus/src/broker.mjs:99` is now exported too, and `tools.test.mjs:64` fails if the two differ (mutants m15, m16). 4. **Bundle text** (`bundle.mjs:92-96`): the "only the human stops a run" bullet is now chosen by whether the session has `end_run`. A `role.end` holder is told to use `end_run` with a reason and what is still the human's (`mosaic stop`, and a `role.revoke` decision for anything the broker refuses). Every other role keeps today's text byte for byte (test `bundle.test.mjs:98`). 5. **README text** in `packages/harness/README.md` and `packages/business/README.md`. ### Bus change: the `export` is the only one `packages/bus/src/broker.mjs` changes by one word, `const` to `export const`, on the line that declares `ORGANIZATION_KEYS` (line 99; +1 −1). **No bus behaviour changes.** No verb, check, schema, migration or message text in `packages/bus` differs. The bus suite is 191/191 on this candidate. ## Out of scope: the chief cannot read the revision (row 134) `change_organization` needs the file's current `revision`. The only read, the broker's `limits` verb, is human-only (`broker.mjs:729`: `if (!role && !s.human) fail("human-required")`), and `organization.set` takes the keys at line 732 with `revision` required. So **today the tool works only when the chief already has the current revision from somewhere else.** A stale or unknown revision fails with `revision-conflict` (`packages/business/src/organization.mjs:185`), so `change_organization` fails closed. This does not block the 3d window, which checks the manifest only. The gap is **row 134**, queued at rev 1327, brief `docs/plans/2026-10-15_organization-revision-read.md` (commit 26f4f9701). Row 132 does not grow to cover it. Other items I did not touch: P19 task 299 (outcome-unknown after the writer) is not part of this row; the `reason` control-character decision from row 123 round 2 stands. ## Tests | File | Change | |---|---| | `packages/harness/tests/tools.test.mjs` | chief test renamed and its tool list updated, `untypedActions` back to `[]` (line 33); shape test (46); parity with the broker's list (64); non-grant roles and per-grant (69) | | `packages/harness/tests/bundle.test.mjs` | chief Bus tools regex (74); typed/untyped for both harnesses (85); stop-line variants (98) | | `packages/harness/tests/runner.test.mjs` | fixture gains `chief` and `assistant`, `setup()` takes `as`/`organization`; test 377 runs both tools through a real broker with a fake organization writer: `{"revoked":true}`, `refused: role-end-refused` for the self and assistant cases, the writer called with `["demo","limits.set",args,"chief-of-staff.chief"]`, `refused: invalid-request` for a key outside the list, `refused: revision-conflict` for a stale revision | ## Mutants `mutants.sh `, started 2026-10-15T18:00:05Z (`mutants-started.txt`), results in `mutants-run.txt`, one `.out`, `.rc` and `.patch` per mutant in `mutants/`. Three controls (the tools, bundle and runner tests on the unmutated candidate) pass: 16/16, 11/11, 23/23. **All 28 mutants fail their tests.** The worktree was restored after the run (`git status` shows the same 8 files). | Mutant | What it breaks | Killed by | |---|---|---| | m1, m2 | `end_run` / `change_organization` for every role | tools test (7 failures each) | | m3, m3b / m4, m4b | either tool never offered (tools, bundle) | tools 3, bundle 3 / tools 3, bundle 2 | | m5, m5b | `end_run` verb wrong (tools; runner) | shape test; runner test 377 | | m6 | `end_run` carries no action, so no grant is checked | tools tests | | m7, m7b | `change_organization` verb wrong (tools; runner) | shape test; runner test 377 | | m8 | `change_organization` carries no action, so no grant is checked | tools tests | | m9 | `reason` optional | shape test | | m10 | `end_run` takes a `decision` | shape test | | m11 | `reason` unbounded | shape test | | m12 | `revision` optional | shape test | | m13 | `revision` pattern dropped | shape test | | m14 | `end_run` description loses the own-run, assistant and `role-end-refused` sentence | shape test | | m15 | a key dropped from the harness list | shape test, parity test | | m16 | a key added to the broker list | parity test | | m17 | the bundle keeps the old stop line for the chief | stop-line test | | m18 | the new stop line for every role | stop-line test, pi bundle test | | m19, m20, m21 | the new text loses the reason requirement, the own-run and assistant sentence, or the "anything the broker refuses" wording | stop-line test | | m22 | the runner drops the tool's arguments | runner test 377, capability test | | m23 | the runner sends the tool name as the verb | runner test 377, capability test | | m24 | the runner hides the broker's refusal code | runner test 377, capability test | ## Gate `suites.sh `, 2026-10-15T18:01:54Z to 18:03:33Z (`gate-started.txt`, `gate-finished.txt`, `suites-run.txt`, output per suite in `suites/`). All rc=0; the full set, including the harness, bus and business suites the brief asks for: | Suite | Result | |---|---| | business | 92/92 | | bus | 191/191 | | plugins | 21/21 | | tasks | 97/97 | | cli | 195/195 | | webui | 112/112 | | harness | 90/90 | | seat | 132/132 | | scripts/test-land-work.sh | 84 passed, 0 failed | No flake (#258, #261, #262, #263) recurred. The gate left no Docker network (before and after `docker network ls` identical). A busy check (by exit status, `/proc` cwd links only) was clear at 2026-10-15T17:59:14Z before the run. ## Not covered - The live chief session: no session was launched. The evidence is the bundle test and the runner test against a real broker. - Whether a model uses `end_run` well. The text tells it what to do; nothing tests the model.