Files
jason.woltjeandClaude Opus 5.5 21e8f64ea7 agents: darkwing row 130 round 1 packet (#1625)
Build patch against c6570f142, candidate manifest (4 files), repro,
mutant (killed, 2 of 191), targeted gate (cli 191/0, webui 23/0) and
build.md.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-14 19:29:04 -05:00

4.9 KiB

Row 130 (#1625) round 1: human and tester commands name the tree the bus runs from

Brief: docs/plans/2026-10-14_clone-cli-copy-path.md (blob 8120438a6, re-pinned at 8089b98c4). Sage assigned the row in sage-0242 and granted the slot in sage-0246.

BASE: c6570f142. The candidate is build.patch against BASE, and candidate-manifest.sha256 lists its 4 files. repro.sh exports BASE with git archive, applies build.patch and checks every manifest digest: DIGESTS_OK (repro-run.log).

File Change
packages/cli/src/cli.mjs the entry block becomes an exported run(argv, io, deps), plus the OTHER_TREE text and a set of the two codes. Nothing above the entry changes
packages/cli/tests/other-tree.test.mjs new, 4 tests
packages/cli/README.md one sentence in the --tester bullet, one paragraph at the end of "One pinned copy"
docs/TOOLS.md one sentence at the end of "Pinned stack update", linking the README section

The code

run() calls main() and catches a CliError, as the entry did. It writes mosaic: <message> as before. When the error's code is human-required or tester-required, it writes one more line:

mosaic: the bus accepts these commands only from the tree it runs from; on a pinned host run `mosaic` on PATH

It returns the error's exit code, which busExit sets to 3 for both codes. The entry sets process.exitCode from it only when it isn't 0, so a success leaves the exit code unset, as before. run()'s default io is the same as main()'s.

The hint goes at the print site, not into the error. app.mjs (lines 347-379) compares e.message to e.code, so the message has to stay the bare code. The code comes from the transport's own stderr parse (error.code), so the agent-check refusal from refuseInsideAgent, which has no code, never gets the line. The line names no path. run() is exported so the tests drive the same print path the command line uses.

Tests

other-tree.test.mjs, 4 of 4 passing, and 191 of 191 in the cli suite (gate/pkg-cli.log).

  1. "a human command refused with human-required ...": inbox --business demo through the real humanTransport, whose spawn is a stand-in that exits 1 with human-required on stderr, so the real code parse runs. It asserts exit 3, stderr exactly mosaic: human-required and then the hint line, and empty stdout.
  2. "a --tester command refused with tester-required ...": agents --tester --business demo from an agent environment, through deps.testerSpawn. It asserts exit 3 and stderr exactly the code line plus the hint.
  3. "other exit-3 refusals print no other-tree line": tester-off the same way gives exit 3 and stderr exactly mosaic: tester-off. The plain human inbox from an agent environment gives the agent-check refusal, exit 3, with no hint.
  4. "the line names no path": OTHER_TREE has no /.

No existing test changes. stack-copy.test.mjs:144 reads human-required from human-cli.mjs run on its own, not through cli.mjs, so it is unaffected.

Mutant

mutants.py drops the if (OTHER_TREE_CODES.has(...)) line from run(), runs the cli suite, restores the file from .orig with a sha256 check and then kills any process whose cwd is under this row's TMPDIR. mutants-run.log has the run.

Mutant Change Result
m1-drop-other-tree-line drop the extra stderr line killed, 2 of 191 fail: tests 1 and 2

The run ended restored: True, and the sweep killed nothing.

Gate

A targeted builder gate under #271 and decision 136 (gate.sh, logs in gate/). Sage's landing gate stays full.

Step Result
packages/cli node --test tests/*.test.mjs 191 pass, 0 fail
packages/webui node --test tests/reads.test.mjs tests/views.test.mjs tests/pinned-copy.test.mjs 23 pass, 0 fail

GATE_PASSED 00:27:55Z (gate-run.log). webui/src/reads.mjs imports readNotifyConfig from cli.mjs, and those three webui tests import reads.mjs directly. serve.mjs and webui's cli.mjs also import reads.mjs, and Sage's full gate covers the rest of webui. No scripts/test-*.sh reads a changed path. packages/queue/tests/dispatch.test.mjs names /packages/cli/src/cli.mjs only as a string. host.mjs:375 matches a process argv ending in that path, and the path doesn't change. No step failed, so the flake rule had nothing to count.

Leftovers (sweep.log)

No process was left under the TMPDIR. The cli suite left 6 mosaic-launch-* directories, 3 from the mutant run and 3 from the gate. They come from the cli tests' own launches (#1624) and are unrelated to this change. sweep.log lists them with their mtimes and records that I removed them.

Going live

This row has no live step. The refusal the line explains happens on the checkout's scripts/mosaic, which runs the checkout's own cli.mjs, so the line shows there once Sage lands the row. mosaic on PATH runs the pinned copy and gets the change only at the next re-pin, on Mos's go (decision 109). Per the brief, that re-pin is when the row counts as live.