Stage A: fix USE's silent stack-underflow/bad-address guards, and the Hera fault-scoping gap they exposed (FABRIC-3.md §XXXII.1)
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run

mama_word_use()'s two silent vm->error=1 guards (dsp<1 stack underflow,
NULL from vm_ptr()) now print a diagnostic and return, matching the
function's other five guards.

Live verification of that fix alone surfaced a bigger problem: with the
guard no longer silent, the REPL proceeds to interpret the leftover
token as an unrecognized word, which independently sets vm->error, and
sk_repl_step()/sk_repl_run()'s Hera-branch still hard-halted on that.
Investigated kernel_main.c's boot/capsule-load paths directly: they
already catch and clear mama->error entirely separately, before
sk_repl_run() is ever entered -- so the "no fallthrough surface" halt
in these two REPL functions was never protecting a boot-time fault, only
an ordinary interactive REPL-turn one. Both functions now recover
unconditionally on any VM's error, Hera included, matching how a
redirected (WIREBIND/USE'd) identity's session already recovered.
Removed the now-fully-unused sk_fault_handler().

Verified live, amd64: USE rajames at Zuse's own console prints the new
diagnostic, then "VM fault -- session recovered, resuming", console
stays interactive afterward. Three-arch clean qemu acceptance passed
before and after the Hera-fault-scoping change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BWpNjdwPtFLuVLaAq44L9K
This commit is contained in:
Robert Allan James
2026-09-15 09:06:33 -04:00
co-authored by Claude Sonnet 5
parent 05159c9f9e
commit 5c5896fbc1
25 changed files with 99587 additions and 63 deletions
+33
View File
@@ -4213,6 +4213,39 @@ whichever fix lands, it must be verified by typing bare `USE <name>` at **Zuse's
Hera). A WIREBIND identity's own guest session already recovers from this exact error today, Hera). A WIREBIND identity's own guest session already recovers from this exact error today,
pre-fix (§XI.4's fix already covers that path) -- testing there would be a false pass. pre-fix (§XI.4's fix already covers that path) -- testing there would be a false pass.
**Stage A -- DONE, 2026-09-15, and escalated past option (a) once live testing forced the
question.** Option (a) (both silent guards in `mama_word_use()` -- line 451's `dsp < 1` and
line 467's `vm_ptr()` NULL check -- now print a diagnostic and `return;` without touching
`vm->error`) landed first and was verified correct on its own terms. But the actual repro
(`USE rajames` at Zuse's console) still halted the kernel afterward -- with the silent guard
fixed, the REPL now proceeds to interpret the leftover `rajames` token as its own word,
`vm_core.c:1106-1107`'s existing "unknown word" handling sets `vm->error = 1` exactly as
designed, and `sk_repl_step()`'s Hera-branch halted on *that* error just as it would have on
any other. This proved the original bug was never really about `USE`'s guard specifically --
**any unrecognized word or ordinary interactive mistake typed at Zuse's own console hard-halted
the entire kernel**, and always had, by the same mechanism.
Brought back for a decision rather than fixed unilaterally, since it meant option (c) was
actually required, not merely a bigger optional follow-on. Decided: narrow it now, same stage.
Investigated first, not assumed: `kernel_main.c`'s boot/capsule-load call sites (the `--doe`
injection at line 899-907, `SK_STARTUP_FORTH` at line 913-922) already catch and clear
`mama->error` directly, entirely independent of `sk_repl_step()`/`sk_repl_run()` -- both of
which are only ever reached once the interactive REPL loop is already running, well after boot.
So the "no fallthrough surface" halt in those two functions was never actually protecting a
boot-time fault; it could only ever fire on an ordinary interactive REPL-turn error, on any VM,
Hera included. Fix (`repl.c`): both `sk_repl_step()`'s and `sk_repl_run()`'s error branches
now recover unconditionally -- print "VM fault -- session recovered, resuming", clear
`error`/`halted`/`abort_requested` -- with no special case for Hera's own direct session. The
now-fully-unused `sk_fault_handler()` (and its "halts VM on error" doc-comment claim) were
removed, matching this same file's own established precedent for retiring a function once its
last caller is gone.
**Verified live, amd64, the actual repro:** `USE rajames` at Zuse's own console now prints the
new diagnostic, then "VM fault -- session recovered, resuming" for the resulting unknown-word
error, and the console stays fully interactive afterward (`3 4 + .` correctly computed `7`
immediately after). Three-architecture `clean qemu` acceptance (amd64/aarch64/riscv64) passed
with the full fix, standard regression unaffected on all three.
### XXXII.2 -- Human vs. unattended identity/console-birth model ### XXXII.2 -- Human vs. unattended identity/console-birth model
**Current model, confirmed by reading `capsule_wirebind_try_attach()` end to end **Current model, confirmed by reading `capsule_wirebind_try_attach()` end to end
+1 -1
View File
@@ -1,5 +1,5 @@
# Capsule Block Manifest — Auto-generated # Capsule Block Manifest — Auto-generated
<!-- Generated by mkcapsule --manifest 2026-09-15T08:37:42Z --> <!-- Generated by mkcapsule --manifest 2026-09-15T12:53:40Z -->
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. --> <!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
<!-- Hand-written justifications and immutability notes live --> <!-- Hand-written justifications and immutability notes live -->
<!-- in MANIFEST.md alongside this auto-generated index. --> <!-- in MANIFEST.md alongside this auto-generated index. -->
BIN
View File
Binary file not shown.
-2
View File
@@ -1,2 +0,0 @@
tick_number,elapsed_ns,tick_interval_ns,cache_hits_delta,bucket_hits_delta,word_executions_delta,hot_word_count,avg_word_heat_q48,window_width,actual_window_size,predicted_label_hits,jitter_bits,apic_ticks,time_trust_q48,variance_q48,vm_call_depth_max,hera_heat_q48,hermes_heat_q48,artemis_heat_q48,fleet_k_q48,fleet_conserved,switch_count_cumulative,switch_current_slot,switch_hera_readiness,switch_hermes_readiness,switch_artemis_readiness,switch_ticks_since
104,1040000,890000,0,0,15665,80,42,256,256,0,4695806354533646336,405,65536,0,1,65536,0,0,65536,1,0,0,0,5,5,5
1 tick_number elapsed_ns tick_interval_ns cache_hits_delta bucket_hits_delta word_executions_delta hot_word_count avg_word_heat_q48 window_width actual_window_size predicted_label_hits jitter_bits apic_ticks time_trust_q48 variance_q48 vm_call_depth_max hera_heat_q48 hermes_heat_q48 artemis_heat_q48 fleet_k_q48 fleet_conserved switch_count_cumulative switch_current_slot switch_hera_readiness switch_hermes_readiness switch_artemis_readiness switch_ticks_since
tick_number elapsed_ns tick_interval_ns cache_hits_delta bucket_hits_delta word_executions_delta hot_word_count avg_word_heat_q48 window_width actual_window_size predicted_label_hits jitter_bits apic_ticks time_trust_q48 variance_q48 vm_call_depth_max hera_heat_q48 hermes_heat_q48 artemis_heat_q48 fleet_k_q48 fleet_conserved switch_count_cumulative switch_current_slot switch_hera_readiness switch_hermes_readiness switch_artemis_readiness switch_ticks_since
104 1040000 890000 0 0 15665 80 42 256 256 0 4695806354533646336 405 65536 0 1 65536 0 0 65536 1 0 0 0 5 5 5
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -449,7 +449,7 @@ void mama_word_use(VM *vm)
const char *src; const char *src;
if (vm->dsp < 1) { if (vm->dsp < 1) {
vm->error = 1; console_println("USE: expects S\" name\" USE -- nothing on the stack");
return; return;
} }
@@ -464,7 +464,7 @@ void mama_word_use(VM *vm)
/* caddr is a VM address; S" ( -- c-addr u ) stores chars directly at caddr */ /* caddr is a VM address; S" ( -- c-addr u ) stores chars directly at caddr */
{ {
const uint8_t *p = vm_ptr(vm, (vaddr_t)caddr); const uint8_t *p = vm_ptr(vm, (vaddr_t)caddr);
if (!p) { vm->error = 1; return; } if (!p) { console_println("USE: invalid address on the stack"); return; }
src = (const char *)p; src = (const char *)p;
} }
for (i = 0; i < (uint32_t)u; i++) name_buf[i] = src[i]; for (i = 0; i < (uint32_t)u; i++) name_buf[i] = src[i];
+45 -58
View File
@@ -1169,16 +1169,17 @@ int sk_console_readline(char* buf, int size, VM* active_vm, int reanchor_prompt)
* - Calls vm_interpret * - Calls vm_interpret
* - Prints " ok" or " ERROR" * - Prints " ok" or " ERROR"
* - When EMERGENCY_CONSOLE_ENABLED=1: resets vm->error and loops (recovery) * - When EMERGENCY_CONSOLE_ENABLED=1: resets vm->error and loops (recovery)
* - When EMERGENCY_CONSOLE_ENABLED=0: halts VM on error (no fallthrough surface) * - When EMERGENCY_CONSOLE_ENABLED=0: an interactive REPL-turn fault on
* any VM (Hera included, as of FABRIC-3.md §XXXII.1) recovers the same
* way -- prints a message, clears vm->error/halted/abort_requested, and
* the loop continues at that VM's own next prompt. Boot-time faults
* (capsule load, birth scripts) never reach this code at all; they are
* caught and cleared directly in kernel_main.c before sk_repl_run() is
* ever entered, so no "no fallthrough surface" halt is needed or given
* up here -- the C-level `sk_fault_handler()` this comment used to
* describe was retired the same day, having no remaining caller.
*===========================================================================*/ *===========================================================================*/
#if !EMERGENCY_CONSOLE_ENABLED
static void sk_fault_handler(VM *vm) {
console_println("VM fault — emergency console disabled; halting");
vm->halted = 1;
}
#endif
/*=========================================================================== /*===========================================================================
* sk_repl_dispatch_line - console-VM + user-VM pair relay (FABRIC-2.md * sk_repl_dispatch_line - console-VM + user-VM pair relay (FABRIC-2.md
* Phase F, 2026-08-28). * Phase F, 2026-08-28).
@@ -1319,22 +1320,23 @@ int sk_repl_step(VM *vm)
console_puts(" ERROR\n"); console_puts(" ERROR\n");
vm->error = 0; vm->error = 0;
#else #else
/* Wired 2026-09-05: sk_fault_handler() existed but was never /* FABRIC-3.md §XXXII.1, 2026-09-15: an ordinary interactive
* called from here -- the "halts VM on error" half of this * mistake (a typo, an unrecognized word, a stack-convention
* function's own doc comment was aspirational, not real, until * violation) at Zuse's own console used to hard-halt the entire
* now. Real for the headless-until-login default. * kernel here, because this branch treated Hera's own session
* * as a "no fallthrough surface" emergency and every other VM's
* FABRIC-3.md, 2026-09-09: scoped to Hera's own session only -- * session as recoverable (2026-09-09 fix, see git history). That
* see sk_repl_run()'s matching fix (and its own doc comment) for * asymmetry was never actually protecting a boot-time fault --
* why a non-mama target must recover instead of halt here too. */ * boot/capsule-load errors are caught and cleared entirely
if (vm == (VM *)sk_get_mama_vm()) { * separately, in kernel_main.c, well before sk_repl_run() is
sk_fault_handler(vm); * ever entered -- so by the time this function runs at all, any
} else { * vm->error here is by construction an interactive REPL-turn
console_println("VM fault -- session recovered, resuming"); * fault, on any VM including Hera. Recovers unconditionally now,
vm->error = 0; * matching every other VM's own session exactly. */
vm->halted = 0; console_println("VM fault -- session recovered, resuming");
vm->abort_requested = 0; vm->error = 0;
} vm->halted = 0;
vm->abort_requested = 0;
#endif #endif
} else { } else {
console_puts(" ok\n"); console_puts(" ok\n");
@@ -1445,40 +1447,25 @@ void sk_repl_run(VM *vm)
console_puts(" ERROR\n"); console_puts(" ERROR\n");
active->error = 0; active->error = 0;
#else #else
/* Wired 2026-09-05, same as sk_repl_step()'s matching branch /* FABRIC-3.md §XI.4 (2026-09-09) scoped this recovery to any
* above -- sk_fault_handler() existed but was never called. * *redirected* (WIREBIND/USE'd) identity, keeping Hera's own
* * direct session (active == vm) on the strict "no fallthrough
* FABRIC-3.md, 2026-09-09: live-caught -- a standalone * surface" halt -- reasoned at the time as protecting against
* WIREBIND identity (no Zuse involved at all, USE never * a genuine full-system emergency. §XXXII.1 (2026-09-15) found
* typed) hitting a denied word halted the *entire machine*, * that reasoning didn't hold: boot/capsule-load errors are
* not just that identity's own session. Root cause: this * caught and cleared entirely separately in kernel_main.c,
* loop's own exit condition (`while (!vm->halted)` above) * before this loop is ever entered, so any active->error seen
* checks vm -- Hera, the loop's original owner -- but * here -- Hera's own session included -- is by construction
* sk_fault_handler() was being called on `active`, which * an ordinary interactive REPL-turn fault (a typo, an
* capsule_wirebind's own attach flow had already redirected * unrecognized word), not a boot-time catastrophe. An
* to the new identity's own VM (sk_repl_set_active_vm()) by * unqualified typo at Zuse's own console was hard-halting the
* the time any command could be typed. Setting active->halted * entire kernel as a direct result of this asymmetry. Recovers
* left Hera's own vm->halted untouched -- the loop kept * unconditionally now, matching every redirected identity's
* running -- but every subsequent iteration kept re-selecting * own session exactly -- no special case for `active == vm`. */
* the same now-halted, never-recovering `active`, producing console_println("VM fault -- session recovered, resuming");
* no further output and no further progress: a de facto full active->error = 0;
* freeze despite Hera's own loop technically still spinning. active->halted = 0;
* Captain Bob, 2026-09-09: "scope the halt to just that active->abort_requested = 0;
* identity's session gracefully and restart the session or
* resume it." Hera's own direct session (active == vm) keeps
* the strict "no fallthrough surface" halt -- a fault in the
* root console is a genuine full-system emergency -- but any
* redirected (WIREBIND/USE'd) identity's own fault now
* recovers instead: clear the fault state and let that same
* identity keep going at its own prompt next iteration. */
if (active == vm) {
sk_fault_handler(active);
} else {
console_println("VM fault -- session recovered, resuming");
active->error = 0;
active->halted = 0;
active->abort_requested = 0;
}
#endif #endif
} else { } else {
console_puts(" ok\n"); console_puts(" ok\n");