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) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s

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
+2 -2
View File
@@ -449,7 +449,7 @@ void mama_word_use(VM *vm)
const char *src;
if (vm->dsp < 1) {
vm->error = 1;
console_println("USE: expects S\" name\" USE -- nothing on the stack");
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 */
{
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;
}
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
* - Prints " ok" or " ERROR"
* - 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
* Phase F, 2026-08-28).
@@ -1319,22 +1320,23 @@ int sk_repl_step(VM *vm)
console_puts(" ERROR\n");
vm->error = 0;
#else
/* Wired 2026-09-05: sk_fault_handler() existed but was never
* called from here -- the "halts VM on error" half of this
* function's own doc comment was aspirational, not real, until
* now. Real for the headless-until-login default.
*
* FABRIC-3.md, 2026-09-09: scoped to Hera's own session only --
* see sk_repl_run()'s matching fix (and its own doc comment) for
* why a non-mama target must recover instead of halt here too. */
if (vm == (VM *)sk_get_mama_vm()) {
sk_fault_handler(vm);
} else {
console_println("VM fault -- session recovered, resuming");
vm->error = 0;
vm->halted = 0;
vm->abort_requested = 0;
}
/* FABRIC-3.md §XXXII.1, 2026-09-15: an ordinary interactive
* mistake (a typo, an unrecognized word, a stack-convention
* violation) at Zuse's own console used to hard-halt the entire
* kernel here, because this branch treated Hera's own session
* as a "no fallthrough surface" emergency and every other VM's
* session as recoverable (2026-09-09 fix, see git history). That
* asymmetry was never actually protecting a boot-time fault --
* boot/capsule-load errors are caught and cleared entirely
* separately, in kernel_main.c, well before sk_repl_run() is
* ever entered -- so by the time this function runs at all, any
* vm->error here is by construction an interactive REPL-turn
* fault, on any VM including Hera. Recovers unconditionally now,
* matching every other VM's own session exactly. */
console_println("VM fault -- session recovered, resuming");
vm->error = 0;
vm->halted = 0;
vm->abort_requested = 0;
#endif
} else {
console_puts(" ok\n");
@@ -1445,40 +1447,25 @@ void sk_repl_run(VM *vm)
console_puts(" ERROR\n");
active->error = 0;
#else
/* Wired 2026-09-05, same as sk_repl_step()'s matching branch
* above -- sk_fault_handler() existed but was never called.
*
* FABRIC-3.md, 2026-09-09: live-caught -- a standalone
* WIREBIND identity (no Zuse involved at all, USE never
* typed) hitting a denied word halted the *entire machine*,
* not just that identity's own session. Root cause: this
* loop's own exit condition (`while (!vm->halted)` above)
* checks vm -- Hera, the loop's original owner -- but
* sk_fault_handler() was being called on `active`, which
* capsule_wirebind's own attach flow had already redirected
* to the new identity's own VM (sk_repl_set_active_vm()) by
* the time any command could be typed. Setting active->halted
* left Hera's own vm->halted untouched -- the loop kept
* running -- but every subsequent iteration kept re-selecting
* the same now-halted, never-recovering `active`, producing
* no further output and no further progress: a de facto full
* freeze despite Hera's own loop technically still spinning.
* Captain Bob, 2026-09-09: "scope the halt to just that
* 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;
}
/* FABRIC-3.md §XI.4 (2026-09-09) scoped this recovery to any
* *redirected* (WIREBIND/USE'd) identity, keeping Hera's own
* direct session (active == vm) on the strict "no fallthrough
* surface" halt -- reasoned at the time as protecting against
* a genuine full-system emergency. §XXXII.1 (2026-09-15) found
* that reasoning didn't hold: boot/capsule-load errors are
* caught and cleared entirely separately in kernel_main.c,
* before this loop is ever entered, so any active->error seen
* here -- Hera's own session included -- is by construction
* an ordinary interactive REPL-turn fault (a typo, an
* unrecognized word), not a boot-time catastrophe. An
* unqualified typo at Zuse's own console was hard-halting the
* entire kernel as a direct result of this asymmetry. Recovers
* unconditionally now, matching every redirected identity's
* own session exactly -- no special case for `active == vm`. */
console_println("VM fault -- session recovered, resuming");
active->error = 0;
active->halted = 0;
active->abort_requested = 0;
#endif
} else {
console_puts(" ok\n");