fix(hermes): one delivery path -- a VM takes its own messages on its own turn, never on another's stack

FABRIC-3.5 SXXXII, closing FABRIC-3 SXXIX bug 2 (SXXXVI).  A send marks the
receiver for the switch signal; the VM's own loop on its native stack takes
its messages and yields; Hera's idle beat makes the pending switch instead
of running other VMs' messages on her stack; every VM gets switcher turns
from birth; the drain refuses any call outside the VM's own context (the
test: it held every relayed line before, none now).

Found on the way: amd64's first-entry frame left rsp 8 bytes off the SysV
entry alignment (#GP on the first interpreter run on a VM's own stack), and
a VM yielding from its loop was marked SWITCHED_OUT, which stopped console
relay after one message and would have made KILL refuse it.

Relay of four lines to a WIREBIND identity on all three ISAs, five
hot-plugged identities, and standard acceptance (POST 1052, hash
0x08873e0f44b7cb2a) all pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-14 10:37:57 -04:00
co-authored by Claude Opus 5.5
parent b7e8e4b058
commit 029a8ed9cf
34 changed files with 110307 additions and 119 deletions
+57 -1
View File
@@ -3992,7 +3992,7 @@ are attached reliably in the first place, including under the "already plugged i
case §XIV's own second reproduction showed failing.
**Bug 2 -- the MSG-TICK/Stage-3-switch dual-ownership rough edge (§XXVIII's own admission,
never revisited).** Both MSG-TICK's idle-pump dispatch and the Stage 3 timer-driven switcher can
never revisited).** *Closed 2026-10-14: one delivery path, built -- §XXXVI.* Both MSG-TICK's idle-pump dispatch and the Stage 3 timer-driven switcher can
independently move control between the same VMs; not reconciled, not observed to interact badly
so far, but never proven safe under all interleavings either. Directly relevant to Stage 4 for
the same reason as Bug 1: WIREBIND VMs bring a second population of dynamically-birthed contexts
@@ -5524,3 +5524,59 @@ device it was minted on.
design/decision only, no capsule/C code, no new disk image, and no re-mint actually performed
or committed for the drive resize, the persistence region, or the N=2 campaign shape.
## XXXVI. One delivery path: a VM takes its own messages, on its own turn -- built (2026-10-14)
Closes the MSG-TICK/Stage-3 dual-ownership item (§XXVIII, §XXIX bug 2, §XXXI), as ruled in
`FABRIC-3.5.md` §XXXII ("one mover"); Captain Bob, 2026-10-14: collapse it before the bare-metal
builds. **The state before:** the MSG-TICK pump was gone (FABRIC-3.6 Stage E), but two paths
still ran a VM's kernel-Hermes messages -- the VM's own outermost colon-word checkpoint, and
Hera's idle beat, which walked every live VM and ran each one's messages *for* it, on Hera's
stack. The second was the only path for a VM nothing else drives (a WIREBIND identity's `~user`
VM, FABRIC-3.6 task 3.9). It was guarded (parked VMs are not LIVE and were skipped) but it was
two paths.
**What changed.**
- `sk_hermes_send_one()` marks the receiver as having work for the switch signal.
- What a VM runs on its own native stack (`sk_vm_switch_entry()`, until now a placeholder that
only yielded) now takes its own messages, then yields. Not while its interpreter is mid-line on
some other, parked, stack (new per-VM `interpret_depth`, counted in `vm_interpret()`): then
they wait and it asks for another turn.
- Hera's idle beat no longer runs anyone's messages but her own; it makes the switch the switch
signal has pending (`sk_vm_switch_service_pending()`, shared with the colon-word checkpoint),
so a message still arrives while she sits at her prompt.
- Every VM is registered with the switch signal at its birth (idempotent; the Tripod and
WIREBIND registrations stay) and unregistered when killed or reaped.
- `sk_hermes_drain_checkpoint()` refuses, with "Kernel-Hermes: message held -- its VM is not the
one running", any call not in the VM's own context: its interpreter running at the outermost
text, or it is the VM switched in and not mid-line elsewhere. This is the test: it failed
first, holding every relayed line (`logs/20261014-092558`), and never fires now.
- A VM yielding from the top of its own loop stays LIVE; only a VM switched out mid-line is
SWITCHED_OUT. Found by the test: with every yield SWITCHED_OUT, the console stopped relaying
to an identity after its first message (it relays only to a LIVE one) and KILL would have
refused every VM that had ever taken a message (`logs/20261014-095904`).
**A real defect found on the way, amd64 only:** `sk_vm_switch_prime()` (`arch/amd64/switch.S`)
built a first-entry frame that left `rsp` a multiple of 16 at `sk_vm_switch_entry()`'s entry; the
SysV ABI wants `rsp + 8` to be. Harmless while that function only switched back; a #GP (an
aligned SSE store to the stack) the first time it ran the interpreter there
(`logs/20261014-093823`). The frame is now 64 bytes with a null return address above the entry
address. aarch64 (160) and riscv64 (208) frames were already 16-aligned. This may also be what
stopped the 2026-09-14 attempt (`vm_interpret(self, "MSG-TICK")` in the same function, reverted
after a stall never root-caused) -- not proven.
**`FABRIC-3.5.md` §XXXII.5's three build questions, as answered here:** (1) the payload waits in
the receiver's kernel-Hermes queue and is run by the receiver on its own stack on its turn; (2)
Hera's special case stays only in this sense -- her messages run at her own idle beat, which is
her own context; (3) `g_mama_interpreting` and `g_idle_pump_active` stay, still guarding Hera's
own drain.
**Verified.** A WIREBIND identity (`S" 00" USE`, then four plain lines: `1 2 + .`, an unknown
word, `5 6 * .`, `7 8 + .`) receives and runs all four on its own stack -- `3`, the unknown-word
error, `30`, `15` -- with nothing held and no fault, on amd64 (`logs/20261014-101933`), aarch64
(`logs/20261014-102701`) and riscv64 (`logs/20261014-100948`). Five identity drives hot-plugged a
second apart at the prompt all attach (their attach acknowledgements are messages to Hera)
(`logs/20261014-102525`). Standard acceptance, committed disks: all three ISAs to `zuse@Hera`,
POST 1052 tests passed, `PARITY:M7.1a ... hash=0x08873e0f44b7cb2a` unchanged, no unknown word
(`logs/20261014-103248`, `-103409`, `-103616`). Superseded attempts, kept: `-092406` (wrong `USE`
syntax), `-094908` (before the LIVE-on-yield fix, one line only).