Scope new initiative: unattended identity model, USE fail-closed-halt root cause, primitive error-handling audit, logging cleanup (FABRIC-3.md §XXXII)
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BWpNjdwPtFLuVLaAq44L9K
This commit is contained in:
Robert Allan James
2026-09-15 07:36:35 -04:00
co-authored by Claude Sonnet 5
parent 16cc74243c
commit 05159c9f9e
+219
View File
@@ -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 correctness regression check, now re-confirmed clean after Stage 4's registry/switch-signal
plumbing changes -- not a preemption-specific test. 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 <name>` 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, `<username>~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\" <user_vm_name>\" 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.