Files
stack/agents/darkwing/work/queue-116/r2/build.md
T
jason.woltjeandClaude Opus 5.5 0a4076e8e6 row 116 round 2 builder packet: C1 cases and mutants, D1
Six cases for Filbert's C1 (f1 split into actor_role and actor_run), the
earlier-merge dry run, a node_modules canary with s27, and the TOOLS.md
usage line. 84 cases, 55 of 56 mutants killed (s26 equivalent), targeted
gate green. Candidate is build.patch against 1223a0f37; not committed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-14 14:11:59 -05:00

10 KiB

Row 116 (#1610) round 2: a resolved merge decision lands as a gated local commit

Brief: docs/plans/2026-10-14_merge-path-for-approved-work.md (blob 7a610f379). Round 1's packet is agents/darkwing/work/queue-116/r1/ (82943bf61). Filbert's round 1 verdict was changes, comment 28464, queue rev 1165, for C1. Sage's sage-0207 asks for C1 as tests only and folds in D1. sage-0204 settles the base and tree question from round 1.

BASE: 1223a0f37, unchanged. No candidate path has changed on refactor from 1223a0f37 to a2ee3a80f. Over packages/bus, scripts/test-conductor.sh and the webui pinned-copy test, the only change is still 7eb5ad0cb's requirement count in packages/bus/tests/requirements.test.mjs. The candidate is build.patch against BASE, and candidate-manifest.sha256 lists the same 6 files. repro.sh exports BASE with git archive, applies build.patch and checks every manifest digest: DIGESTS_OK.

What changed in round 2

Two files changed. read.mjs, land-work.sh, fixture.mjs and CONDUCTOR.md are byte-identical to round 1, as Filbert ruled no change there; their digests in candidate-manifest.sha256 equal round 1's.

File Round 2 change
scripts/test-land-work.sh six cases for C1, and a canary file in the fixture target's node_modules. 84 cases, was 78
docs/TOOLS.md D1: the usage line has --attribution, --business and --project, and the prose says what each does. The Maintenance row says 84 cases

C1: one case per condition

Each case is a gen.mjs variant built through fixture.mjs like the rest. Each mutant is listed with the round 2 mutants below and was run against the full suite.

Filbert Condition in read.mjs Case Mutant Mutant result
f1 the answer's actor_role is null answer-role: the answer event has actor_role pm. Refuses not-human r24-answer-role killed, only this case fails
f1 the answer's actor_run is null answer-run: the answer event has actor_run rfixture. Refuses not-human r25-answer-run killed, only this case fails
f2 the answer's choice equals the close's answer-choice: the answer says no, the close says yes. Refuses not-human r26-answer-choice killed, only this case fails
f3 exactly one answer two-answers: the same answer event twice. Refuses not-human r27-one-answer killed, only this case fails
f4 raise.task_ref agrees with the work ref raise-task-disagrees: the raise cites vikunja:7/99, the ref is work/vikunja-7-42. Refuses work-ref r28-raise-task-ref killed, only this case fails
f5 later counts only later decisions earlier-merge: a merge decision E on the same ref, raised before D and withdrawn. A dry run of D exits 0 r29-later-only killed, only this case fails

I split f1 into two cases and two mutants because read.mjs checks actor_role and actor_run as separate conditions, and a mutant that drops one leaves the other in place. r27 changes answers.length === 1 to >= 1, the narrower form of round 1's r10, which also accepted no answer.

The earlier-merge case goes as far as Filbert asked. E is a merge decision with the same task_ref and question, and a withdrawn close. fixture.mjs writes it before D, so its seq is lower. The dry run passes every check and runs the gate, then writes no ref. With r29, later picks E up and the case refuses superseded.

The canary

Filbert's s26 ruling suggested it. The suite's target node_modules was an empty directory, so "the target's node_modules is still there" couldn't see its contents being deleted. Now it holds a file named canary, and the case also checks that the file is there. s27 is the mutant that shows the case can fail: cleanup does rm -rf "$WT/node_modules/", which follows the link and empties the target's directory. s26, which drops the explicit unlink, stays equivalent, as Filbert confirmed on git 2.55.0.

Tests

scripts/test-land-work.sh, 84 cases, all passing (log: test-land-work.log). The round 1 groups are unchanged except these:

Group Round 2 addition
The decision answer-role, answer-run, answer-choice and two-answers refuse not-human, and raise-task-disagrees refuses work-ref. 25 variants, was 20
A dry run earlier-merge exits 0 with a withdrawn earlier merge decision on the same ref
Cleanup the target's node_modules is still there, with its canary file

Mutants

mutants.py has 56 mutants, the 49 from round 1 plus r24 to r29 and s27. It applies each to the file, runs the whole suite under a 600 second timeout in its own process group, and restores the file from .orig with a sha256 check. I ran the seven new ones first, then all 56 against the 84-case suite. mutants-run.log has both runs and each mutant's failing cases. The full run ended restored: True, and 55 of 56 are killed. s26 survives, and Filbert ruled it equivalent in comment 28464. Four round 1 mutants now fail one more case than they did, each a new case: r10 also fails two-answers, r14 raise-task-disagrees, and r20 and s16 earlier-merge.

Mutant Change Result
r01-schema-digest skip the schema digest compare killed, 1 of 84 fail
r02-schema-meta skip the meta version and digest check killed, 1 of 84 fail
r03-read-only open read-write killed, 2 of 84 fail
r04-business-scope drop business = ? from every query killed, 1 of 84 fail
r05-one-raise accept zero or several raises killed, 1 of 84 fail
r06-not-a-merge skip the action check killed, 1 of 84 fail
r07-resolved-op accept any single close op killed, 1 of 84 fail
r08-human-by skip resolved-by == human killed, 1 of 84 fail
r09-human-via skip via == cli killed, 1 of 84 fail
r10-human-answer accept a missing human answer killed, 2 of 84 fail
r11-merge-option skip choice == approvalChoice killed, 1 of 84 fail
r12-one-work-ref accept several work refs killed, 1 of 84 fail
r13-work-ref-shape loosen the work ref pattern killed, 1 of 84 fail
r14-task-ref-agrees skip the task_ref agreement killed, 2 of 84 fail
r15-target skip the target check killed, 1 of 84 fail
r16-later-supersedes ignore supersedes killed, 1 of 84 fail
r17-later-task ignore a later decision on the task killed, 1 of 84 fail
r18-later-question ignore a later question naming the ref killed, 1 of 84 fail
r19-no-verdict skip the no-verdict refusal killed, 1 of 84 fail
r20-latest-verdict take the earliest verdict killed, 31 of 84 fail
r21-approve skip verdict == approve killed, 1 of 84 fail
r22-verdict-task skip the verdict body's task_ref killed, 1 of 84 fail
r23-full-oid accept a short commit killed, 1 of 84 fail
r24-answer-role skip the answer's actor_role check killed, 1 of 84 fail
r25-answer-run skip the answer's actor_run check killed, 1 of 84 fail
r26-answer-choice skip the answer's choice == the close's killed, 1 of 84 fail
r27-one-answer accept several answers killed, 1 of 84 fail
r28-raise-task-ref drop the raise's task_ref from the agreement killed, 1 of 84 fail
r29-later-only later counts earlier decisions too killed, 1 of 84 fail
s01-base-early skip the early --base check killed, 1 of 84 fail
s02-ref-moved skip ref-moved killed, 1 of 84 fail
s03-merge-base ignore a missing merge base killed, 1 of 84 fail
s04-empty-diff accept an empty diff killed, 1 of 84 fail
s05-root-top-file allow a top-level file killed, 1 of 84 fail
s06-root-new-dir allow a new top-level directory killed, 1 of 84 fail
s07-root-src-v1 allow src/ and v1/ killed, 1 of 84 fail
s08-dirty-changed skip the changed-path check killed, 3 of 84 fail
s09-dirty-staged skip the staged check killed, 1 of 84 fail
s10-dirty-untracked skip the untracked check killed, 1 of 84 fail
s11-dirty-recheck skip the recheck before the commit killed, 2 of 84 fail
s12-apply-conflict ignore an apply failure killed, 1 of 84 fail
s13-patch-drift skip the patch-id compare killed, 1 of 84 fail
s14-gate-node-modules skip the node_modules check killed, 1 of 84 fail
s15-gate-step-fails ignore a failing gate step killed, 5 of 84 fail
s16-gate-skips-queue run the queue suite too killed, 16 of 84 fail
s17-gate-scripts run no scripts/test-*.sh killed, 8 of 84 fail
s18-base-moved-cas update-ref without the old value killed, 2 of 84 fail
s19-dry-run ignore --dry-run killed, 3 of 84 fail
s20-checkout-sync skip the checkout sync killed, 1 of 84 fail
s21-subject a fixed subject killed, 1 of 84 fail
s22-attribution drop the attribution line killed, 2 of 84 fail
s23-cleanup-worktree keep the worktree killed, 6 of 84 fail
s24-cleanup-network keep the Docker network killed, 1 of 84 fail
s25-cleanup-scratch keep the scratch directory killed, 6 of 84 fail
s26-cleanup-link skip the explicit node_modules unlink survived, equivalent (Filbert, comment 28464)
s27-cleanup-follows-link cleanup follows the link and empties the target's node_modules killed, 1 of 84 fail

Gate

A targeted builder gate under #271 (gate.sh, logs in gate/), the same steps as round 1. Round 1 ran the webui pinned-copy test by hand beside gate.sh; this round gate.sh runs it as its fourth step. Sage's landing gate stays full.

Step Result
packages/bus node --test tests/*.test.mjs 156 pass, 0 fail
scripts/test-land-work.sh 84 passed, 0 failed
scripts/test-conductor.sh 17 passed, 0 failed
packages/webui node --test tests/pinned-copy.test.mjs 10 pass, 0 fail

GATE_PASSED (gate-run.log). No step failed, so the flake rule had nothing to count. The suite also passed 84 of 84 on its own before the mutants (test-land-work.log), and s26, the one survivor, passed all 84 in the full mutant run.

Replay

Round 1's replay stands, and I didn't rerun it. It ran read.mjs and land-work.sh, which are byte-identical in this candidate, against scratch copies of the live rows. Filbert checked it under sage-0204: at 1c9a4910d the landed diff is f7c8537b3's own diff and the dry run wrote no ref, and at 4bc908991 the tree equals f7c8537b3^{tree}. The commit stayed local in both. The evidence is in agents/darkwing/work/queue-116/r1/ (build.md's Replay section, replay-1c9a/, replay-4bc9/).

Going live

Nothing goes live. The tool is a conductor command run by hand. No service, role or session calls it. Push, the merge to next or main, re-pins and restarts stay out of this row.