Stage A: fix USE's silent stack-underflow/bad-address guards, and the Hera fault-scoping gap they exposed (FABRIC-3.md §XXXII.1)
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:
co-authored by
Claude Sonnet 5
parent
05159c9f9e
commit
5c5896fbc1
+33
@@ -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,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. -->
|
||||||
|
|||||||
Binary file not shown.
@@ -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
|
|
||||||
|
|||||||
|
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
@@ -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
@@ -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");
|
||||||
|
|||||||
Reference in New Issue
Block a user