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:
+105
@@ -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
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user