Files
stack/agents/dewey/work/chat-03/BRIEF-r3-to-final.diff
T

641 lines
52 KiB
Diff
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
--- BRIEF.md@2c5be6b4 (R3)
+++ BRIEF.md (final, lead decision 32)
@@ -1,7 +1,9 @@
# CHAT-03 brief: live adapters and mediated terminal (#1507, row 5)
-Author: Dewey, 2026-09-26. R3, for review. This is the brief only. It
-includes no source, no contract edits and no seat changes. R1
+Author: Dewey, 2026-09-26/27. Final text for pinning: R3 (`2c5be6b4`,
+frozen as `BRIEF-r3-2c5be6b4.md`) with the one edit lead decision 32
+orders. It is not a review round (lead decision 27). This is the brief
+only. It includes no source, no contract edits and no seat changes. R1
(`5dd447f7`) is frozen as `BRIEF-r1-5dd447f7.md`, and R2 (`5c5b45a2`) as
`BRIEF-r2-5c5b45a2.md`. §0 lists what changed.
@@ -19,13 +21,33 @@
lead decisions gained items 26 and 27, and the goals review
(`docs/plans/2026-09-27_goals-review.md`) was added.
-Lead decision 27 makes R3 the last review round. After it, no CHAT-03
-source work starts until Sage rescopes CHAT-03 against Gate E. So R3's
-answer to anything pinned Pi can't prove is to refuse or report unknown,
-never new machinery to prove it. `REVIEW-REQUEST.md` marks each section
-as needed for Gate E or not, as input for the rescope.
+Lead decision 27 made R3 the last review round, and its answer to
+anything pinned Pi can't prove is to refuse or report unknown, never new
+machinery to prove it. Sage rescoped CHAT-03 against Gate E from R3's
+section map (lead decision 30), ruled on Rocko's R3 finding (lead
+decision 31) and ordered this edit (lead decision 32).
+
+### 0. Changes since R3 (lead decision 32)
+
+Filbert approved R3 on the sections Gate E keeps, with no blocking
+finding (`agents/filbert/work/chat-03-brief-review-r3-2026-09-27.md`,
+`48447592`). Rocko closed his R2 finding and raised one blocking finding
+in the seal (`agents/rocko/work/chat-03-r3-adversarial-2026-09-27.md`,
+`19e3fcff`), which lead decision 31 resolves. This edit does the five
+things lead decision 32 lists, and nothing else.
-### 0. Changes since R2
+| Item | What changed |
+|---|---|
+| 1. Lead decision 30 cuts, removed and not reworded | Removed: C-1, C-2 and C-4; increment I2 and §7's Pi dialogs (P1, P2, P4–P6); increment I1b (C-5 moves to CHAT-04, and the interim rule stays); the §2 idle drift check, W10, W18, W19, mutant 25, the drift choice and the `session-drift` name. H5–H8 stay for Claude in I4. References to removed items were adjusted where they stood: What ships, E5, carry-forwards 1 and 4, §11, Limits 5 and 11, Out of scope and Gate. Two sentences were kept by moving them: the claim's reach, into the live-session guard bullet, and P3's disabled Pi dialog, which is now §7's only Pi rule. |
+| 2. Lead decision 31: no explicit extensions | The seal is `--no-extensions`, `--no-prompt-templates`, `--no-themes` and no `--extension`. The registry, the input-silent review and the tree hashes are removed, not replaced with pinning. N5 and N15 are fake-only. N24 and mutant 37 refuse any `--extension`. Limit 11 and §11 say explicit extensions return with goal in CHAT-06. |
+| 3. Filbert n1: the Pi pin | Pi is pinned by the `package-lock.json` integrity of `@earendil-works/pi-coding-agent` 0.85.1, checked against npm's installed record. The listed `dist/core` and `dist/extensions` files are now labelled as citation sources, since `pi` runs `dist/bundle`. The built-in `llama.cpp` is pinned with the package. |
+| 4. Filbert n2: the clear's own `queue_update` | O5 counts the empty `queue_update` Pi's clear emits before its response as the controller's own. N10's fake emits it, and N25 proves that an ordinary Interrupt reconciles. |
+| 5. Rocko's R3 note | A build note under What ships: the `no-turn` cleanup lifts only its own fence. H10 covers the force-stop, overlap and revocation branches. |
+
+Filbert's n3 (an `aborted` with no stop in progress) isn't in lead
+decision 32, so it isn't applied.
+
+### 0a. Changes from R2 to R3 (historical)
R3 answers Rocko's review of R2
(`agents/rocko/work/chat-03-r2-adversarial-2026-09-26.md`, `07b938fb`),
@@ -49,7 +71,7 @@
| Consistency passes | Two passes over the R3 draft found 22 defects of wording and cross-reference. A third pass over the finished draft found 17, including three where a fixture's expected result didn't follow from the rules (N6, N8, N20). All are fixed. |
| Base | Line numbers and hashes move to `f2b9e622`. No contract, source or pinned Pi file changed since `40a02d2b`. Queue A1 is now committed, so the overlap table names its commit. |
-### 0a. Changes from R1 to R2 (historical)
+### 0b. Changes from R1 to R2 (historical)
The rule numbers in this table are R2's. R2's rule 6 is R3's rule 7.
@@ -204,38 +226,9 @@
approved before the code that needs it. Sage assigns the author; Filbert and
Dewey review, as they did for CHAT-01C.
-- **C-1: withdrawn from CHAT-03 and restated for CHAT-06.** R2's C-1
- would have added a `turnProof` list of the external items that
- `clear_queue` removed, so that `reconciled` could follow a non-empty
- clear. Its premise was that the clear accounts for everything an
- Interrupt removes. Filbert's R2 review (F2) showed that it doesn't.
- Agent-level custom messages are dropped without being returned, and
- `nextTurn` messages survive (§3). A list built from the clear's return
- would claim completeness it doesn't have, so C-1 doesn't ship in
- CHAT-03. The check isn't weakened: CHAT-03 runs only sealed engines,
- where any non-empty clear is an overlap signal and leaves the stop
- `uncertain` (§3 rule 3). Restated for CHAT-06, the item that owns
- turn-starting extensions: a `turnProof` record of removed external
- input, adopted only when pinned Pi gives complete evidence of what an
- abort removed (every queue, including agent-level and `nextTurn`
- messages) and ties runs to prompts, for example with run IDs. That needs
- a Pi upgrade or upstream change, which CHAT-06 names and pins.
-- **C-2: the CHAT-03D native dialog companion.** It covers Pi `confirm` and
- `select` with exact labels; `input` and `editor` stay unsupported. CHAT-01
- lines 239–242 and 292 block non-permission forms until it exists.
- Increment I2 waits for it.
- **C-3: Claude record fields.** Needed only if B1 shows that CHAT-01 lacks
a field, for example the capability-negotiation result on `binding`.
Otherwise C-3 is void.
-- **C-4 (optional; Sage decides): an event type for an unrecognized native
- event.** `event.type` is a closed enum, and `unavailable` is only a
- `visibility` value. Until C-4, an unknown native event gets no client
- event. It is recorded in controller evidence (type name and byte count)
- and counted, and the terminal shows the count. So it is not silent, and
- no record gets a type it doesn't have. Mapping it to an existing type
- with `visibility: unavailable` was the other option Filbert offered. It
- isn't taken because each existing type has meaning a client acts on, for
- example tool-start opening an effect.
- **C-5: interrupt outcomes other than an interrupted turn (R3).**
`turnProof.turnState` allows `interrupted`, `unknown` and
@@ -246,8 +239,9 @@
`completed-before-interrupt`, `failed-before-interrupt`,
`no-run-at-interrupt`),
allowed by `reconcile-interrupt` without recording `turn-interrupted`.
- Until C-5 lands, those stops stay `uncertain` (§3 rule 5). Adoption goes
- through I1b.
+ Until C-5 lands, those stops stay `uncertain` (§3 rule 5). C-5 moves to
+ CHAT-04 with its own contract review (lead decision 30), so CHAT-03
+ ships that interim rule only.
**Deviation V-1, accepted with limits (lead decision 25).** CHAT-01 line 145 says an exact
retry after reconnect returns the existing receipt. CHAT-01C line 212 says
@@ -270,7 +264,7 @@
wire. It stores the CHAT-01 `binding` fields it needs. If one is missing,
that becomes a C item, not an edit.
- New refusal and reason names (`busy`, `stale-incarnation`,
- `engine-pin-mismatch`, `session-drift`, `live-session-refused`,
+ `engine-pin-mismatch`, `live-session-refused`,
`foreign-host`, `transport-unknown`, `handled-without-run`,
`ack-without-start`, `interrupted`, `no-turn`, `unsealed-engine`,
`run-overlap`) are bounded draft IDs, like the existing names CHAT-01
@@ -280,18 +274,21 @@
### What ships
-Four increments. Each is its own candidate and review round, as with the
-queue-as-data A1/A2 split. An increment starts when everything in its
-"Needs first" column is met, so I2 and I3 can run in parallel after I1.
+Three increments: I1, I3 and I4. Each is its own candidate and review
+round, as with the queue-as-data A1/A2 split. An increment starts when
+everything in its "Needs first" column is met. Lead decision 30 cut I2
+(Pi dialogs) and moved I1b (C-5 adoption) to CHAT-04.
| Increment | Content | Needs first |
|---|---|---|
-| I1 | Controller, transport, writer claim, Pi adapter on a fake Pi engine, incremental events, mediated terminal, interrupt, force stop and recovery. Fixtures in §2–§6 and §9, except the approval races H5–H8; the §10 checks. | Brief approved; author named |
-| I1b | C-5 adoption: the controller fills the new `turnState` values, so `reconciled` may follow a Completed first, Failed on its own or No run outcome. C-1 is withdrawn (see "Contracts implemented"). | I1 approved; C-5 approved |
-| I2 | Pi native dialogs (§7) and the approval races H5–H8 on Pi dialogs | I1 approved; C-2 approved |
+| I1 | Controller, transport, writer claim, Pi adapter on a fake Pi engine, incremental events, mediated terminal, interrupt, force stop and recovery. Fixtures in §2–§6 and §9, except the approval races H5–H8, which run in I4; the §10 checks. | Brief approved; author named |
| I3 | The B1 evidence packet for Claude (§8) | I1 approved. Jason's go for any run that calls a model or reads Claude auth. |
| I4 | Claude adapter, catalogue and history (§7, §8), and H5–H8 on Claude permission requests | I3 approved, so B1 passed; C-3 if it exists |
+**Build note (Rocko, R3).** The `no-turn` refusal lifts only the fence
+its own Interrupt set. It never reopens admission that a concurrent force
+stop, overlap signal or revocation closed. H10 covers those branches.
+
**If B1 does not pass.** Sage records B1 as not passed when any of these
happens:
- Jason declines the go for the I3 recordings;
@@ -303,10 +300,6 @@
`unsupported-harness`, and CHAT-06's both-harness gate stays blocked with
CHAT-03 as its owner. The outcome is recorded, not a silent narrowing.
-**If C-2 is declined.** If Sage or Jason declines C-2, I2 is void. Pi
-non-permission dialogs stay disabled, as CHAT-01 lines 239–242 require.
-Sage records that, and CHAT-03 can close without I2.
-
#### 1. Controller and transport
- One controller process per execution owns the engine's stdin. Pi runs in
@@ -416,7 +409,8 @@
- **Fields.** A revision stores:
- the key, the claim ID and the binding ID;
- the harness, conversation, and branch and leaf at launch;
- - the config pins: engine version, binary sha256 and launch argv digest;
+ - the config pins: engine version, engine pin (the Pi package integrity
+ of §3, or the Claude binary sha256 of §8) and launch argv digest;
- the host identity (`/etc/machine-id`) and boot ID;
- the owning controller: pid, process start time and incarnation token;
- the intended scope unit name, derived from the claim ID, recorded
@@ -479,68 +473,16 @@
configured data root, or a path named in any seat registration. This is
what makes CHAT-03 fixture-only in code and not just by convention. The
guard comes out only at cutover (CHAT-07), when the live location is
- reviewed as a data-map change.
+ reviewed as a data-map change. The claim excludes only controllers that
+ use it: a `pi --session` run outside Mosaic isn't prevented, and the
+ guard is what keeps CHAT-03 off real sessions. The idle session drift
+ check moves to CHAT-07 (lead decision 30).
- **What never releases a claim.** Disconnect never changes a claim.
`agent_settled`, EOF, SIGTERM, idle and an abort acknowledgement never
release one. CHAT-00 line 65 says settled must not release a writer
claim. CHAT-01 line 325 says SIGTERM, EOF, an abort acknowledgement and
idle don't prove death, so none of them can support a `stopped`
revision either.
-- **Session drift, not foreign-writer attribution.** Pinned Pi emits
- `message_end` to RPC listeners before `SessionManager.appendMessage`
- creates the entry ID (`dist/core/agent-session.js` lines 386–398).
- `entry_appended` fires only for extension custom entries (line 2033).
- So the stream carries no ID that tells the engine's own appends from
- another writer's. The drift check compares the file with the engine's own
- list instead:
- - `get_entries` returns every entry the engine holds, in append order,
- with stable IDs (`rpc.md` lines 717–745; `rpc-mode.js` line 505).
- - The pinned `SessionManager` reads the file only at construction or
- `setSessionFile` (`session-manager.js` lines 606–684). It never re-reads
- it mid-session, so an entry another writer appends is on disk but not
- in the engine's list. The author confirms that from the pinned source
- before I1 code.
- - For a new session file, Pi keeps entries in memory before the first
- assistant message and writes the file only when that message arrives
- (`_persist`, lines 739–767). The engine's list can run ahead of the
- disk. That is not drift. A loaded file is flushed at load (line 637),
- so Pi appends to it at once.
- - Pi may write the file while loading it. It adds a newline after a
- truncated last line (line 319), writes a header into an empty file
- (lines 632–633), and rewrites the file on version migration (line
- 677). So the controller takes its baseline (size, inode and entry IDs)
- only after load, at the first `get_state` reply, and never earlier.
-
- The check runs when the engine is idle: after `agent_settled`, with no
- command in flight. It reads the file first, then `get_entries`. An
- engine append between the two reads then lands in the engine's list
- after the disk prefix, and the prefix rule absorbs it; the other order
- would count it as drift. Drift is any of:
- - an entry ID on disk that the engine's list lacks, or an ID that appears
- twice (the session header is excluded, since `get_entries` omits it,
- `rpc.md` line 719; the header is checked separately against the
- claim's session identity);
- - disk entries that aren't a prefix of the engine's list, in order;
- - the file shrinks, is replaced (inode change), or has a line that isn't a
- valid entry.
-
- The engine's own settings, model-change, label and compaction entries are
- in its list, so they never count as drift. On drift, admission closes,
- the binding goes to `uncertain` with reason `session-drift`, and views get
- a reconcile marker. Plan lines 179–180 require refusing a second writer
- until the prior execution is reconciled. The file comparison and the
- marker are this brief's design for that. If `get_entries` fails or times
- out, the check reports `uncertain` and admission stays closed.
-
- Stated limits:
- - A foreign write that lands mid-turn is caught at the next idle check,
- not when it happens.
- - A foreign writer that rewrites the file in place with the same entry
- IDs and inode isn't distinguishable from the engine's own writes. The
- content isn't compared.
- - The claim excludes only controllers that use it. A `pi --session` run
- outside Mosaic isn't prevented, and the live-session guard is what
- keeps CHAT-03 off real sessions.
- **No writes to sessions.** The controller never writes a session file and
never uses `SessionManager.open` (CHAT-00 line 67).
@@ -555,7 +497,6 @@
| W7 | Recorded boot ID differs, same machine ID | `stopped` with a boot proof. Open tool calls become `uncertain`. |
| W8 | Resume after a proven stop with the same pins | New claim ID, generation +1, same conversation, branch and leaf |
| W9 | Resume with a changed binary, argv digest, branch or leaf | Refused. The claim is unchanged. |
-| W10 | A valid entry with a new ID appended while the fake engine is idle; a duplicate ID appended; the file truncated; the file replaced | Each gives `session-drift` at the idle check: admission closed, `uncertain`, reconcile marker. The controller writes nothing to the engine or the file. |
| W11 | Controller writes to session files | None. The CHAT-02 F17 check runs over the fixture session directory. The fake engine's own appends are recorded separately and excluded. |
| W12 | A live owner paused with SIGSTOP; a second controller starts | The second refuses `already-active`. The paused owner's revisions are unchanged after it resumes. |
| W13 | Crash after the engine spawns but before `active` is published | Restart finds the reservation and the live unit: `uncertain`, force stop only. No second spawn. |
@@ -564,16 +505,26 @@
| W15 | Crash between the two keys during release | The pair stays held. Restart finishes the release under the same claim ID. |
| W16 | A highest revision that won't parse | Held as `uncertain`, and acquisition refuses. The older `stopped` revision is not reused. |
| W17 | A claim root copied from a fixture "other host" (different machine ID) | `foreign-host`. Nothing is promoted. |
-| W18 | Own appends generated with the pinned `SessionManager` append path into a temp directory (never `SessionManager.open`, never a live file), including settings, model-change and compaction entries and delayed persistence after `message_end`. Also three load-time writes: a fixture file with a truncated last line (line 319), an empty file (lines 632–633), and an old-version file that migrates (line 677). And an engine append that lands between the check's file read and its `get_entries`. | No `session-drift` in any case. The baseline is taken after load. |
-| W19 | Engine list ahead of the disk before the first assistant message of a new session file; `get_entries` times out at the idle check | The first gives no drift. The second gives `uncertain` with admission closed. |
| G1 | Session path or claim root under `.pi/state/`, `~/.claude`, the data root, or named in a registration | `live-session-refused` at construction |
| G2 | A symlink inside the fixture root pointing at a live session file | `live-session-refused` at bind (real-path check) |
| G3 | A fixture path that is swapped for a live path after construction | Refused at bind |
#### 3. Pi adapter, incremental events and R3-1
-Pinned inputs for Pi 0.85.1, under
-`node_modules/@earendil-works/pi-coding-agent/`:
+**The Pi pin (lead decision 32).** Pi 0.85.1 is pinned by the
+`package-lock.json` integrity of `@earendil-works/pi-coding-agent`,
+`sha512-FGRN+OHbWaefBPGaTggAdLjrIHW+s2PzLyglz/5dfLzb9of7uuXMXYC0fJIeZTw+shS32o2cuQ9jF7YSDuL/oQ==`.
+`pi` runs `dist/bundle/cli.js` (the package's `bin`) and its chunks, not
+the `dist/core` and `dist/extensions` files listed below. At launch the
+controller checks that `package-lock.json` and npm's installed record
+(`node_modules/.package-lock.json`) both name 0.85.1 with that integrity,
+and otherwise refuses `engine-pin-mismatch`. That ties the install to the
+package through npm's record. It isn't a hash of the files on disk.
+
+The files below are the sources this brief cites for line numbers, hashed
+at `f2b9e622`, under `node_modules/@earendil-works/pi-coding-agent/`.
+Filbert's R3 review found that the bundle matches them on every point the
+brief relies on.
- `docs/rpc.md` (`15fcd26bee72777b373fd5f2edd77091a01cadd4de95e48b08422ced0552a28d`)
- `dist/modes/rpc/rpc-mode.js` (`e7e4724aa55c5aac73cf36793653b26736200e5c59d58373990fc31028f86477`)
@@ -628,7 +579,7 @@
message-end with `updateMode: replace`, and run-settled.
- Every event carries the execution incarnation and a sequence number
for that execution.
- - An unknown native event gets no client event until C-4. It is
+ - An unknown native event gets no client event. It is
recorded in controller evidence and counted, and the terminal shows
the count. It is never dropped silently and never passed through raw.
- `agent_settled` maps to run-settled, never to cohort termination.
@@ -704,37 +655,33 @@
that ties a run to a prompt. So CHAT-03 removes every other source of turns
and input, and doesn't guess. A binding needs a sealed engine:
- The controller builds the launch argv. Pi starts with `--no-extensions`,
- `--no-prompt-templates` and `--no-themes`, and loads extensions only by
- explicit `--extension` path (`dist/cli/args.js` lines 135–140, 167 and 170;
- `docs/usage.md` lines 224 and 233–236). With `--no-extensions`, Pi loads
- only the paths given on the command line and ignores settings and
- packages (`dist/core/resource-loader.js` lines 316–318). Each
- `--extension` must be a local path; npm and git sources refuse. Skills
- stay allowed: they expand text and start no turn (§4).
+ `--no-prompt-templates` and `--no-themes`, and with no `--extension`
+ argument (lead decision 31; `dist/cli/args.js` lines 135–140, 167 and
+ 170; `docs/usage.md` lines 224 and 233–236). With `--no-extensions`, Pi
+ loads only the paths given on the command line and ignores settings and
+ packages (`dist/core/resource-loader.js` lines 316–318), so no explicit
+ extension loads. Skills stay allowed: they expand text and start no turn
+ (§4).
- Pi also always loads its built-in extensions, whatever the flags
(`dist/main.js` line 439). Pi 0.85.1 has one, `llama.cpp`
(`dist/extensions/index.js`). It registers a provider and a `/llama`
command, and no event handler or input call (`llama/index.js` lines 37
- and 163). The registry lists it by hash like any other extension. A
- different built-in set at the pinned version is `engine-pin-mismatch`.
-- Every explicit extension is in the binding's extension registry, pinned
- by the sha256 of every file in its directory tree, with a review record
- saying it is input-silent. It never
- calls `sendUserMessage`, `sendMessage` or any prompt path, and it queues
- no input from any handler. Extensions that only consume input (an input
- handler that returns handled) or only raise dialogs are allowed, since
- neither starts a turn. In CHAT-03 the registry is the fixture registry.
-- An extension outside the registry, a hash mismatch, a non-local
- source, or an argv without the three `--no-*` flags refuses the binding
- with `unsealed-engine`. The goal
+ and 163). It is part of the pinned package (the Pi pin above), so it
+ is pinned with it.
+- Any `--extension` argument, or an argv without the three `--no-*`
+ flags, refuses the binding with `unsealed-engine`. Explicit extensions
+ aren't pinned or reviewed for binding: Rocko's R3 review showed that an
+ extension can import code outside any hashed tree. They come back with
+ goal in CHAT-06 (lead decision 31). The goal
extension starts turns, so a session that loads it can't bind in CHAT-03
- (Limit 11). CHAT-06 has to resolve that.
+ (Limit 11). Under lead decision 30, a seat bound to the Console runs
+ without goal, and goal continuation is a CHAT-06 item.
Under the seal, the Mosaic prompt in the slot is the only thing that can
start a run, so a run that follows its ack is its run. That exclusion is
the evidence basis for attributing by order, and the brief names it as the
-basis. The seal rests on review of extension source, so the controller
-also watches for signs that it failed.
+basis. The seal rests on the launch argv and the pinned Pi build, so the
+controller also watches for signs that it failed.
**Overlap signals.** Each of these means the seal failed, or Pi behaved
outside the pinned model:
@@ -748,7 +695,12 @@
message between them;
- O5: a non-empty `clear_queue`, a non-zero pending count, or a
`queue_update` the controller didn't cause. Mosaic never queues, so
- under the seal these are always empty;
+ under the seal these are always empty. Pi's own clear emits a
+ `queue_update` before the clear's response (`agent-session.js` line
+ 1201, `rpc-mode.js` line 334). A `queue_update` read after the
+ controller writes `clear_queue` and before that response, with empty
+ `steering` and `followUp`, is the clear's own, and O5 counts it as
+ caused (N25);
- O6: a second user `message_start` in one run.
On any overlap signal, admission closes, the binding goes to `uncertain`
@@ -920,8 +872,8 @@
it. The README says that `working` is attributed through the seal.
The fake engine models the overlap (N10). Fixtures that break the seal
-don't load a real unregistered extension, since the binding would refuse
-it. The fake simulates the extension's effect instead.
+don't load a real extension, since the binding would refuse it. The fake
+simulates the extension's effect instead.
| # | Fixture | Expected |
|---|---|---|
@@ -929,17 +881,17 @@
| N2 | N1 with `abort` sent first (mutant) | The fake runs the external item, so the test fails. This is the ordering guard. |
| N3 | Fence while the Mosaic prompt is in preflight; preflight then errors, and no run exists | Receipt `failed` with the native error. Stop outcome No run: no `turnState: interrupted`, stop `uncertain`, prompts refuse until C-5, force stop available. |
| N4 | Fence while the Mosaic prompt is in preflight; the ack arrives after the first `abort`, and a run starts | Classified by rule 4 as a run. Clear, then abort again. The run ends `aborted`: receipt `failed`, reason `interrupted`; stop outcome Interrupted; `reconciled` after a post-settle empty clear. |
-| N5 | Mosaic prompt acked but handled by a registered input extension, with no run | `get_state` shows no run, and no `agent_start` or `agent_settled` arrived. Receipt `delivery-unknown`, reason `handled-without-run`. Nothing is resent. |
+| N5 | Mosaic prompt acked but handled by an input handler the fake simulates, with no run | `get_state` shows no run, and no `agent_start` or `agent_settled` arrived. Receipt `delivery-unknown`, reason `handled-without-run`. Nothing is resent. |
| N6 | During an active Mosaic run, Interrupt; the fake simulates an unsealed extension that queues between `clear_queue` and `abort` | The `queue_update` is O5. `abort` continues the queued item inside the same run (`agent-session.js` lines 787–810), so its user `message_start` is O6. `run-overlap`, stop outcome Unknown, stop `uncertain`, admission closed, force stop is the way on. |
| N7 | `clear_queue` times out during an active run | No `abort` sent. Stop outcome Unknown. `nativeQueue: unknown`, stop `uncertain`, admission closed. A confirmed force stop still ends the cohort (K1). |
| N8 | Filbert's order one: during Mosaic preflight (paused at the line-915 await), the fake simulates an extension prompt that starts a run first. The wire shows the Mosaic ack, the other run's `agent_start` and user `message_start`, then the losing Mosaic prompt's `agent_settled` | The other run's user `message_start` arrives before any signal, so the receipt goes to `working`. The losing settle is O3: the receipt stays `working`, shown as outcome unknown, never `finished` or `failed`. Binding `uncertain`, admission closed, nothing resent. The fixture pins the misattribution window (Limit 11). |
| N9 | The item's run started before the fence and ends `aborted` | Receipt `failed`, reason `interrupted`, not `delivery-unknown`. Stop outcome Interrupted (`turnState: interrupted`). |
-| N10 | Fake conformance | The fake throws on a prompt while streaming and acks before running. It emits `agent_settled` from a `finally`, and it can hold several `agent_start` … `agent_end` pairs in one run. It pauses at the preflight awaits (lines 843, 895, 915) between the line-860 check and the run start. A colliding prompt is acked, its throw is swallowed, it settles with no `agent_start`, and it leaves `isStreaming` false while the other run goes on. `clearQueue` doesn't return agent-level custom messages, and `nextTurn` messages survive a clear. A fake that queues a Mosaic prompt, or can't produce the overlap, fails the test. |
+| N10 | Fake conformance | The fake throws on a prompt while streaming and acks before running. It emits `agent_settled` from a `finally`, and it can hold several `agent_start` … `agent_end` pairs in one run. It pauses at the preflight awaits (lines 843, 895, 915) between the line-860 check and the run start. A colliding prompt is acked, its throw is swallowed, it settles with no `agent_start`, and it leaves `isStreaming` false while the other run goes on. `clearQueue` emits an empty `queue_update` before its response, doesn't return agent-level custom messages, and leaves `nextTurn` messages in place. A fake that queues a Mosaic prompt, or can't produce the overlap, fails the test. |
| N11 | Ack; then the run ends in a failure message before any user `message_start`; `agent_settled` arrives; the stream is complete and ordered | Receipt `delivery-unknown`, reason `ack-without-start`, never `failed`. The session file gains no user entry. The client shows outcome unknown and no resend offer. The same schedule with the failure line unparseable gives `delivery-unknown` / `transport-unknown`. |
| N12 | During an active run, the fake simulates an extension that queues after the final empty clear | The stop records the clear's `observedAt`. The queued item's run starts with no slot held: O1, `run-overlap`, binding `uncertain`, admission closed. It is not part of the stopped turn's proof. |
| N13 | The fake emits an `agent_start` with no slot held (a simulated turn-starting extension) | O1: binding `uncertain`, `run-overlap`, admission closed. A prompt sent afterwards refuses with zero engine bytes. |
| N14 | The run completes normally (`stopReason: stop`) while `clear_queue` is in flight; `abort` then reaches an idle engine; all clears empty | Receipt `finished`, effects kept. Stop outcome Completed first. No `turnState: interrupted`, no `reconciled`, stop `uncertain`, prompts refuse until C-5, force stop available. |
-| N15 | Fence while the Mosaic prompt is in preflight; after the first clear and abort, a registered input extension handles it and Pi acks with no run | Receipt `delivery-unknown`, reason `handled-without-run`. No second clear or abort is sent. Stop outcome No run: `uncertain` until C-5. |
+| N15 | Fence while the Mosaic prompt is in preflight; after the first clear and abort, an input handler the fake simulates handles it and Pi acks with no run | Receipt `delivery-unknown`, reason `handled-without-run`. No second clear or abort is sent. Stop outcome No run: `uncertain` until C-5. |
| N16 | Interrupt with no slot held and no visible run | Refused `no-turn`. No stop record, no engine bytes. Admission is open afterwards, and the next prompt is admitted. |
| N17 | The run fails on its own (`stopReason: error`) during the clear and abort exchange | Receipt `failed`. Stop outcome Failed on its own: `uncertain` until C-5. |
| N18 | The run settles with no final assistant `message_end`, or a line is lost during the exchange | Receipt `working`, shown as outcome unknown. A lost line after `working` never moves it back to `delivery-unknown`. A lost line before `working` gives `delivery-unknown` / `transport-unknown`. Stop outcome Unknown: `uncertain`. |
@@ -948,7 +900,8 @@
| N21 | The losing prompt's `agent_settled` is delayed until after the Mosaic receipt settled `finished` | O2 on arrival: binding `uncertain`, `run-overlap`, admission closed. The receipt stays `finished`, since receipts are monotonic, and evidence records the overlap against it (Limit 11). |
| N22 | During an active run, the fake simulates an extension `sendMessage` queued straight into the agent; Interrupt | The clear returns empty and the item is gone. Evidence records agent-level queues as unobservable, with the seal as the basis, and never as "nothing removed". This fixture pins the Limit 7 gap: no signal fires. |
| N23 | The fake simulates a `nextTurn` message queued before an Interrupt | It survives clear and abort and attaches to the next Mosaic prompt. No signal fires. The fixture pins the Limit 7 gap. |
-| N24 | Launch with an extension outside the registry, a registered extension whose hash differs, a non-local extension source (npm or git), or an argv missing `--no-extensions`, `--no-prompt-templates` or `--no-themes` | `unsealed-engine` at bind, for each case. No engine is started. |
+| N24 | Launch with any `--extension` argument (a local path, an npm or git source), or an argv missing `--no-extensions`, `--no-prompt-templates` or `--no-themes` | `unsealed-engine` at bind, for each case. No engine is started. |
+| N25 | An ordinary Interrupt of an active Mosaic run: the fake emits Pi's empty `queue_update` before each `clear_queue` response, and the run ends `aborted` | No overlap signal. Stop outcome Interrupted, `reconciled` after the post-settle empty clear, admission reopens. A non-empty `queue_update` in the same window is O5. |
#### 4. The slash path (carry-forward 3)
@@ -1006,7 +959,9 @@
#### 5. Control races (Rocko)
Each race uses the fake engine's pause points to land at an exact step.
-Every race asserts the engine bytes, the receipts and the events.
+Every race asserts the engine bytes, the receipts and the events. H5–H8
+run in I4 on Claude permission requests. Pi dialogs are cut (lead
+decision 30).
| # | Race | Expected |
|---|---|---|
@@ -1019,7 +974,7 @@
| H7 | Conflicting approval answers with the same request ID | The second is refused |
| H8 | An approval answer after a native timeout or cancel | Refused. The state comes from native evidence. |
| H9 | Interrupt racing a prompt's dispatch | Fence set before the write: the item is `dispatch-refused` with no engine bytes, and with no run active the Interrupt then refuses `no-turn` (no stop record). Fence set after the write began: §3 rules 4–6. |
-| H10 | Interrupt and force stop at the same time | One stop chain. Force stop supersedes (CHAT-01 lines 319–322). |
+| H10 | Interrupt and force stop at the same time; again with an overlap signal or a revocation closing admission before the Interrupt finds no slot and refuses `no-turn` | One stop chain. Force stop supersedes (CHAT-01 lines 319–322). The `no-turn` cleanup lifts only its own fence: admission stays closed under the surviving reason. |
| H11 | The controller disconnects mid-turn | Work continues and the claim is unchanged. Control stays with the disconnected connection until an observer takes over explicitly. Nothing happens automatically. |
| H12 | Exact retry of a prompt after reconnecting to the same controller incarnation | The same receipt. No second dispatch. |
| H13 | Retry with the same request ID and different text | Refused |
@@ -1167,25 +1122,12 @@
| K17 | Two launcher calls with one eligibility record | One launch. The other refuses, and no second engine starts. |
| K18 | The leaf changes after eligibility, before launch | Launch refused. The reservation stays until released with proof. |
-#### 7. Native approval dialogs (I2 after C-2; Claude in I4)
+#### 7. Claude permission requests (I4)
-- Pi has no built-in permission system. Approvals come from extensions
- through `extension_ui_request` (`rpc.md` lines 1186–1210). Repository seats
- load no extension that raises dialogs today, so I2 tests with a fixture
- extension.
-- CHAT-01 lines 239–242 and 292 block non-permission forms until CHAT-03D
- exists, and C-2 is that companion. After C-2:
- - `confirm` and `select` render the exact native labels and answer with
- the exact request ID;
- - `input` and `editor` stay unsupported and show as disabled, with a
- reason;
- - nothing is answered on the user's behalf, and a generic "yes" is not
- accepted.
-- Cancel is enabled only where C-2 proves it can't mean allow. A Pi select
- cancel does not inherit confirm's meaning (lines 293–295).
-- Pending dialogs don't survive a Pi restart (CHAT-00 line 69). After a
- takeover, the controller reprojects each dialog: a new projection ID for
- the same decision (lines 282–287).
+- Pi native dialogs are cut from CHAT-03 (lead decision 30). CHAT-01
+ lines 239–242 and 292 block non-permission forms until CHAT-03D exists,
+ so a Pi `extension_ui_request` is shown disabled, with a reason, and is
+ never answered.
- **Claude, in I4 after B1.** `can_use_tool` maps to allow-once and deny.
Choices that widen permissions are disabled, and there is no invented
cancel (lines 294–295). Pending permission requests are re-read from
@@ -1194,12 +1136,7 @@
| # | Fixture | Expected |
|---|---|---|
-| P1 | `confirm` dialog answered by the controller | One `extension_ui_response` with the exact ID and value |
-| P2 | `select` with labels containing markup and bidi controls | Exact labels, rendered inert |
-| P3 | `input` or `editor` dialog | Disabled, with a reason. No response is sent. |
-| P4 | A native timeout before the answer | `uncertain`, then resolved from native evidence. A late answer is refused (H8). |
-| P5 | Takeover while a dialog is pending | New projection ID and the old one superseded. One native answer. |
-| P6 | Interrupt while a dialog is pending | The approval becomes `uncertain`, not denied (lines 297–299) |
+| P3 | A Pi `confirm`, `select`, `input` or `editor` dialog | Disabled, with a reason. No response is sent. |
| P7 | A Claude permission request (I4, recorded fixture) | Allow-once and deny only. Widening choices disabled. |
#### 8. Claude: B1 and the catalogue (carry-forward 2)
@@ -1284,7 +1221,7 @@
| E2 | U+2028 and U+2029 inside JSON strings, and CRLF | Each parsed as one record |
| E3 | Multipart final, two blocks, null request correlation, duplicate delivery | The CHAT-01 cases at lines 110–112 |
| E4 | A page read just after a `message_end` but before its entry is persisted, then a subscription; again with a gap or a new epoch | Replay is advertised unavailable. The client shows a reconcile marker at the seam and re-reads the page after `agent_settled`. After that, each message appears exactly once. A gap or new epoch also reconciles. If the author shows an atomic cut from the source, overlap is deduplicated instead, and the fixture pins that case. |
-| E5 | An unknown native event | No client event until C-4. Controller evidence records its type and byte count, and the terminal count goes up. No record fails the schema. |
+| E5 | An unknown native event | No client event. Controller evidence records its type and byte count, and the terminal count goes up. No record fails the schema. |
| E6 | A tool result delayed across a pause and a reconnect | Reconciled without a manual refresh |
| E7 | The mediated terminal as observer, then as controller | Renders the same stream as the library client, and submits only as controller |
@@ -1338,8 +1275,6 @@
22. a unit with a different invocation ID is signalled;
23. a restart during force stop records the kill phase as done;
24. an eligibility record can be used twice;
- 25. a disk entry ID missing from `get_entries` is ignored (and the
- reverse: an own settings or compaction entry counts as drift);
26. a non-empty `clear_queue` reaches `reconciled`;
27. the pending slot's in-flight preflight is abandoned at the fence
instead of awaited, so a late ack's run is never aborted (N4);
@@ -1363,14 +1298,15 @@
the slot's settle (N8, N19);
36. an `agent_start` or `agent_settled` with no slot held is ignored, so
admission stays open (N13, N21);
- 37. the binding launches without the `--no-*` flags, with an
- unregistered or non-local extension, or without checking the
- registry hashes (N24);
+ 37. the binding launches without the `--no-*` flags, or with an
+ `--extension` argument (N24);
38. an `ack-without-start` run settles `failed` (N11);
39. an empty clear is recorded as proof that no external input was
removed, instead of naming the seal as the basis (N22).
- I4 adds two more: the pin check is skipped, and the decoy file is listed.
+ Mutant 25 (drift) left with the drift check for CHAT-07, and its number
+ isn't reused. I4 adds two more: the pin check is skipped, and the decoy
+ file is listed.
#### 11. Choices in this brief, for review
@@ -1378,30 +1314,28 @@
|---|---|
| No broker queue; a busy engine refuses `busy` | No queued follow-ups until CHAT-04, and no native queueing of mediated input |
| No `streamingBehavior`, `steer` or `follow_up`, and one pending slot | Same as above. On pinned Pi a Mosaic prompt then never enters a native queue (lines 860–863). It can still lose a race in preflight to another run (lines 860–949), which the seal and the overlap signals handle (§3). |
-| Sealed engine: `--no-extensions` and a registry of hash-pinned, input-silent extensions (`unsealed-engine`) | Without run IDs, only excluding every other source of turns lets a run be attributed to the slot by order. The cost is that no session loading goal, or any turn-starting extension, can bind in CHAT-03 (Limit 11). The seal rests on reviewing extension source, so the overlap signals watch for its failure. |
+| Sealed engine: `--no-extensions` and no `--extension` (`unsealed-engine`; lead decision 31) | Without run IDs, only excluding every other source of turns lets a run be attributed to the slot by order. The cost is that no explicit extension loads in CHAT-03, goal included (Limit 11). They come back in CHAT-06. The overlap signals watch for a seal failure. |
| Overlap signals close admission and make the binding `uncertain` (`run-overlap`) | One overlap needs a force stop to move on, and never produces a guessed receipt. Some seal failures give no signal (Limits 7 and 11). |
| The slot settles only on evidence tied to its own prompt, never from `clear_queue`, idle or a settle alone | An ack with no run, and a run that fails before its user message, stay `delivery-unknown`: no "unsent" state and no safe-resend button |
| Receipt settlement and the stop outcome are classified separately (§3 rules 4 and 5) | A turn that finishes during an Interrupt is reported as finished, not interrupted. The cost is that the stop can't reconcile: until C-5 it stays `uncertain` and needs a force stop, even though nothing was lost. |
| Interrupt with nothing running refuses `no-turn` | Narrows the completion race to the clear and abort window. The controller can refuse just before it reads a new run's `agent_start`; the run keeps going and the actor retries. |
-| C-1 withdrawn and restated for CHAT-06 | The clear can't account for everything an abort removes (F2), so no `turnProof` field claims it does. Under the seal a non-empty clear is an overlap signal, and it needs a force stop. |
| Poison the pipe on an unknown write | One transport glitch needs a force stop, with no guessing about half-written lines |
-| Drift by comparing the file with `get_entries` at idle | Catches any foreign entry with a new ID, but only at the next idle check. An in-place rewrite with the same IDs and inode isn't caught. |
| Live-session guard by path | Fixture-only is enforced in code. The guard needs a reviewed change to lift at CHAT-07. |
| No default claim root | Nothing live can use I1 before the CHAT-07 data-map decision |
| Cohort is an `engine` child cgroup in a delegated user scope, held by a shim, with a cgroup namespace against migration | Depends on `systemd-run --user`, delegation, `cgroup.freeze`, `cgroup.kill` and user namespaces. Without them, or if K13 fails, stops end at `uncertain`. The verifier is fixture-grade until B3/B4. |
| One actor, `local-operator`, guarded only by socket-directory mode | Same-uid agents are not kept out (Limit 1) |
| Dedup lasts one controller incarnation, fenced by a token (deviation V-1, accepted with limits in lead decision 25) | A retry across a restart refuses `stale-incarnation` instead of returning the receipt. The client shows "outcome unknown, check the transcript" and never resends. CHAT-04's durable receipts must restore the contract behavior. |
-| Four increments, with I3/I4 gated on B1 | CHAT-03 can close Pi-only, with that outcome recorded |
+| Three increments, with I3/I4 gated on B1 | CHAT-03 can close Pi-only, with that outcome recorded |
| Claude catalogue from launcher receipts only | A Claude session started outside the launcher never appears |
### Carry-forwards
| # | Sage's item | Where | Acceptance evidence |
|---|---|---|---|
-| 1 | D1 writer-claim record and R3-1 (lead decisions item 8) | §1, §2, §3 | W1–W20, G1–G3, N1–N24 and H18–H23 pass. Mutants 2, 3, 12–20 and 25–39 fail tests. R3-1 binds only sealed engines, settles each receipt only on evidence tied to its own prompt (§3 rule 4), and classifies the stop separately (rule 5). Any overlap signal closes admission. Only an interrupted turn reconciles. The other stop outcomes leave the stop `uncertain` until C-5 lands or is carried (Gate). |
+| 1 | D1 writer-claim record and R3-1 (lead decisions item 8) | §1, §2, §3 | W1–W9, W11–W17, W20, G1–G3, N1–N25 and H18–H23 pass. Mutants 2, 3, 12–20 and 26–39 fail tests. R3-1 binds only sealed engines, settles each receipt only on evidence tied to its own prompt (§3 rule 4), and classifies the stop separately (rule 5). Any overlap signal closes admission. Only an interrupted turn reconciles. The other stop outcomes leave the stop `uncertain` until C-5 lands in CHAT-04 (lead decision 30). |
| 2 | The Claude catalogue after B1 | §8; increments I3 and I4 | The B1 packet (parts 1–5), approved by Filbert and Rocko. L1–L7 pass. |
| 3 | The board send that can become a Pi slash command | §4 | S1–S7 pass. Mutants 5 and 10 fail tests. The DEFERRED entry stays open for seats still on tmux. |
-| 4 | The CHAT-01/01C contracts, by path and hash | "Contracts implemented" | The hash table matches `sha256sum` at approval. C-2 to C-5 are separate reviewed items, none folded in. C-1 is withdrawn and restated for CHAT-06. Deviation V-1 is accepted with limits (lead decision 25): H21 and H23 pass, the client never resends, and V-1's closure is a required CHAT-04 item (lead decision 25). CHAT-04's brief must list it. |
+| 4 | The CHAT-01/01C contracts, by path and hash | "Contracts implemented" | The hash table matches `sha256sum` at approval. C-3 is a separate reviewed item if B1 needs it, and C-5 moves to CHAT-04. C-1, C-2 and C-4 are cut (lead decision 30). Deviation V-1 is accepted with limits (lead decision 25): H21 and H23 pass, the client never resends, and V-1's closure is a required CHAT-04 item (lead decision 25). CHAT-04's brief must list it. |
| 5 | Path authority | "Files owned" | The overlap table. Every changed path in each candidate is inside "Files owned". |
| 6 | The source author | "Owner and reviewer" | `<slot>`, named by Darkwing after this brief is approved |
@@ -1421,10 +1355,9 @@
effects stay `uncertain`. A same-uid process outside the cohort can
still move members out. The verifier is the fixture registry. No CHAT-03
stop proof is live authority until B3/B4 and B2.
-5. **Sole writer.** Pinned Pi gives no stream ID for its own appends. The
- `get_entries` comparison catches a foreign entry at the next idle
- check, not during a turn, and misses an in-place rewrite that keeps IDs
- and inode. The claim binds only controllers that use it. Real sessions
+5. **Sole writer.** CHAT-03 has no session drift check; it moves to
+ CHAT-07 (lead decision 30). The claim binds only controllers that use
+ it. Real sessions
stay off-limits through the live-session guard.
6. **Durability.** W5 kills processes at each barrier. Real power loss
isn't tested; the fsync-then-`link()` order is the stated basis.
@@ -1450,20 +1383,21 @@
has to check the transcript. CHAT-04's durable receipts close this.
11. **Sealed engines only.** Pinned Pi ties no run to a prompt, so
CHAT-03 attributes runs by order under the seal. A session that loads
- the goal extension, or any extension that starts turns or queues
- input, can't bind (`unsealed-engine`). The five Pi repository seats
+ any explicit extension, goal included, can't bind (`unsealed-engine`,
+ lead decision 31). The five Pi repository seats
(Darkwing, Dewey, Filbert, Researcher, Sage) load goal through
- `scripts/agent-host-dev.sh` line 137, so none of them could move to
- the mediated path as it stands. The seal
- rests on reviewing extension source. A failure that the overlap
- signals don't catch goes undetected: an input call from an extension
- that passed review and queues straight into the agent (Limit 7).
- Other failures are caught late. Another run's user `message_start`
- that arrives before any signal puts the item in `working`, and the
- signal that follows can only mark it outcome unknown (N8). A spurious
- settle delayed until after the receipt settled signals only once the
- receipt is final (N21). CHAT-06 owns turn-starting extensions, which need
- run-to-prompt evidence from Pi (restated C-1).
+ `scripts/agent-host-dev.sh` line 137, so none of them can bind with
+ goal loaded. Under lead decision 30, a seat bound to the Console runs
+ without goal, and goal continuation is a CHAT-06 item. The seal
+ rests on the launch argv and the pinned Pi build. A failure there
+ that the overlap signals don't catch goes undetected if it queues
+ straight into the agent (Limit 7). Other failures are caught late.
+ Another run's user `message_start` that arrives before any signal puts
+ the item in `working`, and the signal that follows can only mark it
+ outcome unknown (N8). A spurious settle delayed until after the
+ receipt settled signals only once the receipt is final (N21).
+ CHAT-06 owns explicit and turn-starting extensions, which need
+ run-to-prompt evidence from Pi.
### Out of scope
@@ -1478,8 +1412,12 @@
- The Claude adapter and history, unless B1 passes.
- Filbert's idsDigest note, which stays in
`agents/dewey/work/chat-02/FOLLOWUPS.md`.
-- Turn-starting extensions, goal among them, and the restated C-1
- (CHAT-06; Limit 11).
+- Explicit extensions, goal continuation among them, and any pinning of
+ them (CHAT-06; Limit 11; lead decisions 30 and 31).
+- Cut by lead decision 30: Pi native dialogs and the CHAT-03D companion
+ (C-2), the unknown-event type (C-4) and the removed-input `turnProof`
+ (C-1). C-5 moves to CHAT-04, and the idle session drift check to
+ CHAT-07.
### Gate
@@ -1495,18 +1433,9 @@
- Sage commits. A push needs Jason's word.
- **Contract items.** Each is a separate reviewed change to the CHAT-01
files, never folded into an increment.
- - C-1 is withdrawn from CHAT-03. Sage records the restated C-1 and
- turn-starting extensions (Limit 11) as CHAT-06 items before CHAT-03
- is done. Under the seal, a non-empty clear leaves the stop `uncertain`
- in every increment.
- - C-2 is approved before I2 starts, or declined (see "What ships").
- C-3, if B1 shows a gap, is approved before I4 starts.
- - C-4 is optional. Without it the interim rule in "Contracts implemented"
- holds.
- - C-5 is approved and adopted through I1b, or Sage records that it moves
- to CHAT-04 with the interim rule (every stop outcome except Interrupted
- stays `uncertain`). Either one is needed before CHAT-03 is done. I1
- ships with the interim rule.
+ - C-5 moves to CHAT-04 (lead decision 30). CHAT-03 ships the interim
+ rule: every stop outcome except Interrupted stays `uncertain`.
- Deviation V-1: accepted with limits (lead decision 25). I1 is approved
only with H21 and H23 passing and the client's "outcome unknown, check
the transcript" display. V-1 stays open until CHAT-04's durable
@@ -1515,11 +1444,6 @@
- **I3.** Jason's go comes before any model call.
- **CHAT-03 done.** All of these:
- I1 is approved and committed;
- - I2 is approved and committed, or C-2 is recorded as declined;
- - I1b is approved and committed, or C-5 is carried to CHAT-04 with its
- interim rule;
- - the CHAT-06 items (restated C-1, turn-starting extensions) are
- recorded;
- I4 is approved and committed, or B1 is recorded as not passed (the
triggers are in "What ships"), with Claude still refusing and CHAT-06
blocked.