FABRIC-3.5.md §XLIII: B3 settled -- the target drains its own queue at its own checkpoint

The last genuine design question in the reshuffle, and once again the
mechanism already exists, built for the structurally identical problem
and corrected twice in the field.

Delivery must execute the payload inside the target, but §XXXII retired
the pump so kernel-Hermes may not VM-EXEC into anyone. The payload must
wait somewhere the target consumes under its own power, at a moment when
doing so is safe -- and safe is the hard part, since interpreting
mid-word, mid-unwind or nested inside someone else's dispatch is the
reentrancy class this codebase has been bitten by repeatedly.

vm_core.c already has all of it: sk_vm_at_outermost_interpret() as the
predicate, a per-word checkpoint in execute_colon_word() gated on it, a
defer-don't-lose discipline for the nested case whose own comment
explains that a nested word does not own the stack it is running on, and
placement before the error and unwind checks so it only acts when the VM
is in a clean resumable state. That is the exact predicate, placement and
deferral semantics delivery needs, because the switcher had to answer the
same question.

Ruling: kernel-Hermes enqueues and publishes the fact, dispatching
nothing; the target drains its own queue in its own context at its own
outermost checkpoint. One published fact, two independent consumers --
the switcher for eligibility, the VM itself for drain -- and neither
dispatches into anyone.

Notes this is §XX's proven pattern generalized: the pump's defect was
never draining but draining from outside, and Hera was special-cased into
safety. Retire the pump and every VM does what she already does, so the
special case disappears by becoming universal. Also notes recursive drain
is prevented for free, since interpreting increments the same depth
counter that defines the boundary.

Names three constraints rather than leaving them to be discovered:
INPUT_BUFFER_SIZE is 1025 so a payload above 1024 bytes cannot be
interpreted in one drain, starvation is real but is the switcher's
existing risk rather than a new one, and draining one message per
checkpoint rather than the whole queue keeps the work bounded.

