Files
stack/agents/researcher/work/queue-109/build.md
T
jason.woltjeandClaude Sonnet 5.5 8ea93f556e docs: Researcher row 109 candidate packet (#1603, PM reads current launch state and task status before it plans)
Candidate patch (state verb, runner state block, header time, pm.md rule, fake-adapter fixture), six new harness tests, six mutants, full suite outputs, candidate manifest.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-13 18:10:24 -05:00

7.9 KiB

Row 109 (#1603) build notes

Researcher, 2026-10-13. Brief: docs/plans/2026-10-13_pm-tester-and-fresh-state.md, "A new PM run reads current launch state and task status before it plans". BASE: 02fe2d9d5 (origin/refactor after row 106 landed as 8b58cb7e4). Reviewer: Filbert. Gate owner: Sage.

Candidate change: row109.patch (8 files: 7 modified, 1 new). Built and run in /mnt/storage/scratch/researcher/q109, detached at 02fe2d9d5, node_modules linked to the canonical one. The patch applies to BASE; the packet is not in it.

What ships

  • packages/bus/src/broker.mjs: read verb state, no arguments, open to role, human and reader sessions (reader allowlist gains state). It returns {launch:{state,at,event}, tasks:{open:[{task_ref,title}], total, cap}} for the capability's business.
    • launch is the latest launch.revoked / launch.restored event of the business: state revoked, else allowed; at and event are that event's time and id, null when no event exists (allowed).
    • tasks.open is #tasks(business,{done:false}), so it is the tracker snapshot's open tasks, tombstones left out, in task_ref order, refs and titles only. The cap is the tasks page size (TASK_PAGE, 50), stated as cap; total counts every open task.
    • Nothing is stored. No schema change.
  • packages/harness/src/runner.mjs: before each turn the runner calls state and prefixes the turn request with stateBlock(state, readAt): Current state, read at <iso>:, Launches: ..., Open tasks: N[, the first K of cap C]., one line per task. The read time is the runner's clock at the call. A failed read (anything but a dead session) fails that turn without running the adapter: the sender gets the usual RESULT The turn failed: could not read the current state: <code>, the failure is logged, and message.read still runs. A dead session (SESSION_GONE) exits as it does on message.receive.
  • turnRequest(): the header gains sent <at> from the message row's at, after the sender and before the class.
  • roles/pm.md: one rule in Protocol: the block is the truth for launch state and task status; a message or a note that disagrees is out of date; before planning to resume or launch work on a task, check it with get_task.
  • READMEs: one table row in packages/bus/README.md, one paragraph in packages/harness/README.md.
  • packages/harness/src/tools.mjs is untouched: state is not exposed as a tool; only the runner calls it.

Fixture edit (accepted by Sage, rows 109 scope)

packages/harness/tests/fixtures/fake-adapter.mjs took the message body as everything after the first blank line of the request. The state block adds a blank line above the Message ...: header, which broke every TOOL/SLEEP/FAIL/NOMARKER test. It now takes the body after the Message ...: header line (regex), with the old split as fallback. This is the only reason for the edit; no other harness test needed a change. Every other test using the fixture passes in the full harness suite (79/79).

Tests

  • packages/bus/tests/state.test.mjs (new, 6): never switched reads allowed with null time and event; revoked then restored reads allowed with the restore event's time and id (and revoked with the revoke id in between); a task closed on the tracker leaves the open list and total drops; a tombstone is not open; the list stops at the cap and says so while total counts all; role and reader sessions read the same state, an argument refuses invalid-request, an unbound cap refuses unauthenticated.

  • packages/harness/tests/runner.test.mjs (6 new tests, 20 in the file; harness suite 73 -> 79). By name:

    1. turnRequest shows the message's own sent time, a day old as well as new
    2. stateBlock names the launch state with its time and event, and the open tasks with the cap
    3. launches revoked and then restored: the turn's block reads allowed and gives the restore time
    4. a task closed on the tracker is missing from the next turn's open list
    5. a message read a day after it was sent shows the time it was sent (the broker's clock jumps a day after the send)
    6. a failed state read fails the turn: no adapter runs, the sender hears why, the message is read (no adapter turn file, RESULT with the reason, message read, log line)

    My message to Sage before the gate run said five new runner tests. That was a miscount: it left out test 2, the stateBlock unit test, which I wrote as a unit test beside test 1. The suite count (+6) is the evidence. The existing first end-to-end test, a message becomes a turn, the answer goes back as a RESULT, ..., is changed, not new: it now checks the exact block and the header with sent <at>.

  • The state block and the time are checked by the runner tests through the fake adapter, which echoes the request.

Mutants (the kills)

mutants.sh <worktree> <outdir> runs one test file for the control and one per mutant, each mutant one exact-once text edit, and restores every source on exit. Outputs in mutants/; run summary in mutants-run.txt. mutants/<name>.patch is git diff of that file against BASE, so it also shows the candidate's own edits; the mutation is the one changed expression. The candidate was byte-identical to row109.patch after the run.

mutant edit result failing tests
control none runner 20/20, bus 6/6 none
m1 request is turnRequest(m), no block runner 17/20 the first end-to-end test; "launches revoked and then restored"; "a task closed on the tracker is missing"
m2 header drops sent <at> runner 17/20 the first end-to-end test; "a message read a day after it was sent"; "turnRequest shows the message's own sent time"
m3 a failed read sets block = "" instead of failing the turn runner 19/20 "a failed state read fails the turn"
m4 launch event read oldest-first bus 5/6 "launches revoked and then restored read allowed, with the restore time and event id"
m5 open list built without the done:false filter bus 5/6 "a task closed on the tracker leaves the open list"
m6 cap dropped from the output bus 4/6 the closed-task test and the cap test

The roles/pm.md rule has no test; it is prose. Out of scope as the brief says: expiring old messages, tracker poller or list_tasks defaults, and who may switch launches.

Suites (all in q109, output teed to suites/)

BASE 02fe2d9d5; slot held 23:04:51Z to 23:08:36Z, "clear" sent; no process of mine left in q109.

suite result Sage's gate count
node --test packages/bus/tests/*.test.mjs 134 pass, 0 fail 128 (+6 new)
node --test packages/harness/tests/*.test.mjs 79 pass, 0 fail 73 (+6 new)
node --test packages/queue/tests/*.test.mjs 156 pass, 0 fail 156
scripts/test-release.sh 14 passed, 0 failed 14
test-auth 15, test-conductor 17, test-config 24, test-discord 66, test-extension-package 18, test-foundation 44, test-task 98 0 failed each n/a
scripts/test-queue.sh 28 passed, 0 failed n/a

The queue verify and render --check case of test-queue.sh is skipped in a worktree (q109 is not the queue's canonical root /mnt/storage/src/mosaic-stack; Filbert's N3 from row 101). Sage's canonical-root gate rerun is the evidence of record. I did not apply the patch to or run suites in the canonical checkout.

PM workspace (read-only)

pm-workspace-names.txt: the PM's persistent workspace holds one planning-notes file, findings-2026-10-12.md (7737 bytes, 2026-10-12T14:53Z), and stack/, a git checkout of the repository. Names only; no contents read. The note is a likely second source of the stale picture alongside old inbox messages; the pm.md rule covers it. The file is left alone.

Scope and live

Owned files only, plus the accepted fixture. Live use needs a new copy and re-pin under Mos's go, with decision 109 notices before and after (runner.mjs reaches new role sessions at their next start; the broker verb needs a bus restart).