Files
stack/agents/darkwing/work/queue-51-review/review-r2.md
T

6.2 KiB

Row 51, cohort release follow-ups, round 2 review (Darkwing)

Issue #1537, request comment 27072, notes comment 27073, queue revs 304-306. My round 1 was comment 27071 (changes); Sage ruled to move the call and keep the text. Base b7e9efb7, which changes nothing under packages/ or scripts/ since round 1's bbb2167f. Candidate manifest sha256 c34039dc0d52b965bd73cc9f079ae7b253140d77713a3323abc4743f359f0c02, the same 5 files as round 1. ctrl-child.mjs is unchanged from round 1.

Verdict: approve. #recover now retries the release after the confirmation check and before the acquire, so a refused recover has no side effect. The packet's K6 line and the README now match the code. R9 pins the order: every placement of the release above the confirmation check that I tried (round 1's, above the stop-proof check, and between the target and confirmation checks) fails R9. The three notes Dewey took from round 1 each kill the mutant they were aimed at. noseat still survives and the packet lists it as a follow-up, which I accept.

Method

  • Detached worktree at b7e9efb7, the five candidate files copied from a snapshot of the canonical tree, then sha256sum -c: 5 OK. A second worktree for the mutants; its five files check OK after all runs (r2/mut/manifest-after.txt).
  • I read the round 1 to round 2 diff in full, #recover again from top to bottom, R7 and R9-R11, the README bullet, the packet's "Round 2" section, and the recover command in docs/plans/chat-01/contracts.schema.json.
  • 11 mutants (r2/mut/mutate.py, run.sh), the whole conversation suite each, own TMPDIR each, run after the gate finished. After each run the runner lists any shim left under that TMPDIR. No mutant left one.

Node v26.8.1, TMPDIR=~/darkwing-scratch/r51b/tmp, gate.sh with DOCKER_HOST=unix:///nonexistent.sock, 06:43:17Z to 06:46:47Z, then control-board and queue (node).

Suites

Suite Result
conversation 177/0
webui 22/0
control-board 124/0
queue (node) 148/0
test-auth 15/0
test-conductor 17/0
test-config 24/0
test-discord 66/0
test-extension-package 18/0
test-foundation 44/0
test-queue 27/0
test-release 4/0 without Docker, 14/0 with it (test-release-docker.txt)
test-task 26/2

The two test-task failures are "user recall run succeeds" and "recalled user name", the live worker check that needs Docker and a model call, as in round 1. Dewey's 98/0 ran them. The one mosaic-chat-* unit counted right after the conversation suite was gone at the gate's end, and none was listed after the mutants.

The recover order

if (... !this.#stopped(s)) return refused("stop-proof");
... pins, refused(ENGINE_PIN_MISMATCH) ...
... session leaf and branch, refused("target") ...
if (!this.#checkConfirmation(r, c, "recover")) return refused("confirmation");
// After every check, so a refused recover changes nothing (K6).
await this.#releaseListed("recover", this.claim);
... acquire ...

That's option 1 from my round 1 review. R9 now sends two recovers with the socket back before the confirmed one: one with a confirmation ID that was never issued, and one with a confirmation confirmed for force-stop. Both come back confirmation, evidence.releases is still the one unavailable entry, and the unit is still active. The packet says a recover with no confirmation field is refused malformed before #recover. I checked: the schema's recover command lists stop and confirmation as required, and the controller's malformedRequest runs before dispatch and refuses a recover whose fields don't match SHAPES.recover (stop and confirmation, both IDs).

The await between the confirmation check and the acquire lets another command run in between. That isn't new: the acquire was already awaited, so two recovers could interleave before this row.

Mutants

Mutant Change Conversation Result
recconf the release right after the stop-proof check (round 1's order) 176/1 killed: R9
recbefore the release above the stop-proof check 176/1 killed: R9
recpreconf the release after the target check, before the confirmation 176/1 killed: R9
norecover recover releases nothing 176/1 killed: R9
nodelete #releaseListed never drops an earlier result 176/1 killed: R9
startnoawait the free-path release isn't awaited 176/1 killed: R7
nolookup #releaseListed sends a request for a unit already absent 176/1 killed: R11
closeproof close skips the signal only while #stopped() still accepts the stop 176/1 killed: R10
closekill close signals after a proven stop 175/2 killed: R10, R11
recafteracq the release moves to just after the acquire 177/0 survives: see note 1
noseat the free path checks only the session head 177/0 survives: the packet's follow-up

recpreconf is the one placement Dewey's table doesn't have. It's the closest wrong order, past every check except the confirmation, and R9 catches it like the others.

Notes (not blocking)

  1. recafteracq survives. The packet's K17 line says the recover release runs "after them and before the acquire". After the acquire it still acts on this.claim, the stopped record the acquire doesn't replace, so no test can tell the two apart from outside. I see no safety difference either: both orders release only a proven-stopped cohort, and both run only on a recover that passed every check. The one difference is a recover whose acquire throws: before the acquire, the release has already happened. That's fine for a recover that passed its confirmation. R7's trick (the launcher reads evidence.releases.length) would pin the order if anyone wants it. I don't need it.
  2. noseat survives, as the packet says. It needs a second session on the same seat. I agree it's a follow-up and not this row.
  3. Dewey's other follow-ups stand as in round 1. The uncertain binding whose engine exited on its own is row 52's.

Files

  • r2/candidate-manifest.sha256: copy of Dewey's.
  • r2/gate.sh, r2/out/: suite runs and summary.txt.
  • r2/mut/: mutant definitions, runner, per-mutant output and shims-left lists, summary.txt, and the manifest check after the runs.