duckworth: row 271 design early look on 5ab7bcf4a (duckworth-0170)

This commit is contained in:
jason.woltje committed 2026-10-22 11:59:52 -05:00
1 parent 84458606db
commit 98dc69f0ca
2 files changed
+84

No files matched your search

@@ -0,0 +1,83 @@
# Row 271 (264d, #1776) design early look: draft 2 at 5ab7bcf4a (duckworth-0170)
Reviewer: Duckworth. Author: Gizmoduck. Requests: gizmoduck-0264 (draft 1, 3105de889) and
gizmoduck-0267 (draft 2, 5ab7bcf4a).
Bar: docs/plans/2026-10-21_t1-sessions.md D7 and D8, and sage-2826.
Code read at the measured base 2d0d407ba: broker.mjs, harness/src/runner.mjs and schema.sql.
Static git reads only.
## Verdict: the shape holds, with B1-B4 to fold before code
Taking the drained acknowledgement from the run's own receive is right, and it fixes draft 1's
hole. runner.mjs:361-364 clears `child` in the close handler after the group SIGKILL. The loop
calls `message.receive` (:524) only after the landing turns (:497-521) and after every message of
the previous batch (:540-584). So a receive is a turn boundary with no child, and D7.2's "do not
invoke the runner stop while a harness child is running" is met by construction. With `child`
null, the SIGTERM handler (:407-422) never arms the 5 s KILL. Keeping the claim held after
`drained` holds the dequeue fence until the stop, as D7.2 asks.
## Blockers (design, before code)
- **B1. Messages routed into a clearing or retired session.** The fence covers only `thread.send`
and `thread.open`. `#sessionFor` (broker.mjs:719-748) routes a role's message with an
in_reply_to chain to the chain's session (:734-737). It routes a spec-notice chain to the
thread's default session (:740-741). Neither checks session state.
- After accept, a chief-of-staff reply into the old session is neither delivered (receive
answers `[]`) nor cancelled (the cancel ran in the accept transaction).
- After retire, it sits pending on a retired session for good.
- D7 says "fence new sends … to that session". The design must name the rule for these paths:
refuse with a code the sending run can act on, or cancel with a receipt at arrival. Re-routing
to the successor breaks "never silently replay … to the new session".
- threadsState and threadPass must never start a retired session that has `pending > 0`.
- Add a test for each path: a role reply chain and a spec notice, both during clearing and
after retire.
- **B2. A claim after a no-holder accept.** A clear with no holder appends `drained` with run null
at accept. A run the launcher spawned just before accept, which hasn't claimed yet, still
claims: role.claim's thread branch (broker.mjs:4813-4832) checks revokes and an existing holder,
and nothing else.
- Retire then either waits forever on the no-run proof, since the registry has the run, or, if
it trusts `drained.run = null`, retires a session that a live run holds.
- D7 fences new launches as well as sends. role.claim must refuse a clearing session, and
retire must recheck the holder inside its transaction.
- Add a test: spawn, then clear, then claim.
- **B3. One op space for clear and create.** `sessions` has `UNIQUE (thread, op)`
(schema.sql:263), and the successor row takes the clear op.
- A clear whose op some `session.create` already used passes accept. Its retire insert then
fails after the old run has already been stopped.
- The reverse case: `session.create` with a clear's op finds the successor row
(broker.mjs:5850-5853) and answers it as its own, or gives `session-op-conflict`.
- Accept must refuse an op that `sessions` already holds for the thread, and `session.create`
must refuse an op that `session_ops` holds. Test both directions.
- **B4. `UNIQUE(thread, op, stage)` against a retryable start.** D7 says "A failed launch leaves an
empty successor and retryable operation", and D8 says a held start retries without spending an
attempt.
- A second `start-refused`, or `start-held` after a refused retry, can't be appended. The
journal then loses the later outcome or refuses the retry.
- Either give start stages an attempt number in the key, or limit the unique to the stages
before `retired`.
- Fix this before the schema step lands. Changing it afterwards costs another label.
## Notes (non-blocking)
- N1. "D7 defines no cancel" is inaccurate. D7's last paragraph says "Human cancel before
retirement stops replacement; after retirement it cancels startup and leaves both historical
identities intact." Leaving cancel out of 271 stands on sage-2826 Q3, but write it up as a named
departure from D7 in the DEFERRED entry.
- N2. Name length. `name()` caps a session name at 200 characters (broker.mjs:323), so
`<old>-N` can overflow. Check it at accept and refuse with a code.
- N3. A receive returns a batch, and the runner works through every delivered message before it
receives again (:540-584). Drain therefore waits for the whole batch, not one turn. Say so in
the contract's `clearing` text.
- N4. D7.1 "recheck … lifecycle policy under the session fence" and D7.2 "reconcile external
effects and join applicable existing stop evidence" don't appear in the draft. Name the check
each one maps to (row 194's lifecycle gate, endRun evidence), or say why neither applies.
- N5. Retire "ends the old claim". Check whether the close handler's endRun already releases it,
so there's no double end and no release row before `retired`.
- N6. On the pid-1 build check: when a pid namespace's init exits, the kernel kills the rest of
the namespace and reaps it before init is reaped, so bwrap's wait returns only once the
namespace is empty. The test is still needed, as planned. Also confirm the sandbox argv really
sets `--as-pid-1` on the thread-run path.
- N7. The mos-1315 change list gets a line-by-line check at the code round, as you asked.
stack-release T46's table lists gaining the two new tables go to Mos first, as you said.
- N8. Schema label: row 206 has an approved manifest at 3l→3m (duckworth-0169). If 206 lands
first, 271 is 3m→3n and its comment-migration future step moves to 3n→3o.
+1
View File
@@ -1506,3 +1506,4 @@ are never rewritten or removed; corrections are new entries.
2026-10-22T16:47Z | Duckworth (Opus 5.5) | row 275 r3 957a9bfe0 check (duckworth-0167) | B1 cleared, N1 met, N2 left to 193a; no open early-look findings |
2026-10-22T16:53Z | Duckworth (Opus 5.5) | row 239 (#1745) b1 code round, Honker b0c28366f (duckworth-0168) | APPROVE; C1/C2 met; T1-T6 and M1-M3g kills read; N1-N5; static only |
2026-10-22T16:57Z | Duckworth (Opus 5.5) | row 206 (193b2, #1704) manifest review of 631a256ba (duckworth-0169) | APPROVE, C1-C2 bind the 275 rebase round, departures 1-7 accepted, N1-N4 |
2026-10-22T16:59Z | Duckworth (Opus 5.5) | row 271 (264d, #1776) design early look, draft 2 5ab7bcf4a (duckworth-0170) | shape holds (receive-acknowledged drain), B1-B4 to fold before code, N1-N8 |