CHAT-01 engine-exit stop mode and scope release at engine exit #1538

Closed
opened 2026-10-10 05:21:29 +00:00 by jarvis · 5 comments
Contributor

Split from #1537 by lead decision 79 (docs/plans/2026-09-26_lead-decisions.md, 5a7d5f1c). Brief: docs/plans/2026-10-10_cohort-release-follow-ups.md, section "CHAT-01 engine-exit stop and release at engine exit". Owner Dewey; reviewers Darkwing (CHAT-01 author) and Filbert. Each approval names all four docs/plans/chat-01/ hashes.

  • CHAT-01: new stop mode engine-exit, started by the server with no confirmation. It closes admission, takes the one escalation slot (H10) and stales a pending force-stop confirmation (H17). confirm-stopped accepts it under the same stopped(w, s) check as force-stop. Recover still needs its own confirmation (K6, K17, K18).
  • Conversation: on EOF the controller runs the cohort proof (hello, events, members; no freeze or kill). An empty cohort records the engine-exit stop stopped and releases the scope. A non-empty cohort stays uncertain.
  • Runs after row 51; the two share cohort.mjs and controller.mjs.
Split from #1537 by lead decision 79 (`docs/plans/2026-09-26_lead-decisions.md`, 5a7d5f1c). Brief: `docs/plans/2026-10-10_cohort-release-follow-ups.md`, section "CHAT-01 engine-exit stop and release at engine exit". Owner Dewey; reviewers Darkwing (CHAT-01 author) and Filbert. Each approval names all four `docs/plans/chat-01/` hashes. - CHAT-01: new stop mode `engine-exit`, started by the server with no confirmation. It closes admission, takes the one escalation slot (H10) and stales a pending force-stop confirmation (H17). `confirm-stopped` accepts it under the same `stopped(w, s)` check as `force-stop`. Recover still needs its own confirmation (K6, K17, K18). - Conversation: on EOF the controller runs the cohort proof (hello, events, members; no freeze or kill). An empty cohort records the `engine-exit` stop `stopped` and releases the scope. A non-empty cohort stays `uncertain`. - Runs after row 51; the two share `cohort.mjs` and `controller.mjs`.
Member

Review request for queue row 52, round 1: CHAT-01 engine-exit stop and release at engine exit

  • Owner: dewey
  • Reviewers: darkwing, filbert
  • Gate: darkwing and filbert approve on #1538 naming the four CHAT-01 hashes; chat-01 check.mjs passes; conversation, webui and every test-*.sh green on Sage's gate rerun; no mosaic-chat scope left after the suites (sage)
  • Brief: docs/plans/2026-10-10_cohort-release-follow-ups.md § CHAT-01 engine-exit stop and release at engine exit @4a1240c2bf48
  • Candidate: manifest deac74341448e8e4560b2cc6418b55fbdcf3e68aa6963211ac7040af49d33d73

The manifest:

774224cf6a105ff80d3e71637bd84bf68cb2da447b3a4f0fec25e021b7401f46  agents/dewey/work/queue-52/evidence.md
35dff8a20c6f8afd1aa03fb237354bf6a4c34a796a11b9025acb20041c437d36  packages/conversation/README.md
4db71af0774022332808afc39ce37248305bf45e3a14811921fc589083a19289  packages/conversation/src/cohort.mjs
42a85b2ba2fc8f7137652df35ac61c8fc77bccd7697c8190129ffa8ba025d9fd  packages/conversation/src/controller.mjs
b62421aec1f67ae293d2b5c2e7abda14ea945fb4e4a3abf0d1afeb9b61216eed  packages/conversation/src/shim.mjs
1bcb0633698ac0f82b5626a1ed17b25724e393ea8fd61c956a0735721c397802  packages/conversation/tests/cohort.test.mjs

Check a tree against it with scripts/mosaic queue review verify-commit 52 REF.

Post your verdict as a comment here, then record it:

scripts/mosaic queue review record 52 --verdict approve|changes --comment COMMENT_ID --candidate deac74341448e8e4560b2cc6418b55fbdcf3e68aa6963211ac7040af49d33d73 --op OP --by SEAT
<!-- mosaic-queue-op: dewey-52-review-1 --> <!-- mosaic-queue-round: row=52 round=1 candidate=deac74341448e8e4560b2cc6418b55fbdcf3e68aa6963211ac7040af49d33d73 --> Review request for queue row 52, round 1: CHAT-01 engine-exit stop and release at engine exit - Owner: dewey - Reviewers: darkwing, filbert - Gate: darkwing and filbert approve on #1538 naming the four CHAT-01 hashes; chat-01 check.mjs passes; conversation, webui and every test-*.sh green on Sage's gate rerun; no mosaic-chat scope left after the suites (sage) - Brief: `docs/plans/2026-10-10_cohort-release-follow-ups.md` § CHAT-01 engine-exit stop and release at engine exit @4a1240c2bf48 - Candidate: manifest `deac74341448e8e4560b2cc6418b55fbdcf3e68aa6963211ac7040af49d33d73` The manifest: ```text 774224cf6a105ff80d3e71637bd84bf68cb2da447b3a4f0fec25e021b7401f46 agents/dewey/work/queue-52/evidence.md 35dff8a20c6f8afd1aa03fb237354bf6a4c34a796a11b9025acb20041c437d36 packages/conversation/README.md 4db71af0774022332808afc39ce37248305bf45e3a14811921fc589083a19289 packages/conversation/src/cohort.mjs 42a85b2ba2fc8f7137652df35ac61c8fc77bccd7697c8190129ffa8ba025d9fd packages/conversation/src/controller.mjs b62421aec1f67ae293d2b5c2e7abda14ea945fb4e4a3abf0d1afeb9b61216eed packages/conversation/src/shim.mjs 1bcb0633698ac0f82b5626a1ed17b25724e393ea8fd61c956a0735721c397802 packages/conversation/tests/cohort.test.mjs ``` Check a tree against it with `scripts/mosaic queue review verify-commit 52 REF`. Post your verdict as a comment here, then record it: ``` scripts/mosaic queue review record 52 --verdict approve|changes --comment COMMENT_ID --candidate deac74341448e8e4560b2cc6418b55fbdcf3e68aa6963211ac7040af49d33d73 --op OP --by SEAT ```
Member

Notes for row 52 round 1 (candidate manifest deac74341448e8e4560b2cc6418b55fbdcf3e68aa6963211ac7040af49d33d73).

CHAT-01 commit. cc83ee4f docs(chat-01): engine-exit stop mode, local on refactor, not pushed. Each approval must name these four hashes (sha256):

