gizmoduck: row 271 design: duckworth-0170 B1-B4 folded (cancel at arrival for routed messages, claim refused while clearing, op namespace both ways, attempt key on start stages) and N1-N8 answered; cancel recorded as a departure from D7:263-264

This commit is contained in:
jason.woltje committed 2026-10-22 12:01:13 -05:00
1 parent 8a9db2159a
commit 549b5a0ebd
1 file changed
+63 -5
+63 -5
View File
@@ -165,14 +165,72 @@ alone can't show that no child is running.
code. Nothing is retired, and the old session goes back to `active` with its messages still
cancelled.
Human cancel of an open clear is out of 271 (sage-2826 Q3). D7 defines no cancel, and recovery
resumes the same operation. Sage is opening a DEFERRED entry for it.
Human cancel of an open clear is out of 271 (sage-2826 Q3). This is a **departure from D7**
(duckworth-0170 N1). D7's last paragraph (t1-sessions.md:263-264) does define cancel: before
retirement it stops the replacement, and after retirement it cancels startup and leaves both
identities intact. sage-2826 said D7 defines none, and that premise is wrong. I've asked Sage to
re-rule (gizmoduck-0271). Until then, 271 builds no cancel and the DEFERRED entry carries the D7
text.
### Early look d2 folds (duckworth-0170, 98dc69f0c)
- **B1, messages routed by `#sessionFor` (broker.mjs:722-748).** A role reply chain or a spec
notice can resolve to a clearing or retired session with no state check. Rule: **cancel with a
receipt at arrival.** The message is inserted (old results stay as evidence, D7 step 4) and its
`message_cancels` row goes in the same transaction, so it is never delivered and never pending.
The send's answer gains `cancelled: "session-clearing"` or `"session-retired"`, an additive key.
- Refusing was the other option. It would lose a teammate's RESULT outright: the runner only
logs a failed reply (harness/src/runner.mjs:575-580).
- Routing to the successor would replay old input, which D7 forbids.
- A spec notice resolves through `#defaultSession`. During a clear of the default session that
is still the old session, so the notice is cancelled. After retirement the default is the
successor, and a new notice is new input that goes there.
- `threadsState` reports `retired`, and `threadPass` starts neither a `clearing` nor a
`retired` session.
- **B2, a claim racing a no-holder clear.** role.claim's thread path (broker.mjs:4827-4845)
refuses `session-clearing` for a session with an open op and `session-retired` for a retired
one. The runner gets refused and exits 21. Retire rechecks in its own transaction that no
`claim` is current for the session, unless it's the proven-ended run's. The no-run proof also
takes `lockSessionWorkspace`, which a spawned run holds before it claims.
- **B3, op namespace.** `sessions` has `UNIQUE(thread, op)` (schema.sql:263), and the successor
takes the clear op. Both directions refuse `session-op-conflict` at accept, before any stop:
- `session.clear` refuses an op already in `sessions` or `session_ops` for the thread.
- `session.create` (broker.mjs:5859-5872) refuses an op already in `session_ops`, instead of
answering the successor row.
- **B4** is in the schema step below (the `attempt` key).
- **N2, name cap.** `name()` caps names at 200 characters (broker.mjs:323). The successor base is
the old name cut to `200 - len("-N")` characters, then `-N`, computed and bound at accept. Test:
a 200-character name clears to a 200-character successor name.
- **N3, batch.** Drain waits for the runner's whole receive batch. Messages already delivered
are a turn in progress, and the runner runs each of them before its next receive (:543-590).
Only undelivered messages are cancelled. This is a residual: a batch of k delivered messages
delays the drain by k turns.
- **N4, D7 step 1 rechecks.** Retire rechecks owner, write grant and halt in its transaction, and
refuses to `failed` with the code. External effects: the old run's endRun record is the stop
evidence the op joins (its run and generation are on `drained`). No other effect is reconciled
in 271.
- **N5, claim end.** Retire ends the old claim only if that run's `claim` is still the latest
`thread_claims` row for the session. If endRun already ended it, nothing is written twice. Test:
retire after endRun, with exactly one end row.
- **N6, pid 1.** `--as-pid-1` is in `NAMESPACES` (seat/src/sandbox.mjs:68), and `sandboxArgv`
uses it (:168) for the runner command that session.mjs:302 builds. At build I'll confirm that
thread runs reach session.mjs:302. Teardown on exit is kernel behaviour, and the test stays.
- **N7**: Duckworth checks the mos-1315 list line by line at the code round.
- **N8**: row 206 is approved at 3l→3m, so if it lands first, 271 is 3m→3n (as mos-1315 item 4
says).
Tests gained: B1 a cancel at arrival (reply chain and spec notice) for clearing and retired; B2
a claim refused while clearing, and retire refusing a live claim; B3 both op directions; B4 a
second `start-held` appended; N2; N5.
### Schema step (label by landing order, mos-1315 item 4)
- `session_ops(seq, at, business, thread, session, op, stage, successor, successor_name, run,
generation, digest, refusal)`:
- append-only, with `UNIQUE(thread, op, stage)` and a CHECK on `stage`
- `session_ops(seq, at, business, thread, session, op, stage, attempt, successor, successor_name,
run, generation, digest, refusal)`:
- append-only, with `UNIQUE(thread, op, stage, attempt)` and a CHECK on `stage`
- `attempt` is 0 for every stage up to `retired`, and the launcher's start attempt (1, 2, ...)
for `start-held`, `start-started` and `start-refused`, so D7's retryable start can append
again (duckworth-0170 B4)
- a trigger refuses a second open op per session
- the envelope comes from the latest stage, and `revision` is the count of this session's rows
- `message_cancels(seq, at, message UNIQUE, session, op)`, append-only.