FABRIC-3.6.md: B3 cleared, B4 added for the payload bound.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VkM1zHGvBerLF6aqkHPweP
This commit is contained in:
Claude
2026-09-19 12:18:46 +00:00
parent dd255eac58
commit 3a4e5cff07
2 changed files with 110 additions and 2 deletions
+105
View File
@@ -4515,3 +4515,108 @@ left implicit.
- ⬜ **B1/B2/B3 (items 27, 32, §XXXII.5.1)** — the Phase 3 blockers, tracked in both documents.
**§XXXII.5.1, the delivery hand-off, is the last genuine design question in the reshuffle.**
- Design work continues **here**; execution is tracked **there**.
---
## XLIII. B3 SETTLED: the target drains its own queue, at its own outermost checkpoint
The last genuine design question in the reshuffle (§XXXII.5.1, Phase 3 blocker B3). **Settled,
and once again the mechanism already exists — built for the structurally identical problem and
corrected twice in the field.**
### XLIII.1 — What the hand-off has to satisfy
Delivery must cause the payload to **execute inside the target** — `messaging.4th` block 5041:
"Payload is FORTH text, executed by the receiver, same as every other message here." But
§XXXII retired the pump, so **kernel-Hermes may not `VM-EXEC` into anyone.** The payload must
therefore wait somewhere the target consumes under its own power, at a moment when doing so is
safe.
"Safe" is the hard part: interpreting a payload while the target is mid-word, mid-unwind, or
running nested inside someone else's dispatch is the reentrancy class this codebase has been
bitten by repeatedly (§XX, §XXVIII.2).
### XLIII.2 — The safe-boundary machinery is already there
Traced 2026-09-19 in `vm_core.c`:
- **`sk_vm_at_outermost_interpret()`** — `return g_vm_interpret_depth <= 1;` (`:1164`), with
`g_vm_interpret_depth` incremented and decremented by `vm_interpret()` itself (`:1173`,
`:1206`).
- **A per-word checkpoint** in `execute_colon_word()` (`:946`), gated on that predicate.
- **A defer-don't-lose discipline**, in the checkpoint's own words (`:925-935`): if a word is
"executing nested inside a `VM-EXEC`/`VM-CALL` dispatch… `vm` here **does not own the
physical stack it's currently running on**," so acting would corrupt the enclosing VM's call
chain. It is therefore **"left pending… so the next checkpoint at the outermost frame picks
it up instead of losing it."**
- And it runs **before** the error/abort/exit-colon checks, "deliberately, so a switch never
happens mid-unwind" — acting only when the word left the VM "in a clean, resumable state."
**That is the exact predicate, the exact placement, and the exact deferral semantics delivery
needs.** It exists because the switcher needed to answer the same question — *when is it safe to
act on this VM?* — and it has already been corrected twice in the field (2026-09-13 ×2,
2026-09-14).
### XLIII.3 — RULING
> **Kernel-Hermes enqueues the payload onto the target's own pending queue and publishes the
> fact. It dispatches nothing. The target drains its own queue, in its own context, at its own
> outermost interpret checkpoint — the same checkpoint, the same `sk_vm_at_outermost_interpret()`
> gate, and the same defer-if-nested discipline the switcher already uses.**
**One published fact, two independent consumers** — which is precisely the shape §XV.3 and
§XXXII established:
| Consumer | Uses the fact for |
|---|---|
| **The switcher** | eligibility — whether to resume this VM (§XXXII) |
| **The target VM itself** | drain — whether to interpret a pending payload at its next safe boundary |
**Neither dispatches into anyone.** Kernel-Hermes publishes; the switcher moves control; the VM
does its own work. Three roles, no overlap, no second owner of anything.
### XLIII.4 — This is §XX's proven-safe pattern, generalized to the whole fleet
`FABRIC-3.md` §XX already established the safe shape, for Hera specifically. With the pump
skipping her, she drains her own queue via "a plain word dispatch **in her own dictionary**, not
a `VM-EXEC` dispatch into anyone else's input buffer — **no reentrancy risk.**"
**The pump's defect was never draining; it was draining *from outside*.** Hera was special-cased
into safety. Retire the pump (§XXXII) and **every VM does what Hera already does** — the special
case disappears by becoming universal. That is a relocation of a proven pattern, not an
invention, and it keeps this document's scope discipline intact at the last design question.
### XLIII.5 — Recursive drain is prevented for free
Interpreting a payload calls `vm_interpret()`, which **increments `g_vm_interpret_depth`** — so
any checkpoint reached *during* the drain sees `depth > 1` and will not drain again. **The same
counter that defines the safe boundary also makes the drain non-reentrant**, with no flag, no
lock and no new state.
### XLIII.6 — Three constraints, named rather than discovered later
1. **`INPUT_BUFFER_SIZE` is 1025** — 1024 content bytes plus NUL, and `.claude/CLAUDE.md` calls
this non-negotiable because `vm_interpret()` is the shared dispatch path for both REPL lines
and `LOAD`ed block content. **A payload above 1024 bytes cannot be interpreted in one
drain.** Today's `MSG-PADDR`/`MSG-PLEN` are out-of-line and carry no such bound. **Kernel-
Hermes needs either a payload bound at send time or a chunking story — decide at build time,
do not discover it at Stage C.**
2. **Starvation is possible and bounded.** A VM in a tight loop never returns to outermost
depth, so never drains. The switcher can still preempt it, but preemption does not create a
boundary — the VM must still reach one. **This is the switcher's existing risk, not a new
one**, and it should be recorded as shared rather than solved twice.
3. **Drain one message per checkpoint, not all.** Draining the whole queue at one boundary runs
unbounded work in a single checkpoint — the §XXII wall pattern in miniature. One per
checkpoint is bounded and fair. **Recommended, not ruled.**
### XLIII.7 — Punch list
- ✅ **B3 — SETTLED** (§XLIII): the target drains its own queue at its own outermost checkpoint;
kernel-Hermes publishes and never dispatches.
- ⬜ **Item 44, NEW** — payload bound or chunking, against `INPUT_BUFFER_SIZE` 1025
(§XLIII.6.1). **Decide before Phase 3.**
- ⬜ **B1 (item 27)** and **B2 (item 32)** remain — both are rulings, not designs.
**Phase 3's design blocker is cleared. What remains before it is buildable are two decisions,
neither of which requires further investigation:** channels (one membership or negotiation), and
the switch-slot ceiling. **The reshuffle has no undesigned mechanism left.**
+5 -2
View File
@@ -135,8 +135,11 @@ one message type owned by exactly one layer, no message shared.
recommends the latter; unruled.)
- [ ] **B2** — **Item 32**: `SK_SWITCH_MAX_SLOTS` is 16 and §XXXII made the switcher the sole
mover of control. Constant bump or table redesign?
- [ ] **B3** — **§XXXII.5.1**: the delivery hand-off is undesigned. **The last genuine design
question, and on the critical path.**
- [x] **B3** — **CLEARED 2026-09-19 by `FABRIC-3.5.md` §XLIII**: the target drains its own
queue at its own outermost interpret checkpoint; kernel-Hermes publishes and never
dispatches. Reuses `sk_vm_at_outermost_interpret()` and the Stage 3 checkpoint.
- [ ] **B4** — **Item 44**: payload bound or chunking against `INPUT_BUFFER_SIZE` 1025
(§XLIII.6.1). Decide before Phase 3.
## Phase 4 — Category B strip