docs 22 Phase B: gaps 1+2 closed, and the D-on boundary measured on KiCad

Phase B increment recorded in §10. The wx battery is GREEN at D-on (395/1,
the 1 pre-existing) with dispatch contexts, context waits and star transfers
all live - the first clean battery of the migration. Gaps 1 and 2 from the
D-on probe are closed (terminal coroutine finish; wake/refusal semantics),
and a third containment was found and added: an exception escaping a handler
propagates out through drain()'s fiber swap and would otherwise leave the
registry mid-transition, dead-pumping every later wait. Shim carries the new
abandon call; .ci-cache-epoch -> 12.

THE BOUNDARY: on the full KiCad suite D-on loses four canvas-tool specs
(draw-wires, draw-lines, move-with-m, presence-locks move) to `index out of
bounds` in doRewind - the blue screen itself. Real tool coroutines park IN
PLACE inside their bodies, and a star transfer over an already-parked stack
rewinds state the fiber layer cannot see; the harness's coroutines yield
cleanly, so it goes green while KiCad does not (the doc-19 lesson again:
the harness models the shape, not the parks). So D cannot carry KiCad until
the tool-body park sites are contexts too - C+E completion, which §5 already
ordered before the flip. This measurement makes that ordering non-negotiable.

Landing state verified: STAR_DISPATCH=0, kicad 139 passed / 1 (pre-existing
occ-probe glb) = the Phase A baseline exactly, wx battery 395/1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LBjomQfKyRa3jBdeAKpmTw
This commit is contained in:
Gergő Törcsvári 2026-08-07 19:05:00 +02:00
commit 78ec52ea14
No known key found for this signature in database
GPG key ID: 8E75F2CDE64E5322
5 changed files with 64 additions and 3 deletions

View file

@ -643,6 +643,62 @@ close, each with a deterministic repro suite:
topology (every party a context, `return_to` a registry id) makes the question
disappear.
### Phase B at D-on (2026-08-07) — gaps 1+2 CLOSED, and the boundary found
Gaps 1 and 2 are fixed and the wx battery is **green at D-on: 395 passed, 1 failed
(the pre-existing `modal.spec.ts:125`)** — the first clean battery with dispatch
contexts, context waits and star transfers all live. What landed:
- **Terminal finish (gap 2).** `fiber_finish_transfer` marks a finished coroutine
Finished — never re-queued, never re-entered — and `fiber_transfer` refuses into a
Finished context, dropping the caller into libcontext's existing ghost contract.
This replaces the trampoline's `while(true)` ghost re-entry, which as transfers had
two finished coroutines marking each other Ready forever. `fiber_enterable` now
accepts the star statuses (a transfer-parked context holds a capture as valid as a
symmetric Suspended one), which also silenced the `sched-divergence-enterable`
storm.
- **Dispatch is an IDLE-REUSE SET, not one context — doc 22's "one context suffices"
was wrong.** A nested quasi-modal loop is a wait that only a LATER dispatch can
resolve, so a single dispatcher parked in that loop can never be released:
`nested_quasi_modal_pump_error` wedged exactly there. A tick now reuses any context
parked at `dispatch-idle` and allocates only when all are parked deeper. This is
NOT D2's pool (which took a fresh context per tick and grew with the tick rate);
the live count is bounded by real modal-nesting depth — 1 in steady state.
- **`abandon_transition` (a new containment).** With a second dispatcher the race's
bomb finally fired and revealed the next layer: an exception escaping a handler
propagates out THROUGH `drain()`'s fiber swap, so its post-swap bookkeeping never
runs, the registry stays "transition in flight", and every later pump refuses —
a permanently dead pump and every outstanding wait stalled. The JS error paths now
abandon the transition and POISON the half-unwound context (Finished; the dispatch
set prunes it), mirroring `wx_dispatch_abandon`. `.ci-cache-epoch` → 12.
**THE BOUNDARY, measured on the full KiCad suite (135 passed / 5 failed vs the 139/1
baseline).** Four canvas-tool specs — eeschema draw-wires, pcbnew draw-lines,
pcbnew move-with-m, presence-locks move — die with `index out of bounds` in
`doRewind``finishContextSwitch``Fibers.trampoline``maybeStopUnwind`: the
BLUE SCREEN itself, reproduced by the migration's own transitional state.
The reason the harness cannot see this, and the reason it is a boundary rather than a
bug: **real KiCad tool coroutines park IN PLACE inside their bodies** (a tool waiting
for the next event asyncify-parks on its own stack — doc 21's tool-side sites). A star
transfer turns one symmetric swap into a park-and-drain round trip, and doing that
over a stack that is already asyncify-parked mid-body rewinds state the fiber layer
cannot see. The harness's coroutines yield cleanly, so the transfer lane looks correct
there while KiCad's real tools break — the same "the harness models the shape, not the
parks" lesson as doc 19.
**Consequence for the plan: D cannot carry KiCad until the tool-body park sites are
contexts too.** That is C+E completion (every wait yields its owning context; no
`handleSleep` park on any fiber stack), which doc 22 §5 already requires before the
flip — this measurement just proves the ordering is not negotiable and that D-on
without it is precisely the partial migration §7.5 forbids.
**Landing state: `wxWASM_STAR_DISPATCH` back to 0**, with everything above kept —
all of it is correct-or-inert at D-off, and gaps 1+2 stay closed for the flip. The
next Phase B increment is the tool-side park inventory (doc 21 §1's KiCad rows) moved
onto contexts, after which D-on gets re-measured against the KiCad suite, not the
harness.
1. **pthreads.** Doc 21 §2 settled that every Asyncify park is main-thread and the lib
bridge's worker path is a blocking proxy. Phase A must re-check that libcontext is never
driven from a worker before assuming the scheduler is main-thread-only.