From 05159c9f9e490644e5315fcc1e5500e127dc9d6f Mon Sep 17 00:00:00 2001 From: Robert Allan James Date: Tue, 15 Sep 2026 07:36:35 -0400 Subject: [PATCH] =?UTF-8?q?Scope=20new=20initiative:=20unattended=20identi?= =?UTF-8?q?ty=20model,=20USE=20fail-closed-halt=20root=20cause,=20primitiv?= =?UTF-8?q?e=20error-handling=20audit,=20logging=20cleanup=20(FABRIC-3.md?= =?UTF-8?q?=20=C2=A7XXXII)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit USE crash fully root-caused against current source: mama_word_use() has seven guards, two of which set vm->error silently (stack underflow, bad VM address) while the other five print diagnostics; sk_repl_step() halts the whole kernel only when the faulting VM is Hera; Zuse's console runs directly on Hera's own VM (confirmed in capsule_zuse_boot.c), making a benign typo at her prompt the one deterministic path to the fail-closed halt. Three fix options named, none applied yet. Human-vs-unattended identity/console-birth model scoped: console/user VM pairing is pure name-convention + a per-console-VM VM-NAME-REG call, so after-the-fact console attach needs no new mechanism. Named the invariant an unattended birth path must not violate (never touch the global driving the "no thumbdrive, no prompt" gate). Error-handling audit scoped kernel-only (mama_forth_words.c + 5 kernel-only word_source files + vm_core.c/repl.c): 107 vm->error=1 sites across 8 files, counted directly. The vendored word_source sweep is explicitly out of scope here, spun off as future FABRIC-4.md work. Logging cleanup sequenced ahead of the audit's own fixes, with the two open §XXVII gaps (LOG-FLUSH doesn't exist; FIFO eviction is level-blind) named for an explicit in/out-of-scope call. Staged A-E plan proposed; nothing implemented this pass. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01BWpNjdwPtFLuVLaAq44L9K --- FABRIC-3.md | 219 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 219 insertions(+) diff --git a/FABRIC-3.md b/FABRIC-3.md index c0591abd..f025f951 100644 --- a/FABRIC-3.md +++ b/FABRIC-3.md @@ -4128,3 +4128,222 @@ own value here is what it has always been: a large-N cross-identity, cross-archi correctness regression check, now re-confirmed clean after Stage 4's registry/switch-signal plumbing changes -- not a preemption-specific test. +## XXXII. New initiative: human-vs-unattended identity model, `USE` fail-closed-halt root cause, +primitive error-handling audit, logging cleanup -- scoped, staged plan written up before any code +(2026-09-15) + +**Origin.** Closing out §XXX/§XXXI left one flagged, deferred item: the `USE`/BINDSTEP crash +found during Stage 4 verification. Asked directly whether multitasking/multiuser is fully +functional, told to close that gap -- then, before any fix was attempted, redirected: the real +gap is a functional OS, and every WIREBIND identity currently births both a console VM and a user +VM together; the alternative is a "human" identity (gets a console) versus an "unattended +instance" (no console by default, ACL-gated attach later). Refined further: an unattended +identity should carry the *same* real cryptographic identity format a thumbdrive does, baked +directly into a birth capsule instead of requiring a physical/virtual attach event -- and the +`USE` crash stays in scope, to be root-caused independently of the architecture question. Then, +before any code: plan this like every other FABRIC feature -- scope it, stage it into testable +chunks, read all four FABRIC files first, and fold in two more concerns: auditing every primitive +for proper error handling, and the standing logging cleanup already flagged in +`project_production_logging_cleanup_needed.md`. + +**All four FABRIC files read** (0/1/2 archival, 3 living) before writing this section, per that +instruction -- FABRIC-0 directly for its Stadium/patron model (§1-17: heat/TTL/pin as three +distinct departure mechanisms, VMs as patrons, the admission test), FABRIC-1/2's identity and +error-handling history via a background pass, cross-checked against source rather than trusted +at face value (see the correction in §XXXII.1 below, where a first-pass count was itself found +wrong on inspection). Every code claim below was re-verified directly against current source +before being written down here, not carried over from any prior draft. + +### XXXII.1 -- `USE` fail-closed-halt: root cause found, not a Stage 4 regression, not a mystery + +**Root cause, confirmed by reading the actual code path:** + +`mama_word_use()` (`src/starkernel/capsule/mama_forth_words.c:443-546`) has **seven** guards, not +four -- verified by reading the whole function, not sampling it. Two set `vm->error` with **zero +console output**; five print a diagnostic and return without touching `vm->error`: + +| Line | Guard | Sets `vm->error`? | +|---|---|---| +| 451-454 | `dsp < 1` (stack underflow -- nothing pushed via `S"`) | **yes, silent** | +| 459-462 | name too long or empty | no -- prints | +| 467 | `vm_ptr()` returns NULL (bad VM address on stack) | **yes, silent** | +| 473-478 | target not found | no -- prints | +| 480-485 | target dead/stillborn | no -- prints | +| 496-501 | target not ready (interpreter not yet enabled) | no -- prints | +| 526-531 | live identity re-verify fails (BINDSTEP) | no -- prints | + +Typing the bare, syntactically-wrong `USE rajames` (two tokens, nothing pushed via `S"`) hits the +451-454 guard on whatever VM is currently interpreting the typed line. + +`sk_repl_step()` (`src/starkernel/repl.c:1317-1338`) has a Hera-vs-guest fault-scoping split, added +2026-09-09 (§XI.4 above): `if (vm == (VM *)sk_get_mama_vm()) { sk_fault_handler(vm); } else { +/* recover, don't halt */ }` -- confirmed by reading both branches directly. **Zuse's own console +session runs on Hera's own VM, not a separate one**: `capsule_zuse_boot.c`'s +`install_and_activate(VM *mama_vm, ...)` calls `vm_zuse_cert_install(mama_vm, ...)` and sets +`mama_vm->zuse_session` directly on the passed-in Hera VM -- no VM is birthed for her (matching +§X.4's "Zuse costs nothing extra" finding, re-confirmed here by reading the actual install path +rather than citing the earlier finding secondhand). + +So a benign syntax mistake typed at Zuse's own prompt is the one case in this system that +deterministically hits the fail-closed halt -- not a WIREBIND/Stage-4 defect, not intermittent, a +direct consequence of three separately-correct decisions (§VIII.1's fail-closed default, Zuse +running on Hera's own VM, and two of `mama_word_use()`'s seven guards breaking its own five-guard +pattern) composing badly. Already confirmed NOT a Stage 4 regression (§XXX, reproduced identically +on the pre-Stage-4 commit) -- this section supersedes that entry's "root cause not chased further" +note with the actual mechanism. + +**Three fixes are possible, named so Bob can pick, not pre-decided:** + +- **(a) Per-word fix, smallest, precedent-exact.** Make both silent guards (line 451-454 and 467) + match their own five siblings: print a diagnostic and `return;` **without** setting `vm->error`. + Two lines changed in one function, no ripple, closes the reported crash exactly. *Recommended + as the immediate fix.* +- **(b) Per-word fix, keep the fault.** Same diagnostic, but still set `vm->error` -- treats a + stack-convention violation as a genuine FORTH-79 ABORT-worthy fault. Diverges from this same + function's own five-guard precedent. +- **(c) Upstream, bigger and more consequential.** Narrow `sk_repl_step()`'s Hera-branch so an + ordinary *interactive* typo at Zuse's console recovers like any other session, while a genuine + boot/capsule-load fault on Hera still halts. Fixes the entire class (any silent-error site, hit + interactively as Zuse, currently halts the machine) in one place -- but directly amends + §VIII.1's deliberate, named fail-closed "no fallthrough surface" ruling. **A gate decision for + Bob, not an implementer's call.** + +**Verification trap, named explicitly** (matches `feedback_confirm_specific_vm_before_concluding`): +whichever fix lands, it must be verified by typing bare `USE ` at **Zuse's own console** (on +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. + +### XXXII.2 -- Human vs. unattended identity/console-birth model + +**Current model, confirmed by reading `capsule_wirebind_try_attach()` end to end +(`capsule_wirebind.c:174-342`):** every WIREBIND attach unconditionally births two VMs -- +`capsule_console_birth()` (a minimal relay proxy, registered under the bare username) and +`capsule_runcap_birth()` (the real identity, `~user`). Pairing is pure name convention +plus one `VM-NAME-REG` call, executed **inside the console VM's own dictionary** +(`vm_interpret((VM *)console_ctx, "S\" \" 3 VM-NAME-REG")`, `capsule_wirebind.c:331-337`) +-- confirmed this is scoped per-console-VM, not a global singleton, since `VM-NAME-REG` mutates +whichever VM interprets it. **This means after-the-fact console pairing already works +structurally**: a console VM named `X` can be birthed later and paired to an already-live +`X~user` by the identical call, with no new pairing mechanism needed. + +**Proposal, as refined by direct instruction:** distinguish two identity *origins*, not two +identity *kinds*: + +- **Human (WIREBIND, thumbdrive-attach-triggered, unchanged):** births console + user VM + together, exactly as today. A physically-present human gets an immediate console. +- **Unattended (capsule-embedded, new):** the same real cryptographic identity format `MINT` + already produces (cert + seed + personality) is baked directly into a birth capsule instead of + requiring a physical/virtual thumbdrive attach event. Births the user VM only -- no console VM, + and does not touch the console-pairing mechanism. A human can attach a console to an + already-live unattended instance later (ACL-gated), by birthing a console VM under its name and + running the same `VM-NAME-REG` call -- the mechanism already confirmed above to work + after-the-fact, not a new capability to invent. + +**A real invariant this must not violate**, confirmed by reading `capsule_wirebind_try_attach()` +against §VIII.1's own ratified policy: `g_wirebind_attached_username` is what +`sk_console_identity_present()` reads (its own comment says so directly, +`capsule_wirebind.c:321-327`), and that function is §VIII.1's live "no thumbdrive, no prompt" +gate. **An unattended birth path must never set this global or otherwise touch the +console-pairing mechanism** -- doing so would bring up a console with no human present, directly +reopening the headless-until-login gate §VIII.1 ratified. State this as an explicit invariant in +whatever code implements unattended birth. + +**Genuinely open, to settle on paper before any code:** +1. Where does an unattended identity's capsule-embedded cert/seed/personality actually get + produced and packaged -- a new `mkcapsule` mode, a new `MINT` variant that writes to a capsule + instead of a device, or something else? `mkcapsule --resolve` already touches capsule-content + generation and was separately flagged (§XXIV) for block-number-collision risk on the same + path -- check whether that's still open before adding a second capsule-content producer. +2. What births an unattended identity, and when -- at boot (like Hermes/Artemis today), on demand + via a new FORTH word, or something else? Determines whether it needs its own + `capsule_*_birth()` entry point or can reuse `capsule_runcap_birth()` with a capsule-sourced + byte buffer instead of a `blkio_dev*`. +3. What does "ACL-gated console attachment later" concretely check -- a capability bit on the + attaching human's own identity, a fixed allowlist, something else? Not designed here. +4. Confirm §VII's per-device xHCI/live-table machinery genuinely has nothing to do with an + unattended identity's own lifecycle (no `blkio_dev`, no device to attach/detach) before + assuming the two paths share no state. + +### XXXII.3 -- Primitive error-handling audit: kernel-only scope, real surface counted + +**Scope, decided explicitly:** this audit covers kernel-side primitives only -- +`mama_forth_words.c`, the five kernel-only `word_source/` additions (`defer_words.c`, +`inference_words.c`, `lifecycle_words_hosted.c`, `log_words.c`, `q48_words.c`), and the +error-propagation machinery itself (`vm_core.c`, `repl.c`). This is where WIREBIND/ACL/Stadium/ +switch-signal logic lives, where both the `USE` bug and the §XXVIII.3 `SWITCH-MARK-WORK` +off-by-one came from, and where a fix carries no dual-target (hosted + kernel) compileability +risk. **The 26 vendored, shared `word_source/` files (arithmetic, stack, string, control, etc.) +are explicitly out of scope for this initiative** -- they are StarForth's own vendored source, +must stay correct in the hosted build too, and any sweep of them is real, separable work: spun +off as its own future `FABRIC-4.md`, not bundled here. + +**Surface, counted directly, not guessed:** `grep -rn "error = 1"` across the kernel-only scope +above finds **107 sites across 8 files** -- `mama_forth_words.c` (51), `defer_words.c` (15), +`vm_core.c` (24), `log_words.c` (13), `inference_words.c` (3), `repl.c` (1), +`lifecycle_words_hosted.c`/`q48_words.c` (0 each). (For reference: the same grep across the full +vendored+kernel tree returns 659 sites/35 files -- that larger number is `FABRIC-4.md`'s scope, +not this one's.) `USE`'s own two sites (§XXXII.1) are two of the 51 in `mama_forth_words.c`. + +**Not scoped as one pass** -- 107 sites still spans 8 files with different owners (word +primitives vs. the interpreter core vs. the REPL dispatch loop). Proposed shape: + +- **Triage first, fix later.** A read-only pass across the 8 files classifying each site as + (i) already prints a diagnostic before/without setting `vm->error` (fine as-is, matches the + pattern `USE`'s five correct guards already follow), (ii) sets `vm->error` with no diagnostic + at all (the `USE` defect's exact shape, worth a message), or (iii) something else worth + flagging individually (a genuine ABORT-worthy fault correctly reported some other way). One + pass, its own commit, a document, no code changes. +- **Fix in file-sized batches afterward**, each its own commit + 3-arch boot -- not 107 + individual commits, not one 107-site commit. +- `USE`'s own fix (§XXXII.1) is exempt from waiting on this -- already fully diagnosed, lands on + its own as the worked example the triage pass can point back to. + +### XXXII.4 -- Logging cleanup: two already-open gaps decided before the audit's fixes land + +**Standing, already-flagged item** (`project_production_logging_cleanup_needed.md`): +`console_println()` overuse where much INFO-level output should route through `log_message()` +instead. Sequenced *ahead* of §XXXII.3's fix stage, not merely adjacent to it: that audit's own +fixes will add diagnostic output at newly-found silent sites, and the policy for where that +output goes needs to be decided first, or the audit's fixes get redone once the cleanup lands. +Precedent already exists and should be the default: `capsule_mint.c`'s `verify_mint()` (§IX.5) +logs entirely via `log_message()` (`LOG_ERROR`/`LOG_INFO`), explicitly to avoid unconditional +console/serial noise. `USE`'s own fix is the deliberate exception -- an interactive typo's +diagnostic belongs on the console the human is looking at, matching its five siblings, not routed +to a log region nobody's reading in the moment. + +**Two already-open logging gaps from §XXVII, named here so this pass explicitly decides whether +they're in scope rather than silently colliding with them:** +1. **`LOG-FLUSH` does not exist.** Both `init.4th`'s and `log_region.c`'s own doc comments + describe it as the normal path into `LOG-APPEND`; grep matches only those two comments. The + only entry points today are the raw primitive or a manual console call. +2. **FIFO eviction is level-blind.** `log_region.c`'s ring buffer evicts strictly oldest-first + once its 32-devblock ceiling is reached, with no awareness of `LOG_ERROR` vs `LOG_DEBUG` -- a + flood of low-level noise can still evict real error history before it's read. + +**Decision needed at this stage's own gate:** fix both alongside the console-vs-log-level policy +pass, or explicitly defer them again as separate follow-on work. Either is fine; leaving it +unstated is not, per this document's own "never silently drop a stale claim" rule. + +### XXXII.5 -- Proposed staging and sequencing + +Matching §XXVIII's own gated-stage shape (each stage its own commit, its own full 3-architecture +`clean qemu` acceptance boot, no bundling): + +1. **Stage A -- `USE` fix.** Land option (a) from §XXXII.1. Verify per the named trap (test at + Zuse's own console, not a WIREBIND guest session). Ratify or reject option (c) (the upstream + Hera-fault-scoping amendment) as a separate, explicitly-flagged follow-on decision -- not + bundled into this stage. +2. **Stage B -- logging policy pass.** Decide and document the `console_println` vs `log_message` + split (§XXXII.4), and rule on the two open §XXVII gaps (`LOG-FLUSH`, level-aware eviction) + before Stage C begins. +3. **Stage C -- error-handling audit, triage only.** The read-only 107-site classification pass + (§XXXII.3), its own commit, no code changes. +4. **Stage D -- error-handling audit, fixes.** File-sized batches applying Stage B's logging + policy to whatever Stage C's triage found in category (ii), each its own commit + 3-arch + acceptance. +5. **Stage E -- unattended identity design.** Settle §XXXII.2's four open questions on paper, + amend this section with the ratified answers, *then* scope an implementation punch list. + +**Nothing in this section has been implemented.** This write-up is the deliverable for this +pass -- Stage A begins only once reviewed. +