Stage E: Category B strip -- remove Hermes, messaging.4th, old routing (FABRIC-3.6.md task 4.1-4.4)
All FORTH-owned message types were cut over to kernel-Hermes in Phase 3 (tasks 3.8-3.10). This removes the now-dead FORTH messaging layer and the Hermes VM itself: capsules/common/messaging.4th, capsules/hermes/init.4th, the slot-3 VM-NAME-REG pairing convention, and every load-site/birth-site reference across capsules/init.4th, artemis/init.4th, hestia/init.4th, doe-campaign.4th (Artemis-only now), capsule_console.c, capsule_mint.c, capsule_wirebind.c, capsule_birth.c, and kernel_main.c. Verified on all three architectures: clean boot, mkcapsule --lint clean (36 files, 0 violations), zero UNKNOWN WORD, identical dict_hash across amd64/aarch64/riscv64 for every VM, and a full mint -> WIREBIND-attach -> USE -> relay round-trip exercising the two highest-risk edits (capsule_console.c/capsule_mint.c). Found, not fixed: deleting messaging.4th removes SEND-ELEVATE-REQUEST, which was the only caller of KH-ELEVATE-SEND and the only path to ELEVATE-GRANT (zuse-eligibility.4th, still loaded at boot) -- Phase 8 PKI's own elevation entrypoint. Needs a decision before Phase 5 close-out. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
cab9b5a31f
commit
3e201c82a5
+86
-134
@@ -444,19 +444,22 @@ static void sk_word_kh_blk_attach_send(VM *vm) {
|
||||
#define SK_KH_ELEVATE_BUF_SIZE 256
|
||||
static char g_kh_elevate_buf[SK_KH_ELEVATE_BUF_SIZE];
|
||||
|
||||
/* KH-ELEVATE-SEND ( paddr plen -- ok? ): messaging.4th's own
|
||||
* SEND-ELEVATE-REQUEST builds its payload (a FORTH command string
|
||||
* calling ELEVATE-GRANT, block 5040) and hands it here instead of
|
||||
* CH-REQUEST's old COMMON-CH/MSG-SEND path. `from`/`to` are derived the
|
||||
* same way KH-BLK-ATTACH-SEND's own already does (the calling VM and
|
||||
* sk_get_mama_vm() -- ELEVATE-GRANT always runs on Hera, per
|
||||
* zuse-eligibility.4th's own header comment) -- the caller never
|
||||
/* KH-ELEVATE-SEND ( paddr plen -- ok? ): built for messaging.4th's own
|
||||
* SEND-ELEVATE-REQUEST (a FORTH command string calling ELEVATE-GRANT,
|
||||
* zuse-eligibility.4th block 4022), which handed its payload here
|
||||
* instead of CH-REQUEST's old COMMON-CH/MSG-SEND path. `from`/`to` are
|
||||
* derived the same way KH-BLK-ATTACH-SEND's own already does (the
|
||||
* calling VM and sk_get_mama_vm() -- ELEVATE-GRANT always runs on Hera,
|
||||
* per zuse-eligibility.4th's own header comment) -- the caller never
|
||||
* supplies either, which is a real correctness improvement over the
|
||||
* FORTH path it replaces: CH-REQUEST's own "initiator-only gate...
|
||||
* FORTH path it replaced: CH-REQUEST's own "initiator-only gate...
|
||||
* refuses if from doesn't match MY-CH-ID" existed only because a caller
|
||||
* COULD claim to be a different VM by passing the wrong stack value;
|
||||
* deriving `from` from the C-level calling VM's own identity makes that
|
||||
* spoof structurally impossible rather than merely gated. */
|
||||
* spoof structurally impossible rather than merely gated. FABRIC-3.6.md
|
||||
* Phase 4, Stage E deleted messaging.4th -- SEND-ELEVATE-REQUEST no
|
||||
* longer exists anywhere, so this word currently has no FORTH caller.
|
||||
* Found, not fixed; see FABRIC-3.6.md's own Phase 4 entry. */
|
||||
static void sk_word_kh_elevate_send(VM *vm) {
|
||||
if (vm->dsp < 1) {
|
||||
log_message(LOG_ERROR, "KH-ELEVATE-SEND: stack underflow");
|
||||
@@ -527,49 +530,42 @@ static void sk_cursor_show(void)
|
||||
console_fb_draw_cursor();
|
||||
}
|
||||
|
||||
/* Reentrancy guards for the MSG-TICK pump inside sk_repl_idle().
|
||||
/* Reentrancy guards for the kernel-Hermes drain pump inside sk_repl_idle().
|
||||
*
|
||||
* sk_repl_idle() runs vm_interpret(mama, ...) (below) to VM-EXEC MSG-TICK
|
||||
* into every live child VM (FABRIC-2.md Phase C). But sk_repl_idle() is
|
||||
* itself called from the blocking KEY/EXPECT/QUERY reads (sk_console_getkey()
|
||||
* / sk_console_readline(), which run *from inside* the executing VM's own
|
||||
* vm_interpret once a FORTH word reads input mid-line). vm_interpret() is
|
||||
* not reentrant -- it resets the VM's single input_buffer/input_pos/
|
||||
* input_length (vm_core.c) on entry. Calling vm_interpret(mama, ...) at that
|
||||
* point would re-enter mama's interpreter mid-parse and silently truncate
|
||||
* the rest of the line; if the pump's own MSG-TICK work then triggers another
|
||||
* blocking read, it would also recurse unboundedly. Two flags keep the pump
|
||||
* off every path that could re-enter an interpreter:
|
||||
* FABRIC-3.6.md Phase 4 (Stage E), 2026-09-22: this used to guard a
|
||||
* VM-EXEC "MSG-TICK" dispatch into every live child VM (FABRIC-2.md
|
||||
* Phase C, common:messaging.4th, now removed). The mechanism changed --
|
||||
* sk_repl_idle() now calls sk_hermes_drain_checkpoint() directly per VM,
|
||||
* not VM-EXEC -- but the reentrancy hazard these two flags exist for is
|
||||
* unchanged: drain_checkpoint() still calls vm_interpret() internally
|
||||
* (on the drained message's own payload), and sk_repl_idle() is itself
|
||||
* called from the blocking KEY/EXPECT/QUERY reads (sk_console_getkey()/
|
||||
* sk_console_readline(), which run *from inside* the executing VM's own
|
||||
* vm_interpret once a FORTH word reads input mid-line). vm_interpret()
|
||||
* is not reentrant -- it resets the VM's single input_buffer/input_pos/
|
||||
* input_length (vm_core.c) on entry. Calling it on mama at that point
|
||||
* would re-enter her interpreter mid-parse and silently truncate the
|
||||
* rest of the line. Two flags keep the pump off every path that could
|
||||
* re-enter an interpreter:
|
||||
* - g_mama_interpreting: set while Hera is executing a dispatched line, so
|
||||
* the pump defers to the next safe (prompt) boundary.
|
||||
* - g_idle_pump_active: belt-and-suspenders; stops recursive re-entry
|
||||
* from inside the pump's own vm_interpret call.
|
||||
* from inside the pump's own drain call.
|
||||
*/
|
||||
static int g_mama_interpreting; /* zero-initialized (BSS) */
|
||||
static int g_idle_pump_active; /* zero-initialized (BSS) */
|
||||
|
||||
/* FABRIC-3.md SXXII (2026-09-12): the messaging pump below used to walk
|
||||
* the ENTIRE live-VM registry every idle beat and dispatch a full
|
||||
* VM-EXEC "MSG-TICK" into every one of them -- O(N) per tick, forever,
|
||||
* with no cap. Fine at Tripod's original 3-VM scale; live-caught as a
|
||||
* real bottleneck at just 9 VMs on riscv64 (a std79 turn-attractor
|
||||
* campaign that completed in ~200-340s on amd64/aarch64 never finished
|
||||
* on riscv64 at the same VM count, while 8 VMs was fine there too --
|
||||
* the qemu-instance progress check (utime climbing steadily, ~66-75%
|
||||
* CPU) ruled out a hang; it was genuinely just this loop, at riscv64's
|
||||
* slower per-dispatch cost, dominating wall time). The real fleet is
|
||||
* headed toward hundreds of VMs, at which point this isn't a riscv64
|
||||
* quirk -- unconditional O(N) per second is a wall on every
|
||||
* architecture. Fixed by round-robin batching: SK_MSG_PUMP_BATCH live
|
||||
* VMs get dispatched per beat, not all of them, with a persistent
|
||||
* cursor picking up where the previous beat left off (wrapping around
|
||||
* the registry). Bounds both the scan and the dispatch cost to O(K)
|
||||
* regardless of total VM count -- the tradeoff is that any single VM's
|
||||
* own queue now drains roughly every ceil(N/K) beats instead of every
|
||||
* beat, which is still bounded and still matches the pump's own
|
||||
* existing "best-effort, resumes next beat" contract. */
|
||||
#define SK_MSG_PUMP_BATCH 4u /* max VM-EXEC MSG-TICK dispatches per idle beat */
|
||||
static uint32_t g_msg_pump_cursor; /* zero-initialized (BSS) */
|
||||
/* FABRIC-3.md SXXII (2026-09-12) recorded a real O(N) bottleneck here --
|
||||
* the messaging pump below used to walk the entire live-VM registry
|
||||
* every idle beat and dispatch a full VM-EXEC "MSG-TICK" into every one
|
||||
* of them, live-caught as a wall-time-dominating cost at just 9 VMs on
|
||||
* riscv64 -- and fixed it with round-robin batching (a persistent
|
||||
* cursor, a fixed number of dispatches per beat). FABRIC-3.6.md Phase 4
|
||||
* (Stage E), 2026-09-22: that whole mechanism (MSG-TICK, the batching
|
||||
* cap, and this comment's own prior form) is gone along with
|
||||
* common:messaging.4th itself -- see sk_repl_idle()'s own current
|
||||
* comment, just below, for why the replacement (kernel-Hermes drain)
|
||||
* does not carry the same O(N) cost and needs no cap. */
|
||||
|
||||
static void sk_repl_idle(VM *active_vm)
|
||||
{
|
||||
@@ -857,18 +853,25 @@ static void sk_repl_idle(VM *active_vm)
|
||||
* comment. */
|
||||
capsule_wirebind_reap_idle_check();
|
||||
|
||||
/* FABRIC-2.md Phase C (2026-08-28): distributed messaging pump. Every
|
||||
* live VM except Hera herself now owns its own MSG-ARENA/CH-ARENA and
|
||||
* MSG-TICK word (see capsules/common/messaging.4th) instead of only
|
||||
* Hermes having one -- "fully distributed, Hera pumps each VM's
|
||||
* drain" was the confirmed design. Hera is excluded: she never loads
|
||||
* common:messaging.4th (kernel_main.c's own comment at the Hermes-
|
||||
* birth call site explains why -- the STADIUM-* primitives it needs
|
||||
* are deliberately never registered in her own dictionary, to keep
|
||||
* her dict_hash off item 4.1's baseline), so MSG-TICK is genuinely
|
||||
* absent there, not just untried. She is the only VM with a
|
||||
* persistent idle tick, so she walks the registry once per idle beat
|
||||
* and VM-EXECs MSG-TICK into every OTHER live VM's own dictionary. */
|
||||
/* FABRIC-3.6.md Phase 4 (Stage E, Category B strip), 2026-09-22:
|
||||
* this used to be a distributed FORTH messaging pump -- FABRIC-2.md
|
||||
* Phase C's own design, VM-EXEC'ing MSG-TICK into every live VM's
|
||||
* own dictionary once per idle beat (common:messaging.4th, now
|
||||
* removed entirely). That mechanism, and the comment that used to
|
||||
* sit here explaining it, are both gone: kernel-Hermes's own drain
|
||||
* (below) has needed no FORTH word since task 3.9, and every
|
||||
* messaging.4th-owned message type had a real cutover by task 3.10
|
||||
* (FABRIC-3.6.md task 3.11, Phase 3 gate). The prior version of this
|
||||
* comment also claimed "Hera never loads common:messaging.4th" --
|
||||
* that was already wrong before this strip (capsules/init.4th line
|
||||
* 21 EXECs it directly; task 3.10's own live FIND-based check
|
||||
* confirmed it), not just made moot by removing the file. She still
|
||||
* has the only persistent idle tick, so she is still the one that
|
||||
* walks the registry once per idle beat -- now purely to give every
|
||||
* live VM's own kernel-Hermes queue a chance to drain, batched the
|
||||
* same round-robin way the old MSG-TICK dispatch was (SK_MSG_PUMP_
|
||||
* BATCH below), kept for the same reason: bounding this to O(K)
|
||||
* regardless of total VM count, not O(N) every beat. */
|
||||
VM *mama = (VM *)sk_get_mama_vm();
|
||||
|
||||
/* Reentrancy guard: never run the pump while mama is mid-interpret
|
||||
@@ -887,93 +890,42 @@ static void sk_repl_idle(VM *active_vm)
|
||||
|
||||
g_idle_pump_active = 1;
|
||||
{
|
||||
uint32_t count = capsule_vm_registry_count();
|
||||
uint32_t dispatched = 0;
|
||||
uint32_t scanned = 0;
|
||||
uint32_t i = (count > 0) ? (g_msg_pump_cursor % count) : 0;
|
||||
/* FABRIC-3.6.md Phase 4 (Stage E), 2026-09-22: the SK_MSG_PUMP_
|
||||
* BATCH cap this loop used to enforce existed to bound the cost
|
||||
* of the old MSG-TICK VM-EXEC dispatch -- a genuinely expensive
|
||||
* per-VM vm_interpret() call, confirmed live as a real O(N)
|
||||
* bottleneck at just 9 VMs on riscv64 (see this section's own
|
||||
* history above). sk_hermes_drain_checkpoint() (task 3.4) is not
|
||||
* that: its own fast path is a single global-counter read
|
||||
* (sk_hermes_pending_total == 0 -> return immediately,
|
||||
* kernel_hermes.c's own doc comment), so walking every live VM
|
||||
* every beat costs one cheap integer read per VM in the common
|
||||
* case (nothing in flight), not an O(N) wall -- the cap this
|
||||
* loop's own comment history worried about does not apply to
|
||||
* this call. Removed: every live VM (except mama, handled
|
||||
* separately below) is now visited every idle beat, matching
|
||||
* what task 3.9's own testing already exercised, without the
|
||||
* ceil(N/SK_MSG_PUMP_BATCH)-beat delivery latency the cap was
|
||||
* accidentally imposing on kernel-Hermes drain specifically. */
|
||||
uint32_t count = capsule_vm_registry_count();
|
||||
uint32_t i;
|
||||
|
||||
while (count > 0 && dispatched < SK_MSG_PUMP_BATCH && scanned < count) {
|
||||
for (i = 0; i < count; i++) {
|
||||
VMRegistryEntry ent;
|
||||
scanned++;
|
||||
|
||||
if (capsule_vm_registry_get_by_index(i, &ent) == 0 &&
|
||||
ent.state == VM_STATE_LIVE &&
|
||||
ent.vm_ptr != (void *)mama) {
|
||||
/* FABRIC-3.6.md task 3.9, found live 2026-09-22: kernel-
|
||||
* Hermes's own drain (sk_hermes_drain_checkpoint(), task
|
||||
* 3.4) only ever runs as a side effect of vm_interpret()
|
||||
* being called ON the target VM (vm_core.c's own outermost-
|
||||
* interpret checkpoint hook) -- it has nothing to do with
|
||||
* the old FORTH MSG-TICK word this loop was built around.
|
||||
* A WIREBIND identity minted with the STD79-lockdown
|
||||
* personality (MINT_PERSONALITY_STD79_LOCKDOWN) never loads
|
||||
* common:messaging.4th, so the MSG-TICK gate just below
|
||||
* always skips it -- meaning it never got an interpret
|
||||
* tick at all, meaning a CONSOLE-CMD-EVENT queued for it
|
||||
* sat in its pending queue forever, silently. Confirmed
|
||||
* live: sk_hermes_send_one() returned 0 (queued OK) but no
|
||||
* drain ever ran. Called unconditionally here, independent
|
||||
* of the MSG-TICK check below -- kernel-Hermes doesn't need
|
||||
* a FORTH word to exist, it just needs its target VM to get
|
||||
* an outermost interpret call periodically. Direct C call,
|
||||
* not routed through VM-EXEC/vm_interpret: drain_checkpoint
|
||||
* already saves/restores the target's own interpret state
|
||||
* internally (kernel_main.c's own self-test calls it the
|
||||
* same standalone way), so no vm_interpret() wrapper is
|
||||
* needed to reach it from here. */
|
||||
(void)sk_hermes_drain_checkpoint((VM *)ent.vm_ptr);
|
||||
|
||||
/* FABRIC-3.md, 2026-09-09: a per-VM flag (e.g. "this one is
|
||||
* std79-locked") doesn't generalize -- any VM whose own
|
||||
* dictionary lacks MSG-TICK, for whatever reason (never
|
||||
* loaded common:messaging.4th, a future personality that
|
||||
* drops it, ACL denial), hits the identical failure. So
|
||||
* check the actual target VM's own dictionary fresh every
|
||||
* tick, the same source of truth the interpreter's own ACL
|
||||
* enforcement uses (vm_core.c), rather than pre-flag specific
|
||||
* personalities. Skip silently rather than spam "VM-EXEC:
|
||||
* ERROR in <name>" every idle tick forever for a VM that
|
||||
* plain doesn't have -- or isn't allowed -- MSG-TICK
|
||||
* (live-caught 2026-09-07/09). */
|
||||
DictEntry *msgtick = vm_find_word((VM *)ent.vm_ptr, "MSG-TICK", 8);
|
||||
if (msgtick && msgtick->acl_allow) {
|
||||
static const char PFX[] = "S\" MSG-TICK\" S\" ";
|
||||
static const char SFX[] = "\" VM-EXEC";
|
||||
char cmd[128];
|
||||
size_t p = 0;
|
||||
size_t name_len = strlen(ent.name);
|
||||
if (name_len > VM_NAME_MAX - 1u) name_len = VM_NAME_MAX - 1u;
|
||||
|
||||
memcpy(cmd + p, PFX, sizeof(PFX) - 1u); p += sizeof(PFX) - 1u;
|
||||
memcpy(cmd + p, ent.name, name_len); p += name_len;
|
||||
memcpy(cmd + p, SFX, sizeof(SFX) - 1u); p += sizeof(SFX) - 1u;
|
||||
cmd[p] = '\0';
|
||||
|
||||
vm_interpret(mama, cmd);
|
||||
dispatched++;
|
||||
}
|
||||
}
|
||||
|
||||
i = (i + 1u) % count;
|
||||
}
|
||||
g_msg_pump_cursor = i;
|
||||
}
|
||||
/* FABRIC-3.md SXX: Hera now genuinely has her own MSG-ARENA/CH-ARENA
|
||||
* (the messaging-load root cause above is fixed -- see kernel_main.c's
|
||||
* own updated comment at the Hermes-birth call site), so her own
|
||||
* queued messages need draining too, same as every other VM's. She's
|
||||
* excluded from the loop above not because she lacks MSG-TICK now, but
|
||||
* because she IS the pump -- self-targeting VM-EXEC would hit the same
|
||||
* reentrancy class the guard above exists for. No such problem calling
|
||||
* MSG-TICK directly here, though: it's a plain word in her own
|
||||
* dictionary, not a VM-EXEC dispatch into anyone else's input buffer.
|
||||
* Guarded the same defensive way as the loop above, not assumed. */
|
||||
{
|
||||
DictEntry *msgtick = vm_find_word(mama, "MSG-TICK", 8);
|
||||
if (msgtick && msgtick->acl_allow) {
|
||||
vm_interpret(mama, "MSG-TICK");
|
||||
}
|
||||
}
|
||||
/* Hera's own kernel-Hermes queue needs draining too, same as every
|
||||
* other VM's -- she is excluded from the loop above not because she
|
||||
* has nothing to drain, but because she IS the pump: a self-
|
||||
* targeting call here runs directly, no VM-EXEC/reentrancy concern
|
||||
* (sk_hermes_drain_checkpoint() is a plain C call, not a dispatch
|
||||
* into anyone else's input buffer). */
|
||||
(void)sk_hermes_drain_checkpoint(mama);
|
||||
g_idle_pump_active = 0;
|
||||
|
||||
if (console_tx_count() == tx_before_idle) {
|
||||
|
||||
Reference in New Issue
Block a user