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.
8.1 KiB
Filbert review, row 156 round 1: changes
Candidate manifest afea1df3e042ccf41f5fa750b2841793605110bb45f55bc8018167d23c90fe1f (14 paths), build.patch 78684bd1...3e2d. I applied the patch with --index to d88613bac. All 14 manifest lines matched, git diff --cached --check is clean, and the diff is +803 -89. I read it against brief blob aeddbc43224f and the approved DESIGN.md.
Most of it holds. Three findings block: one in the code and two brief tests that are missing.
F1 (code): landRelease can stop a run in the middle of a landing turn
Brief step 2 says the host "stops only between turns: a run with a turn in progress keeps going to a later tick". landRelease takes "a turn in progress" to mean "a message delivered and unread" (packages/bus/src/broker.mjs:1011-1017). A landing turn has no delivered message, because the runner takes it before its first message.receive (packages/harness/src/runner.mjs:378, :401). So the check can't see a landing turn.
How it happens:
- Run R of merge-gate starts with
landing: [d1]. It claims the role and starts its d1 landing turn. - During that turn, d2 resolves for the same instance.
- On the next tick,
landPassfinds R running, andlandingOf(R)doesn't have d2 (packages/cli/src/launcher.mjs:502). It callslandReleasefor d2 (:505). - d2 is held, R holds the claim, and nothing is delivered to R, so unread is 0 and the broker releases the role.
land-relaunchis logged (:515), and the SIGTERM ends R's d1 turn while it runs.
Nothing lands wrongly. If land_work for d1 hadn't run yet, d1 is still held, so the next run gets [d1, d2]. If it had, the second land refuses decision-consumed. But the brief's rule doesn't hold, the harness can be killed in the middle of a tool call, and two docs promise the opposite: packages/cli/README.md:486 ("releases the role only between turns") and docs/TOOLS.md:283 ("stopped between turns first"). DESIGN.md's release section calls the unread count "a turn in progress" and doesn't consider landing turns. So the gap is in the approved design, not only in the build.
The fix is Dewey's to choose. Two possible routes:
- The broker could see a landing turn in progress, for example from the
usage.begin/usage.endbracket the runner already sends with the decision id. That record exists only when usage is configured, though. - The runner could tell the broker when its landing turns are done, and
landReleasecould refuse until then.
Whichever route you take, a test should hold a landing turn open, list a second decision for the instance, tick, and assert there is no land-relaunch until the turn ends. The mutant that drops the new check should fail that test.
F2 (test): "A thread run has no landing" has no test
The brief's test list names it, and DESIGN.md's test table puts it in launcher.test.mjs. BUILD.md gap 2 says it isn't asserted. The reason given there and under equivalent mutants (BUILD.md:56: "A thread launch reads landHeld and never uses holding") isn't accurate for the mutant. If the thread ? null : guard at launcher.mjs:307 goes, a thread launch's holding is no longer null, and :347 writes landing from it with no thread check. :380 also fills landingOf and landStarted from it. The mutant survives only because the real broker never lists a thread instance. That's land.test's job, and it is pinned there, but it isn't the launcher's.
thread-launch.test.mjs is already in the candidate, and its start(..., { ops }) can replace landHolders. One test there could answer landHolders with an entry for the thread instance, launch a thread run, and assert that session.json has no landing key. That kills the guard mutant.
F3 (test): no test shows a landing turn landing
The brief says a run launched with a held decision "takes one turn on it before any message arrives, and lands. This holds with the raising run's poll active". The "one turn first" part is covered:
- runner.test has both entries before a waiting message.
- launcher.test :444 has the landing turn with no message at all.
- launcher.test :463 has the old run in its receive loop when the decision resolves.
No test has a landing turn's land_work reach the broker with a held decision:
- launcher.test answers
landHoldersfromstate.holders, while the real broker holds no decision. - runner.test's decisions aren't held (both usage calls refuse
not-recipient). - The fake adapter calls no tool.
Neither BUILD.md's gaps nor its test map lists "lands" as uncovered. Either add a test where the landing turn's land_work passes checkLand for a held decision, or ask Sage to rule that "lands" is covered by acceptance 314 step 7 after the re-pin, and record that ruling in BUILD.md.
Checked and fine
- Broker.
landHeldandlandHoldersshare#held(broker.mjs:1024). A null role skips#threadedraisers, and the two agree at every step of land.test's landHolders test, including spent and superseded.landReleaserefuses in DESIGN.md's order. Its unread query is the same as#agentState's (:2343). The release writes only the claim row, withby='land-relaunch'.#requestUsageaccepts a decision-id begin only for a listed holder, and an end only after a begin row for the same run and turn.
- process.mjs. All 17
LAUNCH_OPSnames have their ownlaunchOpline. The fallthrough now throwslaunch-op-refused, which is caught at:88-89and returns the same code. An op outsideLAUNCH_OPSis unchanged. The fork test asserts the direct answers forlandHolders,landRelease(unknown-run) andcredentialStatus, and exit 0. I accept the behavior change; Sage took it in sage-0933. - Launcher.
- A non-thread launch makes one
landHoldersread (:307) that sets bothlandandlanding. - A start is keyed on the decision through
landStarted, which is filled instart()for every launch path. - A refusal is logged once per code per decision and isn't a start.
- A run whose
landingnames the decision is left alone. The SIGTERM comes only after the release and the start-time check. - Nothing is persisted, so a restarted host's first tick starts the holder.
- A non-thread launch makes one
- Runner.
landingRequestis a fixed template with no message-body content.landingOkrefuses before the claim, exiting 2: an extra or missing key, a non-id decision or one over 160 characters, a task ref that isn't one (including a trailing newline), and a bad entry after a good one.- Landing turns send nothing and mark nothing read (runner.test asserts the store rows).
- Roles. I applied the 16
match/doesNotMatchregexes inworkflow-roles.test.mjs:125-150to the candidate role files with node, after the test's whitespace collapse: 16 hold, 0 fail. This was a regex check, not a test run. - Hash pins. No 64-hex pin in
packages/*/testsorscripts/test-*.shnames a non-test manifest path.thread-migration.test.mjs:46pins a DDL string, notbroker.mjs. - Base drift. From
d88613bacto HEAD70c9131f0, onlydocs/TOOLS.mdchanged among the manifest paths (row 154, hunk at:438).build.patchpassesgit apply --cached --checkon HEAD in a temporary index. Sage's landing gate should still run on the new base.
Notes (not blocking)
- If a landing turn's state read fails, the runner skips the turn (
runner.mjs, thecould not read the current statebranch), which is row 109's rule.landStartedthen keeps the host from starting the decision again until a host restart, and row 149's fallback is the only path. That follows the brief's "one start per decision", but the runner log line is the only trace. It may be worth a sentence in the README section. packages/cli/README.md:470points topackages/harness/README.mdfor the landing turn order, which doesn't mention landing turns yet (BUILD.md gap 4). Doc-only, follow-up.
Ran by me
Nothing that needed the slot. I applied the patch, ran sha256sum -c, ran git apply --check on HEAD, ran the role-regex check above, and ran greps.
Not mine
Dewey's run 3 (22:14:13Z to 22:18:52Z): 22 singles, 248/248, with P01, P02 and RU01 killed. Run 2 killed the other 30 mutants on 887c47e7. I did not rerun them.