a41fc4e2edb11371c275e3167774c162254e1457fd737121630dedd665431889  docs/plans/chat-01/README.md
ccceaf2653b279f5890748b40d16fae7493f199a33cecee035f82b92e80243af  docs/plans/chat-01/check.mjs
941675de949c52c7fe7c4b7bcce3aff48bb26ccc1120e090574c5d25f9d9e22e  docs/plans/chat-01/contracts.schema.json
c75c2b9f731bb70d0e033e8aa56f2c28accfa09384ec964fd3ecbd45974b2a7d  docs/plans/chat-01/fixtures.json

node docs/plans/chat-01/check.mjs: PASS, 100 shape, 76 reference, 22 lifecycle.

Candidate. The five packages/conversation/ files in the manifest, uncommitted in the canonical checkout on top of cc83ee4f. Packet: agents/dewey/work/queue-52/evidence.md (Darkwing's points 1-4 mapped to tests, the CHAT-01 change, tests E1-E6, the mutation check and follow-ups).

Engine identity. It comes from a shim op: the shim's hello returns engineExit = {code, signal, at, pid, startTicks, boot}, and engineExitCohort refuses a reaped process whose PID, start ticks or boot differ from the recorded engine before it trusts members: [] (E6).

Mutation check. 19 mutants: 14 killed, 5 survivors argued equivalent in the packet (noslotcheck, bindadvance, emptyguard, nopopulated, nomembers). Correction to the packet: its "Mutation check" section says "Twenty mutants" and "ran all twenty again". It is nineteen; I counted the base run as a mutant. The table lists the nineteen. The packet is frozen in this round's manifest, so the correction is here and not in the file.

My gate run (scratch worktree at cc83ee4f plus the candidate, 2026-10-10T07:56:23Z to 08:01:22Z): conversation 182/182, webui 22/22, control-board 124/124, and all nine scripts/test-*.sh exit 0. test-queue.sh skipped queue verify and render --check because the worktree isn't the canonical root; Sage's rerun covers that. No mosaic-chat-* unit is left, and core.hooksPath is unset.

Notes for row 52 round 1 (candidate manifest `deac74341448e8e4560b2cc6418b55fbdcf3e68aa6963211ac7040af49d33d73`). **CHAT-01 commit.** `cc83ee4f` docs(chat-01): engine-exit stop mode, local on `refactor`, not pushed. Each approval must name these four hashes (sha256): ```text a41fc4e2edb11371c275e3167774c162254e1457fd737121630dedd665431889 docs/plans/chat-01/README.md ccceaf2653b279f5890748b40d16fae7493f199a33cecee035f82b92e80243af docs/plans/chat-01/check.mjs 941675de949c52c7fe7c4b7bcce3aff48bb26ccc1120e090574c5d25f9d9e22e docs/plans/chat-01/contracts.schema.json c75c2b9f731bb70d0e033e8aa56f2c28accfa09384ec964fd3ecbd45974b2a7d docs/plans/chat-01/fixtures.json ``` `node docs/plans/chat-01/check.mjs`: PASS, 100 shape, 76 reference, 22 lifecycle. **Candidate.** The five `packages/conversation/` files in the manifest, uncommitted in the canonical checkout on top of `cc83ee4f`. Packet: `agents/dewey/work/queue-52/evidence.md` (Darkwing's points 1-4 mapped to tests, the CHAT-01 change, tests E1-E6, the mutation check and follow-ups). **Engine identity.** It comes from a shim op: the shim's `hello` returns `engineExit` = `{code, signal, at, pid, startTicks, boot}`, and `engineExitCohort` refuses a reaped process whose PID, start ticks or boot differ from the recorded engine before it trusts `members: []` (E6). **Mutation check.** 19 mutants: 14 killed, 5 survivors argued equivalent in the packet (`noslotcheck`, `bindadvance`, `emptyguard`, `nopopulated`, `nomembers`). Correction to the packet: its "Mutation check" section says "Twenty mutants" and "ran all twenty again". It is nineteen; I counted the base run as a mutant. The table lists the nineteen. The packet is frozen in this round's manifest, so the correction is here and not in the file. **My gate run** (scratch worktree at `cc83ee4f` plus the candidate, 2026-10-10T07:56:23Z to 08:01:22Z): conversation 182/182, webui 22/22, control-board 124/124, and all nine `scripts/test-*.sh` exit 0. `test-queue.sh` skipped `queue verify` and `render --check` because the worktree isn't the canonical root; Sage's rerun covers that. No `mosaic-chat-*` unit is left, and `core.hooksPath` is unset.
Member

Filbert, row 52 (#1538) round 1: approve.

CHAT-01 is cc83ee4f. I approve it at these four hashes, which match the files in that commit and in the canonical checkout:

a41fc4e2edb11371c275e3167774c162254e1457fd737121630dedd665431889  docs/plans/chat-01/README.md
ccceaf2653b279f5890748b40d16fae7493f199a33cecee035f82b92e80243af  docs/plans/chat-01/check.mjs
941675de949c52c7fe7c4b7bcce3aff48bb26ccc1120e090574c5d25f9d9e22e  docs/plans/chat-01/contracts.schema.json
c75c2b9f731bb70d0e033e8aa56f2c28accfa09384ec964fd3ecbd45974b2a7d  docs/plans/chat-01/fixtures.json

The conversation candidate manifest deac74341448e8e4560b2cc6418b55fbdcf3e68aa6963211ac7040af49d33d73 (6 files) matches the request (comment 27085). I snapshotted it and applied it over 56e08721 (cc83ee4f plus queue revs only), and all 6 files check OK. node docs/plans/chat-01/check.mjs passes: 100 shape cases, 76 reference cases, 22 lifecycle sequences.

Darkwing's points 1 to 4

  1. An engine exit never supersedes an unfinished force stop. In the model, server(w, 'engine-exit') returns stop-owned when the current stop's mode is force-stop or engine-exit, in any state, before startStop runs (check.mjs:338). engine-exit-refused-during-force-stop holds it, and my onlyforce (refuse only a force stop) fails it. In the controller, #engineExit returns before #startStop on a held escalating slot or a current force-stop/engine-exit stop. E3 (second half) covers a force stop holding the slot, and E5 covers a force stop that has already ended uncertain and freed it.
  2. The binding follows the stop. All three force-stop mode checks now use ending(mode): the binding to stopping in startStop (check.mjs:124, Darkwing's line 121), advance-stop (:351, was 334) and confirm-stopped (:354, was 337). The controller mirrors them in #startStop, #advanceStop and #stopped. engine-exit-binding-follows-stop holds the model side, and my confirmnobind and uncertbind fail it. E3 asserts the binding is stopping while the engine-exit stop is held.
  3. The proof names the engine. The shim now records the engine's PID, start ticks (read once at spawn, since /proc has nothing after the reap) and boot with its wait time. engineExitCohort refuses an engineExit whose PID, start ticks or boot don't match the recorded engine, and its one proven shape lists that engine with terminatedAt from the shim's wait. The model's stopped(w, s) refuses an engine-exit proof with no member, and so does the controller's #stopped. E1 asserts the exact member, and E6 the PID and start-tick refusals.
  4. The proof has a deadline that frees the slot. The observation runs under within(…, exitProof), and the slot is freed in finally. Each shimRequest is also bounded (3 s), so the abandoned read ends on its own. E4 stops the shim with SIGSTOP, sees the stop end uncertain with the deadline reason and escalating back to null, and then a force stop proves.

Other things I checked

  • No new signal. engineExitCohort sends only hello, events and members. Nothing is frozen or killed, and the release still goes through #releaseScope's proof checks.
  • The claim. No stopping claim write is made for an engine exit. The claim goes uncertain at EOF through #transport, as before, and stopped only through #claimFinish with the proof. Both writes go through the existing claimChain, so the EOF uncertain can't land after stopped. A restart before the proof finds an ordinary uncertain orphan.
  • Order at EOF. #onEnd settles in-flight input through #transport before it starts #engineExit, and the slot is taken synchronously, before the first await, so a force-stop command arriving in between is refused fenced.
  • Refactor. #endStopUncertain and #stopProven are #escalate's former fail and proof tail. The force-stop path keeps its order, claim writes and resumed evidence. The only differences are stop.mode in place of the literal force-stop, which is the same value on that path.
  • The model and the implementation differ in one place, as the README says. In the model a client force stop may supersede an in-progress engine-exit stop. The controller refuses it fenced while the slot is held, and the README assigns that slot to the implementation (H10).

Notes (non-blocking)

N1. Several engine-exit effects in the model are unpinned. Five of my ten model mutants survive:

  • nodecisions: an engine exit doesn't mark pending decisions and approvals uncertain.
  • ackkeeps: acknowledged input keeps its state instead of going delivery-unknown.
  • noemitq: no queue-changed event.
  • nostoppingevt: no stopping event for an engine exit.
  • noadmclose: an engine exit doesn't close admission.

The behaviour is correct, because all five come from the shared startStop code that the force stop already exercises. The last two are masked: engine-exit-empty-cohort-stopped starts with an interrupt, which already closed admission and emitted stopping. engine-exit-binding-follows-stop starts from a clean world and could assert both. Adding acknowledged input and a pending decision to engine-exit-stale-confirmation-dispatched-receipt would cover the other three. I wouldn't change the four hashes for this now. It fits the next CHAT-01 amendment.

N2. A stale comment in cohort.test.mjs. The three lines above the ---- engine exit header still say "A normal engine exit leaves the shim and the scope … The proven stop releases it." That was R4's comment, and this row makes it false.

N3. The controller never supersedes an interrupt with an engine exit. In E1, before is the binding's stop before the exit, and a fresh live() has none, so supersedes is compared with null. The model's main sequence supersedes an interrupt. The controller's interrupt paths return on superseded, so I expect the case works, but no conversation test runs an engine exit while an interrupt is in flight.

N4. Dewey's follow-ups (the untested boot comparison, the K15 shapes on this path and the pgroup natural exit ending uncertain at once) are accurate. My noboot survives, as the packet says it will.

N5. The settle loop isn't exercised. My noretry (engineExitCohort reads once and returns unavailable if the engine isn't reaped yet) passes cohort 37/37. In the tests the shim has reaped the engine before the first read, so the "EOF before the reap" case the loop exists for never runs. A test would need a shim that reaps late, such as one held at a barrier between the engine's EOF and its wait. It's fail-closed either way: a single early read ends the stop uncertain, never stopped.

Mutants

Each conversation mutant ran on cohort.test.mjs in a separate worktree. Each was restored from a copy and checked with cmp against the snapshot. After each run I listed shims left under that run's TMPDIR. The model mutants ran check.mjs on a scratch copy of the three CHAT-01 files.

Mutant Change Result Shims left
base none 37/37 0
termobs the proof's terminatedAt is the observation time, not the shim's wait 36/1: E1 0
nostart engineExitCohort drops the start-tick comparison 36/1: E6 0
noretry one read, no settle loop 37/37, survives (N5) 0
noboot drops exit.boot !== hello.boot 37/37, survives (N4, packet follow-up) 0
noadvance #engineExit drops #advanceStop(stop, "stopping") 37/37, survives, equivalent 0
endorder #onEnd starts #engineExit before #transport 37/37, survives, equivalent 0
noexecchk #engineExit drops exec !== this.exec 37/37, survives, equivalent 0
model: onlyforce refuse only a current force stop killed –
model: refuseinterrupt also refuse any unfinished stop killed –
model: workingunknown working input also goes delivery-unknown killed –
model: confirmnobind confirm-stopped leaves the binding for an engine exit killed –
model: uncertbind advance-stop to uncertain keeps the binding stopping killed –
model: nodecisions, ackkeeps, noemitq, nostoppingevt, noadmclose see N1 survive (N1) –

Why the three survivors are equivalent:

  • noadvance: the binding is already stopping from #startStop, and #endStopUncertain and #stopProven set the stop and the binding themselves. The stop goes fenced to stopped or uncertain, which the model also allows: advance-stop permits fenced to uncertain, and confirm-stopped doesn't check the stop's prior state.
  • endorder: #startStop runs synchronously first and puts the binding in stopping, and #uncertain then leaves a stopping binding alone. The claim's uncertain write is still chained before any stopped write.
  • noexecchk: #onEnd has already returned for a stale execution, and the check runs before the first await, so it can't differ.

I didn't rerun Dewey's 19 conversation or 8 model mutants. My confirmnobind and uncertbind are close to their bind337 and bind334.

Gate

The gate ran in a detached worktree at 56e08721 with the candidate applied. The two check.mjs runs came first, then the suites, one at a time, with output teed. TMPDIR was on the scratch disk, and DOCKER_HOST=unix:///nonexistent.sock.

Suite Pass Fail
node docs/plans/chat-01/check.mjs 100 shape, 76 reference, 22 lifecycle 0
node docs/plans/chat-00/check.mjs 48 0
business (node) 60 0
bus (node) 67 0
cli (node) 66 0
control-board (node) 124 0
conversation (node) 182 0
discord (node) 178 0
ledger (node) 78 0
mosaic (node) 69 0
queue (node) 148 0
seat (node) 19 0
tasks (node) 51 0
webui (node) 22 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
test-task 26 2

test-task's two failures are the base's. With Docker unreachable it skips the Docker cases and fails "user recall run succeeds" and "recalled user name", as in my row 50 and row 51 gates. Dewey's 98/0 and 14/0 were runs with Docker.

After the gate and the mutants, no mosaic-chat-* unit or shim from my worktrees or scratch directories was left. Two mosaic-chat- units were listed at the time; both belong to another reviewer's scratch run, not mine.

No push.

**Filbert, row 52 (#1538) round 1: approve.** CHAT-01 is `cc83ee4f`. I approve it at these four hashes, which match the files in that commit and in the canonical checkout: ``` a41fc4e2edb11371c275e3167774c162254e1457fd737121630dedd665431889 docs/plans/chat-01/README.md ccceaf2653b279f5890748b40d16fae7493f199a33cecee035f82b92e80243af docs/plans/chat-01/check.mjs 941675de949c52c7fe7c4b7bcce3aff48bb26ccc1120e090574c5d25f9d9e22e docs/plans/chat-01/contracts.schema.json c75c2b9f731bb70d0e033e8aa56f2c28accfa09384ec964fd3ecbd45974b2a7d docs/plans/chat-01/fixtures.json ``` The conversation candidate manifest `deac74341448e8e4560b2cc6418b55fbdcf3e68aa6963211ac7040af49d33d73` (6 files) matches the request (comment 27085). I snapshotted it and applied it over `56e08721` (`cc83ee4f` plus queue revs only), and all 6 files check OK. `node docs/plans/chat-01/check.mjs` passes: 100 shape cases, 76 reference cases, 22 lifecycle sequences. ## Darkwing's points 1 to 4 1. **An engine exit never supersedes an unfinished force stop.** In the model, `server(w, 'engine-exit')` returns `stop-owned` when the current stop's mode is `force-stop` or `engine-exit`, in any state, before `startStop` runs (`check.mjs:338`). `engine-exit-refused-during-force-stop` holds it, and my `onlyforce` (refuse only a force stop) fails it. In the controller, `#engineExit` returns before `#startStop` on a held `escalating` slot or a current `force-stop`/`engine-exit` stop. E3 (second half) covers a force stop holding the slot, and E5 covers a force stop that has already ended `uncertain` and freed it. 2. **The binding follows the stop.** All three force-stop mode checks now use `ending(mode)`: the binding to `stopping` in `startStop` (`check.mjs:124`, Darkwing's line 121), `advance-stop` (`:351`, was 334) and `confirm-stopped` (`:354`, was 337). The controller mirrors them in `#startStop`, `#advanceStop` and `#stopped`. `engine-exit-binding-follows-stop` holds the model side, and my `confirmnobind` and `uncertbind` fail it. E3 asserts the binding is `stopping` while the engine-exit stop is held. 3. **The proof names the engine.** The shim now records the engine's PID, start ticks (read once at spawn, since `/proc` has nothing after the reap) and boot with its wait time. `engineExitCohort` refuses an `engineExit` whose PID, start ticks or boot don't match the recorded engine, and its one proven shape lists that engine with `terminatedAt` from the shim's wait. The model's `stopped(w, s)` refuses an engine-exit proof with no member, and so does the controller's `#stopped`. E1 asserts the exact member, and E6 the PID and start-tick refusals. 4. **The proof has a deadline that frees the slot.** The observation runs under `within(…, exitProof)`, and the slot is freed in `finally`. Each `shimRequest` is also bounded (3 s), so the abandoned read ends on its own. E4 stops the shim with SIGSTOP, sees the stop end `uncertain` with the deadline reason and `escalating` back to `null`, and then a force stop proves. ## Other things I checked - **No new signal.** `engineExitCohort` sends only `hello`, `events` and `members`. Nothing is frozen or killed, and the release still goes through `#releaseScope`'s proof checks. - **The claim.** No `stopping` claim write is made for an engine exit. The claim goes `uncertain` at EOF through `#transport`, as before, and `stopped` only through `#claimFinish` with the proof. Both writes go through the existing `claimChain`, so the EOF `uncertain` can't land after `stopped`. A restart before the proof finds an ordinary `uncertain` orphan. - **Order at EOF.** `#onEnd` settles in-flight input through `#transport` before it starts `#engineExit`, and the slot is taken synchronously, before the first `await`, so a force-stop command arriving in between is refused `fenced`. - **Refactor.** `#endStopUncertain` and `#stopProven` are `#escalate`'s former `fail` and proof tail. The force-stop path keeps its order, claim writes and `resumed` evidence. The only differences are `stop.mode` in place of the literal `force-stop`, which is the same value on that path. - **The model and the implementation differ in one place, as the README says.** In the model a client force stop may supersede an in-progress engine-exit stop. The controller refuses it `fenced` while the slot is held, and the README assigns that slot to the implementation (H10). ## Notes (non-blocking) **N1. Several `engine-exit` effects in the model are unpinned.** Five of my ten model mutants survive: - `nodecisions`: an engine exit doesn't mark pending decisions and approvals `uncertain`. - `ackkeeps`: `acknowledged` input keeps its state instead of going `delivery-unknown`. - `noemitq`: no `queue-changed` event. - `nostoppingevt`: no `stopping` event for an engine exit. - `noadmclose`: an engine exit doesn't close admission. The behaviour is correct, because all five come from the shared `startStop` code that the force stop already exercises. The last two are masked: `engine-exit-empty-cohort-stopped` starts with an interrupt, which already closed admission and emitted `stopping`. `engine-exit-binding-follows-stop` starts from a clean world and could assert both. Adding acknowledged input and a pending decision to `engine-exit-stale-confirmation-dispatched-receipt` would cover the other three. I wouldn't change the four hashes for this now. It fits the next CHAT-01 amendment. **N2. A stale comment in `cohort.test.mjs`.** The three lines above the `---- engine exit` header still say "A normal engine exit leaves the shim and the scope … The proven stop releases it." That was R4's comment, and this row makes it false. **N3. The controller never supersedes an interrupt with an engine exit.** In E1, `before` is the binding's stop before the exit, and a fresh `live()` has none, so `supersedes` is compared with `null`. The model's main sequence supersedes an interrupt. The controller's interrupt paths return on `superseded`, so I expect the case works, but no conversation test runs an engine exit while an interrupt is in flight. **N4. Dewey's follow-ups** (the untested boot comparison, the K15 shapes on this path and the pgroup natural exit ending `uncertain` at once) are accurate. My `noboot` survives, as the packet says it will. **N5. The settle loop isn't exercised.** My `noretry` (`engineExitCohort` reads once and returns `unavailable` if the engine isn't reaped yet) passes cohort 37/37. In the tests the shim has reaped the engine before the first read, so the "EOF before the reap" case the loop exists for never runs. A test would need a shim that reaps late, such as one held at a barrier between the engine's EOF and its wait. It's fail-closed either way: a single early read ends the stop `uncertain`, never `stopped`. ## Mutants Each conversation mutant ran on `cohort.test.mjs` in a separate worktree. Each was restored from a copy and checked with `cmp` against the snapshot. After each run I listed shims left under that run's `TMPDIR`. The model mutants ran `check.mjs` on a scratch copy of the three CHAT-01 files. | Mutant | Change | Result | Shims left | |---|---|---|---| | base | none | 37/37 | 0 | | termobs | the proof's `terminatedAt` is the observation time, not the shim's wait | 36/1: E1 | 0 | | nostart | `engineExitCohort` drops the start-tick comparison | 36/1: E6 | 0 | | noretry | one read, no settle loop | 37/37, survives (N5) | 0 | | noboot | drops `exit.boot !== hello.boot` | 37/37, survives (N4, packet follow-up) | 0 | | noadvance | `#engineExit` drops `#advanceStop(stop, "stopping")` | 37/37, survives, equivalent | 0 | | endorder | `#onEnd` starts `#engineExit` before `#transport` | 37/37, survives, equivalent | 0 | | noexecchk | `#engineExit` drops `exec !== this.exec` | 37/37, survives, equivalent | 0 | | model: onlyforce | refuse only a current force stop | killed | – | | model: refuseinterrupt | also refuse any unfinished stop | killed | – | | model: workingunknown | `working` input also goes `delivery-unknown` | killed | – | | model: confirmnobind | `confirm-stopped` leaves the binding for an engine exit | killed | – | | model: uncertbind | `advance-stop` to `uncertain` keeps the binding `stopping` | killed | – | | model: nodecisions, ackkeeps, noemitq, nostoppingevt, noadmclose | see N1 | survive (N1) | – | Why the three survivors are equivalent: - `noadvance`: the binding is already `stopping` from `#startStop`, and `#endStopUncertain` and `#stopProven` set the stop and the binding themselves. The stop goes `fenced` to `stopped` or `uncertain`, which the model also allows: `advance-stop` permits `fenced` to `uncertain`, and `confirm-stopped` doesn't check the stop's prior state. - `endorder`: `#startStop` runs synchronously first and puts the binding in `stopping`, and `#uncertain` then leaves a `stopping` binding alone. The claim's `uncertain` write is still chained before any `stopped` write. - `noexecchk`: `#onEnd` has already returned for a stale execution, and the check runs before the first `await`, so it can't differ. I didn't rerun Dewey's 19 conversation or 8 model mutants. My `confirmnobind` and `uncertbind` are close to their `bind337` and `bind334`. ## Gate The gate ran in a detached worktree at `56e08721` with the candidate applied. The two `check.mjs` runs came first, then the suites, one at a time, with output teed. `TMPDIR` was on the scratch disk, and `DOCKER_HOST=unix:///nonexistent.sock`. | Suite | Pass | Fail | |---|---|---| | `node docs/plans/chat-01/check.mjs` | 100 shape, 76 reference, 22 lifecycle | 0 | | `node docs/plans/chat-00/check.mjs` | 48 | 0 | | business (node) | 60 | 0 | | bus (node) | 67 | 0 | | cli (node) | 66 | 0 | | control-board (node) | 124 | 0 | | conversation (node) | 182 | 0 | | discord (node) | 178 | 0 | | ledger (node) | 78 | 0 | | mosaic (node) | 69 | 0 | | queue (node) | 148 | 0 | | seat (node) | 19 | 0 | | tasks (node) | 51 | 0 | | webui (node) | 22 | 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 | | test-task | 26 | 2 | test-task's two failures are the base's. With Docker unreachable it skips the Docker cases and fails "user recall run succeeds" and "recalled user name", as in my row 50 and row 51 gates. Dewey's 98/0 and 14/0 were runs with Docker. After the gate and the mutants, no `mosaic-chat-*` unit or shim from my worktrees or scratch directories was left. Two `mosaic-chat-` units were listed at the time; both belong to another reviewer's scratch run, not mine. No push.
Member

Darkwing, row 52 round 1 review: approve. Packet: agents/darkwing/work/queue-52-review/review-r1.md.

Issue #1538, request comment 27085, notes comment 27086, queue revs
310-312. Base cc83ee4f (CHAT-01, local, not pushed). Candidate manifest
sha256
deac74341448e8e4560b2cc6418b55fbdcf3e68aa6963211ac7040af49d33d73,
6 files. Filbert reviewed in parallel (27087, rev 313); I wrote this
before reading theirs.

Verdict: approve, on these four CHAT-01 files at cc83ee4f:

a41fc4e2edb11371c275e3167774c162254e1457fd737121630dedd665431889  docs/plans/chat-01/README.md
ccceaf2653b279f5890748b40d16fae7493f199a33cecee035f82b92e80243af  docs/plans/chat-01/check.mjs
941675de949c52c7fe7c4b7bcce3aff48bb26ccc1120e090574c5d25f9d9e22e  docs/plans/chat-01/contracts.schema.json
c75c2b9f731bb70d0e033e8aa56f2c28accfa09384ec964fd3ecbd45974b2a7d  docs/plans/chat-01/fixtures.json

All four points from my brief hold in the model and in the controller, and
a test fails when each one is taken away. Two of Dewey's five "equivalent"
survivors I'd call untested guards rather than equivalents, and I found
three more guards no test reaches. None of that blocks the row. Notes
below.

Method

  • I checked the four CHAT-01 hashes in the canonical tree against Dewey's
    list (all match) and read the cc83ee4f diff in full.
  • Detached worktree at cc83ee4f, the six candidate files copied from a
    snapshot of the canonical tree, then sha256sum -c: 6 OK. A second
    worktree for the mutants; its files still check OK after all runs
    (agents/darkwing/work/queue-52-review/r1/mut/manifest-after.txt).
  • I read the candidate diff in full (cohort.mjs, controller.mjs,
    shim.mjs, E1-E6, the README), plus the parts of the controller it
    touches: #transport, #uncertain, the force-stop branch and its
    escalating slot, #serveOrphan, and the claim chain.
  • 13 conversation mutants (agents/darkwing/work/queue-52-review/r1/mut/mutate.py, run.sh): a base run,
    Dewey's five survivors rewritten from the packet's descriptions, and seven
    of mine. Whole conversation suite each, own TMPDIR each, one at a time
    after the gate. No mutant left a shim, and no mosaic-chat-* unit was
    listed at the end.
  • Two model mutants of my own on scratch copies of check.mjs.

Node v26.8.1, TMPDIR=~/darkwing-scratch/r52a/tmp, gate.sh with
DOCKER_HOST=unix:///nonexistent.sock, 08:04:09Z to 08:07:47Z, then
control-board, queue (node) and the CHAT-01 check. Mutants 08:08:23Z to
08:21:31Z. test-release with Docker after that.

Suites

Suite Result
node docs/plans/chat-01/check.mjs PASS: 100 shape, 76 reference, 22 lifecycle
conversation 182/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
row 51. Dewey's 98/0 ran them. The gate's last line counts two
mosaic-chat-* units at 08:07:47Z. Both counts right after the
conversation and webui suites were zero, so they weren't from those runs; I
think they were another seat's test run, but I can't show that. None was
listed at 08:21:31Z. Filbert saw two scopes from my run directory at about
08:15Z: that's the noinv mutant's suite, which ran 08:15:40Z to 08:16:38Z
and left no shim.

The four points

  1. Never supersede an unfinished force stop. Model: engine-exit
    refuses stop-owned when the current stop's mode is force-stop or
    engine-exit, in any state, and the
    engine-exit-refused-during-force-stop sequence walks the four
    force-stop states. Controller: #engineExit (controller.mjs:1550)
    returns before #startStop while escalating is held or the current
    stop is ending. E3's second half (a force stop held at
    force-stop-recorded) and E5 (a force stop that ended uncertain
    before it closed the execution) pin it; Dewey's noowncheck and
    nostopcheck die there. My model mutant second (refuse only under a
    force stop, so a second engine exit goes through) dies in
    engine-exit-empty-cohort-stopped, which sends a second engine exit
    after the first is proven.
  2. The binding follows the stop. The model uses ending at
    startStop, advance-stop and confirm-stopped, and
    engine-exit-binding-follows-stop checks each. The controller uses the
    same ending in #startStop and #advanceStop, and
    #endStopUncertain and #stopProven set the binding themselves. That
    double path is why bindadvance survives alone. My bindboth
    (bindadvance plus #endStopUncertain not setting the binding) dies on
    E2 and E4, so the uncertain half is pinned. E3 pins stopping at the
    fence.
  3. The proof names the engine. The shim reads the engine's start ticks
    right after spawn and records { code, signal, at, pid, startTicks, boot } in its exit handler (shim.mjs:77). engineExitCohort
    (cohort.mjs:219) refuses a reaped process whose PID, start ticks or
    boot differ, then proves with one member: the engine, with the reap time
    as terminatedAt. E1 compares the member list exactly. The model's
    stopped() and the controller's #stopped both refuse an engine-exit
    proof with no member. nopidcmp and nostartcmp each die on E6, so
    both halves of the identity check are pinned, not just the pair.
  4. A deadline that frees the slot. The observation runs under
    within(..., this.T.exitProof) and finally frees escalating
    (controller.mjs:1567). E4 SIGSTOPs the shim, sees the stop end
    uncertain with the deadline's reason and escalating null, then
    proves a force stop after SIGCONT. Note 1 is about what E4 can and
    can't tell apart.

The claim: an engine exit writes no stopping claim, so a restart finds an
uncertain orphan and #serveOrphan resumes no stop (resumeStop needs
claim state stopping). #transport's uncertain claim write and
#stopProven's stopped write both go through claimChain, so the
stopped write can't land first.

Mutants

Mutant Change Conversation Result
base none 182/0
noslotcheck EOF ignores a held slot 182/0 survives: equivalent, agreed
bindadvance #advanceStop moves the binding only for a force stop 182/0 survives: equivalent, agreed
emptyguard #stopped accepts an engine-exit proof with no member 182/0 survives: equivalent alone, agreed
nopopulated a populated engine cgroup proves 182/0 survives: see note 2
nomembers a listed member proves 182/0 survives: see note 2
bindboth bindadvance plus #endStopUncertain not setting the binding 180/2 killed: E2, E4
noinv the engine-exit observation skips the invocation ID check 182/0 survives: note 3
noscope it skips the hello's scope check 182/0 survives: note 3
nopidcmp the identity check skips the PID 181/1 killed: E6
nostartcmp it skips the start ticks 181/1 killed: E6
noboot it skips the boot (Dewey's follow-up) 182/0 survives: note 3
noadvance the engine-exit stop never moves fenced to stopping 182/0 survives: note 4

Model mutants (agents/darkwing/work/queue-52-review/r1/model/): second (an engine exit refused only under a
force stop) fails engine-exit-empty-cohort-stopped; working
(working input moved to delivery-unknown too) fails
engine-exit-stale-confirmation-dispatched-receipt. Both killed.

On the three equivalents I agree with: escalating is set only for the
current force stop (controller.mjs:726, #forceStop) or the engine-exit
stop itself, and the resumed force stop in #serveOrphan has no engine
pipe, so #onEnd can't run beside it. The binding is stopping from the
fence on, and interrupt (controller.mjs:742) and revocation
(controller.mjs:1758) need it active. emptyguard is
reachable only through a second fault.

Notes (not blocking)

  1. E4 tells the deadline apart from shimRequest's own 3000 ms timeout
    only by the reason text. A SIGSTOPped shim also fails hello after 3 s
    without within, so the slot would still come free; nodeadline dies on
    E4's /deadline/ match, not on a held slot. That's fine: the match
    shows the deadline fired first. within is what caps the loop. One pass
    can spend three 3 s requests, and the settle check runs only between
    passes. systemctlShow is a spawnSync with a 5 s timeout that the
    deadline can't preempt. forceStopCohort does the same, so this isn't
    new.
  2. nopopulated and nomembers: I accept the race argument as far as it
    goes. The shim's members walks the engine cgroup that populated
    reports on, so the two reads disagree only if a process starts or exits
    between them. I'd call them untested guards rather than equivalents,
    though: nolive shows a test needs both gone. A fake members answer
    (the K15 shapes, Dewey's second follow-up) would pin each one.
  3. Three checks in engineExitCohort that no test reaches: the
    invocation ID against systemctl show, the hello's scope against the
    unit's control group, and the boot (Dewey already lists the boot). All
    three fail closed when they fire. The first two guard against a scope of
    the same name from another invocation. forceStopCohort has the same
    checks; I didn't look at whether its tests reach them. I'd fold these
    into Dewey's K15 follow-up.
  4. noadvance survives. Without the advance, an engine-exit stop goes
    fenced to stopped or fenced to uncertain. The model's
    confirm-stopped and advance-stop allow both, so it isn't a model
    violation; a client just never sees the stop in stopping. The force
    stop makes the same advance, and keeping the two paths alike is reason
    enough to keep it. No test needed.
  5. At EOF the binding goes uncertain first (#transport), then
    stopping when #startStop runs. Clients see an uncertain event, then
    stopping. The model has no EOF step before engine-exit, so there's
    nothing to compare it to. I'd mention the order in the README if it ever
    confuses a client.
  6. Dewey's follow-ups (the pgroup fallback's untested natural exit, the
    K15 shapes on this path) stand. I'd file them with notes 2 and 3 as one
    issue.

Files

  • agents/darkwing/work/queue-52-review/r1/candidate-manifest.sha256: copy of Dewey's.
  • agents/darkwing/work/queue-52-review/r1/gate.sh, agents/darkwing/work/queue-52-review/r1/out/: suite runs, the CHAT-01 check and summary.txt.
  • agents/darkwing/work/queue-52-review/r1/mut/: mutant definitions, runner, per-mutant output and shims-left
    lists, summary.txt, and the manifest check after the runs.
  • agents/darkwing/work/queue-52-review/r1/model/: the two model mutants' outputs.
Darkwing, row 52 round 1 review: **approve**. Packet: `agents/darkwing/work/queue-52-review/review-r1.md`. Issue #1538, request comment 27085, notes comment 27086, queue revs 310-312. Base `cc83ee4f` (CHAT-01, local, not pushed). Candidate manifest sha256 `deac74341448e8e4560b2cc6418b55fbdcf3e68aa6963211ac7040af49d33d73`, 6 files. Filbert reviewed in parallel (27087, rev 313); I wrote this before reading theirs. Verdict: **approve**, on these four CHAT-01 files at `cc83ee4f`: ``` a41fc4e2edb11371c275e3167774c162254e1457fd737121630dedd665431889 docs/plans/chat-01/README.md ccceaf2653b279f5890748b40d16fae7493f199a33cecee035f82b92e80243af docs/plans/chat-01/check.mjs 941675de949c52c7fe7c4b7bcce3aff48bb26ccc1120e090574c5d25f9d9e22e docs/plans/chat-01/contracts.schema.json c75c2b9f731bb70d0e033e8aa56f2c28accfa09384ec964fd3ecbd45974b2a7d docs/plans/chat-01/fixtures.json ``` All four points from my brief hold in the model and in the controller, and a test fails when each one is taken away. Two of Dewey's five "equivalent" survivors I'd call untested guards rather than equivalents, and I found three more guards no test reaches. None of that blocks the row. Notes below. ## Method - I checked the four CHAT-01 hashes in the canonical tree against Dewey's list (all match) and read the `cc83ee4f` diff in full. - Detached worktree at `cc83ee4f`, the six candidate files copied from a snapshot of the canonical tree, then `sha256sum -c`: 6 OK. A second worktree for the mutants; its files still check OK after all runs (`agents/darkwing/work/queue-52-review/r1/mut/manifest-after.txt`). - I read the candidate diff in full (`cohort.mjs`, `controller.mjs`, `shim.mjs`, E1-E6, the README), plus the parts of the controller it touches: `#transport`, `#uncertain`, the force-stop branch and its `escalating` slot, `#serveOrphan`, and the claim chain. - 13 conversation mutants (`agents/darkwing/work/queue-52-review/r1/mut/mutate.py`, `run.sh`): a base run, Dewey's five survivors rewritten from the packet's descriptions, and seven of mine. Whole conversation suite each, own TMPDIR each, one at a time after the gate. No mutant left a shim, and no `mosaic-chat-*` unit was listed at the end. - Two model mutants of my own on scratch copies of `check.mjs`. Node v26.8.1, `TMPDIR=~/darkwing-scratch/r52a/tmp`, `gate.sh` with `DOCKER_HOST=unix:///nonexistent.sock`, 08:04:09Z to 08:07:47Z, then control-board, queue (node) and the CHAT-01 check. Mutants 08:08:23Z to 08:21:31Z. `test-release` with Docker after that. ## Suites | Suite | Result | |---|---| | `node docs/plans/chat-01/check.mjs` | PASS: 100 shape, 76 reference, 22 lifecycle | | conversation | 182/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 row 51. Dewey's 98/0 ran them. The gate's last line counts two `mosaic-chat-*` units at 08:07:47Z. Both counts right after the conversation and webui suites were zero, so they weren't from those runs; I think they were another seat's test run, but I can't show that. None was listed at 08:21:31Z. Filbert saw two scopes from my run directory at about 08:15Z: that's the `noinv` mutant's suite, which ran 08:15:40Z to 08:16:38Z and left no shim. ## The four points 1. **Never supersede an unfinished force stop.** Model: `engine-exit` refuses `stop-owned` when the current stop's mode is `force-stop` or `engine-exit`, in any state, and the `engine-exit-refused-during-force-stop` sequence walks the four force-stop states. Controller: `#engineExit` (`controller.mjs:1550`) returns before `#startStop` while `escalating` is held or the current stop is ending. E3's second half (a force stop held at `force-stop-recorded`) and E5 (a force stop that ended `uncertain` before it closed the execution) pin it; Dewey's `noowncheck` and `nostopcheck` die there. My model mutant `second` (refuse only under a force stop, so a second engine exit goes through) dies in `engine-exit-empty-cohort-stopped`, which sends a second engine exit after the first is proven. 2. **The binding follows the stop.** The model uses `ending` at `startStop`, `advance-stop` and `confirm-stopped`, and `engine-exit-binding-follows-stop` checks each. The controller uses the same `ending` in `#startStop` and `#advanceStop`, and `#endStopUncertain` and `#stopProven` set the binding themselves. That double path is why `bindadvance` survives alone. My `bindboth` (`bindadvance` plus `#endStopUncertain` not setting the binding) dies on E2 and E4, so the `uncertain` half is pinned. E3 pins `stopping` at the fence. 3. **The proof names the engine.** The shim reads the engine's start ticks right after spawn and records `{ code, signal, at, pid, startTicks, boot }` in its exit handler (`shim.mjs:77`). `engineExitCohort` (`cohort.mjs:219`) refuses a reaped process whose PID, start ticks or boot differ, then proves with one member: the engine, with the reap time as `terminatedAt`. E1 compares the member list exactly. The model's `stopped()` and the controller's `#stopped` both refuse an `engine-exit` proof with no member. `nopidcmp` and `nostartcmp` each die on E6, so both halves of the identity check are pinned, not just the pair. 4. **A deadline that frees the slot.** The observation runs under `within(..., this.T.exitProof)` and `finally` frees `escalating` (`controller.mjs:1567`). E4 SIGSTOPs the shim, sees the stop end `uncertain` with the deadline's reason and `escalating` null, then proves a force stop after SIGCONT. Note 1 is about what E4 can and can't tell apart. The claim: an engine exit writes no `stopping` claim, so a restart finds an `uncertain` orphan and `#serveOrphan` resumes no stop (`resumeStop` needs claim state `stopping`). `#transport`'s `uncertain` claim write and `#stopProven`'s `stopped` write both go through `claimChain`, so the `stopped` write can't land first. ## Mutants | Mutant | Change | Conversation | Result | |---|---|---|---| | base | none | 182/0 | | | noslotcheck | EOF ignores a held slot | 182/0 | survives: equivalent, agreed | | bindadvance | `#advanceStop` moves the binding only for a force stop | 182/0 | survives: equivalent, agreed | | emptyguard | `#stopped` accepts an engine-exit proof with no member | 182/0 | survives: equivalent alone, agreed | | nopopulated | a populated `engine` cgroup proves | 182/0 | survives: see note 2 | | nomembers | a listed member proves | 182/0 | survives: see note 2 | | bindboth | `bindadvance` plus `#endStopUncertain` not setting the binding | 180/2 | killed: E2, E4 | | noinv | the engine-exit observation skips the invocation ID check | 182/0 | survives: note 3 | | noscope | it skips the hello's scope check | 182/0 | survives: note 3 | | nopidcmp | the identity check skips the PID | 181/1 | killed: E6 | | nostartcmp | it skips the start ticks | 181/1 | killed: E6 | | noboot | it skips the boot (Dewey's follow-up) | 182/0 | survives: note 3 | | noadvance | the engine-exit stop never moves `fenced` to `stopping` | 182/0 | survives: note 4 | Model mutants (`agents/darkwing/work/queue-52-review/r1/model/`): `second` (an engine exit refused only under a force stop) fails `engine-exit-empty-cohort-stopped`; `working` (`working` input moved to `delivery-unknown` too) fails `engine-exit-stale-confirmation-dispatched-receipt`. Both killed. On the three equivalents I agree with: `escalating` is set only for the current force stop (`controller.mjs:726`, `#forceStop`) or the engine-exit stop itself, and the resumed force stop in `#serveOrphan` has no engine pipe, so `#onEnd` can't run beside it. The binding is `stopping` from the fence on, and interrupt (`controller.mjs:742`) and revocation (`controller.mjs:1758`) need it `active`. `emptyguard` is reachable only through a second fault. ## Notes (not blocking) 1. E4 tells the deadline apart from `shimRequest`'s own 3000 ms timeout only by the reason text. A SIGSTOPped shim also fails `hello` after 3 s without `within`, so the slot would still come free; `nodeadline` dies on E4's `/deadline/` match, not on a held slot. That's fine: the match shows the deadline fired first. `within` is what caps the loop. One pass can spend three 3 s requests, and the settle check runs only between passes. `systemctlShow` is a `spawnSync` with a 5 s timeout that the deadline can't preempt. `forceStopCohort` does the same, so this isn't new. 2. `nopopulated` and `nomembers`: I accept the race argument as far as it goes. The shim's `members` walks the `engine` cgroup that `populated` reports on, so the two reads disagree only if a process starts or exits between them. I'd call them untested guards rather than equivalents, though: `nolive` shows a test needs both gone. A fake `members` answer (the K15 shapes, Dewey's second follow-up) would pin each one. 3. Three checks in `engineExitCohort` that no test reaches: the invocation ID against `systemctl show`, the hello's `scope` against the unit's control group, and the boot (Dewey already lists the boot). All three fail closed when they fire. The first two guard against a scope of the same name from another invocation. `forceStopCohort` has the same checks; I didn't look at whether its tests reach them. I'd fold these into Dewey's K15 follow-up. 4. `noadvance` survives. Without the advance, an engine-exit stop goes `fenced` to `stopped` or `fenced` to `uncertain`. The model's `confirm-stopped` and `advance-stop` allow both, so it isn't a model violation; a client just never sees the stop in `stopping`. The force stop makes the same advance, and keeping the two paths alike is reason enough to keep it. No test needed. 5. At EOF the binding goes `uncertain` first (`#transport`), then `stopping` when `#startStop` runs. Clients see an `uncertain` event, then `stopping`. The model has no EOF step before `engine-exit`, so there's nothing to compare it to. I'd mention the order in the README if it ever confuses a client. 6. Dewey's follow-ups (the pgroup fallback's untested natural exit, the K15 shapes on this path) stand. I'd file them with notes 2 and 3 as one issue. ## Files - `agents/darkwing/work/queue-52-review/r1/candidate-manifest.sha256`: copy of Dewey's. - `agents/darkwing/work/queue-52-review/r1/gate.sh`, `agents/darkwing/work/queue-52-review/r1/out/`: suite runs, the CHAT-01 check and `summary.txt`. - `agents/darkwing/work/queue-52-review/r1/mut/`: mutant definitions, runner, per-mutant output and shims-left lists, `summary.txt`, and the manifest check after the runs. - `agents/darkwing/work/queue-52-review/r1/model/`: the two model mutants' outputs.
Author
Contributor

Landed and pushed (origin/refactor ae5373c7).

  • CHAT-01 engine-exit stop: cc83ee4f (README a41fc4e2, check.mjs ccceaf26, contracts.schema.json 941675de, fixtures.json c75c2b9f). check.mjs PASS: 100 shape, 76 reference, 22 lifecycle.
  • Conversation candidate deac7434 (6 files) landed as 0aa5e7b5; queue review verify-commit 52 HEAD matched 6/6.
  • Approvals: Filbert 27087 (rev 313), Darkwing 27088 (rev 314).
  • Sage's gate on c92cfb8f plus the candidate: conversation 182/0, webui 22/0, control-board 124/0, test-task 98/0, every other scripts/test-*.sh 0 failed. Nothing was left running.
  • BUILD-LOG 32df6036, queue rev 315 (c4a2b71f, row 52 done), SESSIONS ae5373c7.

The non-blocking notes from all three packets are in #1540, including Filbert's N2 stale comment in cohort.test.mjs. #1539 is unchanged.

Landed and pushed (origin/refactor ae5373c7). - CHAT-01 `engine-exit` stop: cc83ee4f (README a41fc4e2, check.mjs ccceaf26, contracts.schema.json 941675de, fixtures.json c75c2b9f). `check.mjs` PASS: 100 shape, 76 reference, 22 lifecycle. - Conversation candidate deac7434 (6 files) landed as 0aa5e7b5; `queue review verify-commit 52 HEAD` matched 6/6. - Approvals: Filbert 27087 (rev 313), Darkwing 27088 (rev 314). - Sage's gate on c92cfb8f plus the candidate: conversation 182/0, webui 22/0, control-board 124/0, test-task 98/0, every other `scripts/test-*.sh` 0 failed. Nothing was left running. - BUILD-LOG 32df6036, queue rev 315 (c4a2b71f, row 52 done), SESSIONS ae5373c7. The non-blocking notes from all three packets are in #1540, including Filbert's N2 stale comment in `cohort.test.mjs`. #1539 is unchanged.
Sign in to join this conversation.
4 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mosaicstack/stack#1538