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,
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
**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
<!-- 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. -->
<!-- Hand-written justifications and immutability notes live -->
<!-- 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;
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];
+37 -50
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 {
/* 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 {
/* 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");