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

19 KiB
Raw Permalink Blame History

Row 175 (#1673, Q173, mos-0870 N2, mos-0872): tester reads and writes for the test system

Builder: Dewey. Reviewer: Filbert. Gate owner: Sage. Brief: docs/plans/2026-10-19_tester-read-checks.md, blob 41b6033da2 (commit 3dfaea286, queue rev 2048), which adds the scripts/mosaic section, test 12 and its mutant to blob 8973499247 (1c90c68c5). Assignment sage-1074, plus sage-1081 for the wrapper. Round 2: sage-1094 (the rebase past row 169, and Filbert's round 1 notes N1-N3 from comment 29264). Slot: Darkwing's clear after the row 169 mutants (22:53Z).

Round 2 base: 9e74925c2 (base.txt), the refactor tip with row 169's landing 264756a30 and row 179's 9e74925c2. Both changed docs/TOOLS.md, one of this row's 25 paths, so each moved the candidate off base (the candidate-drift rule).

  • Row 169 broke Filbert's round 1 approval of TOOLS.md's sha256. Round 2 first rebased onto 1bfc9833b, and that rebase applied cleanly. Row 169's TOOLS.md edit is in stack-release activate, pm-on and the two-phase PM start; this row's is the one-sentence agent-check exception at :277. At round 2's start, origin/refactor was e7d47c346, which lacks 264756a30. Sage's 1bfc9833b, pushed after, is the base that carries it.
  • Row 179 landed during the round 2 request (sage-1100). git diff --stat 1bfc9833b 9e74925c2 lists BUILD-LOG.md, docs/SESSIONS.md and docs/TOOLS.md; only TOOLS.md is a manifest path. The rebase onto 9e74925c2 applied cleanly. git range-diff shows all three commits as =, and git patch-id --stable on the whole diff is c1cf0ed66006 both before and after. Only TOOLS.md's hash changed in the manifest.

Candidate: 989699f7c (candidate.txt), three commits on the base in Dewey's worktree, not on refactor:

  1. c0b06ed89, the row (round 1's 84e350f99, rebased).
  2. 3067ae93e, sage-1081's wrapper line, test 12 and the bad-key asserts (round 1's 7b27ab3f9, rebased).
  3. 989699f7c, N1-N3.

build.patch is git diff 9e74925c2 989699f7c. candidate-manifest.sha256 (digest 2e30f2a1e667) lists the 25 changed or added paths with their sha256. That is the same path list as round 1. Six hashes differ from round 1's manifest: docs/TOOLS.md (the rebases), and broker.mjs, cli README, tester-reads.test, paths.mjs and tester-init.test (N1-N3).

The first round 2 request (comment 29275 on #1673, manifest bdc4e74f4aa6, f9e41b918 on 1bfc9833b) is superseded. Its packet files are kept in r2a/.

Follow-up, not in this row: row 179's TOOLS.md step 2 says "Until row 175 lands, scripts/mosaic doesn't dispatch plugins". After this row lands, the condition that matters is the window's target copy. A copy from before the landing still needs the node <copy>/packages/cli/src/cli.mjs form, and a later one takes mosaic plugins set. The 179 brief says the second row to land rebases. This row changes no wording there, so the sentence should be reworded once it lands.

Round 1 (approved by Filbert, 29264): base d63cfca22, candidate 7b27ab3f9, manifest dc5c0361a310. Its packet files are kept in r1/.

Row 164's pinned files

packages/bus/src/human.mjs, packages/bus/src/human-cli.mjs, packages/cli/src/transport.mjs and packages/webui/src/bus.mjs are untouched (not in the diff). channel-proof.test.mjs passes on the candidate. A grep of every packages/*/tests file and scripts/test-*.sh for a 64-hex value finds pins on those four files only. A grep for each changed path finds one test that reads a changed doc: packages/cli/tests/host.test.mjs:826 reads the cli README for the unit file's name, which this row doesn't touch; it passes.

What ships

Bus (packages/bus/src/broker.mjs)

  • TESTER_VERBS is exported and frozen. It gains usage, names, limits, plugins, instances, project.list, thread.list, name.set and project.create (19 entries).
  • New exported, frozen TESTER_ORGANIZATION_VERBS: limits, plugins, instances, plugins.set, instances.set, routes.set. In the organization branch the tester gate runs first, before any argument check, as the store's gate does: a tester sending any other organization verb (limits.set, organization.set, organization.revision, routes) refuses tester-refused. Then the branch passes a tester where it used to refuse human-required, and actor = s.tester ? "tester" : s.human, so W1-W3 receipts name tester. Every check the human's write runs still runs (revision, key lists, plugin and route checks, the instance writer's refusals).
  • name.set (W4): a tester skips #classification and #consumeAuthority, runs every name check, and the insert writes by = 'tester'.
  • project.create (W6) and project.list: a tester skips #human; created_by is tester.
  • thread.list: a tester skips #human and the query binds owner to the business human (b.human), so it lists the human's threads. Row 177 gives the tester its own.
  • message.send (W5): the [mos test] check, tester-prefix and TESTER_PREFIX are gone. The keys, role-only target, no decision, no authority, no human.input and tester-reply-required stay.
  • #requestProjectFile (project.files, project.file, project.file.save): these dispatch before the store's tester gate, so a tester used to get human-required from #human. It now refuses tester-refused first, so the brief's item 9 code holds for project.file.save. A behavior change for a tester only; the human's path is unchanged.

CLI (packages/cli/src/)

  • --tester on the seven reads: usage (usage.mjs), names (cli.mjs), limits (the read only), plugins, threads (thread.mjs), project list (project.mjs) and business instances (instances.mjs). The flag stays in parsed until callFor/channelCallFor, which pick the tester transport.
  • --tester on the write forms: plugins set (W1), business instance add|set (W2), name (W4), talk --tester (W5, any text), project create (W6) and project init (W7). W3 has no CLI form.
  • limits refuses --tester on set, reset and migrate (exit 4, before any call).
  • talk loses the early prefix check and TESTER_PREFIX.
  • project init --tester (projectCommand), in order, before any disk write:
    1. testerProjectArea(context): refuses tester-project-area (ProjectError, exit 2) for a repo outside <dataRoot>/projects/<business id>/.
    2. One agents read through the tester transport, so tester-off and tester-required exit 3.
    3. initProject(context, { author: 'tester' }). project check takes no --tester, and a duplicate --tester on init is exit 4.
  • Usage strings gain [--tester] on each form that takes it.

Wrapper (scripts/mosaic, sage-1081)

  • The case line that sends verbs to packages/cli/src/cli.mjs gains plugins, name, names, follow, threads and thread. Before, they fell through to the seat CLI, which refused with usage: mosaic launch ..., so mosaic plugins set (W1), mosaic name (W4) and mosaic names failed through the wrapper. queue and business keep their own branches. scripts/mosaic is not a row 164 pin, and no test pins its hash.

Round 2 (sage-1094, Filbert's notes)

  • N1: testerProjectArea also refuses the area root itself, <dataRoot>/projects/<business>, with tester-project-area. A repository there would hold every other project's default repository. contains is unchanged, since projectRepo's overlap check still needs it to count equal paths. The check is relative(area, repo) === '' || !contains(area, repo). The cli README's project init --tester item says so.
  • N2: test 10's list gains bus start acme --tester and bus stop --tester. Both already refused with exit 4 in parse, so this is a test-only change. The cli README's no---tester list names them.
  • N3: a comment at the reader view in request (broker.mjs, after the TESTER_VERBS gate). It says the view now also carries name.set and project.create, and that neither branch checks reader: each passes a tester by s.tester and records tester.

Project (packages/project/src/)

  • initProject(context, { author }): the layout commit's author and committer are author, checked with checkedId before anything is made; without it, the business human as before.
  • testerProjectArea(context) in paths.mjs: projectRepo(context) (its own checks first), then the same contains test projectRepo uses (hoisted to module scope), against the area. It writes nothing.

Docs

bus README (verb table rows for organization.set, organization.revision and name.set; the instances, routes, tester and project-file paragraphs), cli README (usage block, the --tester paragraph, a new project init --tester item, limits, instances and usage sections), plugins README (step 5) and docs/TOOLS.md (one sentence: the --tester forms are the exception to the agent check). grep -rn "tester-prefix\|mos test\]" packages scripts docs now finds only sample message bodies in tests, which are valid bodies, not prefix rules.

Design choices and tradeoffs

  • Both tester lists are exported so the item 13 test can pin them.
  • The organization gate sits before the argument checks, so a tester's refused verb never reveals argument errors, as at the store gate.
  • contains counts the area root itself as inside (rel === ''), as projectRepo's own use does. In round 1 that let a tester init a repo at <dataRoot>/projects/<business> itself. Filbert's N1 flagged it, and round 2 refuses it in testerProjectArea alone.
  • The project file verbs' tester code changes from human-required to tester-refused, so the brief's item 9 holds for project.file.save and the tester's codes stay uniform.
  • The W7 CLI tests spawn scripts/mosaic as its own process. The tester transport is spawnSync, so a tester-cli.mjs spawned from the test process couldn't reach a broker in that process; as a child of a child it can. The child's env drops MOSAIC_BUS_(TESTER|CLI)_NONCE.

Tests (the brief's thirteen)

New files: packages/bus/tests/tester-writes.test.mjs (8 tests), packages/cli/tests/tester-reads.test.mjs (6), packages/project/tests/tester-init.test.mjs (3).

  1. bus tester-writes "reads": the human sets a name, a project and a thread; each of the seven reads deep-equals between tester and human, and thread.list, names and project.list are non-empty.
  2. cli tester-reads "each read with --tester": for the seven forms, the output inside an agent with --tester equals the human form's; the tester transport is called and the human's isn't; without --tester exit 3 before any call.
  3. bus tester-writes "W1-W3": plugins.set, instances.set and routes.set as the tester each log actor tester with a matching transaction; the human's routes.set logs jason. Also bus instances-verb.test and routes-verb.test pass the tester's actor through.
  4. bus tester-writes "W2 refusals": a credential-declaring definition (organization-credentials-out-of-scope), a protected definition (organization-definition-refused), the names human and tester (invalid-request), and a bus.tester var on add and on change (invalid-request); the file bytes, revision and log are unchanged after each. Control: an add with a model var lands with actor tester.
  5. bus tester-writes "W4" and names.test: by = 'tester', no new events, the human's jason; name-taken, name-is-instance (instance key, human, tester) and name-is-role still refuse.
  6. bus tester.test.mjs "a tester message is from tester, needs no prefix, and writes no human.input or authority" (updated): stored body without the prefix, unknown-role for the targets human and tester, invalid-request for corrects and decision, no event. cli tester.test "talk --tester sends text with no prefix" checks the stored body.
  7. bus tester-writes "W6": created_by tester and jason, project-exists still refuses, the tester's project.list equals the human's.
  8. cli tester-reads, three W7 tests through scripts/mosaic: init makes the repo under the area, committed tester|tester, and plain init inside an agent still exits 3; a repo outside the area (bus.tester off) exits 2 tester-project-area with the target missing and the data root listing unchanged; off exits 3 tester-off and each managed-run marker exits 3 tester-required, with no projects directory. project tester-init covers initProject's author option and testerProjectArea directly; since round 2 its refused list includes the area root <dataRoot>/projects/acme (N1).
  9. bus tester-writes "human-only": the 13 sync verbs refuse tester-refused, and so do organization.set and organization.revision with a key neither takes (the gate runs before the argument checks), project.file.save and project.files reject tester-refused; the organization file bytes, the limits revision, the human's views and thread.messages are unchanged.
  10. cli tester-reads: --tester exits 4 with no calls and no projects directory on limits set/reset/migrate, decide, inbox --read/--read-all, thread create/open/read, talk/follow --thread, stop, launches off/on/list, bus start and stop (round 2, N2), project check and a duplicate --tester on init.
  11. bus tester-writes: vars null, {}, false, 'true' and 1 each refuse tester-off.
  12. cli tester-reads "scripts/mosaic sends plugins, name, names, follow, threads and thread to packages/cli": as instances.test.mjs:146, it spawns the wrapper with no config. Each verb, in a form packages/cli refuses, exits 4 with packages/cli's own message, and stderr has no seat usage (mosaic launch <). app.test.mjs:832, which pins the seat usage, passes unchanged.
  13. bus tester-writes pins TESTER_VERBS (19) and TESTER_ORGANIZATION_VERBS (6) and checks both are frozen.

Existing tests changed

  • bus tester.test: the message test drops the prefix and asserts the stored body.
  • bus names.test: the tester's name.set lands with by = 'tester' instead of refusing.
  • bus instances-verb.test: the tester leaves the refused set; its forged actor refuses invalid-request, its instances and instances.set pass with actor tester.
  • bus routes-verb.test: the tester's routes refuses tester-refused; its routes.set passes.
  • bus limits.test: the refusal loop covers role and reader; the tester's limits.set refuses tester-refused and its limits read passes.
  • cli names.test: the usage pins gain [--tester].
  • cli tester.test: the prefix test becomes "talk --tester sends text with no prefix, and follow --tester reads"; the tester-prefix exit map line goes.

Runs

All runs are Dewey's, in Dewey's worktrees with TMPDIR under Dewey's scratch, on the heavy-run slot (Darkwing's clear at 22:53Z, released to Sage at 23:10:15Z). Every count below is an awk sum over the ℹ lines of the run's logs.

Round 1a, candidate 84e350f99 (manifest b9f438788558, 24 paths), 22:55:08Z-23:00:17Z

  • The 21 touched test files, each run alone: 169 tests, 169 pass.
  • Suites: bus 436/436, cli 369/369, plugins 111/111, project 40/40, business 149/149, webui 239 pass and 1 skip of 240. The skip is the existing editor-rebuild check, which needs esbuild 0.28.2; 0.28.1 is installed. Total: 1345 tests, 1344 pass, 0 fail, 1 skip.
  • Mutants: 25 run in a second worktree that matches the manifest, after a control pass of every named test file. 24 killed. m2b (organization.set let through for the tester) survived: it is a role verb, so #checkAuthority → #agent refuses a tester tester-refused anyway, and the test sent valid arguments. Round 1b adds bad-key asserts that kill it.

Round 1b, candidate 7b27ab3f9 (manifest dc5c0361a310, 25 paths), 23:04:15Z-23:10:04Z

  • The 21 touched files, plus app.test.mjs, stack-copy.test.mjs and stack-release.test.mjs (the other tests that spawn or copy scripts/mosaic), each run alone: 285 tests, 285 pass.
  • The cli suite: 370/370 (369 plus test 12).
  • Mutants after a control pass: m2b, now killed by tester-writes. m10a (drop plugins from the wrapper's case line) and m10b (drop name) are both killed by tester-reads test 12. Each file was restored and its hash checked, and the wrapper kept its mode.
  • Both worktrees matched the manifest at the start and at the end. A sweep after the run found no process with a working directory in Dewey's run dirs.

After the slot release, a grep for scripts/mosaic in every packages/*/tests file and scripts/test-*.sh found more readers than the cli files above. The queue suite's dispatch.test.mjs runs the real wrapper against fake CLIs: run alone, 4/4. None of its cases uses the six verbs. bash -n scripts/mosaic passes and the file is still executable, which is what test-queue.sh checks. The webui, runs and other hits mention the path only as text: unit file lines, a usage string, or scripts/mosaic-task.mjs/mosaic-config.mjs.

The bus, plugins, project, business and webui suites weren't rerun in 1b. Their source is unchanged between 84e350f99 and 7b27ab3f9; the one bus file 1b changes is tester-writes.test.mjs, which ran alone (8/8).

Round 2, candidate f9e41b918 (manifest bdc4e74f4aa6, 25 paths), 23:33:12Z-23:41:13Z

The slot came from sage-1094. The base is 1bfc9833b. At the start origin/refactor was e7d47c346, which didn't have row 169's landing 264756a30, so I based the round on the local commit Sage said they would push. When the runs ended, origin/refactor was ce5807989, which contains 1bfc9833b and changes only BUILD-LOG.md and docs/SESSIONS.md. Neither is a manifest path.

  • Singles: the 21 touched files, plus app, stack-copy, stack-release and queue dispatch.test, each run alone. 309 tests, 309 pass. stack-release.test.mjs (93/93) covers row 169's change, which came in with the base.
  • Suites: cli 390/390 (370 plus row 169's stack-release tests) and project 40/40.
  • Mutants: all 28 rerun after a control pass of every named file, and all 28 were killed. m11 restores rel === '' in testerProjectArea (if (!contains(area, repo))), and tester-init.test.mjs kills it. m2b is still killed by the bad-key asserts. Each file was restored and its hash checked, and the wrapper kept its mode.
  • Both worktrees matched the manifest at the start and at the end. A sweep after the run found no process with a working directory in Dewey's run dirs.

The bus suite wasn't rerun. Round 2 changes broker.mjs by two comment lines only (N3), and the base move changed no bus file. tester-writes, tester and the other touched bus tests ran alone.

After the rebase onto 9e74925c2 (candidate 989699f7c, manifest 2e30f2a1e667), I ran stack-release.test.mjs alone at 23:44:37Z-23:47:40Z, as sage-1100 asks: 93/93. The runs above stand for this candidate. Row 179 changed only docs/TOOLS.md among the manifest paths, and no test reads TOOLS.md. Both the busy check and the manifest check passed before and after, and a sweep found no process left.

Greps, as sage-1094 asks:

  • I grepped every packages/*/tests file and scripts/test-*.sh for each of the 25 manifest paths. No test names paths.mjs, which the tests reach through packages/project/src/index.mjs. The only test that reads a changed doc is host.test.mjs:826, on the cli README, and it ran alone (41/41). The other README hits are text. No test reads docs/TOOLS.md; its one mention, stack-release.test.mjs:428, is a refusal string.
  • The 64-hex pins sit in 11 files. Six are bus migration tests that pin DDL, and four are discord, queue and webui fixtures. The last is channel-proof, which pins the four row 164 files. None of them is a manifest path. channel-proof.test.mjs passed alone (2/2), so those four files are unchanged.