Files
jason.woltje 6bad02612a lead: commit dewey's untracked evidence (721 files: queue-124-review queue-143 queue-149 queue-165-review queue-170 queue-175 queue-177 queue-184 queue-191)
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

10 KiB

Row 184 (#1682): tester thread verbs in the CLI

Builder: Dewey. Reviewer: Filbert. Gate owner: Sage. Brief: docs/plans/2026-10-20_tester-thread-follow-ups.md, section "Brief: tester thread verbs in the CLI", blob 3b4313cb9f (commit 841c56d05, rev 2161). Assignment sage-1202. Design and changed tests accepted in sage-1208.

Base: 7fc98cf19 (base.txt), origin/refactor after row 178 landed as b2b33aee5 (sage-1220). The candidate is build.patch on that base. candidate-manifest.sha256 lists the 7 changed paths (changed-paths.txt) with their sha256. The manifest digest is e7e2c76308e1b00391bb758d66fb8b427aad3e2985f30c651f5ef84c02ee2630, and the stable patch-id is ad846db177b35b7c64b333900554bbca75852bce. The patch applied to a fresh index at the base rebuilds every manifest line.

Head moved after run 2 (sage-1225)

origin/refactor is 3da362982. Past 7fc98cf19 it adds row 182 (33ab5eeb9), Sage's SESSIONS and BUILD-LOG sweep (ef09f8b3f) and queue commit 3da362982. None of them touches a 184 path. Sage ruled no rebase or rerun before review; the landing gate reruns everything on the head current then.

Row 182 changes packages/bus/src/broker.mjs, and tests/tester-threads.test.mjs runs a real Broker in-process, so I read the change. #testerOwns refuses thread-not-owned when the tester's reply chain reaches a thread the tester doesn't own, and it leaves a role's reply into a tester thread unchanged. In this row's test, the tester's message.send calls (:136, :179, :210) all go to chief, and none has in_reply_to. chief's definition is chief-of-staff (:25), and THREAD_DEFINITION is assistant (business vocabulary.mjs:131), so #threaded is false for chief and #threadFor returns null. The tester reaches threads only through thread.send, which #thread() already checks. The replies into the tester's threads are role sends from a. So I expect no change. This is a read of the code, not a run on 3da362982.

Rebase from 841c56d05

The first build was on 841c56d05. Its patch, manifest (aa15e024...), base and patch-id are in r1-841c56d05/. Of the 7 paths, only docs/TOOLS.md changed between the bases. Row 178 added its Console proxy proof section at :856, and this row's hunks are at :264-281, so all 7 paths apply cleanly with no conflict. The TOOLS.md lines this patch adds and removes are the same before and after, the patch-id is unchanged, and only the TOOLS.md blob in the manifest differs. Row 184 doesn't change packages/cli/tests/stack-release.test.mjs, which row 178 also touched. 178's two T178 tests there run stack-release activate with the proof pair and --tester-port and read nothing this row changes. They run in the cli suite in run 2.

No bus change

Every verb the CLI now sends as the tester is already in the broker's TESTER_VERBS: project.list, thread.list, thread.create, thread.open, thread.send, thread.messages and message.receive. The cli channel module's verb set covers the same names. The broker's #thread() refuses thread-not-owned when the thread's owner isn't the caller, and the tester's owner is tester (row 177), so the CLI adds no check. A thread run's reply to the tester lands in the run's thread, so follow's thread filter (threadMembers over thread.messages) works as it does for the human. sage-1208: the ask-first clause doesn't apply.

What ships

  • packages/cli/src/cli.mjs: talk drops the row 177 refusal and its comment:

    // Row 177 (#1675): the tester has threads of its own now, but the CLI's thread verbs stay the
    // human's; a tester talk --thread is a follow-up row.
    if (threadRef !== undefined && parsed.flags.has("--tester")) throw usage("--thread takes no --tester");
    

    talk's header comment gains three lines for row 184. No other line changes: callFor and channelCallFor already route --tester to tester-cli.mjs, and the follow loop already receives with message.receive.

  • packages/cli/src/thread.mjs: thread create|open|read accept --tester (flags: sub === "read" ? ["--json", "--tester"] : ["--tester"]), so callFor(parsed) runs them as the tester. The usage lines show [--tester]. The header comment names row 184. threadsCommand already took --tester (row 175), so only its comment changes.

  • docs/TOOLS.md and packages/cli/README.md list [--tester] on threads, thread create/open/read, talk --thread and follow --thread. The README's tester paragraph names row 184, and its refusal list no longer names the thread verbs.

Changed existing tests (sage-1208)

Both tests pinned the refusal the brief removes.

  • packages/cli/tests/thread-cli.test.mjs:311: the exit-4 loop drops one argv, ["talk", "--thread", "stack/alpha", "hi", "--tester"]. The other three argvs and the unchanged-writes assert stay.

  • packages/cli/tests/tester-reads.test.mjs, test "row 175: --tester on limits set, reset and migrate and every write form outside W1-W7 exits 4": the list drops five entries, and a two-line comment points to tests/tester-threads.test.mjs:

    ["thread", "create", "alpha", "one", "--business", "acme"],
    ["thread", "open", "t-1", "--business", "acme"],
    ["thread", "read", "t-1", "--business", "acme"],
    ["talk", "--thread", "t-1", "hello", "--business", "acme"],
    ["follow", "--thread", "t-1", "--business", "acme"],
    

New tests: packages/cli/tests/tester-threads.test.mjs

The test runs an in-process Broker on a Store with business demo (bus.tester true, roles chief and assistant). The human and tester caps sit behind stand-in transports that record each call and turn a bus refusal into a CliError carrying the code. Tester forms run with the CLAUDECODE markers set, as an agent would.

  1. With --tester, thread create, threads, open, talk --thread and read run on the tester's own thread. Checks: owner tester, the open recorded for the tester, from_role tester and from_run tester-cli on the sent message, the exact printed lines, no human-transport call, no inbox call, and the human's threads still printing "no threads".
  2. talk --thread --follow and follow --thread with --tester print and read only the probe thread's messages. A message outside any thread and a reply in another thread stay unread. Each receive is message.receive; inbox is never called (brief item 3).
  3. On a human thread, --tester open, read, talk, follow and talk --follow refuse with thread-not-owned, and the CLI prints it. A label or prefix reference gives "no thread of yours matches". Creating the same name gives thread-exists. Writes are unchanged and the waiting tester message stays unread. In reverse, the human gets thread-not-owned on the tester's thread.
  4. Without --tester, every human form runs and never calls the tester transport. The existing thread-cli tests pin the human output unchanged.
  5. In a real process, scripts/mosaic with a started broker and tester-cli.mjs runs thread create, threads --json, talk --thread --wait 0 and thread read --json as the tester.

Deviations

  1. The usage text shows [--tester] on the thread lines, so a usage error prints the new text. Without --tester, each verb's result is unchanged; thread-cli.test.mjs pins it.
  2. Brief item 3 (follow receives through message.receive) needed no code change. The loop already did, and test 2 proves it for the tester.
  3. thread.send returns no waiting field, so the tester-target-not-running line doesn't apply to --thread.

Runs

Run 1, on 841c56d05

Slot sage-1211, after my row 178 r2 review. Busy check CLEAR at 05:19:37Z; the run went 2026-10-20T05:19:37.901Z to 05:24:06.168Z. The summary is in run-841c56d05.txt. After each mutant, cli.mjs and thread.mjs were restored and their sha256 checked. No process was left in the run directory.

Run Expect Result
red-base: base cli.mjs and thread.mjs with the new test file fail 1/5, the four tester tests fail
tester-threads.test.mjs pass 5/5
thread-cli.test.mjs pass 11/11
tester-reads.test.mjs pass 6/6
tester.test.mjs pass 8/8
follow.test.mjs pass 6/6
names.test.mjs pass 2/2
channel-cli.test.mjs pass 2/2
app.test.mjs pass 30/30
mark-read.test.mjs pass 6/6
cli.test.mjs pass 12/12
awk sum of the singles pass 88/88
cli package suite (node --test tests/*.test.mjs in packages/cli) pass 418/418
M1a: talk --thread drops --tester (sent as the human; brief mutant 1) killed 1/5
M1b: thread create/open/read drop --tester before callFor killed 2/5
M2: no thread filter on follow with --tester (brief mutant 2) killed 3/5
M3: the refusal put back killed 1/5
M4: thread flags without --tester killed 2/5

The base passes test 4 (without --tester), as it should. M2 fails test 2 at :162, where follow prints "outside any thread" and "other answer". It also fails test 3 at :216: follow by full id on a human thread exits 0 and prints the waiting message. Without the filter no thread.messages call is made, so the broker never says thread-not-owned.

Run 2, on 7fc98cf19

Slot sage-1223. Busy check CLEAR at 05:47:40Z; the run went 2026-10-20T05:47:40.864Z to 05:51:48.408Z. The summary is in run-7fc98cf19.txt. No process was left in the run directory, and the worktree matched the manifest after the run.

Run Result
tester-threads.test.mjs 5/5
thread-cli.test.mjs 11/11
tester-reads.test.mjs 6/6
names.test.mjs (reads USAGE) 2/2
awk sum of the singles 24/24
cli package suite 420/420

The suite count is run 1's 418 plus row 178's two T178 tests in stack-release.test.mjs. The mutants weren't rerun. The source and test files are byte-identical to run 1, and only docs/TOOLS.md changed, which no test reads. stack-release.test.mjs:436 names "TOOLS.md step 4" only inside an expected refusal string.

Readers and pins, checked on 7fc98cf19. host.test.mjs:826 reads packages/cli/README.md to find the bus unit's EnvironmentFile line, which this row doesn't change, and it passes in both suite runs. names.test.mjs reads USAGE for the name and names lines only. No test or scripts/test-*.sh pins the base sha256 of any of the 7 paths.