# FABRIC-3.5.md — the Tripod/kernel reshuffle **Status: CLOSED/ARCHIVAL as of 2026-09-22, at tag `v2.1.0`.** Per §XXVI.5's own close condition ("this document closes when the tag exists"). Produced the full design ruling for the Tripod/kernel reshuffle: Hermes moves from a FORTH VM into the kernel as kernel-Hermes; the Tripod becomes Hera/Artemis/Hestia; every design question this document opened is ruled (§XXX.7 / §XLI's punch list). Execution against that design is recorded in `FABRIC-3.6.md`, not here — all five phases (0–5) closed there, ending in the `v2.1.0` tag this header names. **Nothing is carried forward and no successor is created** — this document is not part of the `FABRIC-0 → -1 → -2 → -3` chain (§XXVI.5), so closing it hands nothing to a successor. **`FABRIC-3.md` remains open, living and authoritative for its own topic** (bare metal boot); nothing here ever superseded it. > **Prior status headers, kept rather than overwritten, per this series' own rule against > silently rewriting a prior state:** > > **REOPENED 2026-09-19**, by direct instruction ("reopen 3.5 and write it up as a gap analysis > section"), to add §XXXI — a gap-analysis sweep of the whole FABRIC set. > > **DESIGN PHASE CLOSED as of 2026-09-19**, by direct instruction ("close the document for > now"). Not yet archival at that point — the execution sequence (surgical strip, build, > Isabelle pass, doc sweep, SBOM, merge, tag) was still entirely ahead. That gap is now closed: > `FABRIC-3.6.md`'s Phases 0–5 are the execution this header once described as pending. Three things recorded here are explicitly *outside* this reshuffle and still need their own authorization — the `.claude/CLAUDE.md`/`MANIFEST.md` documentation reconciliation (§XXVI.1), the `src/*.c.bak` hygiene question (§XXII.5), and the stray `refs/heads/v2.0.1` branch and PR #1 (§XXVI.4). --- **Topic and original framing, as opened.** Standalone working document, opened 2026-09-18 by direct instruction ("we write this in `FABRIC-3.5.md` as its own document, it's this important"). Topic: **relocating Hermes into the kernel, reconstituting the Tripod as Hera/Artemis/Hestia, generalizing `BIRTH`, and adopting one fleet-wide failure/recovery ladder.** **Why 3.5 and not 4, and why not a section of 3.** `FABRIC-4.md` is explicitly the lower-discipline scratchpad for theory-stage ideas "caught early... often missing a stated 'why' on purpose." This is not that: it is a ratified architectural direction with a stated shape, and it needs the full discipline every numbered FABRIC document carries. But it is also not bare-metal boot, which is `FABRIC-3.md`'s declared topic, and it is not a successor to `FABRIC-3.md` — **`FABRIC-3.md` remains open, living, and authoritative for its own topic; nothing here supersedes it, and this document does not trigger the close-and-carry-forward discipline** the `FABRIC-0` → `-1` → `-2` → `-3` chain used (that chain triggers on *closing* a document). It sits at 3.5 because it is a peer in rigor and an outsider in topic. **How to use this document.** Same discipline as the rest of the series: write the decision and its reasoning down before building, close items with a dated note citing real evidence, never silently drop a stale claim. New findings and decisions for the reshuffle get added here, not to `FABRIC-3.md`. **Scope discipline, stated up front: this is a reorganization, not an invention.** Per the instruction opening it — "most of the underlying logic (Hermes routing, Artemis block I/O, Hera lifecycle/ACL, Console framebuffer) already exists and is expected to be relocated and rewired rather than rewritten." Any item in this document that turns out to need a genuinely new algorithm is by that fact out of scope and gets flagged, not quietly built. **No code is authorized by this document.** Design only. Per Captain Bob's Law (`.claude/CLAUDE.md`): never apply a fix not explicitly requested. **Provenance convention used below.** This series distinguishes what was read from what was recalled, so each claim says which. **"Traced 2026-09-18"** = the file was read directly while writing this document. **"Per §X"** = the fact is reported by an existing FABRIC section that did its own end-to-end read; it is cited, not re-verified here. **"Open"** = not decided. Several items below are deliberately marked for re-verification before any code is written, because a reorganization that trusts a stale map moves the wrong things. --- ## I. The current shape, traced rather than recalled Before moving anything, what is actually there today. All of §I traced 2026-09-18 against the working tree at commit `e56974e`, except where marked. ### I.1 — Hermes is a FORTH capsule VM, and the routing layer is FORTH, not C `capsules/hermes/init.4th` is short and load-bearing. Its block 5116 says it outright: > Hermes owns the one real, canonical `COMMON-CH`. Every other VM subscribes into it via > `VM-EXEC` (see Artemis's and Hera's own `init.4th`); Hera always exists first, so Hermes > adds her here rather than Hera subscribing to a VM that doesn't exist yet at Hera's own > birth. It then does `S" common:messaging.4th" EXEC`, `MSG-CD-INIT`, `1 MY-CH-ID !`, and adds members `1` and `0` to `COMMON-CH`. That is the whole of Hermes's own identity: it is the VM that happens to hold the canonical channel. The mechanism it holds is `capsules/common/messaging.4th` (504 lines) — **generic per-VM messaging vocabulary that every participating VM loads its own copy of.** This matters more than it first appears. Block 5005 allocates the arenas with `CREATE ... ALLOT`: ``` CREATE MSG-ARENA MSG-MAX MSG-CELLS * CELLS ALLOT CREATE CH-ARENA CH-MAX CH-CELLS * CELLS ALLOT CREATE MBR-ARENA MBR-MAX MBR-CELLS * CELLS ALLOT ``` `CREATE`/`ALLOT` carves out of *the dictionary of whichever VM is interpreting*. So today there is no single routing fabric — there are N per-VM copies of the same vocabulary, and Hermes is distinguished only by convention (it holds `COMMON-CH`; it is routing table index 1). **The arbitration is distributed and conventional, not centralized and structural.** ### I.2 — Hermes is a hard, by-name dependency of its peers > **CORRECTED 2026-09-18 by §XIII.1 — this section undercounts.** It names two call sites; the > full repo-wide inventory is **14 by-name FORTH references across 5 files, plus 3 C-side > sites and a DoE CSV schema site.** The two below are real, are among the only three *live* > ones, and remain the sharpest edge — but read §XIII.1 for the actual extent. Left in place > rather than rewritten, per this series' own "never silently drop a stale claim" rule. Artemis does not talk to an abstract routing layer. It talks to a VM called `Hermes`, by name, via `VM-EXEC`. Two live call sites in `capsules/artemis/init.4th`: - line ~138: `S" 2 ENQUEUE-READY" S" Hermes" VM-EXEC` - line ~501: `S" 2 COMMON-CH @ CH-ADD-MBR" S" Hermes" VM-EXEC` Both execute FORTH text *inside Hermes's own dictionary*. Either one breaks the moment Hermes is not a VM with a dictionary. This is the single most concrete consequence of the move and §III.3 is about it. ### I.3 — The routing table pins 0/1/2 and says so `messaging.4th` block 5006, verbatim comment: > VM routing table. §XIX: 16 slots, not 8 — see `FABRIC-3.md`. Hera/Hermes/Artemis stay > 0/1/2 (`artemis:init.4th` depends on it); identities get NEW slots 3–10, never renumbered. `VM-NAMES-INIT` registers `Hera` 0, `Hermes` 1, `Artemis` 2, then identities from 3 up via `DOE-IDX>MSG-IDX ( doe-idx -- msg-idx ) 2 +`. **"Never renumbered" is an existing, written commitment, and the reshuffle walks straight into it** (§IV.2). ### I.4 — Message types in use, and the free numbers Traced across `messaging.4th`: `SPAWN-EVENT` 1, `PAUSE-EVENT` 2, `RESUME-EVENT` 3, `KILL-EVENT` 4, `CONSOLE-CMD-EVENT` 7, `ELEVATE-REQUEST` 8, `BLK-ATTACH-EVENT` 9. Reserved status sentinels at the top of the range: `MSG-NACKED` 253, `MSG-DELIVERED` 255. **5 and 6 are unallocated gaps; 10 is the next in sequence.** Relevant to §VII, which needs a number for `SOS`. Also relevant: per §XX, `SPAWN-EVENT` was grepped for every consumer across the tree and **has none** — "an unwired placeholder, not working infrastructure." Do not model `SOS` on it as though it were a working precedent. ### I.5 — `BIRTH` is already VM-agnostic in C; Hera's monopoly is registration-time Per §XX, which read `mama_word_birth` (`mama_forth_words.c:229`, `BIRTH`'s C implementation) in full: > It is genuinely VM-agnostic — it operates on whichever `vm` calls it, with no check that > the caller is Hera specifically. The only Hera-specific logic anywhere in it is the reverse > guard preventing Hera from re-birthing *herself*. `capsule_birth_baby()` treats > `vm->stadium_vm_id` generically as "whoever is birthing this VM." So "any VM could > theoretically birth another" is already true in the code... Hera's practical monopoly on > `BIRTH` is a registration-time fact (only `register_mama_forth_words()` registers it), not > a check inside `BIRTH` itself. **This is the single most important pre-existing fact in this document.** §VI is not a redesign of `BIRTH`; it is a change to which VMs get it registered, plus an inheritance rule. ### I.6 — Hestia already has a real bind mechanism, built and live-verified Per §XXXII.2, closed 2026-09-16: `CONSOLE-ATTACH ( name-c name-u -- ok? )` exists, is a plain unconditional primitive (deliberately *not* an identity capability bit — that was tried and rejected on two counts), and was verified end to end on amd64: `UNATTENDED-BIRTH` a VM named `bob` → `CONSOLE-ATTACH bob` → `S" bob" USE` → typing `5 6 + .` at the new console relayed to and executed on the target identity VM, printing `[zuse@bob~user] 11 ok>`. Also live: `capsule_console_birth()` (minimal relay proxy, bare username) paired to `capsule_runcap_birth()` (the real identity, `~user`) by name convention plus one `VM-NAME-REG` executed inside the console VM's own dictionary — **confirmed per-console-VM, not a global singleton, so after-the-fact pairing already works structurally.** **§V is therefore mostly a promotion of an existing, proven mechanism to an architectural role — not new construction.** ### I.7 — The headless-until-login invariant Per §VIII.1 (ratified) and §XXXII.2: `g_wirebind_attached_username` is what `sk_console_identity_present()` reads (`capsule_wirebind.c:321-327`), and that function is the live "no thumbdrive, no prompt" gate. §XXXII.2 states the rule as an explicit invariant for any new birth path: **never set that global, never touch the console-pairing mechanism, or you bring up a console with no human present and reopen a gate that was deliberately closed.** §V and §VI both have to carry this invariant forward. ### I.8 — The idle pump, and why Hera is special-cased in it Per §XX: `repl.c`'s idle loop walks the VM registry every idle beat, `VM-EXEC`ing `"MSG-TICK"` into every *other* live VM to drain its queue, skipping Hera — because Hera *is* the pump and self-targeting `VM-EXEC` would hit a known reentrancy class. Hera instead gets a direct `MSG-TICK` word dispatch in her own context, guarded by a fresh `vm_find_word` + `acl_allow` check every tick rather than a cached flag. **So there is already a kernel-resident component driving the messaging layer's clock.** The pump is in `repl.c`, in C, today. §III is in part an admission that the arbiter's center of gravity is already partly there. --- ## II. What the reshuffle is, in one paragraph Hermes stops being a peer VM on the Stadium floor and becomes a kernel-resident arbiter between the floor and the HAL. It stops being a *client* of ACL-checked, Hermes-routed messaging and becomes the routing and arbitration layer itself. The Tripod — which was Hera, Hermes, Artemis — is reconstituted as **Hera, Artemis, Hestia**, with Artemis keeping its existing storage/block-arbitration role and Hestia taking the vacated third slot. Hestia's own role widens from owning the drawing fabric to being the **bind point** where users and agent VMs (the GPIO VM of `FABRIC-4.md` §2, future networking VMs) attach. `BIRTH` becomes a general primitive any VM with standing may invoke, with authority bounded by inheritance rather than by a separate check. And every VM in the fleet — current and future — adopts one escalation ladder on failure: recover gently, escalate to a from-scratch birth, then fail fast and total. --- ## III. Hermes moves into the kernel ### III.1 — Decided (Captain Bob, 2026-09-18) Hermes is no longer a Tripod VM. It becomes kernel-resident, sitting between the Stadium floor and the HAL. **It no longer participates in Hermes-routed, ACL-checked messaging the way the other VMs do — it *is* the routing/arbitration layer now, not a client of it.** ### III.2 — What that actually changes, structurally The self-reference in "Hermes cannot be a client of Hermes" is the whole point, and it has a concrete reading against §I.1: today Hermes holds `COMMON-CH` inside a FORTH dictionary that is itself subject to the same ACL and Stadium-heat economy as every other VM's. An arbiter whose own arbitration state can be evicted, cooled, or ACL-denied by the mechanism it arbitrates is a circular dependency that happens not to have bitten yet. Moving it into the kernel breaks the circle by construction. Note this is also what makes §VII's `SOS` exception (Hermes cannot route a message announcing its own routing failure) not a special case but a direct corollary — see §VIII. ### III.3 — The by-name `VM-EXEC` dependency has to go somewhere. Open. > **SUPERSEDED 2026-09-18 by §XIV.1/§XIV.2 — this question dissolved.** The three options below > all assume the existing FORTH callers must be preserved. Captain Bob ruled the FORTH > messaging layer legacy, so §XIII.1's inventory becomes input to a dead-FORTH cleanup rather > than a migration constraint. Left in place, not rewritten. §I.2's two Artemis call sites are the sharp edge. `S" ..." S" Hermes" VM-EXEC` requires a target VM with a dictionary. Three shapes are visible; **none is chosen here:** 1. **Kernel primitives replace the by-name calls.** `ENQUEUE-READY` and `CH-ADD-MBR` become registered C words callable directly by any VM, and Artemis simply calls them. Closest to "relocated, not rewritten"; largest registration-surface change. 2. **A vestigial `Hermes` name that the kernel answers.** `VM-EXEC` to `Hermes` is intercepted and serviced by the kernel arbiter, leaving caller text untouched. Smallest diff, but it preserves a fiction and this project has a standing distaste for exactly that (§XXXII.2 Q3's rejection of an unreachable gate on doctrine grounds, not just mechanics). 3. **Artemis subscribes through a new kernel-side registration call at birth**, removing the peer-to-peer step entirely. **Decide this on paper before touching code.** Whichever wins, the two Artemis lines and the `STARTUP-BANNER`/`LOG-INFO"` scaffolding in `capsules/hermes/init.4th` are the concrete edit set, plus whatever else a full grep for `S" Hermes" VM-EXEC` turns up — **that grep has not been run exhaustively for this document and must be, repo-wide, before scoping.** ### III.4 — The per-VM-arena question. Open, and the biggest unknown here. > **RESOLVED 2026-09-18 by §XIV.1/§XIV.2.** Answered in the "full centralization" direction, > but without the migration cost this section feared: with the FORTH legacy there is no live > state to port. This was named the root dependency throughout §X; it is closed. Left in > place, not rewritten. Per §I.1 the arenas are per-VM dictionary allocations. If arbitration centralizes into the kernel, does `MSG-ARENA`/`CH-ARENA`/`MBR-ARENA` centralize with it, or does each VM keep its own outbox/inbox with only the *routing* centralized? These are very different amounts of work and very different risk: - **Routing-only centralization** keeps `messaging.4th` largely intact, keeps the Stadium heat economy's "sender pays" property (per §XX: `STADIUM-RES-PULL`/`-PUSH` operate on the *calling* VM's own reservoir), and is genuinely a reshuffle. - **Full arena centralization** moves FORTH dictionary state into kernel C, changes every VM's `dict_hash`, and needs the three-arch parity story re-established from scratch. **Not decided.** Flagged here because the executive framing ("relocated and rewired rather than rewritten") holds comfortably for the first and is strained by the second. ### III.5 — Parity consequence, stated so it is not discovered late Any change to what `register_*_words()` registers, or to what `init.4th` loads, changes VM dictionary contents and therefore `dict_hash`. §XX's own verification standard applies: the property that matters is **identical `dict_hash` across amd64/aarch64/riscv64, not an unchanging absolute value.** Expect the absolute hashes to move; require the cross-arch identity to hold. Acceptance is the three-arch QEMU boot to `zuse)ok>` with zero `UNKNOWN WORD` faults, per `.claude/CLAUDE.md`'s non-negotiable criteria — there is no other test. --- ## IV. The Tripod reconstituted: Hera, Artemis, Hestia ### IV.1 — Decided (Captain Bob, 2026-09-18) Tripod = **Hera, Artemis, Hestia.** Artemis keeps its existing role (storage/block arbitration) unchanged. Hestia takes the slot Hermes vacates. ### IV.2 — The "never renumbered" collision. Open, needs a ruling. > **DISSOLVED 2026-09-18 by §XIV.1/§XIV.2 — via option 3 below, exactly as this section > predicted.** The FORTH routing table is legacy, so the slot-numbering question has no > subject. Option 3's own caveat ("settle §III.4 first") was the correct call. Left in place, > not rewritten. §I.3's comment commits to Hera/Hermes/Artemis at 0/1/2 and to identities at 3–10 *never* being renumbered. Hermes leaving index 1 forces a choice, and the existing comment forecloses the laziest option: 1. **Hestia takes index 1.** Tidy, preserves the "Tripod occupies 0/1/2" shape, and requires no identity renumbering — but it silently redefines what slot 1 *means*, and `artemis:init.4th`'s dependence on the numbering is on 2, not 1, so it may be survivable. 2. **Index 1 is retired; Hestia takes a fresh slot.** Honest, costs a slot out of 16, and leaves a permanent hole that needs a comment explaining itself forever. 3. **The routing table stops being a FORTH array at all**, because §III moved routing into the kernel — in which case this question dissolves into §III.4's arena question and should not be answered separately. **Not decided. Note option 3 makes 1 and 2 moot, so settle §III.4 first.** This ordering matters: answering IV.2 before III.4 risks ratifying a table that the kernel move deletes. --- ## V. Hestia's role expands: the bind point ### V.1 — Decided (Captain Bob, 2026-09-18) Beyond owning the drawing fabric, Hestia becomes **the bind point for users and agent VMs** — GPIO VM, future networking VMs, and whatever follows. The attach point that was previously implicit or undecided now lives here, explicitly. ### V.2 — This is mostly promotion of an existing mechanism, not new construction Per §I.6, `CONSOLE-ATTACH` already resolves a target's liveness by name *before* birthing a console (so a typo refuses cleanly with no orphaned VM — verified live), and console/user pairing already works after the fact. The reshuffle's contribution is **declaring that this is the fleet's attach point**, and then asking the question the current mechanism does not yet answer: it was built for *human at a console attaching to an identity VM*. An agent VM — a GPIO VM per `FABRIC-4.md` §2, pinned and born at boot with no human anywhere near it — is a different shape wearing the same word. ### V.3 — Genuinely open 1. **Does an agent VM bind through `CONSOLE-ATTACH` or through a sibling word?** `sk_repl_dispatch_line()`'s pairing check reconstructs the target as `console_get_vm_name() + "~user"` (per §XXXII.2, and the cause of a real bug caught live when a console was named anything else). A GPIO VM is not a `~user` identity. Either the naming convention widens or agent binding takes its own path. 2. **Does binding an agent VM touch `g_wirebind_attached_username`?** It must not — §I.7. State this as an explicit invariant in whatever implements it, exactly as §XXXII.2 required of unattended birth. 3. **Is Hestia's bind role ACL-gated, and if so where?** Per `.claude/CLAUDE.md`'s hard rule and §XXXII.2 Q3's correction: policy belongs in `ACL.4th`, never in C. The shape is `' CONSOLE-ATTACH ACL-PIN` (or a sibling word's equivalent) in `ACL.4th`, not a C-side capability check — and specifically **not** a `vm_identity_has_cap()` gate, which §XXXII.2 proved unreachable because `identity.installed` is 0 for Hera/Hermes/Artemis and for every console-proxy VM. 4. **Does Hestia-as-bind-point survive Hestia being a Tripod member?** A Tripod VM is pinned and born at boot (`session_register()`/`session_set_pinned()`, per `FABRIC-2.md` §H.12 step 4; "pinned sessions never leave," §H.1 — **cited from `.claude/CLAUDE.md`'s and §XX's references, not re-read for this document; verify before relying on it**). Whether the drawing-fabric owner and the bind-point arbiter are the same VM or two roles that happen to share a name is not settled here. --- ## VI. `BIRTH` as a general primitive ### VI.1 — Decided (Captain Bob, 2026-09-18) `BIRTH` is a general primitive, not Hera-exclusive. **Any VM with standing can invoke it.** A birthed VM inherits its ACL/DNA from the birthing VM. **A VM can never birth something with more authority than it itself holds — enforced by inheritance, not by a separate authorization check.** ### VI.2 — Why this is small in C and large in policy Per §I.5 the C implementation is already VM-agnostic; the monopoly is which registration function hands out the word. So the mechanical change is registration, and the design work is entirely in the inheritance rule. The rule as stated is elegant precisely because it needs no gate: if a child's authority is *derived* from the parent's, "cannot exceed the parent" is not a check that can be bypassed, forgotten, or misconfigured — it is a property of how the child is constructed. This is the same doctrine §XXXII.2 Q3 landed on from the opposite direction (a bespoke gate that can never open is worse than no gate), and the same doctrine `ZUSE-ELIGIBILITY-ADD` already carries. ### VI.3 — "ACL/DNA" needs a precise referent. Open, and this is the real work. The system has a **word-level** ACL: four `DictEntry` fields (`acl_ttl`, `acl_allow`, `acl_mode`, `acl_pinned`), a C primitive `ACL-INHERIT` (traced 2026-09-18 in `capsules/ACL.4th`'s own primitive list, line 6), and the semantics "pin is one-way; inheritance clears pin, copies mode" (per `.claude/CLAUDE.md`). What §VI.1 describes is **VM-level** inheritance — a different axis. Two things must be settled before any code: 1. **Is VM-level DNA just "the child's dictionary is built from the parent's, with per-word state carried by the existing `ACL-INHERIT`"?** If yes, this is genuinely a reshuffle and the existing primitive does the work. If no, something new is being invented and that contradicts this document's own scope discipline — flag it rather than build it. 2. **Hard constraint:** `.claude/CLAUDE.md` states "Never add `acl_*` fields to `DictEntry` beyond the four already present." Any DNA design that wants a fifth per-word field is ruled out at the door. If VM-level authority needs state, it belongs on the VM, not on every dictionary entry. ### VI.4 — "With standing" is undefined. Open. §VI.1 says *any VM with standing*. Standing is not currently a concept in this codebase — traced: no such notion appears in the ACL vocabulary or the Stadium words. Candidate readings: (a) standing = simply having `BIRTH` registered, which collapses it into §VI.2's registration question and makes the phrase decorative; (b) standing = a Stadium-economy property (sufficient reservoir to pay for a child, consistent with "sender pays"); (c) standing = an `ACL.4th` policy predicate. **Not decided.** (a) is the minimal reading and the one most consistent with "do not over-engineer"; (b) is the one most consistent with the rest of the fleet's physics. ### VI.5 — Invariants that must survive - The reverse guard preventing Hera from re-birthing herself (per §I.5) still has to hold, and now has to hold for *every* birther with respect to itself. - `capsule_birth_baby()` must stay the generic path — per §XXXII.2 it is already what every `(p)` capsule uses across four call sites, and `capsule_runcap_birth()` was explicitly established as the wrong tool for build-time-sourced births. - Unattended/agent births must not set `g_wirebind_attached_username` (§I.7). - `' BIRTH` must not enter `capsules/ACL.4th` — per `.claude/CLAUDE.md`, `ACL.4th` is shared and portable, `BIRTH` is kernel-only, and the file deliberately omits it (comment at ~line 64). Generalizing `BIRTH` does **not** change this; pin it in a kernel-specific capsule. --- ## VII. One fleet-wide failure/recovery ladder ### VII.1 — Decided (Captain Bob, 2026-09-18) A single escalation shape, applied consistently across the fleet — current VMs and future ones (GPIO, networking, whatever comes): 1. **Recover gently first.** For a VM with continuity to preserve (Hera being the type case), this means **warm restart — resume with prior state intact.** 2. **Escalate if gentle recovery fails.** Fall back to a **from-scratch `BIRTH`** — no continuity, rebuild fleet-state/trust from nothing. 3. **Fail brutally once genuinely exhausted.** No lingering, no partial states. Once recovery options are spent the failure is **fast and total, not a slow degrade.** The value here is uniformity: one shape for Hera, Hermes, Hestia and every future VM, instead of bespoke handling per component. ### VII.2 — `SOS` as a standard message type **Decided:** `SOS` is a standard message type **any** VM can emit — not specific to any one component. It applies to non-Tripod VMs and to two of the three Tripod VMs (Hera and Hestia). Hermes is the exception, for a structural reason — §VIII. **Grounding and open points:** - It takes a number from §I.4's space. **5 and 6 are unallocated gaps; 10 is next in sequence.** Picking a gap vs. appending is a small call but should be made deliberately, not by whoever types first. - **Do not model it on `SPAWN-EVENT`.** Per §XX that constant has zero consumers anywhere in the tree — it is an unwired placeholder. A message type with no dispatcher is exactly the failure mode `SOS` cannot afford. - **Open: who consumes an `SOS`, and what do they do with it?** The executive framing says `SOS` is emitted; it does not say who acts. Given §IX (no VM assumes another's authority), the consumer set is constrained but not specified. ### VII.3 — The existing precedent this should be reconciled with > **WITHDRAWN 2026-09-18 by §XV.2 — this section's premise is wrong.** It assumed the ladder > needs a detection mechanism. Death is compudynamic: no detector, no declarer, nothing to > build. The closing claim below ("a recovery ladder with no detection story only ever triggers > on failures loud enough to notice by accident") does not hold. The wellness-check idea stands > on its own merits, unrelated to this ladder. Left in place, not rewritten. Per §XX, closed as captured-for-later: Captain Bob's self-healing idea — "all VMs participating in a wellness check at their nearest participating wellness center" — a distributed liveness/failure-detection concept "deliberately not built as part of this fix." **That idea and this ladder are the same problem approached from two ends**: wellness checks are how a failure gets *detected*; the ladder is what happens *after*. Neither document has reconciled them. **Flagged as open, and as the natural next thing to settle**, because a recovery ladder with no detection story only ever triggers on failures loud enough to notice by accident. ### VII.4 — Open: what "warm restart" concretely means Warm restart implies a state boundary — what is "prior state" for a VM, and where does it live across the restart? Candidate anchors traced or cited: the Stadium reservoir/heat state, the VM's dictionary, its message arenas (§III.4 — if those centralize into the kernel, warm restart gets *easier*, which is an argument to settle §III.4 first), its `identity` struct. **Not designed here.** Note also that "fail brutally" must not mean "leave the Stadium's conservation invariant K broken" — per `FABRIC-3.md` §XVIII that invariant is continuously verified, so a brutal death still has to return what it held. **Cited from §XVIII's heading, not re-read for this document; verify before relying on it.** --- ## VIII. Hermes's exception: the sinking semaphore ### VIII.1 — Decided (Captain Bob, 2026-09-18) **Hermes cannot emit `SOS`**, because Hermes is the message arbiter and it cannot route a message announcing its own routing failure. Instead of emitting `SOS`, Hermes **raises a semaphore signaling that it is sinking, and holds it until it goes down.** This gives the rest of the fleet a window to shut down cleanly rather than going dark with no warning. ### VIII.2 — Why this is a corollary, not a special case This is the direct consequence of §III.2. The instant Hermes stops being a client of its own routing layer, any failure-announcement it might send has no transport — the transport is the thing that failed. A semaphore is the right shape precisely because it is *not* a message: it does not require the failed subsystem to work, and a held-until-death signal degrades correctly (release = gone) rather than requiring a successful final transmission. Worth stating plainly because it reframes the whole design: **the fleet's failure signalling is two mechanisms, not one, and which one a VM uses is determined by whether it is above or below the routing layer.** Everything on the Stadium floor sends `SOS`. The arbiter beneath it raises a semaphore. That is a clean rule with no exceptions list to maintain. ### VIII.3 — Open > **FULLY SETTLED — §XVI (2026-09-18) and §XXIII (2026-09-19).** The first bullet, where the > semaphore lives, is answered by §XXIII: a kernel-resident single-writer scalar read through a > registered primitive, and a one-way latch rather than a counting semaphore. The note below is > kept as written. > > **PARTLY SETTLED 2026-09-18 by §XVI.** The second bullet (what a clean-shutdown routine does) > is answered: forced `blk_flush(0)`, then `BYE`; Hera's is suicide via a permanent > `arch_halt()` loop. The third (is the window bounded) answers itself — the window ends when > Hera halts, a bound by construction rather than by timer (§XVI.4). **The first bullet, where > the semaphore mechanically lives, remains open.** Left in place, not rewritten. - **Where does the semaphore live, mechanically?** It must be readable by VMs whose messaging is dying. Kernel-resident, below the routing layer, is the only coherent answer, but the concrete form is unspecified. - **What does a VM's "clean shutdown routine" actually do?** §IX makes this fleet-wide behavior, so it needs a single definition — and it must satisfy §VII.4's note about the conservation invariant. - **Is the window bounded?** "Holds it until it goes down" gives no deadline. If Hermes sinks slowly, do VMs shut down on the signal alone or wait for release? --- ## IX. Birther failure: no provisional handoff ### IX.1 — Decided (Captain Bob, 2026-09-18) If a VM responsible for birthing/lifecycle decisions (total Hera failure is the type case) exhausts its own ladder — warm restart, then from-scratch `BIRTH` — and still cannot recover, there is **no provisional handoff of its authority to another VM. No VM assumes partial or stolen authority on another's behalf.** Instead, the rest of the fleet runs its own shutdown routines — the same clean-shutdown behavior §VIII's sinking semaphore triggers, applied fleet-wide. ### IX.2 — Why this is consistent rather than merely austere It is the same invariant as §VI, read from the other side. §VI says a VM can never birth something with more authority than it holds. §IX says a VM can never *acquire* authority it was not born with, even when the holder is gone and the authority is going begging. Together: **authority only ever flows down the birth graph, never sideways and never up.** No VM ends up holding authority it wasn't born with, and no VM limps in a half-failed state — every path terminates in either full recovery or fast, clean termination. That is a strong, checkable property, and it is worth writing down as the thing to defend when some future failure mode makes a provisional handoff look temporarily attractive. ### IX.3 — Open > **PREMISE REJECTED 2026-09-18 by §XV.2.** The first bullet asks the wrong question. Nobody > declares a VM dead — it dies a compudynamic death. The "circular by construction" difficulty > was an artifact of assuming death is a judgment someone renders rather than a physical > outcome of the physics. Left in place, not rewritten. - **What has standing to declare a birther dead?** Circular by construction: the fleet must conclude Hera is gone without Hera participating. Ties directly to §VII.3's undesigned detection story. - **Does fleet shutdown mean the kernel halts, or that the floor empties and the kernel survives?** Materially different outcomes, not specified. --- ## X. Sequencing — dependencies between the open questions These do not decompose into independent work items; several answers foreclose others. The dependency order that fell out of writing this up: 1. **§III.4 (arenas: routing-only vs. full centralization) is the root.** It determines whether §IV.2's routing-table question exists at all, and materially changes §VII.4's warm restart. 2. **§III.3 (the by-name `VM-EXEC` dependency)** — needs a repo-wide `S" Hermes" VM-EXEC` grep first, which has not been done. 3. **§IV.2 (slot numbering)** — only if §III.4 leaves a FORTH routing table standing. 4. **§VI.3 (what "ACL/DNA" refers to)** — independent of the above; gates all of §VI. 5. **§VII.3 (reconciling the ladder with the wellness-check idea)** — gates §VII.2's consumer question and §IX.3's declare-dead question, which are the same question twice. 6. **§V.3 (agent-VM binding)** — needs §VI settled, since an agent VM is birthed before it is bound. --- ## XI. Punch list — design only, no code authorized Nothing here is started. Nothing here authorizes an edit. Numbered for discussion order, not execution order. 1. ✅ **CLOSED 2026-09-18 (§XIII.1).** Repo-wide by-name Hermes inventory: 14 FORTH sites across 5 files, 3 C-side sites, 1 DoE CSV schema site. Only 3 are live. §I.2 corrected. 2. ✅ **CLOSED 2026-09-18 (§XIII.4).** All three citations verified against source; §V.3.4 and §VII.4 may now be relied on. 3. ⬜ Settle §III.4: routing-only vs. full arena centralization. **Root dependency — do first.** 4. ⬜ Settle §III.3 given 3. 5. ⬜ Settle §IV.2 if and only if 3 leaves a FORTH routing table standing. 6. ⬜ Settle §VI.3: precise referent for "ACL/DNA", against the four-field `DictEntry` constraint. 7. ⬜ Settle §VI.4: what "standing" means, or rule it decorative. 8. ⬜ Reconcile §VII.3: the ladder vs. the wellness-check detection idea. 9. ⬜ Assign `SOS` a message-type number deliberately (§VII.2), with a named consumer. 10. ⬜ Specify the sinking semaphore's mechanism and the clean-shutdown routine (§VIII.3). 11. ⬜ Answer §IX.3: what declares a birther dead, and what fleet shutdown means concretely. 12. ⬜ **NEW 2026-09-18 (§XIII.5).** Rule on whether the Hestia Tripod leg is a *new* singleton, distinct from today's plural per-attach console proxies. **Gates §IV.1 and §IV.2** — sits above the slot-numbering question, not beside it. 13. ⬜ **NEW 2026-09-18 (§XIII.2), not part of this reshuffle.** Authorize a `capsules/MANIFEST.md` correction pass for two false claims: block 4055's "immutable ABI" (`FABRIC-2.md` declared it stale and it was never corrected) and block 2049's stated contents. Reported, not fixed, per Captain Bob's Law. **Acceptance for anything that eventually comes out of this list**, per `.claude/CLAUDE.md`'s non-negotiable criteria: three-arch QEMU boot (`amd64`, `aarch64`, `riscv64`, one at a time, in the foreground, `clean` before `qemu`), all reaching `zuse)ok>` with zero `UNKNOWN WORD` faults, logs present under `logs/`, and `dict_hash` identical across all three architectures. There is no other test. --- ## XII. Explicitly not decided anywhere in this document Collected so nothing here is mistaken for settled. **Updated 2026-09-18 after §XV's rulings.** Most of this list is now closed — see §XIV.2 and §XV.6 for what resolved, dissolved, or had its premise rejected. **Still genuinely open — updated again 2026-09-18 after §XVII closed item 12. Nothing structural remains; what follows are small local decisions, one placement question, and one build-time check.** - ~~**Item 9**~~ — **SETTLED 2026-09-19 by §XXI**: actionable to the sender's birther, advisory to peers; payload descriptive, never executable; number **10**. - ~~**§VIII.3 first bullet**~~ — **SETTLED 2026-09-19 by §XXIII**: a single-writer kernel scalar in kernel-Hermes's scope, read through a registered primitive; a one-way latch, not a counting semaphore. **This was the last open design question.** - **Item 16 (§XV.4)** — an implementation check rather than a decision: every message-holding teardown path must reach `STADIUM-EVICT`, verifiable via `fleet_conserved`. - ~~**Item 17**~~ — **SETTLED 2026-09-19 by §XXIV**: a separate word; `BYE` left alone. The reshuffle therefore changes zero live registered words. - ~~**Item 18**~~ — **SETTLED 2026-09-19 by §XXVII**: the empty floor is unreachable (Hera is pinned, unkillable, and every path by which she can cease already halts or reboots the machine). Build nothing; §XVI.7's proposed shape is withdrawn. §XIII.5's proposed Hestia resolution shape remains **analysis, not a ruling.** §XIV.4's and §XIV.5's proposals were both superseded by §XV.4 and §XV.3 respectively. What **is** decided, all by Captain Bob on 2026-09-18 and recorded in §III.1, §IV.1, §V.1, §VI.1, §VII.1, §VII.2, §VIII.1 and §IX.1: Hermes goes into the kernel and becomes the arbiter rather than a client; the Tripod becomes Hera/Artemis/Hestia; Hestia becomes the bind point; `BIRTH` is general with authority bounded by inheritance; the fleet shares one gentle→from-scratch→brutal ladder; `SOS` is a standard message type with Hermes excepted via a sinking semaphore; and a failed birther's authority is never provisionally handed off. --- ## XIII. Punch-list items 1 and 2 worked — CLOSED 2026-09-18, with three corrections to this document's own §I Worked per the series' standing discipline: items 1 and 2 were the two pure-investigation entries on §XI, carrying no design commitment, so they were executable without a ruling. All findings below traced directly against the working tree at `e56974e` on 2026-09-18. **No code written, no design question answered, nothing in the tree modified.** ### XIII.1 — Item 1 CLOSED: the by-name Hermes inventory. §I.2 undercounted by an order of magnitude. **Correction to §I.2, which is wrong as written.** It claimed the by-name dependency was Artemis's "two live call sites" and framed Artemis as *the* dependent. The real inventory is **13 by-name FORTH references across 5 files, plus 3 independent C-side sites.** §I.2's two sites are real and still the sharpest edge, but they are not the extent of it, and the executive framing ("relocated and rewired") has to cover all of it. Full inventory: | # | Site | Call | Live? | |---|---|---|---| | 1 | `common/msg.4th:7` | `S" MSG-ACK-LAST" S" Hermes" VM-EXEC` | **dead** — §XIII.2 | | 2 | `common/msg.4th:10` | `S" MSG-NACK-LAST" S" Hermes" VM-EXEC` | **dead** — §XIII.2 | | 3 | `artemis/init.4th:138` | `S" 2 ENQUEUE-READY" S" Hermes" VM-EXEC` | live | | 4 | `artemis/init.4th:501` | `S" 2 COMMON-CH @ CH-ADD-MBR" S" Hermes" VM-EXEC` | live | | 5 | `process.4th:12` | `S" 1 EVENT-EMIT" S" Hermes" VM-EXEC` (SPAWN) | **dead** — §XIII.2 | | 6 | `process.4th:15` | `S" 2 EVENT-EMIT" S" Hermes" VM-EXEC` (PAUSE) | **dead** | | 7 | `process.4th:21` | `S" 3 EVENT-EMIT" S" Hermes" VM-EXEC` (RESUME) | **dead** | | 8 | `process.4th:26` | `S" 4 EVENT-EMIT" S" Hermes" VM-EXEC` (KILL-VM) | **dead** | | 9 | `doe-campaign.4th:6` | `S" Hermes" BIRTH` | not auto-run | | 10 | `doe-campaign.4th:7` | `S" LOAD-DOE" S" Hermes" VM-EXEC` | not auto-run | | 11 | `doe-campaign.4th:17` | `S" DOE-WORK" S" Hermes" VM-EXEC` | not auto-run | | 12 | `doe-campaign.4th:28` | `S" DOE-WORK" S" Hermes" VM-EXEC` | not auto-run | | 13 | `doe-campaign.4th:54` | `S" 1959 1 EXEC-DOE" S" Hermes" VM-EXEC` | not auto-run | | 14 | `messaging.4th:77` | `S" Hermes" 1 VM-NAME-REG` | live (§I.3) | **The good news is real: only 3 of the 14 are live production paths** (#3, #4, #14). Seven are dead code (§XIII.2), four are in a campaign orchestrator `docs/working/architecture/ DOE-LIBRARY-HOWTO-20260819.md` itself records as "Not auto-run anywhere." **This materially lowers the estimated cost of §III.3** — but it must be re-confirmed rather than trusted, since "not auto-run" is a claim about today's boot path, not a guarantee nothing invokes it. ### XIII.2 — Seven of those sites are dead code, and `MANIFEST.md` carries a claim FABRIC-2 already declared stale **`common/msg.4th` is loaded by nothing.** Traced: `S" common:msg.4th" EXEC` appears exactly once in the tree — inside `common/msg.4th`'s own header comment, as usage documentation. No capsule EXECs it. `capsules/init.4th` (block 2049) loads `ACL.4th`, `block-acl.4th`, `zuse-eligibility.4th`, `lib.4th`, `fabric.4th`, `font.4th`, `common:messaging.4th` — and nothing else. This is **already known and already written down.** `FABRIC-2.md:2770-2773` states it outright: the `HERMES-ACK`/`HERMES-NACK` wrappers "are now obsolete (every VM has its own local `MSG-ACK-LAST`/`MSG-NACK-LAST` — no `VM-EXEC` indirection needed) but the file itself was left in place, unloaded, rather than deleted unprompted; `capsules/MANIFEST.md`'s 'immutable ABI' claim for block 4055 is now stale." `messaging.4th:411` carries the same note from the other side: "common:msg.4th's HERMES-ACK/NACK indirection is retired." **`capsules/MANIFEST.md` was never corrected.** Line 317 still reads: "Immutable: this is the cross-VM ACK/NACK ABI. Every messaging VM (Hera, Hermes, Artemis) loads this at birth. Changing the block or the word names breaks the Hermes delivery protocol." That is false on every clause. A second MANIFEST claim also fails against the source: line 52 describes block 2049 as loading "compudynamics, VM-INIT, lib, common:msg, fleet-k, process; BIRTHs Artemis + Hermes" — `init.4th` block 2049 loads none of `common:msg`/`process` and contains no `BIRTH` at all (Hermes and Artemis are birthed from C — §XIII.3). **`process.4th` is likewise EXEC'd nowhere**, making sites #5–#8 dead with it. **Reported, not fixed**, per Captain Bob's Law ("if you identify a bug, report it; do not fix it unless the user says to"). Flagged here because a reshuffle that reads `MANIFEST.md` as current will conclude the ACK/NACK path is a live immutable Hermes ABI and scope around a constraint that stopped existing in `FABRIC-2.md`'s era. **Recommend a MANIFEST.md correction pass as its own authorized item; it is not part of this reshuffle.** ### XIII.3 — The Tripod is hardcoded as a name-triple in C, in three independent places Not previously recorded in this document. Each is an edit site the moment the Tripod's membership changes (§IV.1): 1. **`capsule_birth.c:793-796`** — `is_fleet_foundation` is literally `vm_name_prefix_eq_nocase(capsule_name, "Hera") || ... "Hermes" || ... "Artemis"`. It gates `StadiumPatronHeader` setup and the `session_register()`/`session_set_pinned()` pinning calls. **This is the Tripod, encoded as a string test.** Swapping Hermes for Hestia is a one-line change here — and that single line is what makes a VM pinned. 2. **`kernel_main.c:864-874`** — `vm_interpret(mama, "S\" Hermes\" BIRTH")` plus a `capsule_vm_find_by_name_nocase("Hermes", ...)` liveness check and console banner. The comment cites `FABRIC-2.md` D.7 (birth-by-message-only, 2026-08-28): the Tripod legs "must be alive session-less so a later thumbdrive-attach flow has a running Hermes/Artemis to message." **Note the stated rationale for Hermes being born early is precisely that other flows need it available — which a kernel-resident arbiter satisfies trivially and permanently.** §III is consistent with D.7's intent, not in tension with it. 3. **`kernel_main.c:1007-1009`** — `sk_vm_switch_signal_register(hermes_entry.vm_id)`, the preemptive context-switch registration from `FABRIC-3.md` §XXVIII. **A fourth site is not code but schema, and is the expensive one: `doe_log.c`.** The per-tick DoE CSV hardcodes the Tripod across six columns — 16/17/18 (`hera_heat_q48`, `hermes_heat_q48`, `artemis_heat_q48`, populated by `doe_log_heat_by_name("Hera"/"Hermes"/ "Artemis")` at lines 206-208) and 23/24/25 (`switch_*_readiness`), with column 22 documented as "0=Hera, 1=Hermes, 2=Artemis by current registration order." **Changing Tripod membership changes the DoE CSV schema**, which bears directly on comparability with every campaign already run (§XXXIV/§XXXV's segmented per-ISA design in `FABRIC-3.md` is mid-flight). Flagged as a real cost, not a blocker, and **not** something to resolve by quietly renaming a column. ### XIII.4 — Item 2 CLOSED: all three flagged citations verified against source §XI item 2 flagged three citations this document took from headings and `.claude/CLAUDE.md` rather than from the source. All three check out; §V.3.4 and §VII.4 may now be relied on. - **`FABRIC-2.md` §H.1** — real, at line 3699, "Session = Stadium patron, admission restated." The pinning claim is at line 3713: pinned sessions are exempt from "the normal departure path (heat decay / `COOL`): they never leave, permanently." Confirmed as §V.3.4 used it. - **`FABRIC-2.md` §H.12** — real, at line 4057, "Implementation punch list (2026-09-03)." Independently corroborated from the code side: `capsule_birth.c:785-788` cites "§H.12 step 4" by name for the `session_register()`/`session_set_pinned()` soft-fail convention. - **`FABRIC-3.md` §XVIII** — confirmed. `K` is `vm_physics_fleet_heat_sum()` over **all** live VMs, which the reservoir-transfer accounting holds exactly at `Q48_ONE`, surfaced as the `fleet_k_q48`/`fleet_conserved` CSV columns. §VII.4's warning stands as written: a brutal death must still return what it held, or it breaks a continuously-verified invariant. ### XIII.5 — New finding, and the most consequential of this pass: Hestia today is plural, ephemeral, and capsule-less **This was not known to §IV or §V when they were written, and it changes what §IV.1 is asking for.** Traced in `capsule_console.c`: - **Hestia has no capsule.** `capsules/` contains `hermes/` and `artemis/` directories but **no `console/`**. A console VM's entire personality is a 3-line C string literal, `CONSOLE_IDENTITY_SRC` (`capsule_console.c:28-31`): `Block 4997`, `S" common:messaging.4th" EXEC`, `MSG-CD-INIT`. That is all of it. - **Hestia is deliberately *not* on the routing table.** The source comment is explicit: "No `COMMON-CH` subscription: a console's own traffic is direct 1:1 with its paired user VM (`CONSOLE-CMD-EVENT`), not broadcast, so there's no need to resolve an index in Hermes's own routing table for it." - **Hestia is plural and per-attach.** `capsule_console_birth(const char *console_name, ...)` is called from three sites (`capsule_wirebind.c:245`, `mama_forth_words.c:1591` and `:2015`) and mints a fresh heap-built single-entry capsule directory each time. There are as many console VMs as there are attachments. **Consequence.** Hera, Hermes and Artemis are singular, pinned, born-at-boot, capsule-backed, routing-table-indexed. Hestia today is **none of those five things.** So §IV.1's "Hestia takes the vacated third slot" is **not** a relocation of an existing pinned VM — as stated it would create a Hestia that does not currently exist. That is worth naming plainly, because it is the one place this document's "reorganization, not invention" scope discipline is genuinely strained. > **CORRECTED 2026-09-18 by §XVII — the paragraph above compares the wrong object.** The > singular pinned Hestia *does* already exist: it is the HAL drawing fabric > (`hal/console.c`, `framebuffer.c`, `vt100.c`, `font_8x16.c`), already singular, permanent > and kernel-resident — it simply has no VM face and no owner. The per-attach proxies this > section measured against are what *binding produces*, not what Hestia *is*. The scope > discipline is therefore **not** strained: the leg is an existing singleton given an owner, > and the only genuinely new artifact is a capsule personality. The resolution shape proposed > below was right; this justification for it was wrong. **RATIFIED — see §XVII.2.** Left in > place, not rewritten. **The shape that resolves it without inventing anything** — offered as analysis, **not ratified, Captain Bob's call**: distinguish **Hestia** (singular, pinned, capsule-backed, the Tripod leg — owns the drawing fabric and is the bind point, per §V.1) from **console proxies** (plural, ephemeral, per-attach — exactly what `capsule_console_birth()` mints today, unchanged). The Tripod leg is new; the proxies are untouched. This reading makes §V.1's "bind point" precise: the leg is what you bind *to*, the proxy is what binding *produces*. ### XIII.6 — §V and §VI are load-bearing for each other, which neither section noticed If Hestia is the bind point (§V.1), it is Hestia that must mint console proxies — and minting a VM is `BIRTH`. Today all three `capsule_console_birth()` call sites run in Hera's or the caller's context. **So "Hestia is the bind point" cannot be implemented while `BIRTH` remains Hera-exclusive; it requires §VI.1's generalization.** They are one change, not two. Mechanically this already works, which strengthens both: two of the three call sites pass `vm->stadium_vm_id` — *whichever VM invoked* — rather than a hardcoded Hera (`capsule_wirebind.c:245` passes `mama_vm->stadium_vm_id` because that path genuinely is Hera's). That matches §I.5's finding that `capsule_birth_baby()` treats `stadium_vm_id` generically as "whoever is birthing this VM." **Parentage is already generic; only registration is not.** This also supplies the first concrete answer to §VI.4's open "what is standing": Hestia needs `BIRTH` to do its declared job, so it has standing by role. That is evidence for reading (a) (standing = having `BIRTH` registered), not a ruling. ### XIII.7 — Effect on §X's sequencing Nothing found here dislodges §III.4 as the root dependency. Two adjustments: - **§III.3 is cheaper than §I.2 implied** (3 live sites, not a broad web) and can be scoped as soon as §III.4 lands. - **§IV.1 needs a prior ruling that §IV.2 does not cover**: is the Tripod's Hestia leg a *new* singleton distinct from today's proxies (§XIII.5)? That question sits *above* the slot numbering, not beside it. **Added to the punch list as item 12.** ### XIII.8 — Punch-list status after this pass - ✅ **Item 1 CLOSED** — inventory complete (§XIII.1), 14 FORTH sites + 3 C sites + 1 CSV schema site; §I.2 corrected. - ✅ **Item 2 CLOSED** — all three citations verified against source (§XIII.4). - ⬜ **Item 12, NEW** — rule on §XIII.5: is the Hestia Tripod leg a new singleton, distinct from today's per-attach proxies? **Gates §IV.1 and §IV.2.** - ⬜ **Item 13, NEW, not part of this reshuffle** — authorize a `capsules/MANIFEST.md` correction pass for the two false claims in §XIII.2 (block 4055 "immutable ABI"; block 2049 contents). Reported, not fixed. Items 3–11 unchanged and still open. --- ## XIV. Two rulings that collapse the root dependency (Captain Bob, 2026-09-18) ### XIV.1 — RULING: the existing FORTH messaging layer is legacy. Kernel Hermes is greenfield. **Captain Bob, 2026-09-18, verbatim:** "as far as Hermes the VM and Hermes the kernel component, forget all the forth existing and call it legacy for now. We'll do a code cleanup for all the dead FORTH anyway." So kernel-Hermes is **not** a migration of `capsules/common/messaging.4th`. It is a new C implementation; the FORTH messaging layer becomes legacy on arrival and is retired by a separate dead-FORTH cleanup pass. This reverses the framing §III.3 and §III.4 were built on — both assumed the existing FORTH had to be carried across. ### XIV.2 — What this collapses Four of this document's open questions dissolve rather than get answered: - **§III.4 (the root dependency) — RESOLVED by fiat, in the "full centralization" direction, but without the migration cost that made it frightening.** The kernel owns message and channel state; `messaging.4th`'s per-VM `CREATE`/`ALLOT` arenas are legacy, not something to centralize. §III.4 feared a painful port of live FORTH state; there is no port. - **§III.3 (the by-name `VM-EXEC` dependency) — dissolved.** The three-option framing (kernel primitives / vestigial name / birth-time registration) was about preserving callers. With the FORTH legacy, §XIII.1's 14-site inventory stops being a migration constraint and becomes an input to the cleanup pass. **§XIII.1's inventory is still the right list — its purpose changed, not its content.** - **§IV.2 (routing-table slot numbering) — dissolved, exactly as §IV.2's own option 3 predicted:** "the routing table stops being a FORTH array at all... in which case this question dissolves into §III.4's arena question." It did. `VM-NAMES-INIT`'s 0/1/2 pinning and the "never renumbered" commitment are legacy artifacts, not constraints on the new design. - **§XI item 13 (the `MANIFEST.md` corrections) — absorbed.** Bob's "we'll do a code cleanup for all the dead FORTH anyway" covers §XIII.2's findings. Still worth doing deliberately; no longer a separate ask. **§XIII.1, §XIII.2 and §XIII.3 keep their value** — the C-side sites (§XIII.3) are *not* legacy and remain real edit sites, and the dead-FORTH inventory now feeds the cleanup. ### XIV.3 — RULING: Artemis is the persistence path. And it needs no layering inversion. **Captain Bob, 2026-09-18:** "For persistence though, use Artemis as much as possible. We're good with that part for sure." Ratified. **A layering question this raises, traced 2026-09-18 and answered cleanly.** Artemis is a VM on the Stadium floor; kernel-Hermes sits *below* the floor. "Persist via Artemis" reads at first like an inversion — the kernel arbiter calling up into a floor VM. It is not, because storage is already two layers, not one: - **The block subsystem is kernel/shared C, below the floor:** `src/block_subsystem.c`, `src/blkio_*.c`, `src/starkernel/virtio/virtio_blk.c`. `capsule_loader.c:98-100` calls `blk_subsys_init()` and `blk_subsys_add_raw_device()` directly, with no Artemis involved at all. - **Artemis is the storage *policy arbiter* on the floor:** it owns attach/registration (`blk_subsys_attach_device()` via the `BLK-ATTACH` primitive — `repl.c:525-531` states it outright, "Artemis's own domain now, not Hera's") and the on-disk Artemis format. So the ruling is satisfiable without inversion, by keeping the two straight: **floor-level and fleet persistence goes through Artemis, its domain and its format; anything kernel-Hermes itself needs uses the same block subsystem Artemis is built on — never a new one, and never by messaging Artemis.** Worth recording that the kernel *already* calls up into Artemis by name today (`repl.c:539-541`, `S" %llu HERA-BLK-ATTACH-REQ" S" Artemis" VM-EXEC`), with a comment explaining it is a direct `VM-EXEC` rather than a real message because Hera can't use her own `MSG-SEND` there. That precedent exists; this section is about not *depending* on it. ### XIV.4 — The one real hazard in the persistence ruling, and the shape that avoids it **Kernel-Hermes must not depend on Artemis for the persistence it needs during its own failure.** This is the same structural trap as §VIII: if Hermes must write something at death and reaching Artemis requires routing, the dependency closes a circle on exactly the mechanism that is broken. §VIII solved the *signalling* case with a semaphore; the persistence case needs the same discipline rather than a second special case. **Recommended shape — analysis, not a ruling, Captain Bob's call: kernel-Hermes holds only reconstructible state.** If the arbiter's routing state can be rebuilt from the live VM registry (which `capsule_birth.c` already maintains as the authority on who exists), then: - Hermes has nothing that *must* be persisted, so the Artemis dependency never arises. - §VII.4's "what does warm restart mean for Hermes" becomes trivial — rebuild from the registry, no saved state to reconcile. - Losing in-flight messages when the arbiter dies is consistent with §VII.1 step 3's "no lingering, no partial states," and with §VIII's clean-shutdown window. This costs message durability across an arbiter death. **Flagged as the real trade**, and the one to rule on: if in-flight messages must survive Hermes dying, Hermes needs durable state, and that durable state cannot route through Artemis at death-time. ### XIV.5 — Still open, and now more urgent, not less: the scheduler firewall Raised 2026-09-18 and **not yet ruled on**. Recorded here because §XIV.1 changes its timing. The fleet already has a scheduler: §XXVIII built timer-driven preemption, and `MSG-SEND` (`messaging.4th` block 5021) already calls `SWITCH-MARK-WORK` → `sk_vm_switch_signal_mark_work()`, setting `has_work` on the target's switch slot; `sk_vm_switch_signal_tick()` switches when `readiness >= SK_SWITCH_READINESS_THRESHOLD` **and** `has_work`. **Message arrival is already a scheduling input.** This directly contradicts `VM-PHYSICS-DYNAMIC-FLEET-DESIGN-20260705.md`'s "Explicitly not wanted: a VM scheduler. Nothing gets built that decides whose turn it is to execute" — a live tension in the project that predates this document and is not this document's to resolve, but is this document's to not make worse. **Why §XIV.1 sharpens it.** Today that coupling runs FORTH `MSG-SEND` → C primitive → switch slot. With the FORTH legacy, **kernel-Hermes becomes the caller of `sk_vm_switch_signal_mark_work()` directly** — eligibility marking moves inside the arbiter. That is the consolidation to guard against, and it happens by default unless the boundary is stated up front. Greenfield is the right time to state it; retrofitting it later is how this becomes a conventional scheduler by accident. **Proposed invariant, for ruling:** *kernel-Hermes carries, resolves and ACL-checks. It publishes facts — "this VM has mail" — and never reads readiness, never orders traffic by priority, and never decides turn order. `switch.c` remains the sole owner of "who runs next."* The argument for it is empirical and from this project's own history: §XXVIII.2 is a record of switch-storms caused by **two sources of truth disagreeing** about who was running (`vm_log_attributed_vm()` vs. the real trampoline state) — QEMU pinned near 100%, serial log frozen solid. This codebase punishes duplicated authority with hangs. Two deciders would be worse than one scheduler. ### XIV.6 — Punch list after these rulings - ✅ **Item 3 (§III.4, the root) — RESOLVED** by §XIV.1. Kernel owns messaging; FORTH is legacy. - ✅ **Item 4 (§III.3) — DISSOLVED** by §XIV.1; §XIII.1's inventory becomes cleanup input. - ✅ **Item 5 (§IV.2) — DISSOLVED** by §XIV.1, as §IV.2 option 3 predicted. - ✅ **Item 13 — ABSORBED** into the dead-FORTH cleanup pass. - ⬜ **Item 14, NEW** — rule on §XIV.5's scheduler firewall invariant. **Raised, not ruled.** - ⬜ **Item 15, NEW** — rule on §XIV.4: does message durability survive an arbiter death? If yes, Hermes needs durable state that cannot route through Artemis at death-time. - ⬜ Items 6, 7, 8, 9, 10, 11, 12 unchanged and still open. **Note the shape of what remains.** With the root dependency resolved, the surviving open items are no longer about *mechanism* — they are about *authority*: who may birth (6, 7), who decides a VM is dead (8, 11), who owns turn order (14), and what survives a death (15). That is a better class of question to be left with, and §IX's "authority only ever flows down the birth graph" is the principle most of them should be tested against. --- ## XV. The authority questions, answered (Captain Bob, 2026-09-18) §XIV.6 observed that the surviving open items were no longer about mechanism but about authority — who may birth, who declares a VM dead, who owns turn order, what survives a death. All four are answered here, and three of the four are answered by **rejecting the question's premise** rather than by naming an owner. That pattern is the finding. ### XV.1 — RULING: any VM may birth a near-clone of itself, carrying its own ACL DNA **Captain Bob, verbatim:** "Any VM can birth another VM that is almost a clone of itself with its own ACL DNA whatever ya wanna call it." This closes **§VI.3** and **§VI.4** together: - **§VI.3 ("ACL/DNA" needs a precise referent) — ANSWERED: inheritance by copy, from the parent.** The child is *almost a clone* of its birther: it starts from the parent's own state and carries **its own** DNA — a copy it owns and may diverge, not a shared reference to the parent's. This is the reading §VI.3 hoped for: it uses the existing word-level `ACL-INHERIT` semantics (pin cleared, mode copied) rather than inventing a VM-level construct, so it stays inside the "reorganization, not invention" scope, and it does not need a fifth `acl_*` field on `DictEntry` (the constraint §VI.3 flagged). - **§VI.4 ("standing" is undefined) — ANSWERED: standing is decorative.** "Any VM can birth" confirms reading (a) — standing means nothing more than having `BIRTH` registered. Per §I.5 the C implementation is already VM-agnostic, so this is a registration change, exactly as §VI.2 predicted. No Stadium-economy gate, no `ACL.4th` predicate. Consistent with `.claude/CLAUDE.md`'s "do not over-engineer — if the user says 'BIRTH is a primitive', that is the complete specification." **Why the authority bound still holds without a check.** A near-clone cannot exceed its parent because it is *built from* the parent. §VI.1's "enforced by inheritance, not by a separate authorization check" is therefore literal, not aspirational — there is no gate to bypass because there is no gate. ### XV.2 — RULING: nobody declares a VM dead. It dies a compudynamic death. **Captain Bob, verbatim:** "Nobody declares a VM dead, it died a compudynamic death." **This rejects the premise of §IX.3 and of §VII.3's detection gap, and both were wrong to ask what they asked.** §IX.3 asked "what has standing to declare a birther dead?" and called it "circular by construction." The circularity was an artifact of assuming death is a *judgment* someone renders. It is not: death is a physical outcome of the compudynamics — heat exhausts, the reservoir empties, the patron leaves the Stadium floor. There is no detector, no quorum, no supervisor, and nothing to build. **This retires the largest scheduler-shaped risk in the whole design.** §VII's ladder appeared to need a supervisor — some component watching liveness and deciding to escalate — and §XIV.5 warned that a supervisor with a timer is a scheduler's twin. With death compudynamic, **that component does not exist and is not needed.** The ladder is self-applied while a VM is alive; once it is dead it is dead, which is exactly §VII.1 step 3's "fast and total, not a slow degrade." No actor, no handoff (§IX.1 already forbade one), no partial states. **Consequence for §VII.3.** The "wellness check at the nearest participating wellness center" idea recorded in `FABRIC-3.md` §XX does **not** need to be reconciled with this ladder as a detection mechanism, because no detection mechanism is required. It remains an independent idea on its own merits. §VII.3's framing ("a ladder with no detection story only fires on failures loud enough to notice by accident") was built on the same wrong premise and is withdrawn. ### XV.3 — RULING: nobody owns turn order. Turn order is compudynamic. **Captain Bob, verbatim:** "Nobody owns the turn order. The turn order is compudynamic." This answers **§XIV.5 (item 14)** with a stronger invariant than the one proposed there. §XIV.5 suggested a firewall — *"`switch.c` remains the sole owner of who runs next."* That named an owner, and naming an owner is what a conventional scheduler is. **The correct invariant names none:** > **Turn order is an emergent output of the compudynamics, not a decision any component makes. > Kernel-Hermes publishes facts into that system — "this VM has mail" — and computes no > ordering. Neither does anything else.** This is consistent with the project's own standing position, independently of this document: `FABRIC-3.md` §XIX records the design direction as "fix the gap **without a scheduler**," with the reasoning that "a fixed round-robin across identities would just be a different hardcoded policy — not in the spirit of" the design. §XV.3 is that same position restated for the reshuffle. **One honest observation, offered as a flag rather than an objection.** Traced 2026-09-18: `sk_vm_switch_signal_tick()` gates on `readiness >= SK_SWITCH_READINESS_THRESHOLD && has_work`, and `SK_SWITCH_READINESS_THRESHOLD` is `50u`, a hardcoded constant (`capsule_vm_switch_signal.c:52`). That is a fixed policy sitting in the middle of a mechanism this section calls compudynamic — the same category §XIX rejected as "a different hardcoded policy." The layering does reconcile: the compudynamic **turn-attractor** (§XIX/§XXI) decides what work is assigned, and the CPU-level switch signal is the mechanical follower that moves the processor. Turn order in the meaningful sense is the attractor, and it is compudynamic as ruled. But the constant `50` is a real seam, and it is the exact place where a hardcoded policy could quietly start deciding turns. **Flagged for later, not proposed as work here** — it predates this reshuffle and is `FABRIC-3.md` §XXVIII's own territory. Noted so it is not discovered later and mistaken for something this document introduced. (`FABRIC-4.md` §1's fixed-then-adaptive `rate` graduation is the precedent for how such a constant earns its way to adaptive: measure against a fixed baseline first, self-tune only after.) ### XV.4 — §XIV.4 reframed: the question was persistence; the answer is conservation §XIV.4 asked whether in-flight messages must survive an arbiter's death, and warned that answering "yes" would force Hermes to hold durable state it could not safely write through Artemis at death-time. **Captain Bob, on that item: "What survives the death of what? I don't understand that last part."** Fair — it was posed as a persistence question, which is the wrong frame and the reason it read as opaque. **The right frame, given §XV.2.** A message is not only content; it is *heat*. Traced 2026-09-18 in `capsules/common/messaging.4th`: `MSG-ALLOC` pulls `Q.SLOT` from the caller's own reservoir before admitting a message (rolling back via `STADIUM-RES-PUSH` on refusal, line 127), and `MSG-FREE-NODE` releases it with `STADIUM-EVICT` (line 135-136). An arbiter dying while holding N messages is holding **N × `Q.SLOT` of fleet heat.** So the question is not "must the payloads be saved." It is: > **Does a dying arbiter return the heat its undelivered messages were holding?** - **Payloads may vanish.** Consistent with §VII.1 step 3's "no lingering, no partial states." - **The heat may not.** `K` = `vm_physics_fleet_heat_sum()` is held at `Q48_ONE` and continuously verified (`FABRIC-3.md` §XVIII, `fleet_k_q48`/`fleet_conserved`). Heat that dies with the arbiter is heat that breaks a live invariant. **The answer to "what survives the death of what" is therefore: nothing survives except K.** **This dissolves §XIV.4's hazard rather than resolving it.** There is nothing to persist, so there is no death-time write, so there is no circular dependency on Artemis, so the stateless-arbiter shape §XIV.4 recommended is simply correct rather than a trade-off. And since death is compudynamic (§XV.2), a VM's death *is* already a Stadium eviction event — returning heat is not a special case bolted onto death, it is what dying consists of. **§XIV.3's "Artemis for persistence" ruling stands untouched and applies to fleet/floor persistence; the arbiter simply never needed any.** **The one thing to verify when this is eventually built** (not a design question, an implementation check): that every message-holding structure's teardown path actually reaches `STADIUM-EVICT`, so a mass teardown returns heat rather than leaking it. `fleet_conserved` already makes that directly testable — a leak shows up as K ≠ `Q48_ONE`, not as a silent error. Added as punch item 16. ### XV.5 — What these four rulings have in common Three of the four answers reject the question rather than answer it. There is no death declarer, no turn-order owner, and nothing to persist — and in each case the thing that seemed to need an authority turned out to be an outcome of the physics instead. The one question that *was* answered directly (§XV.1, who may birth) was answered with "anyone," which also declines to create an authority. **That is the actual defence against becoming a conventional scheduler**, and it is stronger than the firewall §XIV.5 proposed. A firewall constrains a component that owns something. These rulings mean **there is no owner to constrain.** The design's protection is structural, not procedural: authority only ever flows down the birth graph (§IX.2), and everything else — death, turn order, conservation — is physics rather than policy. ### XV.6 — Punch list after these rulings - ✅ **Item 6 (§VI.3, "ACL/DNA" referent) — ANSWERED** (§XV.1): inheritance by copy; existing `ACL-INHERIT` semantics suffice; no fifth `DictEntry` field. - ✅ **Item 7 (§VI.4, "standing") — ANSWERED** (§XV.1): decorative; reading (a) confirmed. - ✅ **Item 8 (§VII.3, ladder vs. detection) — WITHDRAWN** (§XV.2): premise wrong, no detection mechanism required. - ✅ **Item 11 (§IX.3, who declares a birther dead) — PREMISE REJECTED** (§XV.2): nobody; compudynamic death. - ✅ **Item 14 (§XIV.5, scheduler firewall) — ANSWERED** (§XV.3), with a stronger invariant than the one proposed: no owner, not a named owner. - ✅ **Item 15 (§XIV.4, message durability) — DISSOLVED** (§XV.4): reframed from persistence to conservation; nothing survives but K. - ⬜ **Item 16, NEW** (§XV.4) — implementation check, not a design question: verify every message-holding teardown path reaches `STADIUM-EVICT` so mass teardown returns heat. Testable directly via `fleet_conserved`. - ⬜ **Item 9** (§VII.2 — `SOS` message-type number and its consumer) — still open. Note §XV.2 narrows it: with no death-declarer, an `SOS` consumer cannot be a supervisor, so the question is now specifically what a *peer* does on receipt. - ⬜ **Item 10** (§VIII.3 — the sinking semaphore's mechanism, the clean-shutdown routine's definition, and whether the window is bounded) — still open, and now the largest remaining design item. - ⬜ **Item 12** (§XIII.5 — is the Hestia Tripod leg a new singleton, distinct from today's per-attach proxies?) — still open, and still gates §IV. **Remaining open: items 9, 10, 12, 16.** Item 10 is the substantive one; 9 is downstream of it, 12 is independent, and 16 is a build-time check rather than a decision. --- ## XVI. Item 10 settled: clean shutdown is forced flush + `BYE`; Hera's is suicide (Captain Bob, 2026-09-18) **Captain Bob, verbatim:** "A clean shutdown is a VM termination where flush is forced and the VM says BYE. Hera is the special case that should just halt the processor. Call it suicide I guess." This settles §VIII.3's second open point (what a clean-shutdown routine concretely does) and, as a consequence, its third (is the window bounded). Traced 2026-09-18: **the ruling is almost entirely existing mechanism, with exactly one behavioural change to a word that already exists.** ### XVI.1 — Every piece of this already exists - **Forced flush — `blk_flush(0)`.** `block_subsystem.c:988-990`; the source comment states it directly: "**0 is the 'flush all' sentinel**." Already called that way at `block_subsystem.c:849`, and already the Stadium's own write-back action for `STADIUM_BEHAVIOUR_MIGRATE` (`stadium.c:336`, `:373`). Nothing new to build. - **A child VM's `BYE` — `system_word_bye()`** (`src/word_source/system_words.c:143-147`): sets `vm->halted = 1`, signalling the REPL to stop. That is already precisely "a VM termination." Confirmed by `mama_forth_words.c:2262-2263`'s own doc comment: "In child VMs this word is never registered; children use the standard `system_word_bye` which sets `vm->halted` and returns to the parent's REPL." - **Halting the processor — `arch_halt()`**, declared at `include/starkernel/arch.h:64` and implemented on **all three architectures** (`amd64/arch.c:207`, `aarch64/arch.c:126`, `riscv64/arch.c:170`). Three-arch parity already holds, so the non-negotiable acceptance criteria are satisfiable without new per-arch work. **So a child VM's clean shutdown is `blk_flush(0)` then `BYE`** — two existing calls in sequence. This sits comfortably inside the "reorganization, not invention" scope discipline. ### XVI.2 — The one real change: Hera's `BYE` does not currently halt. It cold-restarts. **This is the finding of this section, and it is a behavioural change to a live, registered word — not a gap to fill.** `mama_word_bye()` (`mama_forth_words.c:2265-2271`) is Hera's own `BYE`, and its doc comment says outright: "**Hera-only: reap all children then cold-restart the machine.**" The body is exactly two actions: ```c capsule_vm_kill_all_nonmama(); /* "BYE: reaping children" */ arch_cold_reset(); /* "BYE: cold restart" */ ``` Against Bob's ruling: - **`capsule_vm_kill_all_nonmama()` already matches** the "fleet shuts down" half. (The contract is documented downstream too — `include/starkernel/capsule_birth.h:339` describes it as called "from Hera's BYE immediately before `arch_cold_reset()` to reap all children.") - **`arch_cold_reset()` does not match.** The ruling is *halt*, not restart. A cold reset reboots the machine; suicide stops it. These are opposite outcomes, and today's code does the wrong one. **Implementation shape, traced:** `arch_cold_reset()` is `__attribute__((noreturn))` (`arch.h:67`), but `arch_halt()` is **not** — it halts only until the next interrupt and is called once per idle iteration in normal use (`amd64/arch.c:200-205`'s own comment). A permanent halt is therefore `arch_disable_interrupts()` followed by a `for(;;) arch_halt();` loop — which is exactly the pattern amd64's *own* `arch_cold_reset()` already uses as its unreachable fallback (`for (;;) __asm__ volatile ("cli; hlt");`, `arch.c:218`) and the same shape the kernel panic path uses. **No new primitive; an existing pattern applied deliberately.** **Flagged for a conscious decision, not resolved here.** `.claude/CLAUDE.md` carries a hard rule: "Never modify a registered, tested word to 'fix' it." This is not a fix — it is a ruled behavioural change — which is a different thing, and the rule's own reasoning ("the problem is almost certainly in the caller") does not apply. But the change is real and should be made knowingly rather than discovered in a diff. **Whether Hera's suicide is spelled as a changed `BYE` or as a separate word leaving `BYE`'s cold-restart intact is an implementation choice, and is not decided here.** Worth noting that cold-restart-on-`BYE` is plausibly wanted behaviour for an interactive operator typing `BYE` at Hera's REPL, which is an argument for two words rather than one. ### XVI.3 — A shared-source constraint on where the flush goes `system_word_bye()` lives in `src/word_source/system_words.c` — **vendored/shared source that must compile and behave correctly in the hosted build as well as the kernel** (`.claude/CLAUDE.md`: gate kernel-only code with `#ifdef __STARKERNEL__`). Forcing a `blk_flush(0)` inside `system_word_bye()` itself would change hosted-build behaviour for a word that has nothing to do with this design. **So the forced flush belongs in the kernel-side shutdown path that calls `BYE`, not inside `BYE`.** Stated here as a constraint on implementation so the obvious-looking edit is not made in the obvious-looking place. ### XVI.4 — §VIII.3's third question answers itself: Hera's halt is the bound §VIII.3 asked whether the shutdown window is bounded, noting that "holds it until it goes down" gives no deadline. **It is bounded, and by construction rather than by a timer: the window ends when Hera halts the processor.** Children flush and say `BYE`; Hera reaps whatever remains and stops the machine. There is no deadline to choose, no timeout to tune, and — consistent with §XV.3 — nothing that has to *decide* when time is up. The sequence terminates because its last step is physically terminal. This also satisfies §VII.1 step 3's "no lingering, no partial states" literally: after `arch_halt()` in a disabled-interrupt loop there is no state left to linger in. ### XVI.5 — Item 9 is now half-answered §VII.2 left two questions on `SOS`: which message-type number, and "who consumes an `SOS`, and what do they do with it?" **The second is answered for the two fleet-fatal cases:** on Hermes's sinking semaphore (§VIII.1) and on total birther failure (§IX.1), the response is now concrete — forced flush, `BYE`, and Hera's suicide as the terminal step. **Still genuinely open, and narrower than before:** what a peer does on receiving an *ordinary* VM's `SOS`. A single non-Tripod VM in trouble is presumably not fleet-fatal, so "everyone shuts down" cannot be the universal answer — but §XV.2 rules out the obvious alternative, since with no death-declarer an `SOS` consumer cannot be a supervisor that decides another VM's fate. The remaining question is therefore specifically: **is a peer's `SOS` actionable at all, or is it purely advisory/diagnostic?** Plus the trivial number allocation (§I.4: 5 and 6 are unallocated gaps, 10 is next in sequence). ### XVI.6 — Punch list after this ruling - ✅ **Item 10 (§VIII.3) — SETTLED** (§XVI). Clean shutdown = `blk_flush(0)` + `BYE`; Hera = suicide via a permanent `arch_halt()` loop. Window bounded by Hera's halt (§XVI.4). All mechanism exists on all three architectures. - 🔶 **Item 9 (§VII.2) — HALF-ANSWERED** (§XVI.5). Consumer action defined for the two fleet-fatal cases. Open: whether a peer `SOS` is actionable or advisory, plus the number. - ⬜ **Item 12 (§XIII.5)** — Hestia Tripod leg: new singleton or not? Unchanged; still gates §IV. - ⬜ **Item 16 (§XV.4)** — implementation check: teardown paths must reach `STADIUM-EVICT`. - ⬜ **Item 17, NEW** (§XVI.2) — decide whether Hera's suicide replaces `BYE`'s cold-restart or becomes a separate word. Small, but it changes a live registered word either way. ### XVI.7 — One residual edge, raised not answered **If Hera is already dead, nobody performs the halt.** §XV.2 makes death compudynamic and §IX.1 forbids any VM assuming her authority, so on total Hera failure there is no actor left to run `arch_halt()`. The machine would be a live kernel with an empty Stadium floor — which is precisely the "lingering, partial state" §VII.1 step 3 rules out. The shape that resolves it without violating §IX: **an empty floor is a kernel-observable condition, not a VM's decision.** The kernel noticing "no live VMs remain" and halting is not a VM inheriting Hera's authority — it is the machine having nothing left to run. That keeps authority flowing only down the birth graph (§IX.2) while still terminating. **Offered as analysis, not a ruling — Captain Bob's call.** Added as punch item 18. --- ## XVII. Item 12 CLOSED — "Console" already exists as a singleton; it just has no VM face Taken as closing **item 12** (§XIII.5's Hestia-singleton question), the largest remaining design item and the one gating §IV. **§XIII.5 framed this wrongly, and the error is worth naming before the answer.** It compared the Tripod legs against `capsule_console_birth()`'s per-attach proxies, found Hestia "plural, ephemeral and capsule-less," and concluded that a singular pinned Hestia "does not currently exist" — so §IV.1 would be creating one, straining the reorganization-not-invention discipline. **That comparison picked the wrong object.** Traced 2026-09-18: there are three distinct things called "console" in this kernel, not two. ### XVII.1 — The three consoles, traced 1. **The HAL console — the drawing fabric itself.** `src/starkernel/hal/console.c` (14 KB), `framebuffer.c` (13 KB), `vt100.c` (43 KB), `font_8x16.c` (27 KB) — ~98 KB of kernel C. **Already singular. Already permanent. Already kernel-resident.** It is not a VM and has no owner in the fleet sense. 2. **The console proxy VMs** — `capsule_console_birth()`, plural, ephemeral, minted per attach from a 3-line C string literal (§XIII.5). 3. **The active-console-VM name** — a single global, `console_set_vm_name()`/ `console_get_vm_name()` (`include/starkernel/console.h:190-191`), swapped by every dispatch in the "switch, do work, switch back" pattern. ### XVII.2 — RULING: the Hestia Tripod leg is (1), given a VM face. Not a promoted proxy. The singular, permanent Hestia this design wants **already exists in substance** — it is the drawing fabric. What it lacks is an *owner*: today the fabric is an ownerless kernel singleton that any VM reaches into through a global. So §IV.1's "Hestia takes the vacated third slot" is **not** the invention §XIII.5 feared. It is giving an existing singleton a VM face. That resolves the tension cleanly and without inventing anything: - **The Hestia leg is singular, pinned and born-at-boot** — because the fabric it owns already is all three. - **The proxies are untouched.** They stay plural and ephemeral, which is correct: they are what *binding produces*, not what Hestia *is* (§XIII.5's own phrasing, now properly grounded). - **§V.1's "bind point" falls out rather than being asserted.** The VM that owns the fabric is necessarily what a user or agent VM binds *to*. **§XIII.5's proposed shape was right; its justification was wrong.** It proposed exactly this leg/proxy split but called the leg new. The leg is not new — only its VM face is. ### XVII.3 — The evidence that the fabric actually wants an owner This is not merely tidy. The ownerless global in (3) has already caused a real, live bug, documented in the kernel's own header (`console.h:193-203`): `console_get_vm_name()` alone is **not** safe for save-then-restore, because it returns a pointer into the single internal buffer that `console_set_vm_name()` copies into — so an intervening call in the normal "switch, do work, switch back" pattern overwrites the very bytes the saved pointer points at **before the restore runs**. Found live 2026-08-28; the restore silently no-opped. `console_save_vm_name()` exists solely to work around it. That is the signature of shared mutable state with no owner. **Giving the fabric an owning VM is a structural fix for the class**, not just a reorganization — and it is an argument *for* Bob's reshuffle that this document had not previously identified. ### XVII.4 — What this commits to, concretely Consequences of the ruling, so they are not discovered late: - **Hestia needs a capsule personality.** `capsules/hestia/init.4th` — a new directory beside `hermes/` and `artemis/`. This is the one genuinely new artifact, and it is a capsule, not a mechanism. - **`is_fleet_foundation` changes membership** (`capsule_birth.c:793-796`): the name-prefix triple becomes Hera / Artemis / Hestia. That single line is what makes a VM pinned (§XIII.3), so it is also what makes Hestia a Tripod leg. - **`kernel_main.c:865`'s `S" Hermes" BIRTH` becomes Hestia's birth**, and the switch-signal registration at `:1007` follows it. - **The DoE CSV schema changes** — `doe_log.c`'s six Tripod-named columns (§XIII.3). Still the expensive consequence, still not to be resolved by quietly renaming a column. - **Hestia must have `BIRTH` registered** (§XIII.6): binding mints a proxy, and minting is `BIRTH`. Already settled in principle by §XV.1's "any VM can birth." - **Hestia becomes the owner of the `console_set_vm_name()` discipline** — the fix in §XVII.3 is available once there is an owner, but is **not** in this reshuffle's scope. Flagged, not scheduled. ### XVII.5 — §IV is unblocked, and §XIII.5's marker corrected With item 12 closed, **§IV.1 (Tripod = Hera/Artemis/Hestia) stands as ratified with no outstanding objection**, and §IV.2 remains dissolved per §XIV.2. §XIII.5's "this is the one place the scope discipline is genuinely strained" no longer holds and is corrected there. ### XVII.6 — Punch list after this ruling - ✅ **Item 12 — CLOSED** (§XVII). The Hestia leg is the existing drawing-fabric singleton given a VM face; proxies unchanged. §IV unblocked. - 🔶 **Item 9** — half-answered (§XVI.5): open is whether an ordinary peer's `SOS` is actionable or advisory, plus the number allocation. - ⬜ **§VIII.3 first bullet** — where the sinking semaphore mechanically lives. - ⬜ **Item 16** (§XV.4) — implementation check: teardown paths must reach `STADIUM-EVICT`. - ⬜ **Item 17** (§XVI.2) — does Hera's suicide replace `BYE`'s cold-restart or become a separate word? - ⬜ **Item 18** (§XVI.7) — if Hera is already dead, nobody performs the halt. **Nothing structural remains open.** Items 9, 17 and 18 are small, local decisions; §VIII.3's first bullet is a placement question; item 16 is a build-time check. Every load-bearing architectural question this document opened — what Hermes becomes, what the Tripod is, what `BIRTH` means, who owns death, who owns turn order, what survives, what shutdown is — is now ruled. --- ## XVIII. The Hestia component, designed in full (2026-09-18) §XVII settled *what* the Hestia leg is. This section designs the component. Everything below traced 2026-09-18 against `e56974e`; decisions are marked, proposals are marked, and open questions are marked. ### XVIII.1 — Hestia is three layers, and only the middle one is new | Layer | What | Where it lives today | Change | |---|---|---|---| | **0 — the fabric** | Raw output: framebuffer, VT100, glyph raster, UART | `hal/console.c`, `framebuffer.c`, `vt100.c`, `font_8x16.c` (~98 KB C) | **None.** Stays kernel HAL. | | **1 — Hestia the VM** | Tripod leg. Owns fabric *policy*, is the bind point | **Does not exist** | **New — and it is a capsule, not a mechanism.** | | **2 — console proxies** | Per-attach relay VMs, one per bound user/agent | `capsule_console_birth()` | **None**, except their birther becomes Hestia. | The design is almost entirely layer 1, and layer 1 is mostly a *relocation* of vocabulary that already exists in the wrong dictionary (§XVIII.3). ### XVIII.2 — The constraint that shapes everything: the HAL must never know a VM exists **This is not a preference, it is a defended invariant with a documented rationale** (`include/starkernel/console.h:220-224`, verbatim): > `console.c` is a clean HAL module with no dependency on capsule/WIREBIND logic > (`zuse_session`, `capsule_wirebind_attached_username()`) — pulling either in directly here > would be a **real layering violation, not just a style preference.** So **Hestia-the-VM may not be implemented by making `console.c` VM-aware.** Ownership flows one way only: Hestia reaches *down* into the HAL; the HAL never reaches *up*. **The sanctioned pattern for the cases where the HAL does need something VM-shaped already exists and should be reused rather than reinvented:** `console_set_user_prefix_provider()` (`console.h:231`) — a callback that lets `repl.c` supply the "user" half of the `[user@VMName]` prompt "without `console.c` knowing anything about VMs, sessions, or WIREBIND." Any further HAL→Hestia coupling this design needs takes that shape: a registered callback, never an include. **Consequence, stated plainly:** Hestia's "ownership" of the fabric is by *convention, vocabulary and registration* — it is the VM that holds the drawing words — **not** by any enforcement inside `console.c`. That is weaker than it sounds and is exactly right: it keeps the HAL reusable and the layering intact. ### XVIII.3 — Hestia's dictionary: 1,051 lines of Hestia vocabulary currently live in Hera **The most concrete finding in this section.** `capsules/fabric.4th`'s own header reads: > `fabric.4th` — **Console drawing-fabric coordinate machinery.** `FABRIC-0.md` item 4.3.3. > 45-degree cavalier orthographic projection... Raw pixel write (`PLOT`/`FB-WIDTH`/`FB-HEIGHT`) > is C; **this capsule is the FORTH-side policy on top of it.** It is named Hestia's fabric, it *is* the FORTH-side fabric policy — and it loads into **Hera's** dictionary (`capsules/init.4th` block 2049: `S" fabric.4th" EXEC`, `S" font.4th" EXEC`). | Capsule | Lines | Loads into today | Should load into | |---|---|---|---| | `fabric.4th` | 319 | **Hera** (`init.4th` b2049) | **Hestia** | | `font.4th` | 733 | **Hera** (`init.4th` b2049) | **Hestia** | | `turtle.4th` | 99 | `sdk.4th` (opt-in, not boot) | unchanged — it is SDK, not fabric | **Plus the C primitives underneath:** `PLOT`, `FB-WIDTH`, `FB-HEIGHT` (`src/word_source/framebuffer_words.c:62-64`). Their *registration* moves to Hestia's word table; the implementations do not change. **DECIDED (proposal — Captain Bob's call, but this is the direct consequence of §XVII.2):** `fabric.4th` and `font.4th` move from `init.4th` to `capsules/hestia/init.4th`. This is a pure relocation of 1,051 lines — the single most obviously-correct edit in the whole reshuffle, and the clearest evidence that the fabric wants an owner: it already *has* the vocabulary, just attached to the wrong VM. **Parity consequence** (§III.5): Hera's `dict_hash` will shrink, Hestia's is new. Per §XX's standard, the property that matters is cross-architecture identity, not an unchanging absolute. ### XVIII.4 — Hestia's capsule personality `capsules/hestia/init.4th` — a new directory beside `hermes/` and `artemis/`, and **the one genuinely new artifact this reshuffle creates.** Contents, in load order: 1. `S" common:messaging.4th" EXEC` + `MSG-CD-INIT` — Hestia is a Tripod leg and a full messaging participant. **Note this differs from the proxies**, which deliberately skip `COMMON-CH` (`capsule_console.c:23-26`: "a console's own traffic is direct 1:1 with its paired user VM"). The *leg* subscribes; the *proxies* still do not. 2. `S" fabric.4th" EXEC` and `S" font.4th" EXEC` — per §XVIII.3. 3. Hestia's own bind vocabulary (§XVIII.5). 4. A `WELCOME`/banner word, matching `hermes/init.4th`'s and `artemis/init.4th`'s shape. **Block allocation is a real to-do, not a formality.** `mkcapsule`'s actual constraints, per §XXXII.2's correction: block range **`[2048, 5120)`** and a hard **16-content-line-per-block** cap (*not* the 1024-byte framing `.claude/CLAUDE.md` still describes — that doc error is outstanding). **Block 4997 is already taken** by the proxy's own C string literal (`capsule_console.c:28-31`), so Hestia's range must avoid it. Allocate against `capsule-reserved.txt` and verify with `mkcapsule --lint capsules/` (§XXIV's collision machinery covers this; it is blind to content, only to numbers). ### XVIII.5 — The bind point: flow and vocabulary **Binding, end to end, after the reshuffle:** ``` human attaches thumbdrive agent VM needs a console │ │ ▼ ▼ WIREBIND path CONSOLE-ATTACH │ │ └──────────► Hestia ◄───────────┘ │ BIRTH a proxy (§XV.1 makes this legal: │ any VM may birth) ▼ proxy paired to target (VM-NAME-REG, unchanged) ``` - **`CONSOLE-ATTACH ( name-c name-u -- ok? )` already exists and is live-verified** (§I.6, closed 2026-09-16). It resolves the target's liveness *before* birthing, so a typo refuses cleanly with no orphaned VM. **Keep it exactly as is.** The change is *who runs it*: today the three `capsule_console_birth()` call sites run in Hera's or the caller's context (`capsule_wirebind.c:245`, `mama_forth_words.c:1591`, `:2015`). Under this design Hestia is the birther. - **This is why Hestia needs `BIRTH` registered** (§XIII.6) — binding *mints a proxy*, and minting is `BIRTH`. Already legal per §XV.1. - **Mechanically this already works:** two of the three call sites pass `vm->stadium_vm_id` — whichever VM invoked — so parentage is already generic (§I.5, §XIII.6). Only registration changes. **OPEN — agent-VM binding and the `~user` convention** (§V.3.1, still unresolved). The pairing check in `sk_repl_dispatch_line()` reconstructs the target as `console_get_vm_name() + "~user"` — a convention built for *human at a console → identity VM*. A GPIO VM (`FABRIC-4.md` §2) is not a `~user` identity. §XXXII.2 records that a console named anything else "silently falls back to direct interpretation with no error," caught live. **Two shapes, neither chosen:** (a) widen the convention so the suffix is a property of the *binding*, not hardcoded; (b) give agent binding its own word alongside `CONSOLE-ATTACH`. **(a) is preferable on no-bespoke-gate grounds but touches a live, bug-prone path; flagged rather than decided.** ### XVIII.6 — Headless-until-login survives, and the invariant that makes it survive **Hestia the leg is born at boot. A console session is not.** These must not be conflated, or §VIII.1's ratified headless-until-login policy reopens — the exact failure §XXXII.2 warned about for unattended birth. **INVARIANT, to be stated in whatever code implements Hestia:** > Hestia's birth at boot must not set `g_wirebind_attached_username`, must not cause > `sk_console_identity_present()` (`repl.c:122`) to report an identity, and must not mint a > proxy. Hestia owns the fabric from boot; it presents nothing until something binds. This is the same invariant §XXXII.2 imposed on unattended identity birth, applied to a second path. It is cheap to hold — Hestia births no proxy until asked — but it is exactly the kind of thing that gets violated by a well-meaning "initialise the console at startup" line. ### XVIII.7 — What Hestia does *not* own Stated because a component that owns "the bind point" attracts responsibilities that belong elsewhere, and this document has already had to defend against exactly that (§XV.3): - **Not turn order.** Nothing owns it (§XV.3). - **Not routing.** That is kernel-Hermes (§III), which sits *below* Hestia. - **Not the lifecycle of anything but its own proxies.** Hestia births proxies; it does not birth, kill, or supervise identity VMs. Authority flows down the birth graph only (§IX.2). - **Not the prompt's user half.** That stays `repl.c`'s, supplied to the HAL via the existing provider callback (§XVIII.2). Moving it into Hestia would be the layering violation `console.h` explicitly names. ### XVIII.8 — Hestia's own failure behaviour Hestia is a Stadium-floor VM **above** the routing layer, so §VIII.2's rule applies without an exception: **Hestia emits `SOS`**; only Hermes uses the semaphore. Its clean shutdown is §XVI's: forced `blk_flush(0)`, then `BYE`. **A pleasant property worth recording: Hestia's death degrades gracefully by construction.** Because layer 0 is HAL C (§XVIII.1) and ownership is by convention rather than enforcement (§XVIII.2), a dead Hestia leaves the framebuffer, VT100 and UART fully functional — the fabric simply becomes ownerless again, which is precisely today's status quo. The kernel can still print. **This is the opposite of Hermes**, whose death takes the transport with it. So the Tripod's three legs have three genuinely different failure characters: Artemis's death costs storage arbitration, Hestia's costs only fabric *policy*, Hermes's costs the transport itself — which is why only Hermes needed the semaphore. ### XVIII.9 — Punch list for Hestia Design items, in dependency order. **No code authorized.** 1. ⬜ Ratify §XVIII.3's relocation: `fabric.4th` + `font.4th` move from `init.4th` to `capsules/hestia/init.4th`; `PLOT`/`FB-WIDTH`/`FB-HEIGHT` registration moves to Hestia. 2. ⬜ Allocate Hestia's block range against `capsule-reserved.txt`, avoiding 4997; verify with `mkcapsule --lint` (§XVIII.4). 3. ✅ **SETTLED 2026-09-19 by §XX** — neither option. The pairing is already recorded at bind time; the `~user` reconstruction is a redundant derivation and is removed. Agent binding then needs no new mechanism. 4. ⬜ Confirm Hestia in the `is_fleet_foundation` triple (`capsule_birth.c:793-796`) and its birth at `kernel_main.c:865`, replacing Hermes's (§XVII.4). 5. ⬜ Handle the `doe_log.c` CSV schema change (§XIII.3) — still the expensive consequence. 6. ⬜ State §XVIII.6's headless invariant in the implementation. 7. ⬜ **Not in this reshuffle:** the `console_set_vm_name()` ownerless-global fix (§XVII.3) is now *available* once Hestia exists, but remains out of scope. Flagged, not scheduled. --- ## XIX. RULING: the third Tripod leg is named Hestia, not Console (Captain Bob, 2026-09-19) **The Tripod is: Hera, Artemis, Hestia.** Applied throughout this document — 99 occurrences renamed; three verbatim quotations deliberately left saying "Console" (§XIX.4). ### XIX.1 — The naming convention, stated so it stops being re-litigated `antiprosopos` (Gk. representative/delegate) was considered first and **rejected, on Captain Bob's reasoning, which is the correct reasoning and is recorded here because it generalizes:** > "Now this is just a name of a Greek mythos god, it doesn't have to carry full/perfect > [fidelity], it's only a contextual display... After all, Artemis isn't exactly the perfect > name for a memory manager/storage manager either." That is the convention, and it had never been written down: **a Greek deity name, evocative rather than literal.** Checked against what exists — Hera (queen/mother → the Mama VM) is a close fit, Hermes (messenger → messaging) is the closest, and **Artemis (huntress, wilderness → block storage and freemap arbitration) is a loose fit that has never caused anyone a problem.** Fidelity was never the standard. `antiprosopos` failed on being the wrong *kind* of word: a common noun straining for literal accuracy, which is the opposite of how the other three work. `Hestia` — goddess of the hearth — is a deity, is evocative (the hearth is the fixed centre of the household, where everyone gathers), and carries a pleasing incidental: **Hestia is the one who never leaves.** That maps onto the pinned-session model (`FABRIC-2.md` §H.1, "they never leave, permanently") without having been chosen for it. **Two costs that `antiprosopos` carried are retired by this choice, not merely tolerated:** - **Pattern.** `antiprosopos` is Greek vocabulary, not a deity — it half-joined the set. `Hestia` joins it fully. - **Typo surface.** Twelve characters of unusual spelling, in a path where a VM-name mismatch fails *silently* (§XIX.5). `Hestia` is six, comparable to `Hermes` and `Artemis`. ### XIX.2 — Why renaming at all is structural, not cosmetic §XVII.1 found three distinct things in this kernel all called "console" — the HAL fabric, the VM, and the per-attach proxies — and that collision is exactly what led §XIII.5 to the wrong conclusion about whether a singular Console existed. Naming the VM separately resolves it by construction: | Layer | Name after this ruling | |---|---| | 0 — the drawing fabric (HAL C) | **the console** — `console.c`, unchanged | | 1 — the Tripod leg, owns the fabric | **Hestia** | | 2 — per-attach relay VMs | **console proxies** — unchanged | "Hestia owns `console.c`" is unambiguous in a way "Console owns `console.c`" cannot be. This document has already paid once for that ambiguity; the rename is what stops it recurring. ### XIX.3 — What is NOT renamed The ruling names a VM. It does not rename the subsystem: - **The HAL stays `console.c`/`console.h`** and every `console_*()` function. Renaming them would be churn across ~98 KB of C for no gain, and would re-create the confusion by making the HAL sound like the VM. - **`capsule_console_birth()` / `CONSOLE_IDENTITY_SRC` stay** — they mint console proxies, still called console proxies. - **`CONSOLE-ATTACH` stays.** `.claude/CLAUDE.md`'s "never modify a registered, tested word" applies with more force to *renaming* one, and the word remains accurate: it attaches a console. **Recommended, not ruled.** - **`CONSOLE-CMD-EVENT` and `sk_console_identity_present()` stay** — both are layer 0/2 business (a console session), not Hestia's (§XVIII.6). **Net code-visible rename surface:** the VM's registry name, its capsule directory (`capsules/hestia/init.4th`), and the places the Tripod is enumerated (§XIX.6). ### XIX.4 — Three quotations deliberately still say "Console" Quoted source is not silently edited to match a later decision: 1. **Line 26** — the opening brief's own wording ("Hera lifecycle/ACL, Console framebuffer"). 2. **§XVIII.3** — `capsules/fabric.4th`'s verbatim header, "Console drawing-fabric coordinate machinery." That is what the file says *today*; it changes when the file is edited, and **that edit belongs to the §XVIII.3 relocation, not to this ruling.** 3. **§XVII's heading** — a meta-reference to the name itself, which is that section's subject. ### XIX.5 — `~user` is a separate question, unaffected `antiprosopos` was first raised as a replacement for the `~user` registry-name suffix. **That remains punch item 3** (§XVIII.5), and the Hestia ruling does not touch it. Recorded so the two never get conflated: Traced 2026-09-18 — as a *suffix* it does not fit the buffers. `VM_NAME_MAX` is 64 and `USER_IDENTITY_USERNAME_MAX` 32, so the name fits in principle, but every construction buffer is sized `+ 8`, tuned exactly to `"~user"` (5 chars + NUL = 6): `capsule_wirebind.c:222` (=40), `mama_forth_words.c:1855`/`:1991` and `repl.c:1243` (=72), each guarding on a hardcoded `+ 6`. A 14-byte suffix would not overflow them — it would make them **refuse**, silently failing to bind usernames over 26 characters and registry names over 58. Six `memcpy` sites and five guards hardcode that `6`, and `FABRIC-3.md` §XXVI was itself a truncation fix. **And it would not answer item 3 regardless:** a GPIO VM is neither a user nor a human's representative. The structural fix is making the suffix a property of the *binding*; settle that first and land any suffix rename inside it, since the same eleven sites are touched either way. ### XIX.6 — Edit surface All were already edit sites for the Tripod membership change (§XVII.4); the rename changes the string, not the count: - `capsule_birth.c:793-796` — `is_fleet_foundation`'s prefix triple becomes Hera / Artemis / **Hestia** (matched by `vm_name_prefix_eq_nocase`, as today). - `kernel_main.c:865` — `S" Hestia" BIRTH`; the liveness check and the switch-signal registration at `:1007` follow. - `capsules/hestia/init.4th` — the new capsule directory (§XVIII.4). - `doe_log.c` — CSV columns become `hestia_heat_q48` and `switch_hestia_readiness`. Still the expensive consequence (§XIII.3), but `hestia` is the same width as `hermes`, so the schema churn is a rename rather than a reflow. ### XIX.7 — One hazard, recorded and generalized A VM-name mismatch in this system **fails quietly.** §XXXII.2 records it caught live on the first attempt: a console named anything other than the expected form "silently falls back to direct interpretation with no error" — typing `5 6 + .` printed a direct `11` instead of a relayed one, with nothing reported. `Hestia` is short and ordinary enough that this is no longer an argument about the name. But the class remains: **if the §XVIII.5 binding work touches that path anyway, making an unresolved or mismatched name *say so* would retire the hazard for every VM name, not just this one.** Raised as punch item 19, not scheduled. --- ## XX. Item 3 SETTLED: delete the suffix from the mechanism — neither widen it nor add a word §XVIII.5 posed agent-VM binding as a choice between (a) widening the `~user` convention and (b) a sibling word for agent binding. **Both options are wrong, because both accept a premise the code does not support.** Traced 2026-09-18/19: the pairing is *already recorded explicitly*, and the `~user` reconstruction is a redundant second derivation of information the mechanism already holds. ### XX.1 — The relay does not route by name. It routes by a slot recorded at bind time. `sk_repl_dispatch_line()` (`repl.c:1225-1265`) does two separate things, and only one of them involves the suffix: 1. **The guard** builds `paired_name = console_get_vm_name() + "~user"` (`repl.c:1243-1247`) and checks `capsule_vm_find_by_name(paired_name, ...)` is `VM_STATE_LIVE`. 2. **The actual relay** then sends `CONSOLE-CMD-EVENT 0 3 S" " 0 MSG-SEND` — **to index 3**, with this comment verbatim: *"to-index 3: the fixed convention this console's own `VM-NAME-REG` entry for its paired user VM uses (**set once at pairing time** — see the pairing word)."* `PAIR-TEST`'s own header says the same from the other side (`mama_forth_words.c:1542-1543`): "**Registers the pairing in the console's own VM-name routing table at the fixed index (3) that relay uses.**" **So the target is already stored, per-proxy, at bind time.** The suffix string is used only to answer "is my partner alive?" — a question the stored pairing can answer directly, without deriving anything. ### XX.2 — This is the documented cause of the known silent-failure bug `CONSOLE-ATTACH`'s own doc comment (`mama_forth_words.c:1931-1940`) states it outright: > `sk_repl_dispatch_line()`'s own pairing check (`repl.c`) reconstructs the target as > `console_get_vm_name() + "~user"`... **A console named anything other than the identity's own > base name reconstructs the wrong target string and silently falls back to direct > interpretation — no error, just quietly never relays.** `CONSOLE-ATTACH` already carries a workaround for this: it "takes exactly one name, not an independently chosen console name," collapsing two arguments into one *so the reconstruction cannot disagree*. That is a constraint imposed by the derivation, not by the problem. ### XX.3 — RULING (proposed): the bind records its target; nothing re-derives it **Replace the derivation with a read of what bind time already stored.** - **`sk_repl_dispatch_line()`'s guard** asks "does this console have a recorded pairing, and is that target live?" instead of "does `~user` exist?" - **`CONSOLE-ATTACH`** resolves the target **as given**, rather than appending `~user` (`mama_forth_words.c:2000`). - **`~user` survives as a naming convention, not as a mechanism.** It still disambiguates `rajames` the proxy from `rajames~user` the identity in the prompt — the whole point of the `[user@VMName]` form (§XXV follow-up, 2026-09-13). It simply stops being load-bearing. **Four things fall out, none of which needed designing:** 1. **Agent-VM binding stops being a special case.** A GPIO VM named `gpio` binds by the same unchanged path as `bob~user`, because nothing appends anything to its name. **There is no agent-binding mechanism to build.** 2. **`CONSOLE-ATTACH`'s one-name constraint can relax** if ever wanted — it exists only to prevent a reconstruction that no longer happens. Not proposed as work; noted so the constraint is not later mistaken for a requirement. 3. **The silent-fallback class is dissolved rather than fixed.** With no string to mismatch, the states are "a pairing is recorded" or "none is" — determinate and reportable. **This retires punch item 19** (§XIX.7's "make mismatch loud") by removing what could mismatch, which is strictly better than making it noisy. 4. **The suffix-rename question becomes purely cosmetic** (§XIX.5). Once lookup no longer derives the suffix, the remaining sites only ever *construct* a name. ### XX.4 — The six `memcpy` sites split cleanly in two This is what makes the change small. Traced 2026-09-18: | Site | Role | Under this ruling | |---|---|---| | `capsule_wirebind.c:229` | **names** the identity VM at birth | **stays** — naming | | `mama_forth_words.c:1864` (`UNATTENDED-BIRTH`) | **names** the registry entry | **stays** — naming | | `mama_forth_words.c:1579` (`PAIR-TEST`) | **names** both halves; diagnostic word | **stays** — naming | | `repl.c:1247` | **re-derives** the partner for a liveness guard | **goes** | | `mama_forth_words.c:2000` (`CONSOLE-ATTACH`) | **re-derives** the target | **goes** | **Only the two derivation sites change.** The naming sites are untouched, which is why `~user` can remain a convention without remaining a mechanism. ### XX.5 — What "binding" means for an agent VM, stated because §V.1 conflated two things §V.1 called Hestia "the bind point for users and agent VMs." Worth separating, since the two need different things and only one of them is binding: - **Reachability** — being addressable by other VMs — is **kernel-Hermes's** job (§III), not Hestia's. A GPIO VM is reachable because routing exists, not because it bound to anything. - **Fabric access** — a VM that wants to *draw* — is **vocabulary and ACL**, not binding: it needs Hestia's drawing words (§XVIII.3), gated the `ACL.4th` way if gated at all. - **Binding proper** — acquiring a console proxy so a human can type at a target — is what `CONSOLE-ATTACH` does, and after §XX.3 it works for **any** live VM by name. **So an agent VM does not routinely bind at all.** It binds only when a human wants to attach to it for inspection or control — and that is the ordinary console path, unchanged. The "agent-binding problem" was an artifact of the hardcoded suffix; removing the suffix removes the problem rather than solving it. ### XX.6 — Punch list - ✅ **Item 3 — SETTLED** (§XX.3): the bind records its target; nothing re-derives it. Agent binding needs no new mechanism. - ✅ **Item 19 — RETIRED** (§XX.3.3): dissolved by removing the mismatch, not by making it loud. - 🔶 **Item 9** — unchanged: is an ordinary peer's `SOS` actionable or advisory, plus its number. - ⬜ **§VIII.3 first bullet** — where the sinking semaphore mechanically lives. - ⬜ **Items 16, 17, 18** — unchanged (teardown/`STADIUM-EVICT` check; `BYE` vs. a separate suicide word; the empty-floor halt). **Caveat on scope, per §XIV.1.** `repl.c`'s relay and the routing-slot-3 convention are part of the FORTH messaging layer Captain Bob ruled legacy. This ruling states the *principle* — the pairing is recorded, never derived — which kernel-Hermes must carry forward. It is not a proposal to patch the legacy path in place. --- ## XXI. Item 9 SETTLED: `SOS` travels up the birth graph. It is actionable there and advisory everywhere else. §VII.2 left two questions: who consumes an `SOS` and what do they do with it, and which number. The first turns out not to need a new rule — **§IX.2's "authority only ever flows down the birth graph" already answers it**, and answering it supplies something this document had been missing without noticing. ### XXI.1 — RULING: actionability is a property of the relationship, not of the message - **To the sender's birther — actionable.** The parent may re-birth. This is the parent **exercising its own birth authority** (§XV.1: any VM may birth), *not* inheriting the child's. §IX.2 is untouched: nothing flows sideways or up. - **To every other VM — advisory.** A peer may adjust its *own* behaviour (stop sending to a failing VM, note it, shed work that depended on it). A peer may **not** act on the sender's behalf, re-birth it, or reclaim anything of its own. One rule, no exceptions list: **an `SOS` is actionable exactly where the birth graph already grants authority.** ### XXI.2 — Why this does not smuggle in a supervisor Pressure-tested against the doctrine and the rulings, because "parent watches child and restarts it" is precisely the shape §XIV.5 warned about: - **Message-driven, not polling.** The parent acts on receipt, during its own tick. That is the doctrine's own sanctioned form — `VM-PHYSICS-DYNAMIC-FLEET-DESIGN-20260705.md`: "a VM's own internal state (e.g. Hermes's message queue) determining whether it does anything." No timer, no scan, no watch loop. - **Optional, never obligatory.** A parent that ignores an `SOS` is behaving correctly. Nothing polices it. - **No declarer.** The sender reports *its own* trouble. Nobody judges anyone else's liveness, so §XV.2 holds exactly as ruled. - **No authority transfer.** The parent uses authority it already had, on a VM it already birthed. A supervisor is a component that *must* watch and *may* decide another's fate. This is neither. ### XXI.3 — What this actually closes: the ladder's missing actor §VII.1's ladder has three rungs, and until now only the first and third had an obvious actor. With `SOS` settled, each rung has exactly one: | Rung | Actor | |---|---| | 1. Recover gently (warm restart) | **The VM itself**, while still alive — self-applied (§XV.2) | | 2. Escalate to a from-scratch `BIRTH` | **The birther**, on receipt of the `SOS` | | 3. Fail brutally | **Nobody.** Compudynamic death (§XV.2) | **And this is why §IX.1's fleet-shutdown rule is not a special case.** Hera has no birther — `capsule_birth.c:178` sets her name at the root, and the Tripod legs are birthed *by her* (`kernel_main.c:865`). So for Hera, rung 2 has no actor, and the ladder falls straight through to rung 3. §IX.1's "no provisional handoff → the fleet runs its own shutdown" is not an extra rule bolted on for Hera; **it is what the ladder already does when you have no parent.** The ladder and §IX are the same rule seen from two ends: **rung 2 exists if and only if you have a birther.** ### XXI.4 — An `SOS` must not carry an executable payload A real hazard specific to this system, worth stating before anything is built. In the existing mechanism a message's payload **is FORTH text executed by the receiver** — `messaging.4th` block 5041 says so directly of `BLK-ATTACH-EVENT`: "Payload is FORTH text, executed by the receiver, same as every other message here." An `SOS` whose payload were executed by the birther would let **a failing VM drive its own parent** — precisely inverting the authority direction §XXI.1 exists to preserve, and doing it at the moment the sender is least trustworthy. **So: an `SOS` payload is identifying and descriptive only — who is failing, and what for — never instructions.** The birther decides what to do; the sender only reports. This is a deliberate departure from the "payload is executed text" convention, and the reason is the whole point of the message. ### XXI.5 — `SOS` is best-effort, and that is not a gap A VM that dies instantly emits nothing. There is no `SOS`, nobody notices, and per §XV.2 nobody declares anything — it is simply gone. **This is correct, not a hole to plug.** Covering it would require something watching for absence, which is a detector, which §XV.2 rules out. `SOS` covers exactly the case where a VM can see its own failure coming; the silent case is covered by nothing, deliberately. (`FABRIC-3.md` §XX's wellness-check idea remains the separate, optional thing that *could* address it — on its own merits, not as a dependency of this design. §VII.3 was withdrawn for assuming otherwise.) ### XXI.6 — RULING: `SOS` takes **10**. Do not reuse the gaps. Verified 2026-09-19: message types in use are 1, 2, 3, 4, 7, 8, 9; **5 and 6 are genuinely unallocated** — a repo-wide search for either as a message-type constant returns nothing (the sole `6` is `CH-CELLS`, unrelated). Status sentinels sit at the top: `MSG-NACKED` 253, `MSG-DELIVERED` 255. **Take 10, the next in sequence, rather than filling a gap.** The reasoning is legibility across the transition: during the dead-FORTH cleanup (§XIV.1) legacy constants and the new kernel enumeration coexist, and logs will span both eras. Monotonic allocation means **a number appearing in any log means exactly one thing, forever.** A reused gap would make an old log ambiguous for no gain. The gaps stay gaps; a documented hole is more honest than a silent reuse. **Scope note per §XIV.1:** `SOS` properly belongs to **kernel-Hermes's own message-type enumeration**, since the FORTH layer is legacy. The legacy numbering is not binding on the new enum — but it should be honoured as a floor, for the same log-legibility reason. ### XXI.7 — Named consumers, so this does not become another `SPAWN-EVENT` §I.4 recorded the warning: `SPAWN-EVENT` was grepped across the whole tree and **has zero consumers** — "an unwired placeholder, not working infrastructure." A message type with no dispatcher is exactly what `SOS` cannot be. So, stated explicitly: **`SOS`'s consumer is the sender's birther**, whose handler is the rung-2 re-birth decision (§XXI.3), plus **any peer that chooses to shed dependence on the sender** (advisory, optional, and correct to omit entirely in a first implementation). If no birther is live, the `SOS` is simply never consumed — a determinate outcome, not a silent gap, because the birth graph says plainly whether a consumer exists. ### XXI.8 — Punch list - ✅ **Item 9 — SETTLED** (§XXI). Actionable to the birther, advisory to peers; payload descriptive, never executable; number **10**; consumer named. - ⬜ **§VIII.3 first bullet** — where the sinking semaphore mechanically lives. **Now the last open design question in this document.** - ⬜ **Items 16, 17, 18** — the `STADIUM-EVICT` teardown check; `BYE` vs. a separate suicide word; the empty-floor halt when Hera is already gone. --- ## XXII. Execution method: the surgical strip is punch item 1, and it is iterative (Captain Bob, 2026-09-19) **Captain Bob, verbatim:** "As far as any of the existing forth we anticipate will no longer be used. That's probably going to get totally stripped out before we code. The existing gaps we need to fill in will reveal themselves as we code/test iterate and dead FORTH code falls out at the same time. That's going to be the first very surgical item on the punchlist." So the method is: **strip the anticipated-dead FORTH first, then code; further dead FORTH falls out as coding and testing reveal the real gaps, and gets stripped in the same loop.** This becomes **punch item 1**, ahead of everything else in §XVIII.9 and §XX. ### XXII.1 — Two categories, and only one of them can be stripped first "Strip before we code" must not be read as "delete the working system before the replacement exists." The legacy FORTH splits cleanly, and the split is the ordering: - **Category A — dead *today*, independent of this reshuffle.** Nothing loads or invokes it now. Strippable immediately, before a line of new code, with no dependency on kernel-Hermes existing. This is the genuine first item. - **Category B — dead *on arrival* of kernel-Hermes.** `capsules/hermes/init.4th`, most of `capsules/common/messaging.4th`, the routing table, the slot-3 pairing convention (§XX.1). **These are load-bearing until the replacement boots.** Stripping them first would remove a working messaging layer with nothing behind it. They fall out *during* the iterate loop, as Captain Bob describes — not before it. §XIII.1's fourteen-site inventory is the map for both; §XIII.2 already identified the Category A members it found. ### XXII.2 — The trap: reachability here cannot be established by grep, in either direction **Recorded because this document nearly walked into it while building the strip inventory.** A first pass counted textual references per capsule and produced a list with `init-l8-diverse.4th`, `init-l8-omni.4th`, `init-l8-stable.4th`, `init-l8-temporal.4th`, `init-l8-transition.4th`, `init-l8-volatile.4th`, `sdk.4th` and others at **zero references** — i.e. apparently dead. **They are not dead. They are DoE experiment infrastructure**, and a second pass including `experiments/`, `tools/` and `docs/` found **18–19 references each** for the `init-l8-*` family and 9 for `sdk.4th`. The first pass searched only `capsules/` and `src/`. The failure is structural, not a careless grep: - **It under-reports.** Capsules are birthed *by name from runtime strings* — `S" " BIRTH`, campaign scripts, `EXEC` of a name assembled at run time. Static reference counting cannot see any of that. - **It over-reports.** A capsule named for a common word returns noise: `process` scores 180 hits across the tree, essentially all prose. (`process.4th`'s actual deadness was established the other way — by confirming nothing `EXEC`s it — not by counting.) **So no strip list in this document is authoritative, including any list this document produces.** Reachability must be established per capsule by all three routes: loaded from a boot path, invoked from `experiments/`/tooling, or present in the capsule directory `mkcapsule` bakes into the image. ### XXII.3 — The acceptance asymmetry, which is what makes "surgical" necessary `.claude/CLAUDE.md` is unambiguous that the three-architecture QEMU boot is the only acceptance test there is. **For this particular task it is necessary but not sufficient**, and the gap is specific: - A boot exercises the **boot path**. Deleting something Category A and boot-reachable fails loudly and immediately — good. - A boot does **not** exercise DoE/experiment capsules. Deleting one **passes all three architectures cleanly** and surfaces months later, when a campaign is run. That asymmetry is the real hazard of this item. And the stakes are not only code: `.claude/CLAUDE.md` records that the `logs/` artifacts "are audit artifacts — they are committed to the repo. Do not delete them," and that the DoE campaign report is **patent support material**. Experiment infrastructure is part of a measurement apparatus with an evidentiary role. **Rule, proposed: treat every `experiments/`-reachable and DoE-related capsule as live by default.** Removing one is its own decision with its own justification, never a side effect of a messaging cleanup. ### XXII.4 — How the loop should run Consistent with this series' own discipline and with the acceptance criteria: 1. **Strip Category A only**, in a commit *separate* from any new code, so a single file can be restored without unpicking a feature. Nothing else in the same commit. 2. **Boot all three architectures** after the strip, before writing anything new — establishing that the strip alone is clean, rather than discovering later which half of a mixed commit broke something. 3. **Then code**, and let the gaps reveal themselves as Captain Bob describes. 4. **Each time coding proves a Category B item dead, strip it in its own commit too**, with its own three-arch boot. **Expected and not a defect:** every strip changes `dict_hash`. Per §III.5 and §XX's standard, the property that must hold is **cross-architecture identity**, never an unchanging absolute value. `mkcapsule --lint capsules/` must stay clean throughout, and freed block ranges should be returned to `capsule-reserved.txt` rather than silently reused (§XVIII.4). ### XXII.5 — What rides along - **The `MANIFEST.md` corrections** (§XIII.2, formerly item 13) belong to this pass: block 4055's "immutable ABI" claim that `FABRIC-2.md:2773` already declared stale, and block 2049's wrong contents list. Correct them **as the files they describe are stripped**, so the manifest never describes a file that no longer exists. - **`SPAWN-EVENT`** (§I.4: zero consumers, confirmed twice) is Category A by definition. - **`src/*.c.bak`** — `.claude/CLAUDE.md` flags `vm.c.bak`, `doe_metrics.c.bak` and `inference_engine.c.bak` as tracked-but-stale repo hygiene debt, to "report it if it comes up; don't delete unprompted." It has now come up. **Not part of this reshuffle**, but the natural companion pass — flagged, not scheduled, and still requiring its own authorization. ### XXII.6 — Punch list, re-ordered **1. ⬜ SURGICAL STRIP — Category A dead FORTH.** Establish reachability per §XXII.2's three routes (never by grep alone), strip in isolated commits, three-arch boot each time, `mkcapsule --lint` clean, MANIFEST corrected alongside, freed blocks returned. **Precedes all other items.** Then, unchanged in content but now downstream of it: 2. ⬜ Hestia: relocate `fabric.4th` + `font.4th`; move `PLOT`/`FB-WIDTH`/`FB-HEIGHT` registration (§XVIII.9.1). 3. ⬜ Hestia's block range against `capsule-reserved.txt`, avoiding 4997 (§XVIII.9.2). 4. ⬜ Hestia into `is_fleet_foundation`; `kernel_main.c:865` birth; switch-signal registration (§XVIII.9.4, §XIX.6). 5. ⬜ `doe_log.c` CSV schema change (§XIII.3) — still the expensive consequence. 6. ⬜ State §XVIII.6's headless invariant in the implementation. 7. ⬜ **§VIII.3 first bullet** — where the sinking semaphore mechanically lives. **The last open design question.** 8. ⬜ Item 16 — every message-holding teardown path reaches `STADIUM-EVICT`; verify via `fleet_conserved` (§XV.4). 9. ⬜ Item 17 — Hera's suicide: replace `BYE`'s cold-restart, or a separate word (§XVI.2). 10. ⬜ Item 18 — the empty-floor halt when Hera is already gone (§XVI.7). 11. ⬜ Category B strips, each as coding proves the item dead (§XXII.1, §XXII.4.4). --- ## XXIII. §VIII.3 SETTLED: the sinking latch is a kernel-resident scalar, read through a registered primitive The last open design question in this document. §VIII.1 ruled *what* the signal is and *why* it is not a message; this settles where it mechanically lives and who reads it. ### XXIII.1 — What the mechanism has to satisfy Collected from the rulings, because together they constrain the answer almost completely: 1. **Readable by a VM whose messaging is dying** (§VIII.2) — so it cannot itself route. 2. **Below the routing layer** (§III.2) — kernel-resident, in kernel-Hermes's own scope. 3. **Must not require Hermes to still be working** — the whole point is that the transport is the thing that failed. 4. **Must not make the kernel a supervisor** (§XV.3, §XIV.5) — the kernel may publish a fact; it may not decide another VM's fate. 5. **Must be actionable by a VM that has no thread of its own** — per `FABRIC-3.md` §XXVIII there is no per-VM native stack; every VM runs on the one shared kernel C stack and only executes when dispatched. ### XXIII.2 — RULING: a single-writer kernel scalar, exposed as a registered primitive **The latch is a plain scalar in kernel-Hermes's own translation unit, written only by Hermes, read by every VM through a primitive registered into its dictionary** — e.g. `SINKING? ( -- flag )`. This invents nothing. The registration path is the one §XX of `FABRIC-3.md` already established and fixed: `register_child_vm_words()` hands the eight `STADIUM-*` primitives to every child VM, and Hera's two registration sites mirror them so her dictionary is a proper superset. **A ninth kernel-state reader is that same pattern, not a new mechanism** — and it satisfies (1) and (3) exactly, because reading a scalar needs no queue, no channel, no routing table and no working arbiter. **Deliberately a plain unconditional primitive, not gated.** Same doctrine `CONSOLE-ATTACH` and `ZUSE-ELIGIBILITY-ADD` already carry (§XXXII.2 Q3): restricting who may call it, if that is ever wanted, is `' SINKING? ACL-PIN` in `ACL.4th`, never a bespoke C check. Reading a fact is not an authority. **Keep it a bare scalar, not a field in a larger struct.** Robustness, not style: the reader must get a coherent answer while the writer's own subsystem is failing. A standalone word-sized value cannot be observed mid-update; a field inside a structure being torn down can. ### XXIII.3 — It is a one-way latch, not a counting semaphore — and that is what makes it safe "Semaphore" was the word in the original framing; the accurate word is **latch**, and the distinction matters both for naming and for correctness. **It is monotonic.** Per §VII.1, a VM attempts its own gentle recovery *before* anything is announced. So Hermes raising the latch means recovery has already failed and it is committed to going down — there is no path back to "not sinking." Raise once; never lower. That gives a concurrency story with nothing in it: - **Single writer** (Hermes, mainline), **many readers**, **one irreversible transition.** A reader either sees the old value or the new one, and **both are valid** — seeing "not sinking" one moment before the raise is indistinguishable from having read a moment earlier, which is fine. - **This is the discipline this codebase has already sanctioned twice, and reuses on purpose.** `heartbeat.c:33-34` states it verbatim: "Single writer (mainline, via `vm_tick()`'s Loop #7 site), single reader (the ISR's re-arm call) — **no lock needed**, per §21.1's finding." `FABRIC-3.md` §XXVIII Stage 3 then reused that same sanctioned pattern for a second variable rather than "inventing new locking or overturning the ruling itself." **This is the third such variable, and it takes the same route for the same reason.** Naming it a semaphore in code would imply counting and blocking semantics it does not have and must not acquire. **Call it what it is.** ### XXIII.4 — Who reads it, and when: the kernel delivers the fact, the VM performs the act The subtle part, and the one constraint (5) forces. **A VM cannot poll.** With no per-VM thread (§XXVIII), a VM only runs when dispatched — so "every VM watches the latch" is not implementable as stated. **So the check belongs at the dispatch point, and the split of responsibility is the whole design:** > On giving a VM its turn, the kernel checks the latch. If raised, that VM runs **its own** > clean-shutdown routine — `blk_flush(0)` then `BYE` (§XVI) — **in its own context**, instead > of its normal work. - **The kernel publishes a fact and delivers it.** It does not shut anyone down, does not decide who dies, does not order anything. §XV.3 and §XIV.5 hold. - **Each VM performs its own shutdown**, which is exactly what §IX.1 already ruled for fleet-wide shutdown ("the rest of the fleet runs its own shutdown routines") and §XVI defined the content of. - **No timer, no watcher, no supervisor.** A VM acts when it next runs, which is the doctrine's own sanctioned "a VM's own internal state determining whether it does anything." **And the window closes by itself.** Per §XVI.4, Hera's suicide bounds it: children flush and `BYE` as they are dispatched, Hera reaps what remains and halts the processor. §VIII.3's "is the window bounded?" needs no deadline because the last step is physically terminal — there is nothing to time out. ### XXIII.5 — The two-mechanism rule, now complete §VIII.2 predicted the shape; with §XXI and §XXIII both settled it closes cleanly, with no exceptions list to maintain: | Sender is… | Signal | Why | |---|---|---| | **On the Stadium floor** (Hera, Artemis, Hestia, identity VMs, agent VMs) | **`SOS`**, a routed message (§XXI) | Routing is available to them; it travels up the birth graph to the one VM with authority to act | | **The arbiter beneath the floor** (kernel-Hermes) | **the sinking latch** (§XXIII) | It cannot route a message announcing that routing has failed | **Which mechanism a VM uses is determined entirely by which side of the routing layer it sits on.** Nothing is special-cased, and there is no list of exceptions to keep in sync — a property worth defending if a future component is ever tempted to want "just a small" third signal. ### XXIII.6 — Punch list: the design phase is complete **Every design question this document opened is now ruled.** What remains is build sequencing and three small local decisions: 1. ⬜ **SURGICAL STRIP — Category A** (§XXII.6). Precedes everything. 2. ⬜ Hestia: relocate `fabric.4th` + `font.4th`; move `PLOT`/`FB-WIDTH`/`FB-HEIGHT` (§XVIII.9.1). 3. ⬜ Hestia's block range, avoiding 4997 (§XVIII.9.2). 4. ⬜ Hestia into `is_fleet_foundation`; birth at `kernel_main.c:865`; switch registration (§XIX.6). 5. ⬜ `doe_log.c` CSV schema (§XIII.3) — the expensive consequence. 6. ⬜ §XVIII.6's headless invariant stated in the implementation. 7. ⬜ Item 16 — teardown paths reach `STADIUM-EVICT`; verify via `fleet_conserved` (§XV.4). 8. ⬜ Item 17 — Hera's suicide: replace `BYE`'s cold-restart, or a separate word (§XVI.2). 9. ⬜ Item 18 — the empty-floor halt when Hera is already gone (§XVI.7). 10. ⬜ Category B strips, each as coding proves the item dead (§XXII.4). **Items 8 and 9 are the only two carrying an unmade decision**; the rest are execution. Nothing on this list is authorized by this document — per Captain Bob's Law, no code without an explicit instruction. --- ## XXIV. Item 17 SETTLED: Hera's suicide is a separate word; `BYE` is left alone (Captain Bob, 2026-09-19) **Captain Bob, 2026-09-19:** "Separate word for Hera's suicide, leave BYE alone." ### XXIV.1 — What this buys, beyond settling the question `mama_word_bye()` (`mama_forth_words.c:2265-2271`) keeps its current behaviour exactly: `capsule_vm_kill_all_nonmama()` then `arch_cold_reset()` — reap children, cold-restart the machine. That is the right thing for an operator typing `BYE` at Hera's REPL (§XVI.2 flagged this as the argument for two words), and it stays untouched. **The larger consequence: this reshuffle now modifies the behaviour of zero live registered words.** §XVI.2 flagged Hera's `BYE` as the one place the design proposed changing one, and noted that `.claude/CLAUDE.md`'s hard rule — "Never modify a registered, tested word to 'fix' it" — did not strictly apply to a *ruled* change but that the change should be made knowingly. It is now not made at all. Every word this design touches is either new or unchanged. ### XXIV.2 — The two words differ only in their terminal action They share their first half. Per §XVI.1 and §XXIII.4 everything already exists: | | `BYE` (unchanged) | the suicide word (new) | |---|---|---| | Reap remaining children | `capsule_vm_kill_all_nonmama()` | same | | Terminal action | `arch_cold_reset()` — reboot | `arch_disable_interrupts()` + `for(;;) arch_halt();` — stop | | Registered on | Hera only | Hera only | **The permanent-halt idiom is not new either** — it is the pattern amd64's own `arch_cold_reset()` already uses as its unreachable fallback (`for (;;) __asm__ volatile ("cli; hlt");`, `arch.c:218`) and the same shape the kernel panic path uses. `arch_halt()` is declared at `arch.h:64` and implemented on all three architectures (`amd64:207`, `aarch64:126`, `riscv64:170`), so three-arch parity holds with no per-arch work. **Registration:** Hera-only, the same way `mama_word_bye` is registered only in `register_mama_forth_words()` and never by `register_child_vm_words()`. A child VM must not have it — §IX.1's no-handoff rule means no other VM may stop the machine on Hera's behalf. ### XXIV.3 — Naming: all candidates are free; `SCUTTLE` recommended, not ruled Checked 2026-09-19 — `HALT`, `SCUTTLE`, `SINK`, `DIE`, `EXPIRE`, `SUICIDE` and `GO-DOWN` are **all unregistered**; none collides with an existing word. **Recommendation: `SCUTTLE`.** To scuttle is to deliberately sink the vessel you command — which is exactly what this word does, performed by the one VM with the standing to do it. It also rhymes with the imagery already in the design without colliding with it: Hermes *sinks* (§VIII.1, a thing that happens to it), Hera *scuttles* (a thing she does). **`HALT` is the obvious alternative and the one caution worth stating:** it reads naturally and matches `arch_halt()`, but `vm->halted` already exists and means something quite different — a single VM stopping, not the machine. Reusing the stem invites exactly the ambiguity §XIX renamed a Tripod leg to avoid. **Captain Bob's call; this section does not decide it.** ### XXIV.4 — Where its ACL pin goes: the project's own docs give three different answers The new word is kernel-only and privileged, exactly like `BIRTH` — so it needs the same ACL treatment, and **traced 2026-09-19, there is no single agreed answer in this repo:** 1. **`.claude/CLAUDE.md`'s hard rule:** "ACL policy belongs in `ACL.4th`, never in C. **No policy logic in `kernel_main.c`, no `vm_find_word` + field assignment for pinning.**" 2. **`.claude/CLAUDE.md`, later:** "`' BIRTH` in shared capsules breaks the hosted build — BIRTH is kernel-only. **Pin it in a kernel-specific capsule**, not in `ACL.4th`." 3. **`capsules/ACL.4th`'s own comment (block 4005):** "BIRTH/CAPSULE-BIRTH are omitted: kernel-only, not in hosted VM. **Pinned in C (`kernel_main.c`) after capsule load** instead, so this file stays host-portable." **The live code does (3), and (3) is what (1) forbids.** `kernel_main.c:771-782` is literally `vm_find_word(mama_vm_ptr, "BIRTH", 5)` followed by `birth->acl_mode = ACL_MODE_STRICT; birth->acl_pinned = 1;` — a `vm_find_word` + field assignment for pinning, in `kernel_main.c`, printing "ACL: BIRTH pinned STRICT". It works and it is deliberate; it simply contradicts the stated rule. **Recommendation: follow the live precedent — pin the suicide word in `kernel_main.c` beside `BIRTH` and `CAPSULE-BIRTH`** — on the grounds that matching working code beats matching a rule the working code already breaks, and that a lone exception is worse than a consistent one. `BYE`'s own pin stays where it is, in `ACL.4th:70` (`['] BYE ACL-STRICT ['] BYE ACL-PIN`), which is correct because `BYE` is not kernel-only. **Reported, not fixed** (Captain Bob's Law): `.claude/CLAUDE.md` carries two statements that disagree with each other and with the code. **Added as punch item 20** — a documentation reconciliation, outside this reshuffle, and not something to resolve by quietly editing one of the three. ### XXIV.5 — Punch list - ✅ **Item 17 — SETTLED** (§XXIV): separate word, `BYE` untouched. Reshuffle now changes zero live registered words. - ⬜ **Item 18** (§XVI.7) — the empty-floor halt when Hera is already gone. **The last undecided item in this document.** - ⬜ **Item 20, NEW** (§XXIV.4) — reconcile the three-way ACL-pinning contradiction between `.claude/CLAUDE.md` (twice) and `ACL.4th`/`kernel_main.c`. Documentation, not code; outside this reshuffle. - Build sequencing otherwise unchanged (§XXIII.6), with the surgical strip still item 1. --- ## XXV. DoE constraint relaxed; a formal re-verification pass closes the sequence (Captain Bob, 2026-09-19) **Captain Bob, verbatim:** "Don't worry about the DoE, I'm anticipating a rewrite of its invocative paths. AND at the end redo a lot of Isabelle/HOL." ### XXV.1 — RULING: the DoE CSV schema stops being "the expensive consequence" §XIII.3 named `doe_log.c`'s six Tripod-hardcoded columns as the costly part of changing Tripod membership, and §XVII.4/§XIX.6 carried it forward as such. **With the DoE's invocative paths slated for rewrite, that cost is absorbed rather than paid.** Two items collapse: - **Punch item 5** (the CSV schema change) is no longer a constraint on the reshuffle. The columns become `hestia_heat_q48`/`switch_hestia_readiness` as part of the DoE rewrite, not as a reluctant edit to a schema that had to stay comparable. - **§XIII.1's sites 9–13** — `doe-campaign.4th`'s five `S" Hermes" VM-EXEC` calls — fold into the same rewrite. They were already classed "not auto-run"; they are now explicitly someone else's problem, in a good way. **One thing this does *not* relax, stated to avoid over-reading it.** §XXII.3's strip-safety rule stands on its own footing: the hazard there was a naive grep deleting `init-l8-*`, the workloads and `sdk.4th` **by accident**, passing all three architectures cleanly and surfacing months later. Rewriting how the apparatus is *invoked* is not the same as discarding the apparatus, and its evidentiary role is unchanged — `.claude/CLAUDE.md` still records the `logs/` artifacts as committed audit artifacts and the campaign report as patent support material. **Experiment capsules remain live-by-default during the strip.** ### XXV.2 — RULING: Isabelle/HOL re-verification is the closing item A formal re-proof pass runs **at the end**, after the build work. That gives the sequence a deliberate symmetry: **the surgical strip opens it (§XXII.6 item 1), formal re-verification closes it.** **Correction to `.claude/CLAUDE.md` while establishing scope.** It states `proof/` "contains 23 Isabelle/HOL theory files (same composition as the standalone StarForth repo — 18 core VM/ word-category theories + 5 ACL theories)." Traced 2026-09-19: there are **52 `.thy` files** — 5 `ACL_*` and 47 `StarForth_*` — and **all 52 are listed in `proof/ROOT`**, so none is orphaned. `proof/COVERAGE.md` says 52 and is correct; CLAUDE.md is stale by more than a factor of two. **Added to item 20's documentation reconciliation.** Build invocation is `isabelle build -D proof/` directly; neither Makefile defines a target (that part of CLAUDE.md is accurate). ### XXV.3 — The standard this pass must meet is already written, and it is not "the proofs build" `proof/COVERAGE.md` states the goal in Captain Bob's own framing: > Prove the StarForth VM system as close to bare metal as possible under Isabelle/HOL, and — > just as importantly — **identify precisely what cannot be proven and why. A clean pass/fail > isn't the deliverable; the boundary between "formally verified" and "not, for this specific > reason" is.** So the deliverable of the closing pass is **a restated boundary**, not a green build. A suite that still compiles while quietly covering less than it did would satisfy the build and fail the actual standard. ### XXV.4 — The honest cost: this reshuffle moves messaging *out* of the provable region **Not previously stated anywhere in this document, and it should be, before it is discovered.** `COVERAGE.md` defines what the suite can reach: words "that operate purely on modelled per-VM state (stacks, dictionary, and the ~40 scalar fields this suite has added to an abstract `vm_state` record)" — and, explicitly, where an implementation "reaches outside that model (raw pointers, **file-scope statics**, TIB/stdio, an unmodelled subsystem), the theory says so explicitly rather than silently modelling something else." Today's messaging layer is FORTH over per-VM dictionary state — **inside** the model. Kernel-Hermes makes it C with file-scope statics — **outside** it. Likewise the sinking latch (§XXIII.2) is by design a kernel-resident scalar. **So the reshuffle is very likely a net reduction in formal coverage of the messaging area.** That may well be the right trade — §III.2's circular-dependency argument for moving it is strong — but it is a real cost, and by this project's own standard the pass must *name* it rather than let the boundary quietly move. **Partially offsetting, and cheap:** the one-way latch is close to the easiest thing in this design to prove. Single writer, one irreversible transition, both observable values valid (§XXIII.3) — and `StarForth_Mutex.thy`, `StarForth_Concurrent.thy` and `StarForth_Transition.thy` already exist as the homes for exactly that reasoning. ### XXV.5 — What the pass will actually touch, mapped | Theory | Why this reshuffle touches it | |---|---| | **`ACL_No_Escalation.thy`** | **The central one — see below.** | | `ACL_Inherit_Clears_Pin.thy` | §XV.1's "almost a clone with its own ACL DNA" *is* inheritance semantics | | `ACL_Pin_Monotone.thy` | Same monotonicity shape as the §XXIII.3 latch | | `StarForth_System_Words.thy` | `BYE` lives here; §XXIV adds a Hera-only sibling beside it | | `StarForth_Framebuffer_Words.thy` | `PLOT`/`FB-WIDTH`/`FB-HEIGHT` registration moves to Hestia (§XVIII.3) | | `StarForth_Lifecycle_Words_Hosted.thy` | Birth/lifecycle adjacency | | `StarForth_Mutex` / `_Concurrent` / `_Transition` | The sinking latch's safety argument | **The observation worth carrying into the pass:** `ACL_No_Escalation.thy` already proves a no-escalation property at the **word** level. §VI.1's central safety claim — *a VM can never birth something with more authority than it itself holds* — is **the same theorem one level up**, at VM granularity. And §IX.2's "authority only ever flows down the birth graph, never sideways and never up" is a graph invariant of exactly the kind this suite is good at. **The reshuffle's most important safety property is therefore already half-proven, at a different granularity.** Lifting it is a better-defined task than it would be from a standing start — and per §VI.1 the property is structural (enforced by inheritance, no gate to bypass), which is precisely the kind of claim that survives formalization. ### XXV.6 — Punch list - ✅ **Item 5 (DoE CSV schema) — RELAXED** (§XXV.1). Absorbed by the DoE invocative-path rewrite; no longer a constraint. - ⬜ **Item 21, NEW — the closing Isabelle/HOL pass** (§XXV.2–XXV.5). Runs last. Deliverable is a restated boundary per §XXV.3, explicitly including §XXV.4's coverage loss, not a green build. - ⬜ **Item 20 grows** (§XXV.2): CLAUDE.md's "23 theory files" joins the ACL-pinning contradiction in the documentation reconciliation. - ⬜ **Item 18** (§XVI.7) — the empty-floor halt. **Still the last undecided design item.** - Sequence otherwise unchanged (§XXIII.6): surgical strip first, build, formal re-verification last. --- ## XXVI. The close protocol: doc sweep → SBOM → master HEAD → tag → close this document (Captain Bob, 2026-09-19) **Captain Bob, verbatim:** "and documentation sweep, SBOM + spdx before we make it masters head and tag it. That's when FABRIC-3.5.md is closed." So the tail of the sequence is fixed, and this document now has a defined close condition: 1. Build work (§XXIII.6), ending with the Isabelle/HOL pass (§XXV.2). 2. **Documentation sweep** (§XXVI.1). 3. **SBOM + SPDX regeneration** (§XXVI.2). 4. **Merge to `master` HEAD** (§XXVI.4). 5. **Tag** (§XXVI.3). 6. **Close `FABRIC-3.5.md`** (§XXVI.5). ### XXVI.1 — The documentation sweep, inventoried Item 20 has been accumulating known-stale claims throughout this document. Consolidated, so the sweep is a checklist rather than a hunt. **Every entry below was traced, not recalled:** **`.claude/CLAUDE.md`** — four separate errors found during this design pass: - "23 Isabelle/HOL theory files (18 core + 5 ACL)" → **52**, all in `proof/ROOT` (§XXV.2). - `LITHOS_VERSION` "currently `2.0.1`" → **`2.0.0`** (`Makefile.starkernel:83`); `FABRIC-3.md` §I.2 corrected it down on 2026-09-04 and CLAUDE.md never followed. - The ACL-pinning three-way contradiction (§XXIV.4) — its own hard rule vs. its own later advice vs. `ACL.4th`/`kernel_main.c`'s live behaviour. - `mkcapsule`'s "1024-byte-per-block" framing → the real constraints are block range `[2048, 5120)` and a **16-content-line** cap (§XVIII.4, per §XXXII.2's own correction, still outstanding). - Plus, after this reshuffle: the Tripod described as "(Hera the Mama VM, two Hermes instances, Artemis)" becomes **Hera / Artemis / Hestia**, and the superseded-subsystem note must add `FABRIC-3.5.md`. **`capsules/MANIFEST.md`** (§XIII.2) — block 4055's "immutable ABI" claim that `FABRIC-2.md:2773` already declared stale, and block 2049's wrong contents list. Correct **as the files they describe are stripped** (§XXII.5), so the manifest never describes a file that no longer exists. **Superseded subsystem docs** — `.claude/TRIPOD.md`, `HERMES.md`, `ARTEMIS.md`, `CONSOLE.md` already carry superseded headers, but after this reshuffle they describe a fleet that no longer exists. Same for `docs/03-architecture/tripod/{TRIPOD,HERMES,ARTEMIS,README}.md`, which `.claude/CLAUDE.md` still lists as current for Tripod. **Decide deliberately: update, or mark archival and point at `FABRIC-3.5.md`.** Not a silent edit either way. **Also in scope:** `capsules/README.md` (lists `hermes/init.4th` as a live personality), `ROADMAP.md` / `docs/lithosananke/ROADMAP.md`, `CHANGELOG.md`, `README.md`, `docs/lithosananke/SYSTEM_ARCHITECTURE.md` and `M7.1.md`, and `proof/COVERAGE.md` (after the §XXV pass — and per §XXV.3 its *boundary* statement matters more than its file count). `capsules/BLOCK_MAP.md` is a generated artifact; regenerate, don't hand-edit. ### XXVI.2 — SBOM: a real target, and currently stale **`make sbom`** (`Makefile:740-752`) runs **syft**: `syft dir:. -o spdx-json=sbom.spdx.json -o spdx=sbom.spdx`. Reproducible, not hand-maintained — so this step is "run the target and commit the output," provided `syft` is available. Two things to check rather than assume: - **The committed SBOM was generated 2026-07-24** (`sbom.spdx`'s own `Created:` field) — roughly two months stale before this reshuffle adds or removes a single file. - **`DocumentName: StarForth`**, not LithosAnanke — plausibly an artifact of the monorepo split, since `syft` derives it from the directory name. Worth a look during the sweep; a bill of materials naming the wrong product is exactly the kind of thing a licensee or patent reviewer reads literally. - The target lives in the **hosted `Makefile`**, not `Makefile.starkernel`. ### XXVI.3 — The tag: version semantics here are real, and `2.0.1` is not available **Tags are sacred ground** (`.claude/CLAUDE.md`) — "each tag has logs attached proving its state." And this project has already corrected itself once on exactly this, so the policy must be read before choosing, not after. Per `FABRIC-3.md` §I.2 (2026-09-04), `Makefile.starkernel`'s own versioning policy is **semantic, not sequential**: - **`v2.0.0`** = the QEMU release line, even-major/LTS. - **`v2.0.1`** = the SER5 **hardware-track** line (RDRAND backend + thumbdrive image goal). §I.2 rolled `LITHOS_VERSION` *back* from 2.0.1 to 2.0.0 precisely because "claiming `2.0.1` implies hardware-track progress that was never actually verified on real hardware." **That verification is still `FABRIC-3.md`'s open topic.** This reshuffle is QEMU-only work. **So `2.0.1` must not be used for this**, and the version for the reshuffle is a genuine decision — **Captain Bob's, not this document's.** The shape of it: a structural Tripod change on the QEMU line reads as a minor bump (`v2.1.0`) rather than a patch, and `VERSION` (the embedded engine, currently `3.1.0`) likely moves too, since `BIRTH` registration, the new Hera-only word and the messaging layer all change. Both are hand-edited in `Makefile.starkernel` — the `bump-z`/`bump-y` targets were removed 2026-08-15 as non-functional and must not be resurrected from memory. ### XXVI.4 — Unexpected remote state, to investigate before the merge — not to clean up Traced 2026-09-19 against `origin`. **Reported, not acted on**, per the standing rule about unfamiliar state: - **`refs/heads/v2.0.1` still exists** at `fcba5282`. `FABRIC-3.md` §I.2 item 5 records that branch as deleted "local and `origin`" on 2026-09-04, having first confirmed it a strict ancestor, and concludes "`master` is the repo's only branch from here on." **It is not.** Either the deletion did not take or the branch was recreated. Worth resolving before a tag is cut, since the whole point of §I.2 was that the `v2.0.1` *name* carries a claim. - **`refs/pull/1/head` exists** at `bcf41e5` — this document's own §XIII commit. A pull request was opened against this branch at some point; this session did not create one, and `.claude/CLAUDE.md`'s subversion-like workflow says "no pull requests unless explicitly requested." Check what it is before merging, in case the intended path is through it. ### XXVI.5 — How this document closes `FABRIC-3.5.md` closes when the tag exists. The close is a dated header in the form `FABRIC-2.md` uses — **"Status: CLOSED/ARCHIVAL as of ``"** — stating what the document produced and naming the tag it closed at. **Two things the close must get right**, both established in this document's own opening: 1. **It does not trigger the carry-forward chain.** The `FABRIC-0 → -1 → -2 → -3` discipline triggers on *closing* a document and hands its open items to a successor. This document is not in that chain — it is a standalone topic document — so **closing it creates no successor and hands nothing forward.** 2. **`FABRIC-3.md` remains open, living and authoritative for its own topic** (bare metal boot). Nothing here supersedes it, and closing this document does not close that one. Any item still open at close time is either carried into `FABRIC-3.md` explicitly (if it has become bare-metal-boot work) or recorded in the close header as deliberately unfinished — never silently dropped. ### XXVI.6 — Punch list, complete **Design phase: complete.** One item (18) still carries an unmade decision; the rest is execution. | # | Item | § | |---|---|---| | 1 | **Surgical strip — Category A.** Precedes everything | §XXII.6 | | 2 | Hestia: relocate `fabric.4th` + `font.4th`; move `PLOT`/`FB-*` registration | §XVIII.9 | | 3 | Hestia's block range vs. `capsule-reserved.txt`, avoiding 4997 | §XVIII.4 | | 4 | Hestia into `is_fleet_foundation`; birth at `kernel_main.c:865`; switch registration | §XIX.6 | | 5 | §XVIII.6's headless invariant stated in the implementation | §XVIII.6 | | 6 | Kernel-Hermes; the sinking latch; `SOS` type 10 | §III, §XXI, §XXIII | | 7 | `BIRTH` generalization + ACL/DNA inheritance | §VI, §XV.1 | | 8 | The Hera-only suicide word (`SCUTTLE` recommended); pin beside `BIRTH` | §XXIV | | 9 | ~~item 18~~ — **CLOSED §XXVII**: empty floor unreachable, build nothing | §XXVII | | 10 | Teardown paths reach `STADIUM-EVICT`; verify via `fleet_conserved` | §XV.4 | | 11 | Category B strips, each as coding proves the item dead | §XXII.4 | | 12 | Isabelle/HOL pass — deliverable is the restated boundary | §XXV | | 13 | Documentation sweep | §XXVI.1 | | 14 | `make sbom`; check `Created:` and `DocumentName` | §XXVI.2 | | 15 | Resolve the stray `v2.0.1` branch and PR #1 | §XXVI.4 | | 16 | **Version RULED `2.1.0`** (§XXVIII); merge to `master`, tag `v2.1.0`. Engine `VERSION` still open | §XXVIII | | 17 | Close this document | §XXVI.5 | **No code is authorized by this document.** Per Captain Bob's Law, nothing on this list starts without an explicit instruction. --- ## XXVII. Item 18 SETTLED: the empty floor is unreachable — build nothing §XVI.7 raised it and §XVI.7's own proposed shape (the kernel observing an empty floor and halting) was offered as analysis. **Tested against the code 2026-09-19, the premise is false: the state cannot occur.** The ruling is therefore to build nothing, and the value of the item is the invariants it surfaced. ### XXVII.1 — The premise, tested **An empty Stadium floor requires Hera to be gone.** Every other VM is her descendant, and `capsule_vm_kill_all_nonmama()` spares her by construction. So item 18 reduces to: *can Hera cease to exist while the machine keeps running?* Five paths, all traced: | Path | Result | Evidence | |---|---|---| | **Compudynamic death** (heat decay / `COOL`) | **Impossible** — she is pinned | `stadium.c:453` `if (header->flags & STADIUM_FLAG_PIN) return -1;`; `:559` `if (h->flags & STADIUM_FLAG_PIN) continue;`. Hera is pinned via `session_set_pinned()` (§H.10/§H.12 step 4) | | **`KILL`** | **Impossible** — refused | `mama_word_kill()`'s own doc: "Destroy a named VM unconditionally. **Hera cannot be killed.**" | | **Reaped with the children** | **Impossible** — spared by name | `capsule_vm_kill_all_nonmama()` | | **`BYE`** | Machine **reboots** | `arch_cold_reset()` (§XXIV.2) — not a lingering state | | **Scuttle** (§XXIV) / **panic** | Machine **halts** | Both end in a permanent `arch_halt()` loop | **Every path by which Hera can cease already terminates the machine.** There is no route to a live kernel over an empty floor. **Note the pinning result generalises:** all three Tripod legs are pinned by the same `is_fleet_foundation` line (§XIII.3), so **no Tripod leg can die a compudynamic death.** §XV.2's compudynamic death applies to the unpinned population — identity VMs, agent VMs — which is exactly where it was always aimed. ### XXVII.2 — RULING: build nothing `.claude/CLAUDE.md` is direct about this class: *"Don't add error handling, fallbacks, or validation for scenarios that can't happen."* An empty-floor halt would be precisely that — defensive code for an unreachable state, carrying its own maintenance and its own risk of firing wrongly. **§XVI.7's proposed shape is withdrawn, not adopted.** A supporting argument, recorded but *not* load-bearing (the ruling stands on §XXVII.1 alone): even were the state reachable it would be provably terminal rather than a judgement call, since `BIRTH` is a word a VM must call — **zero VMs means zero possible births**, so nothing could ever run again. That is a dead end, not a decision. But it is moot. ### XXVII.3 — The panic path already is the empty-floor halt `sk_hal_panic()` (`hal/hal.c:490-506`) prints `System halted.` and executes: ```c while (1) { arch_halt(); } ``` **That is character-for-character the idiom §XXIV.2 specified for Hera's suicide word.** The two converge on the same terminal state by different triggers, which is a pleasing consistency rather than a duplication: **a scuttle is a panic Hera chose.** Whatever kills her involuntarily already routes here; what she does deliberately looks the same from outside. Nothing new is needed at either end. ### XXVII.4 — What this ruling depends on: three invariants to defend The ruling is conditional on facts that a future change could break silently. **State them as invariants, since that is the durable part of this item:** 1. **Hera stays pinned.** If she is ever admitted unpinned, or the `is_fleet_foundation` triple (`capsule_birth.c:793-796`) stops covering her, she becomes evictable and the empty floor becomes reachable. **Note this triple is edited by this very reshuffle** (§XIX.6, Hermes out, Hestia in) — so the change most likely to break this is one already on the punch list. 2. **`KILL` keeps refusing Hera**, and any new teardown path spares her the way `capsule_vm_kill_all_nonmama()` does. §XXVIII's already-confirmed unguarded-kill UAF shows this area has been got wrong before. 3. **Every involuntary-death path ends at `sk_hal_panic()` or an equivalent permanent halt** — never at a `return` that leaves the kernel looping over nothing. **If all three hold, the empty floor cannot occur and no code is needed. If any is broken, this ruling must be revisited rather than patched around.** ### XXVII.5 — Design phase complete **Every design question this document opened is now ruled.** Item 18 was the last, and it closes by dissolution — the fourth to do so, after §XIV.2's four, §XV.2's two and §XX's one. That pattern is worth naming as the document closes its design phase: **of the questions that dissolved rather than resolved, every one turned out to rest on a premise the code did not support** — a supervisor that was not needed, a declarer that could not exist, a suffix that was never load-bearing, a state that cannot occur. The design got simpler each time it was checked against the tree rather than reasoned about in the abstract. That is the single most useful habit to carry into the build. **Punch list (§XXVI.6) stands as written, with item 9 now closed and one decision outstanding: the version number (§XXVI.3).** No code is authorized. --- ## XXVIII. RULING: this release is `2.1.0` (Captain Bob, 2026-09-19) `LITHOS_VERSION` becomes **`2.1.0`**, and the tag cut at §XXVI's close is **`v2.1.0`**. ### XXVIII.1 — Checked against the real policy, not §I.2's paraphrase §XXVI.3 worked from `FABRIC-3.md` §I.2's summary. The authoritative statement is the roadmap table in `Makefile.starkernel` itself (lines ~74–82), which is more specific: ``` # Roadmap (per docs/lithosananke/ROADMAP.md "Release Versioning Policy" and # FABRIC-2.md §G — X.0.0 = QEMU release, X.5.0 = hardware bare-metal release): # v1.0.x — serial-only production (released) # v1.5.x — framebuffer VT100 terminal/console milestone (released) # v2.0.0 — QEMU release (even major = LTS): three-arch QEMU story complete # v2.0.1 — SER5 hardware-track line: RDRAND backend + generic thumbdrive image goal # v2.2.0 — amd64 bare-metal (Beelink SER5) — see ROADMAP "Board-by-board rollout" # v2.5.0 — hardware bare-metal release: real per-arch RNG + real-board boot ``` **`2.1.0` is unallocated** — the ladder currently steps 2.0.0 → 2.0.1 → 2.2.0 → 2.5.0. So the ruling takes a genuine gap rather than overloading an existing rung, and it lands correctly: - **Below `v2.2.0` (amd64 bare-metal) and `v2.5.0` (hardware bare-metal release)** — right, because this reshuffle is QEMU-only work and must claim no hardware progress. That was §I.2's entire concern when it rolled the version back. - **Does not disturb `v2.0.1`'s meaning** as the SER5 hardware-track *line*. That name keeps what it means; nothing is reused. - **Consistent with `X.0.0` = QEMU / `X.5.0` = hardware bare-metal** — a minor bump inside the 2.x QEMU-side band, before the hardware rungs. A structural change to the Tripod, a new kernel-resident arbiter and a generalized `BIRTH` are comfortably a minor-version event rather than a patch. ### XXVIII.2 — The roadmap table must gain a `2.1.0` line, in three places The table above is not decoration — it is how the next person reads the ladder. Left alone it will step 2.0.1 → 2.2.0 with a released `v2.1.0` tag sitting in a gap the table does not mention. **Add the line wherever the table lives:** 1. `Makefile.starkernel` (the copy above, beside the `LITHOS_VERSION` edit itself). 2. `docs/lithosananke/ROADMAP.md`, "Release Versioning Policy" — the comment names it as the upstream source. 3. `FABRIC-2.md` §G is the origin of the scheme but is **CLOSED/ARCHIVAL** — **cite it, do not edit it.** Amending a closed document to match a later decision is exactly what this series' discipline forbids. ### XXVIII.3 — A trap the documentation sweep must not walk into, with precedent **`Makefile.starkernel` is not a `.md` file, and this project has already been bitten by exactly that.** `FABRIC-3.md` §I.2 records it plainly: a FABRIC-series rename looked complete but had silently skipped `Makefile.starkernel`, `Kconfig.kernel`, `scripts/bleach_zuse_img.sh`, **four `proof/*.thy` files** and `src/starkernel/arch/amd64/isr.S` — > the original sweep's file-list only matched `--include=*.md/*.c/*.h/*.4th`, which silently > skipped every file without one of those four extensions. Found by re-grepping with the > extensions **excluded** instead of included. **So item 13's sweep (§XXVI.1) must grep by exclusion, not inclusion**, and must expect version/policy/citation text in Makefiles, `Kconfig.*`, `scripts/*.sh`, `proof/*.thy`, `.S` sources and `.gitea/workflows/`. The technique §I.2 used to fix it — ordered placeholder substitution, one pass per file — is the recorded-working method and should be reused rather than reinvented. **And the same exclusion that §I.2 made deliberately still applies:** `.claude/settings.local.json`'s historical command log and `ClaudeEXPORT/` are frozen audit records — **rewriting them would falsify an audit trail, not fix a stale citation.** Leave them. ### XXVIII.4 — Still unruled: `VERSION`, the embedded engine string `LITHOS_VERSION` and `VERSION` are independently tracked (`.claude/CLAUDE.md`, and confirmed at `Makefile.starkernel:83-84`). **`2.1.0` settles the kernel version; the engine version `VERSION ?= 3.1.0` is not settled by it.** It plausibly moves — this reshuffle changes `BIRTH`'s registration, adds a Hera-only word, relocates the framebuffer primitives' registration and replaces the messaging layer, all of which are engine-side. But it is a separate string with its own meaning, and `.claude/CLAUDE.md` warns it "does not auto-sync with the standalone StarForth repo's own version." **Captain Bob's call, at tag time.** Both are hand-edited. The `bump-z`/`bump-y` targets were removed 2026-08-15 as non-functional (they referenced fields that never existed in the generated `include/version.h`) and must not be resurrected from memory. ### XXVIII.5 — Punch list - ✅ **Version — RULED**: `LITHOS_VERSION = 2.1.0`, tag `v2.1.0` (§XXVIII). - ⬜ **Roadmap-table line added in two live places**, `FABRIC-2.md` §G cited not edited (§XXVIII.2). Folds into item 13. - ⬜ **Item 13's sweep greps by exclusion**, per §XXVIII.3's precedent. - ✅ **`VERSION` (engine) — CLOSED as a rule** (§XXX.6), applied at tag time against the real diff rather than guessed now. **Every design question is ruled and the release number is set.** What remains is execution, one engine-version call, and the close. --- ## XXIX. Versioning policy closed; the LTS designation deferred with written criteria (2026-09-19) Captain Bob proposed a revised scheme — **odd major = LTS, even major = working line; minor = release; patch = working builds; CI build number appended once CI is exposed** — and asked directly whether this release deserves an LTS number as "the very first fully production ready version," inviting disagreement and noting the policy should be closed either way. **Policy: closed here. LTS designation: deferred, with the criteria written down rather than left to judgement.** The reasoning below is the disagreement, stated plainly because it was asked for. ### XXIX.1 — Why not LTS at this tag: five findings, not an opinion 1. **§XIV is open, and was reproduced live after being filed.** "Concurrent WIREBIND attach detection gap — found live, **NOT fixed**, flagged for later" (2026-09-11). §XXXI then caught it again on the post-Stage-4 rerun: near-simultaneous thumbdrive attach meant **6 of 9 identities silently never triggered `WIREBIND: attached` at all**, with QMP `query-block` confirming every device was genuinely present. That is the **identity-attach path** — how humans get into the system — failing silently and reproducibly. It is not a corner case, and "silently" is the part that disqualifies it. 2. **It has never booted on real hardware.** That is `FABRIC-3.md`'s entire declared topic, and the roadmap reserves `v2.2.0` (amd64 bare-metal) and `v2.5.0` (hardware release) for it. "Production ready" invites the question *production on what?* 3. **ACL Phase 8 is open** — PKI / thumbdrive, Ed25519 challenge-response, user minting by Zuse: "⬜ **This is the open item — pick up here next**" (`.claude/CLAUDE.md`). That is the last mile of the security story, and the security story is much of the production claim. 4. **At tag time this reshuffle is brand-new code.** Kernel-Hermes is greenfield C (§XIV.1), Hestia is a new VM, `BIRTH` is newly general, the latch is new. **LTS normally means soaked — years of support promised on something proven.** Declaring it at the moment of a structural rewrite inverts the word. The reshuffle is an excellent reason to tag; it is a poor reason to call the result long-term-stable. 5. **§XXV.4: formal coverage likely decreases at this tag**, as messaging moves from modelled FORTH to file-scope C statics. For the audience an LTS label is aimed at — licensees, SSRN, patent support — the proof story is load-bearing, and this is the wrong moment to claim maximum stability while the verified boundary contracts. **And the precedent is this project's own.** `FABRIC-3.md` §I.2 rolled `LITHOS_VERSION` *back* from 2.0.1 because the label "got ahead of the real state." **"First fully production ready" is a far larger claim than `2.0.1` was.** The same instinct that caught that one should catch this. ### XXIX.2 — The constructive form: make LTS a test, not a judgement This document has twice turned a judgement into a structural fact and got a better answer both times (§XV.2's death, §XXVII's empty floor). Same move here. **Proposed LTS criteria, to be ratified as part of the policy:** A tag may take an **odd (LTS) major** only when **all** hold: - **Real-hardware boot demonstrated** on at least the amd64 reference board, with logs committed as audit artifacts (the existing acceptance standard, extended off QEMU). - **No known-reproducible silent-failure defect open** — §XIV's class specifically: a failure that produces no error and is caught only by inspection. - **ACL Phase 8 closed**, or its absence explicitly scoped out of the LTS claim in writing. - **The `proof/` boundary restated and not contracted** relative to the prior LTS (§XXV.3). - **A soak**: the architecture unchanged for at least one full release cycle before the LTS tag — LTS follows stability, never announces it. Then **the first tag meeting them takes the odd major**, and nobody has to decide whether it feels ready. **So: `2.1.0` stands as ruled (§XXVIII) — even major, working line, correct under both the old and new schemes.** LTS is not refused, only sequenced. ### XXIX.3 — Three conflicts in the new scheme, to resolve while closing it 1. **The minor position is already taken, and by a live plan.** The current policy uses *minor* for QEMU-vs-hardware (`X.0.0` = QEMU release, `X.5.0` = hardware bare-metal release), and **`v2.5.0` is already on the roadmap**. The new scheme wants minor = "release version." Both readings cannot hold. **Adopting the new scheme retires `X.5.0` semantics and must say so explicitly**, or `v2.5.0` becomes ambiguous the day it is cut. 2. **Odd/even major collides with "major = breaking change."** Binding the major to LTS-ness means a breaking change inside the working line cannot be expressed without also flipping LTS status, and an LTS line cannot take a breaking fix without ceasing to be LTS. Node and the pre-2.6 Linux kernel both used this shape and both paid for it. **Worth choosing deliberately**: either accept that majors no longer signal breakage, or move LTS-ness out of the version number (a tag suffix, a channel name) and leave the major for compatibility. 3. **`3.x` is already in use, by the other string.** `VERSION ?= 3.1.0` is the embedded engine. If `LITHOS_VERSION` takes an odd major it lands on **3.0.0** — two different `3.x` values in the same generated `include/version.h`. Not fatal, since they are separate fields, but confusing at precisely the moment a release wants to be legible. **Flag before, not after.** ### XXIX.4 — CI build numbers: use SemVer build metadata, not a fourth field Specific because it interacts with something already shipped. `sbom.spdx` / `sbom.spdx.json` are generated by **syft** and consumed as SPDX (§XXVI.2), and SPDX/SemVer tooling parses `X.Y.Z+build.N` correctly while `X.Y.Z.N` is not valid SemVer — it sorts wrongly or is rejected outright. **Recommendation: `2.1.0+ci.1234`.** Build metadata is ignored in precedence comparison, which is exactly right for a build number: two builds of the same source rank equal, and the SBOM stays machine-readable for the licensee and patent audiences it exists for. ### XXIX.5 — What this section rules and what it leaves open - ✅ **Policy closed** as §XXIX.2–XXIX.4, subject to Captain Bob ratifying the three conflicts in §XXIX.3. - ✅ **`2.1.0` stands** (§XXVIII) — even major, working line. - 🔶 **LTS deferred, not refused.** Criteria written (§XXIX.2); the first tag meeting them takes the odd major. - ⬜ **Captain Bob to rule on §XXIX.3's three conflicts**, and on the engine `VERSION` string (§XXVIII.4). **The disagreement is narrow and worth stating once more so it is not mistaken for reluctance: the work is real and the tag is deserved. What is not yet earned is the word *long-term stable*, on code that will be new the day it ships.** --- ## XXX. Versioning policy — CLOSED (Captain Bob ratified, 2026-09-19) §XXIX's recommendations accepted. This section is the closed policy, written as the text to carry into the two live places §XXVIII.2 names. **Two items §XXIX left as open alternatives are resolved here rather than left hanging.** ### XXX.1 — The scheme | Field | Meaning | |---|---| | **Major** | **Odd = LTS line. Even = working line.** | | **Minor** | Release within that line | | **Patch** | Working builds within a release | | **`+ci.N`** | CI build number, SemVer build metadata (§XXIX.4) — ignored in precedence | ### XXX.2 — Conflict 1 RESOLVED: `X.5.0` semantics are retired The old encoding (`X.0.0` = QEMU release, `X.5.0` = hardware bare-metal release) **used the minor field to carry a milestone meaning. That is retired** — minor now means only "release within the line." Stated explicitly, per §XXIX.3.1, so `v2.5.0` cannot be read two ways the day it is cut. **The milestones themselves survive; only their encoding changes.** They become roadmap entries rather than arithmetic, which re-expresses the ladder cleanly: ``` v1.0.x — serial-only production (released, historical encoding) v1.5.x — framebuffer VT100 console milestone (released, historical encoding) v2.0.0 — QEMU release: three-arch QEMU story complete (released) v2.0.1 — SER5 hardware-track line (historical; superseded by this policy) v2.1.0 — Tripod/kernel reshuffle: Hermes into the kernel, Tripod = Hera/Artemis/Hestia v2.2.0 — amd64 bare-metal bring-up (Beelink SER5) v2.x — further working releases; riscv64 / aarch64 board bring-up v3.0.0 — FIRST LTS, when and only when §XXX.5's criteria are met ``` **A pleasing consequence, not a contrivance:** the hardware bare-metal release that was going to be `v2.5.0` is exactly the point where §XXX.5's first criterion (real-hardware boot) is satisfied. **The milestone that used to be encoded in the minor field becomes the natural candidate for the first LTS major.** The scheme and the roadmap agree without being forced to. ### XXX.3 — Conflict 2 RESOLVED: the *line* signals breakage, not the major §XXIX.3.2 offered two options without recommending. **Resolved in favour of keeping the major for LTS parity**, since that is the scheme's whole point, and relocating the compatibility signal rather than abandoning it: > **Breaking changes land on the even (working) line. An odd (LTS) line takes non-breaking > fixes only — backports, never redesigns.** So the major no longer means "something broke"; **the line you are on tells you whether breakage is possible at all.** That is how odd/even schemes are meant to work, and it repairs §XXIX.3.2's objection rather than absorbing it: a breaking change is expressible (bump the minor on the working line), and an LTS line cannot silently acquire one (it is not allowed to). **Corollary worth writing down:** an LTS line that *needs* a breaking fix has failed as an LTS. The fix goes to the working line and the next LTS picks it up. No exceptions, because the exception is the whole failure mode this rule exists to prevent. ### XXX.4 — Conflict 3 RESOLVED: the two version strings are independent and may coincide `LITHOS_VERSION` reaching `3.0.0` while `VERSION` (the engine) already sits at `3.1.0` is **not** a collision to engineer around. `.claude/CLAUDE.md` already states they do not auto-sync; they are separate fields in the generated `include/version.h` describing different artifacts — the kernel and the embedded engine. **Ruling: state the independence in the policy text itself**, so a reader meeting two `3.x` values does not infer a relationship that has never existed. Documentation, not renumbering. ### XXX.5 — LTS criteria (ratified) A tag may take an **odd (LTS) major** only when **all five** hold: 1. **Real-hardware boot demonstrated** on at least the amd64 reference board, logs committed as audit artifacts — the existing acceptance standard, extended off QEMU. 2. **No known-reproducible silent-failure defect open** — §XIV's class specifically: a failure producing no error, caught only by inspection. 3. **ACL Phase 8 closed**, or its absence explicitly scoped out of the LTS claim in writing. 4. **The `proof/` boundary restated and not contracted** relative to the prior LTS (§XXV.3). 5. **A soak** — the architecture unchanged for at least one full release cycle. **LTS follows stability; it never announces it.** **The first tag meeting all five takes the odd major.** No judgement call, no debate about whether it feels ready. ### XXX.6 — The engine `VERSION` string: a rule, not a number §XXVIII.4 left it to tag time. **Closed here with a rule instead**, because the correct bump is a function of the realised diff and the code is not written yet — picking a number now would be guessing, and guessing is what §I.2's rollback was about. > **Engine `VERSION` bumps at tag time by what actually changed:** > **major** — any FORTH-79-visible word semantics change; > **minor** — words added, removed, or their registration relocated; > **patch** — build-only, no dictionary-visible change. On the current plan this reshuffle is a **minor** bump (`3.1.0` → `3.2.0`): `BIRTH`'s registration widens, a Hera-only suicide word is added, `PLOT`/`FB-*` registration relocates to Hestia, the messaging layer is replaced — all dictionary-visible, none changing a FORTH-79 word's semantics. **Confirm against the real diff at tag time; the rule decides, not this paragraph.** Both strings stay hand-edited in `Makefile.starkernel`. The `bump-z`/`bump-y` targets were removed 2026-08-15 as non-functional and must not be resurrected. ### XXX.7 — Punch list - ✅ **Versioning policy — CLOSED** (§XXX). All three §XXIX.3 conflicts resolved; LTS criteria ratified; engine `VERSION` closed as a rule. - ✅ **This release is `2.1.0`** (§XXVIII), even major, working line. - ⬜ **Carry §XXX.1–XXX.6 into `Makefile.starkernel` and `docs/lithosananke/ROADMAP.md`** (§XXVIII.2), replacing the old roadmap table. `FABRIC-2.md` §G is cited, never edited. - ⬜ Engine `VERSION` applied at tag time per §XXX.6's rule. **Every decision this document required is now made.** What remains is execution: the surgical strip, the build, the Isabelle pass, the sweep, the SBOM, the merge, the tag, and the close. --- ## XXXI. Gap-analysis sweep of the FABRIC set (2026-09-19) Run by direct instruction — "we need a gap analysis sweep and I'd look HARD at the entire fabric document set." The set is 25,493 lines across `FABRIC-0` … `FABRIC-4` plus this document. **A gap analysis of a series whose central rule is "never silently drop a stale claim" is, in effect, an audit of whether the series kept its own rule.** Mostly it did. Where it did not, the failures are structural and in one case operationally urgent. ### XXXI.1 — Method, and an honest statement of coverage **Done:** mechanical harvesting across all six documents — open/closed checkbox counts, deferral vocabulary (`deferred`, `not in scope`, `for later`, `still open`, `NOT fixed`), cross-document reference counts for every long-lived item id — followed by targeted reads of everything the counts flagged. **Not done:** an end-to-end read of all 25,493 lines. `FABRIC-0.md` §1–24 (the theory) and the bulk of `FABRIC-1.md`'s resolved narrative were not read line by line. **So this sweep finds structural gaps and orphaned items; it does not certify that every claim in the set is accurate.** Stated plainly because §XXII.2 was written about exactly this failure mode, and it would be poor form for the sweep to repeat it. ### XXXI.2 — The carry-forward chain held twice and then terminated The series' defining discipline is that closing a document carries its open items forward. Audited mechanically: | Link | Status | Evidence | |---|---|---| | `FABRIC-0` → `FABRIC-1` | **Held** | F0's open item ids are referenced throughout F1 (1.11 ×8, 4.4s ×5, 4.6 ×9, 5.1 ×3) | | `FABRIC-1` → `FABRIC-2` | **Held, and independently verifiable** | F2's header claims a "full, non-sampled carry-forward of every open item… **51 items**." `grep -c '^\s*-\s*\[ \]' FABRIC-1.md` returns **51** today. The claim is exactly true | | `FABRIC-2` → `FABRIC-3` | **Terminated** | Every long-lived F0 id — 1.11, 4.4s, 4.6, 5.1, 5.2, 5.3 — returns **zero** references in `FABRIC-3.md` | **`FABRIC-3.md` contains no checkboxes at all** (0 open, 0 closed), having moved to a prose and `CLOSED`-heading convention. That is a legitimate stylistic choice, but it ends the mechanical audit trail that made the F1 → F2 link provable. **After `FABRIC-2`, "is anything still open?" stops being a `grep` and becomes a reading exercise** — which is how the items in §XXXI.3 and §XXXI.4 went quiet without anyone deciding to drop them. ### XXXI.3 — `FABRIC-2`'s close claim is overstated: six design items are open, not hardware-blocked `FABRIC-2.md`'s close header states it was "closed after §I… was worked through to completion — **every item either closed with a dated note or correctly left open pending a physical machine (§I.6)**." It closed with **14 open checkboxes.** Eight are the real-hardware USB boot checklist (build the ISO, `dd` it, boot the machine, capture with no serial log, confirm POST `1012/0/0`, reach `ok>`, document the result) — **legitimately FABRIC-3's topic, correctly left open.** The claim holds for those. **The other six have no hardware dependency and appear nowhere since:** | Item | Line | Picked up in F3 / F3.5? | |---|---|---| | **§17.4 — framebuffer heat/decay dynamics, undesigned** | 77, 4366 | **No** (§XXXI.6) | | First-touch allocation (identity pubkey → claimed block range) | 192 | **No** | | Migration state machine — states, transition triggers | 228 | **No** | | Block-namespace sandboxing / `mkcapsule` conflict extension | 1011 | **No** | | ACL `EXPIRE` — "confirmed genuinely unscoped (2026-08-26)" | 1219 | **No** | The one apparent hit for `EXPIRE` in this document is a **false positive** — §XXIV.3's name-collision check for the suicide word, unrelated to the ACL item. **None of these is necessarily urgent. All of them are unowned**, and the close header says otherwise. ### XXXI.4 — `FABRIC-0`'s seven open items, three of which this document re-derived `FABRIC-0.md` closed 2026-08-12 with seven open items. All seven remain `- [ ]` today: | Item | Bearing on this reshuffle | |---|---| | **4.3 — Console** | **Direct. See below.** | | 1.11 — Dirty-event granularity | Blocked on 4.3; unblocks with it | | 4.4s — `(user)` prompt segment | Plausibly *done* — `FABRIC-3.md` §XXV's `[user@VMName]` work looks like it, never checked off | | 4.6 — "Artemis last. It works today; it is the thing that cannot be broken" | Sequencing advice this reshuffle happens to follow (§IV.1 leaves Artemis unchanged) without citing it | | 5.1 — Re-run the DoE on the new substrate | Overtaken by §XXV.1's DoE rewrite | | **5.2 — Isabelle/HOL** | **Direct. See below.** | | **5.3 — Shrink the subsystem documents** | **Direct. See below.** | **Three were re-derived by this document without either side knowing:** 1. **4.3's deferred tail is this reshuffle's Hestia work.** Its 2026-08-07 discussion note scopes 4.3.1–4.3.4 and defers, verbatim, "fonts, scrolling, cursor/VT100 semantics, the Hermes message protocol, **and Console as a fleet VM under Hera's birth protocol** — later 4.3.x items, scoped once this slice is reviewed." **That last clause is §XVII and §XVIII.** Also worth noting: **4.3 carries a standing instruction** — "Captain Bob wants a discussion before any work starts on this item… raise it and wait." Satisfied in substance (Captain Bob directed this work personally), but the linkage was never recorded. **The slices themselves are complete:** 4.3.1 through 4.3.7f — framebuffer, keyboard on all three architectures, fonts, UTF-8, TTF rasterization — are **all `[x]`**. Only the parent stays open, for the tail this document just designed. 2. **5.2 already specifies the Isabelle target §XXV left generic:** "**One datatype, one index space, one conservation theorem.**" §XXV.5 mapped affected theories without citing it — and "one conservation theorem" is almost certainly **K**, the fleet conservation invariant that §XV.4 independently rediscovered as *the only thing that must survive a death*. Two documents converged on the same theorem from opposite ends, a year apart in the series, with no cross-reference. 3. **5.3 already asked for §XXVI.1's documentation work:** "`ARTEMIS.md`, `HERMES.md`, `CONSOLE.md`, `TRIPOD.md` should each reduce to roughly three lines. **Any that grows is fighting the design.**" It also flags that `TRIPOD.md`'s Immediate Goal "currently requires Hera to spawn Hermes and Artemis at boot, which 0.1 undoes" — **worth checking, because `kernel_main.c:865` does exactly that today** (§XIII.3), so either 0.1 was reverted or that note is stale. ### XXXI.5 — The urgent finding: `FABRIC-3` holds an open item that contradicts §XV.3 **This is the one that affects the build, and it should be reconciled before kernel-Hermes is written.** `FABRIC-3.md` §XXVIII.2 records, and §XXXI restates as still open: > **Still open:** the MSG-TICK/Stage-3-switch dual-ownership rough edge §XXVIII itself flagged > (**both mechanisms can independently move control between the same VMs**) is unchanged by > this pass. …and in §XXXI's own summary: "the MSG-TICK/Stage-3 dual-ownership rough edge (**still open, not observed failing**)." **§XV.3 of this document ruled that *nothing owns turn order*.** That ruling is a principle; `FABRIC-3` records a **live, known, unfixed instance of exactly the dual ownership the principle forbids** — two mechanisms independently moving control between the same VMs. **Three consequences:** - **§XIV.5's scheduler-firewall concern was a rediscovery**, not a new finding. `FABRIC-3` named this seam first. This document should have cited it and did not. - **Kernel-Hermes lands directly on it.** Per §XIV.5, once the FORTH layer is legacy, kernel-Hermes becomes the caller of `sk_vm_switch_signal_mark_work()` — i.e. the reshuffle moves one of the two owners into the new arbiter. Building on an open dual-ownership defect is how it stops being "not observed failing." - **"Not observed failing" is not "does not fail."** §XXVIII.2's own history is a record of this neighbourhood producing switch storms — QEMU pinned near 100%, serial log frozen solid — when two sources of truth disagreed. **Added as punch item 22, ahead of the kernel-Hermes work rather than alongside it.** ### XXXI.6 — §17.4 became Hestia's problem and nothing says so `FABRIC-2.md` §17.4, still open: "**framebuffer utility's internal heat/decay dynamics.** Undesigned, blocked on 1.11 specifically… **Still open 2026-09-04**: 1.11's *decision* closed, but no dirty-region-tracking mechanism exists yet for this item's physics to attach to — that mechanism, not a further ruling, is the real remaining blocker." §XVIII.3 relocated the drawing fabric's vocabulary to Hestia and §XVII.2 gave the fabric an owner — **without inheriting this open item.** The framebuffer now has a VM that owns it and an undesigned heat/decay story attached to nothing, and the two facts live in different documents. Not a blocker for the reshuffle — §XVIII deliberately scoped Hestia to ownership and binding, not physics. **But it is now unambiguously Hestia's, and should be recorded as such rather than left in a closed document.** Punch item 23. ### XXXI.7 — Three "reported, not scheduled" registries, with inconsistent hygiene The set carries at least three separate registries of found-but-unfixed defects — `FABRIC-0` §25.7 "Reported, not scheduled", `FABRIC-1` §C "Reported bugs and dead code, not yet fixed", and scattered prose flags through `FABRIC-2`/`FABRIC-3`. **They have no consolidated home and no shared convention.** **§25.7 is actively misleading as a registry**, because resolved entries are sometimes struck through and sometimes not: - `~~stadium_admit() never writes stadium_owner[idx]~~` — struck, correctly. - **"Fleet heat leak… `vm_physics_touch()` integer-truncation drift"** — **not struck, but resolved**: `FABRIC-1.md:468` carries it as `- [x] **Fleet heat leak.**` Anyone reading §25.7 today would believe the project sits on an unmeasured monotonic leak in a conservation law it makes claims about. - **`bump-z`/`bump-y` broken** — **not struck, but resolved**: removed 2026-08-15 as non-functional (§XXVIII.4). - Still genuinely live in §25.7: `heartbeat_trust()` exported with zero callers; `m5_time_trust`/`m5_variance` declared and never used; `hotwords_cache_promote()`'s NULL write; Kconfig/`menuconfig` never exercised end to end; and **`src/*.c.bak` tracked in git** — which is now reported in *three* places (§25.7, `.claude/CLAUDE.md`, and §XXII.5 of this document) and actioned in none. **Punch item 24: consolidate the registries and apply one convention.** Reported, not fixed — and deliberately not folded into this reshuffle. ### XXXI.8 — What the sweep changes **Nothing in §I–§XXX is invalidated.** No ruling is contradicted by a finding here; §XXXI.5 is a conflict between this document's *principle* and an open defect in the code, not an error in the ruling. Every design decision stands. **New punch items, appended to §XXX.7's list:** - ⬜ **22 — Reconcile §XV.3 against the MSG-TICK/Stage-3 dual-ownership defect** (§XXXI.5). **Before kernel-Hermes, not alongside it.** The reshuffle moves one of the two owners into the new arbiter. - ⬜ **23 — Re-home `FABRIC-2` §17.4** (framebuffer heat/decay) as Hestia's (§XXXI.6). - ⬜ **24 — Consolidate the three "reported, not scheduled" registries** and mark the resolved entries (§XXXI.7). - ⬜ **25 — Decide the fate of `FABRIC-2`'s five other orphaned design items** (§XXXI.3): first-touch allocation, migration state machine, block-namespace sandboxing, ACL `EXPIRE`. Adopt, re-home, or explicitly abandon with a reason — but not leave unowned. - ⬜ **26 — Reconcile `FABRIC-0`'s seven opens** (§XXXI.4): close 4.3 against §XVII/§XVIII, check whether 4.4s is already done, fold 5.2 into item 12's Isabelle pass and 5.3 into item 13's sweep, and confirm the `TRIPOD.md`/0.1 contradiction. **And one recommendation the sweep argues for on its own evidence:** the carry-forward discipline was *verifiable* only while the documents used checkboxes (§XXXI.2). If the series continues in prose, "what is still open" needs a deliberate mechanism — because two documents have now closed with items nobody chose to drop. --- ## XXXII. Item 22 SETTLED: retire the pump — the switcher becomes the sole mover of control §XXXI.5 found that `FABRIC-3.md` carries an open defect — "**both mechanisms can independently move control between the same VMs**" — which is a live instance of what §XV.3 forbids as a principle. Settled here, and **the reshuffle turns out to be the thing that closes it rather than the thing that inherits it.** ### XXXII.1 — The two mechanisms, and why only one should survive | | How it moves control | Fate | |---|---|---| | **The MSG-TICK pump** (`repl.c` idle loop) | `VM-EXEC`s `"MSG-TICK"` into a VM — a nested `vm_interpret()` on the **shared kernel C stack** | **Retired** | | **The Stage-3/4 switcher** (`sk_vm_context_switch()`) | Saves and restores a **per-VM native stack**, timer-driven | **Sole survivor** | **RULING: kernel-Hermes publishes eligibility; it does not dispatch.** It marks a target as having work and the switcher — the one mechanism that moves control — delivers when it next resumes that VM on the VM's own stack. Two control-movement paths collapse into one. ### XXXII.2 — Three reasons this is the right direction, not merely a tidy one 1. **The pump is already legacy by §XIV.1.** It exists for exactly one purpose: to drain *per-VM FORTH message queues* by executing `MSG-TICK` inside each VM's dictionary. Once kernel-Hermes holds the queues, there is nothing for it to drain. **Retiring it is not a new decision — it is a consequence of a ruling already made.** 2. **The pump is a known scaling wall, already band-aided once.** `FABRIC-3.md` §XXII records it walking the entire live-VM registry every idle beat, `VM-EXEC`ing into every VM — "O(N) per tick, forever, with no cap" — **caught live as a real bottleneck at just 9 VMs on riscv64**, where a campaign that finished in 200–340 s on amd64/aarch64 never finished at all. Fixed by round-robin batching (`SK_MSG_PUMP_BATCH`), which bounds cost to O(K) **at the price of per-VM latency depending on registry position.** Its own comment names the endgame: "the real fleet is headed toward hundreds of VMs, at which point this isn't a riscv64 quirk — unconditional O(N) per second is a wall on every architecture." **Retiring the pump removes the wall and the latency tradeoff together.** 3. **"Not observed failing" is worth very little in this specific subsystem**, and `FABRIC-3.md` says so itself. §XXVIII.1 is a correction to §XXVIII's own "Stage 3 CLOSED" claim: "Zero fault indicators" was true only of what was *visible*, because "**Stage 3 emits nothing per switch by default, so a switch storm and a healthy idle REPL produce an identical serial log.** The corruption below was already live in every one of those 'clean' Stage 3 boots; nothing in that verification pass could have shown it." **A defect flagged "not observed failing" in a subsystem that is invisible by default is not evidence of health.** That is the project's own finding, not an inference. ### XXXII.3 — Reconciling with §XV.3: one *mover*, still no *owner* §XV.3 ruled that **nothing owns turn order.** Naming the switcher the sole mover looks like it names an owner. It does not, and the distinction is the one §XV.3 already relies on: > **Moving control is a mechanism. Deciding whose turn it is is a policy.** The switcher is the > mechanism. The compudynamics remains the policy, and it is still owned by nobody. So §XV.3 stands unamended. **The dual-ownership defect was never about deciding** — both the pump and the switcher move control without either claiming to choose who runs next. It is about two *physical paths* for the same transfer, which is a correctness problem (stack ownership), not an authority problem. Collapsing them resolves it without touching the principle. **And it makes §XXIII.4 implementable.** That section ruled the sinking latch is checked "on giving a VM its turn" — which silently assumes there is **one** place a VM gets its turn. With two movers that check has two homes, or one, and misses. **§XXIII.4 already depended on this ruling without saying so.** ### XXXII.4 — The dependency this rests on, verified The ruling strands identity VMs unless the switcher covers them. **Checked:** `FABRIC-3.md` §XXX — "**Stage 4 — WIREBIND-scope extension, built and verified live (2026-09-14/15)**," Captain Bob's call to proceed immediately, built in the same one-commit-per-increment discipline as Stages 0–3. **So the switcher already covers WIREBIND identity VMs, not just the Tripod fleet.** Had Stage 4 still been the "ratified-decision-only, not implemented" step §XXVIII left it as, this ruling would have been unbuildable and identity VMs would have lost their delivery path entirely. **It holds only because Stage 4 landed.** ### XXXII.5 — Open for the build, not decided here 1. **Where the payload waits.** Delivery must still cause the payload to *execute inside* the target — `messaging.4th` block 5041: "Payload is FORTH text, executed by the receiver, same as every other message here." Under this ruling the switcher resumes the target on its own stack, so kernel-Hermes must leave the payload somewhere the target consumes on resume. The shape of that hand-off is a build decision. 2. **Hera's special case.** Per §XX of `FABRIC-3.md`, Hera is skipped by the pump and instead gets a direct `MSG-TICK` word dispatch in her own context, because "she *is* the pump" and self-targeting `VM-EXEC` would hit a known reentrancy class. **With the pump gone, that special case should go with it** — her queue is kernel-held like everyone's. Confirm rather than assume. 3. **The reentrancy guards.** `g_mama_interpreting` and `g_idle_pump_active` (`repl.c`) exist specifically because the pump makes nested `vm_interpret()` calls. They likely retire with it — **but carefully**, since §XXVIII.2's switch storm lived in this neighbourhood. Remove only what is provably unreachable, per §XXII's own strip discipline. ### XXXII.6 — Punch list - ✅ **Item 22 — SETTLED** (§XXXII): retire the pump; the switcher is the sole mover of control; kernel-Hermes publishes eligibility and does not dispatch. **Folds into the kernel-Hermes work (item 6) rather than preceding it** — §XXXI.5 filed it ahead on the assumption it was a separate defect to fix first; it is instead a property of how kernel-Hermes is built. - ⬜ Items 23–26 unchanged (§XXXI.8): re-home §17.4; consolidate the reported-not-scheduled registries; decide `FABRIC-2`'s five orphaned design items; reconcile `FABRIC-0`'s seven opens. **Worth recording as the pattern this makes four of:** §XXXI.5 read as a risk the reshuffle would walk into. Examined, it is a defect the reshuffle *removes* — because the mechanism that creates it is the same FORTH messaging layer already ruled legacy. **The gap analysis found a problem that the design had already solved without noticing.** --- ## XXXIII. Sizing kernel-Hermes: what actually has to be reimplemented (2026-09-19) **Why this section exists.** §XIV.1 ruled the FORTH messaging layer legacy and kernel-Hermes greenfield. That was the right call, but it quietly exited this document's own scope discipline — "a reorganization, not an invention… relocated and rewired rather than rewritten" — and **nobody counted what the rewrite actually amounts to.** With the build trigger approaching and confidence the stated concern, an unsized centerpiece is the wrong thing to carry into it. `capsules/common/messaging.4th` is 504 lines defining **85 words**. That number is the reason to look, and it turns out to be badly misleading in the reassuring direction. ### XXXIII.1 — The 85 words, by category | Category | Words | Fate in kernel C | |---|---|---| | **Field accessors** (`MSG-TYPE@/!`, `CH-ID@/!`, `MBR-VM@/!`, …) | ~34 | **Vanish** — they become struct members | | **Channel abstraction** (`CH-*`) | **28** | **Mostly unexercised — see §XXXIII.2** | | **Message core** (alloc/free, send, deliver, tick, reap, ack/nack, redeliver) | ~20 | **Must survive. The real work.** | | **Member list** (`MBR-*`) | 7 | Collapses to a small membership list | | **Events** (`EVENT-EMIT/WAIT/DRAIN`) | 3 | **Dead — see §XXXIII.3** | | **Elevation** (`ELEVATE-*`, `SEND-ELEVATE-REQUEST`) | 5 | **Live. Must survive.** | | **Status/diagnostics** (`MSG-USED`, `CH-USED`, `MSG-STATUS`) | 3 | Cheap, keep | ### XXXIII.2 — Finding: the channel abstraction serves exactly one static channel **28 of the 85 words are channel machinery.** Traced across `capsules/`, `src/` and `experiments/` for callers outside `messaging.4th` itself: | Word | Live callers outside `messaging.4th` | |---|---| | `CH-REQUEST` | **none** — only `capsules/MANIFEST.md` (documentation) | | `CH-ACCEPT` | **none** — documentation only | | `CH-CONFIRM` | **none** — documentation only | | `CH-CLOSE` | **none** — documentation only | | `CH-MINT-ID` | **none** — documentation only | | `CH-ADD-MBR` | **3** — the only live channel operation in the system | **The entire `CH-NEGOTIATING → CH-OPEN → CH-CLOSING` handshake has no caller anywhere.** The whole live channel lifecycle is: Hermes creates `COMMON-CH` once at birth, adds itself and Hera (`hermes/init.4th:20-21`), Artemis adds itself (`artemis/init.4th:501`). **One channel, three members, created at boot and never negotiated, never closed, never reaped.** So `CH-ARENA`, `CH-MAX 16`, `CH-ALLOC`, `CH-FREE-NODE`, `CH-FIND-FREE-SLOT`, `CH-COOL-ALL`, `CH-TOTAL-HEAT`, `CH-REAP-SAFE`, per-channel Stadium heat accounting and the three-state machine all exist to support a generality **nothing has ever used**. Console proxies explicitly opt out of channels entirely (`capsule_console.c:23`). **Kernel-Hermes does not need a channel subsystem. It needs one broadcast membership list.** ### XXXIII.3 — Finding: the event words die with `process.4th` `EVENT-EMIT`, `EVENT-WAIT`, `EVENT-DRAIN` have exactly one live caller — **`capsules/process.4th`, which §XIII.2 established is `EXEC`'d nowhere.** The only other reference is `MANIFEST.md`. They go out with it, along with `SPAWN-EVENT` (§I.4: grepped tree-wide, zero consumers, "an unwired placeholder"). **Do not port the event layer.** ### XXXIII.4 — What must actually survive Stripped of the above, kernel-Hermes's real surface is: 1. **Message allocation and release, coupled to Stadium heat.** `MSG-ALLOC` pulls `Q.SLOT` from the *caller's own* reservoir and rolls back on refusal; `MSG-FREE-NODE` returns it via `STADIUM-EVICT`. **This is the hard part, and the one that cannot be approximated** — §XV.4 established that K conservation is the only thing that must survive a death, and `fleet_conserved` verifies it continuously. 2. **Send / deliver / deliver-all / tick / reap / ack / nack / redeliver-nacked** — the core protocol, ~9 words of real logic. 3. **One broadcast membership list** (replacing 28 words of channel machinery). 4. **The elevation path** — `SEND-ELEVATE-REQUEST` / `ELEVATE-GRANT`, live in `zuse-eligibility.4th` (loaded at boot by `init.4th`) and `mama_forth_words.c`. **Must survive; it is the word-ACL elevation ask carried to Zuse.** 5. **Status words**, cheap and worth keeping for the same reason §XXVIII.1 gives: a subsystem that emits nothing by default cannot be debugged. **Roughly half the file is accessors that become struct fields, and another third is generality with no caller. The genuinely new C is the heat-coupled allocator plus about nine protocol words.** That is a much smaller thing than "reimplement 504 lines of messaging," and it is the first honest estimate this document has had. ### XXXIII.5 — The caveat, and the part that is not mine to decide **"Unexercised today" is not "unwanted."** The channel abstraction may have been built for a future that has not arrived — per-VM private channels, capability-scoped groups. Deleting it is a **decision**, not an observation, and it belongs to Captain Bob. Two honest qualifications on the evidence: - §XXII.2's lesson applies in weakened form. Capsules are birthed by name from runtime strings, which defeats grep; **FORTH words are not** — a word is invoked by its literal text, so this search is far more reliable than the capsule-reachability search that nearly deleted the `init-l8-*` family. **But a human can still type `CH-REQUEST` at a REPL**, and no grep sees that. - The negotiation machinery is *described* in `MANIFEST.md` as though live. That is the same class of stale-documentation problem §XIII.2 found for block 4055 — **another entry for item 13's sweep.** **Recommendation: build kernel-Hermes for one broadcast membership and no negotiation, and keep the FORTH channel words in the Category B strip until something asks for them.** If per-VM channels are wanted later, they are a clean addition to a working arbiter rather than a speculative port into a new one. ### XXXIII.6 — Effect on confidence, stated plainly This was opened expecting to find the scope larger than advertised. **It is smaller**, and for a reason that generalizes: the FORTH layer accreted a general mechanism where the system only ever used a specific one. The reshuffle's value is partly that it forces that accounting. **What remains genuinely hard is narrow and now named:** the heat-coupled allocator, because K is continuously verified and any drift is visible. **That is the piece to build first and prove first** — before send/deliver, before delivery hand-off, before the latch. If K holds across alloc/free/evict under the new arbiter, the rest is protocol plumbing. **Punch list:** ⬜ **Item 27 — rule on §XXXIII.5**: does kernel-Hermes implement channel negotiation, or one broadcast membership? ⬜ **Item 28 — build and prove the heat-coupled allocator first**, verified against `fleet_conserved`, before any protocol work. --- ## XXXIV. The transition state: how two messaging layers coexist without corrupting K (2026-09-19) **The question nobody asked.** §XXII.1 draws the ordering distinction that protects the strip — Category B (`hermes/init.4th`, most of `messaging.4th`, the routing table, the slot-3 pairing) is "load-bearing until the replacement boots." That sentence implies a window in which **both messaging layers are live**, and nothing in this document says how they behave in it. ### XXXIV.1 — The real hazard is not control. It is heat. Item 22 (§XXXII) dealt with two mechanisms moving *control*. The transition creates the same shape one layer down: **two allocators drawing on the same per-VM Stadium reservoir, both counted in K.** The budget is explicit. `messaging.4th` derives it: ``` Q.SLOT = (Q.1 - Q.1/3) / (MSG-MAX + CH-MAX - 1) ``` — each VM's reservoir (`Q.1` = `Q48_ONE`, per `stadium.c`'s "item 4.1's reservoir starts each VM's quota at 1.0"), less `COMMON-CH`'s `Q.1/3` floor, split across **32 messages + 15 channel slots = 47 items.** That pool is sized for *one* messaging layer. **Conservation itself is not at risk** — K holds as long as each layer honestly pulls and returns, regardless of who holds the heat. **What is at risk is the budget**: two layers each sized to consume the same reservoir is oversubscription, and the failure mode is allocation refusals under load, not silent drift. `fleet_conserved` makes it visible rather than quiet, which is the saving grace. **A helpful interaction with §XXXIII:** retiring the channel abstraction removes `CH-MAX - 1` = **15 of those 47 slots**, so the divisor falls from 47 to 32 and per-message heat rises by ~47%. **Deleting the unused generality recovers roughly a third of the per-VM message budget** — useful headroom precisely during the window when two layers share it. ### XXXIV.2 — RULING: partition by message type. One owner per message, never shared. > **During the transition, every message type is owned by exactly one layer. A given message is > allocated, held, delivered and released by that layer alone. No message crosses.** This is the heat-side analogue of §XXXII's control-side ruling, and it has the same effect: **two systems coexist without any shared object.** Double-accounting becomes structurally impossible rather than carefully avoided — there is no message for both layers to charge for. ### XXXIV.3 — The staging, reusing §XXVIII's own discipline rather than inventing one `FABRIC-3.md` §XXVIII faced the structurally identical problem — standing up a second control-transfer mechanism beside a live one — and solved it with "**explicit go/no-go gates, not attempted as one pass**," each stage inert until the next enables it: Stage 1 allocated per-VM stacks with "nothing executes on them yet"; Stage 2 built the switch primitive "cooperative only, no timer"; Stage 3 turned on the timer. One commit per increment, full three-architecture acceptance before the next begins. **That discipline is the answer here, applied to messaging:** | Stage | Content | Live layers | |---|---|---| | **A** | Kernel-Hermes structures + heat-coupled allocator. **Wired to nothing, draws no heat.** Boot is byte-identical; `dict_hash` unmoved (no FORTH edit) | FORTH only | | **B** | **Prove the allocator** (item 28): a test path allocates and frees N messages, `fleet_conserved` holds across it. Heat drawn and returned within the test | FORTH only | | **C** | **Cut over exactly one message type** — the narrowest live one. `BLK-ATTACH-EVENT` (a single Hera↔Artemis request/ack pair) is the natural candidate | Both, **disjoint** | | **D** | Remaining live types cut over one at a time | Both, disjoint, shrinking | | **E** | Category B strip: `hermes/init.4th`, `messaging.4th`, routing table, slot-3 pairing | Kernel only | **Stage A is the one that makes this safe**, and it is the stage most likely to feel like wasted work. It is not: it is what makes every later stage a revert of one commit. ### XXXIV.4 — A coupling nobody noticed: the Tripod change cannot land before the cutover §XIX.6 puts Hestia into `is_fleet_foundation` (`capsule_birth.c:793-796`) and replaces Hermes's birth at `kernel_main.c:865`. **But FORTH Hermes must stay alive through Stages C and D** — it is still routing every type not yet cut over. **So the two changes are sequenced, not simultaneous, and the intermediate state is explicit:** - `is_fleet_foundation` **temporarily holds four names** — Hera, Artemis, Hestia **and** Hermes. That line is an OR-chain of prefix comparisons, so a fourth is trivial; what matters is that it is *deliberate and temporary*, not an oversight for a later reader to "clean up." - **Both births run** at `kernel_main.c`: Hestia's is added, Hermes's is not yet removed. - Hermes's birth and its `is_fleet_foundation` entry are removed **in Stage E, with the strip**, not with the Tripod change. **This contradicts nothing in §XIX — it sequences it.** Worth writing down because the obvious reading of §XIX.6 ("swap Hermes for Hestia") would break Stages C and D on the first boot. ### XXXIV.5 — Cost and rollback, honestly **Rollback is one commit per stage**, which is the entire reason for staging. Tags remain the oracle (`.claude/CLAUDE.md`: "when in doubt about the correct state of any file or branch, look at the tag first"). **The cost is acceptance runs.** Per `.claude/CLAUDE.md` the only valid acceptance is all three architectures booted in QEMU, one at a time, in the foreground, `clean` before `qemu`. Stages A–E plus the Category B strips is **at least eight full three-architecture cycles**, each with logs committed. That is the price of the discipline, and it is the same price §XXVIII paid across Stages 0–4. **Naming it now so it is a plan rather than a surprise** — amd64 under TCG is the slow one, and `.claude/CLAUDE.md` warns the session must stay engaged through long runs. ### XXXIV.6 — What would make me stop and re-plan Stated as tripwires rather than hopes, since the point of this pass is confidence: - **Stage B fails** — `fleet_conserved` will not hold across a bare alloc/free cycle under the new allocator. That is the piece §XXXIII named as the only genuinely hard one, and failing it early is the cheap outcome. **Do not proceed to C.** - **Stage C shows allocation refusals** that the FORTH-only baseline did not. That is the oversubscription of §XXXIV.1 arriving, and the answer is to retire channels first (§XXXIII, recovering ~a third of the budget) rather than to push on. - **`dict_hash` diverges across architectures** at any stage. Not "changes" — changing is expected and normal (§III.5). **Diverging between amd64/aarch64/riscv64 is the signal that something non-deterministic entered the dictionary**, and it is the one failure this project's acceptance criteria are specifically built to catch. ### XXXIV.7 — Punch list - ⬜ **Item 29 — adopt §XXXIV.2's partition rule and §XXXIV.3's Stage A–E staging** as the kernel-Hermes build plan (replacing item 6's single line). - ⬜ **Item 30 — sequence the Tripod change per §XXXIV.4**: Hestia added alongside Hermes, `is_fleet_foundation` temporarily four names, Hermes removed only at Stage E. - Item 28 (prove the allocator) becomes **Stage B** and keeps its priority. - Item 27 (channels: negotiate or one membership) **gains urgency** — §XXXIV.1 shows retiring channels directly relieves the transition's budget pressure. --- ## XXXV. Pre-mortem: it is six months on, the reshuffle failed — why? (2026-09-19) Run at Captain Bob's request before pulling the build trigger. **Method: assume failure, work backwards, and ground every cause in this project's own recorded failure record rather than in generic risk.** Two of the six are new findings that change the design; one is a hypothesis that weakened when checked, recorded as such. ### XXXV.0 — This codebase's dominant failure mode, evidenced Before the specific causes, the pattern they mostly instantiate. **Things here fail without saying anything:** - §XXVIII.1: "Stage 3 emits nothing per switch by default, so **a switch storm and a healthy idle REPL produce an identical serial log.** The corruption was already live in every one of those 'clean' Stage 3 boots." - §XXXII.2 (`FABRIC-3`): a console-name mismatch "**silently falls back to direct interpretation with no error** — just quietly never relays." - §XIV/§XXXI: near-simultaneous attach — **6 of 9 identities silently never triggered** their attach line. - §XX: "Referencing an undefined word during FORTH compilation doesn't raise a hard error, it **silently drops the definition being compiled**." **Four independent subsystems, one signature.** Any pre-mortem for this project that does not put silent failure first is not about this project. ### XXXV.1 — Cause 1, most likely: the sinking latch never fired, because nothing could raise it **This is the pre-mortem's principal finding, and it is a design gap, not a risk.** §VIII.1 has Hermes raise a latch when it detects itself sinking, holding it until it goes down, to give the fleet a window. That was conceived when Hermes was **a VM on the Stadium floor** — a patron with heat, a TTL and a reservoir, capable of degrading. **Kernel-Hermes is none of those things.** `stadium_admit()` is called only from `capsule_birth.c:808`, on birth. Kernel-Hermes is not born, so **it is not a Stadium patron: no heat, no TTL, no eviction, no compudynamic death.** Which raises the question §VIII never had to answer: > **What does it mean for a kernel-resident C subsystem to *sink*?** Its real failure modes are: - **A bug → `sk_hal_panic()` → `while(1){arch_halt();}`.** Immediate and total. **No window at all** — the fleet never gets to shut down cleanly, which is precisely what §VIII.1 exists to provide. - **Resource exhaustion → allocation refusals.** Degraded, but not dying, and recoverable. Raising a monotonic "I am sinking" latch here would be wrong (§XXIII.3 made it irreversible). **And §VII's ladder does not apply to it either.** Rung 1 is warm restart, rung 2 is a from-scratch `BIRTH` — **but `BIRTH` births VMs.** There is no birthing a kernel subsystem. §XXI.3's table gives every rung an actor *for VMs*; kernel-Hermes sits outside that table entirely. **So the failure story for the one component that cannot use `SOS` is undesigned.** Six months on, the most likely shape of "it failed" is: the arbiter hit a bug, panicked, halted the machine instantly, and every VM died mid-write with no window — with the latch never set, because there was no state between healthy and gone. **Punch item 31, and it gates §VIII/§XXIII rather than following them.** Candidate directions, none chosen: give kernel-Hermes an explicit degraded state it *can* detect and announce (queue exhaustion, corrupted routing table) and raise the latch there; or accept that its only failure is a panic, and make the panic path itself raise the latch and yield briefly before halting; or conclude §VIII was scoped to a component that no longer exists and retire it. **The third is a real possibility and should not be dismissed for tidiness.** ### XXXV.2 — Cause 2: the fleet hit a ceiling of 16 `SK_SWITCH_MAX_SLOTS` is **16** (`capsule_vm_switch_signal.c:42`), with the header noting compaction "after `SK_SWITCH_MAX_SLOTS` attach/detach cycles." That was a reasonable bound when the switcher was one of *two* ways a VM got control. **§XXXII made it the only one.** So the moment that ruling lands, **16 becomes a hard ceiling on how many VMs can receive control at all** — and §XXII's own comment says "the real fleet is headed toward hundreds of VMs." **This is the §XXII pump story repeating one layer over**: a bound that is invisible at Tripod scale, fine at 9, and a wall later. §XXII found its wall live, at 9 VMs, on the slowest architecture, mid-campaign. **This one is findable now, on paper, before it is built on.** **Punch item 32: size the switch-slot table against the intended fleet before §XXXII's ruling is implemented.** Not necessarily hard — but it must be a decision, not a default carried forward from when it did not matter. ### XXXV.3 — Cause 3, weakened on inspection and recorded honestly **The hypothesis:** the reshuffle's tripwires (§XXXIV.6) all depend on `fleet_conserved`, while §XXV.1 has the DoE's invocative paths being rewritten in the same period — so the instrument that verifies K would be under reconstruction exactly when it is most needed. **Checked, and it is weaker than it looked.** `vm_physics_conserved()` lives in `capsule_vm_physics.c:492` — a kernel function, independent of the DoE — and is exposed as a FORTH word at `mama_forth_words.c:2099` (`VM-CONSERVED?`). `doe_log.c` only *reports* it, in columns 19/20. **The capability survives a DoE rewrite.** **The residual is real but smaller:** what the DoE provides is the *continuous, per-tick record* of K across a long run. A word you have to call gives you a point sample. **Stage B's proof (§XXXIV.3) should therefore not lean on the DoE** — it needs its own deterministic alloc/free/check loop calling `VM-CONSERVED?` directly. Recorded so Stage B is designed for an instrument that exists rather than one mid-rewrite. ### XXXV.4 — Cause 4: Hestia turned out to be ceremonial §XVIII.2 established that the HAL must never know a VM exists, so **Hestia's ownership of the fabric is by vocabulary and registration — not enforcement.** §XVIII.2 called that "weaker than it sounds and exactly right." The six-months-on failure: **nothing changed.** Other VMs still reach the framebuffer, Hestia owns the fabric only in the documentation, and the reorganization was cosmetic — a rename plus a capsule. **The test is narrow and checkable:** after the reshuffle, is `PLOT`/`FB-WIDTH`/`FB-HEIGHT` registered *anywhere* except Hestia's word table? In particular, `register_child_vm_words()` must **not** hand them out the way it hands out the eight `STADIUM-*` primitives. If it does, ownership is a fiction on day one. **Punch item 33: verify the framebuffer primitives are registered to Hestia alone**, and treat any other registration site as a defect rather than a convenience. ### XXXV.5 — Cause 5: the strip took something live, and the usual net was down §XXII.2's near-miss (the `init-l8-*` family reading as zero-reference) plus §XXII.3's acceptance asymmetry — a bad DoE-capsule strip **passes all three architectures cleanly** and surfaces months later during a campaign. **What makes this worse than §XXII assumed:** §XXV.1 has the DoE's invocative paths being rewritten in the same window. **The campaign that would eventually catch a wrong strip is itself in flux**, so "we would have noticed at the next campaign" is a weaker assurance than it was when §XXII was written. **Mitigation, no new mechanism needed:** do the Category A strip **before** the DoE rewrite begins, while the existing campaign apparatus still runs, and run one campaign against the stripped tree as the strip's real acceptance — not just a boot. ### XXXV.6 — Cause 6: it was declared done, and it wasn't The project's most repeated failure, by its own record: §XXVIII's "Stage 3 CLOSED" corrected by §XXVIII.1; §XXV corrected by §XXVI ("wrong VM tested"); `FABRIC-2`'s close header overclaiming (§XXXI.3); the `v2.0.1` bump rolled back (§I.2). **Already partly defended** — §XXIX refused the LTS label and wrote criteria instead, §XXXIV.6 states tripwires as stop conditions. **The remaining exposure is the acceptance definition itself:** three green boots prove the boot path, and §XXXV.0 says this codebase's failures do not announce themselves on the boot path. **Punch item 34: for each of Stages A–E, name in advance the observation that would prove it worked** — not "it booted," but the specific evidence, chosen before the stage runs. §XXVIII.1 is the cautionary case: a stage that emits nothing cannot be verified by reading a log that looks identical either way. ### XXXV.7 — What the pre-mortem changes **Two new design gaps** (§XXXV.1's undesigned kernel-arbiter failure story, §XXXV.2's slot ceiling), **two verification corrections** (§XXXV.3's Stage B instrument, §XXXV.6's name-the-evidence rule), **one sequencing change** (§XXXV.5: strip before the DoE rewrite), and **one checkable invariant** (§XXXV.4). - ⬜ **31 — design kernel-Hermes's own failure story**, or retire §VIII. **Gates §VIII/§XXIII.** - ⬜ **32 — size `SK_SWITCH_MAX_SLOTS` against the intended fleet** before §XXXII lands. - ⬜ **33 — framebuffer primitives registered to Hestia alone**, verified not assumed. - ⬜ **34 — per-stage success evidence named in advance**, per §XXVIII.1's lesson. - ⬜ **35 — Category A strip before the DoE rewrite**, with a campaign as its acceptance. **The honest summary: the most likely cause of failure is not the messaging rewrite.** That is now sized (§XXXIII), staged (§XXXIV) and instrumented. **It is that the one component which cannot call for help has no defined way to fail** — and that this codebase's failures are characteristically quiet. --- ## XXXVI. Item 31 SETTLED: kernel-Hermes sinks when its own heat accounting stops adding up §XXXV.1 found §VIII's latch had no trigger: kernel-Hermes is not a Stadium patron, so it has no heat, no TTL and no compudynamic death, and §VII's ladder does not reach it. Settled here — **§VIII survives, with a trigger that is concrete, cheap, and already precedented twice in this codebase.** ### XXXVI.1 — What can actually go wrong, enumerated | Mode | Shape | Is it "sinking"? | |---|---|---| | **kmalloc failure** for a message | Refuse the send, return the heat | **No** — refusal, by design | | **Budget exhaustion** (reservoir empty, `Q.SLOT` unaffordable) | `MSG-ALLOC` refuses, rolls back | **No** — this is the economy working | | **Corrupted internal state** — queues, routing table, free list, heat bookkeeping | Still executing, **can no longer route correctly**, and may not know | **Yes. This is the only real one.** | | **Hard bug in its own code** | `sk_hal_panic()` → `while(1){arch_halt();}` | **No — it is already gone.** §XXXVI.4 | **Only the third mode is a state between healthy and gone**, which is precisely what a held-until-death latch is for. The other three are either recoverable or instantaneous. ### XXXVI.2 — What §VIII's window is actually worth, which is why it is not retired §XXXV.1 raised retiring §VIII as a legitimate option. **It is not the right call, and the reason is concrete: the window buys a flush.** Per §XVI, clean shutdown is forced `blk_flush(0)` then `BYE`. `block_subsystem.c` carries dirty RAM blocks (`g.dirty_ram`) that a hard halt loses. Artemis holds block state; identity VMs may have written. **The window's value is not messaging continuity — §XV.4 already ruled that payloads may vanish. It is that dirty blocks reach the disk.** That is worth a mechanism. ### XXXVI.3 — RULING: the sinking condition is a failed self-audit of its own heat accounting > **Kernel-Hermes continuously audits its own heat bookkeeping. When the audit fails, it can no > longer be trusted to route, and *that* is sinking: raise the latch, hold it, let the fleet > flush and `BYE`, then stop.** The invariant is internal and needs no reservoir of its own: ``` sum(heat held in live messages) == total_pulled - total_returned ``` If those diverge beyond an epsilon, the arbiter's accounting is corrupt — and an arbiter that has lost track of the heat it holds is exactly an arbiter that will break K for everyone else. **It is the earliest honest moment it can know it is failing.** **This is not a new mechanism. It is the third instance of a pattern already in the tree:** - **`MSG-K`** (`messaging.4th:364`) — the FORTH messaging layer *already* audits its own heat: `MSG-TOTAL-HEAT CH-TOTAL-HEAT + STADIUM-RES@ + STADIUM-WORD-HEAT +`. **The layer being replaced already does this.** - **`vm_physics_conserved()`** (`capsule_vm_physics.c:492`) — the C shape, verbatim: sum, compare against the expected constant, tolerate `VM_PHYSICS_EPSILON_Q48`. **One honest adaptation:** `MSG-K` is *per-VM* — it sums a patron's arena plus its own reservoir. Kernel-Hermes has no reservoir, holding heat pulled from *senders'*. So the formula is not a straight port; the invariant above is its analogue for a non-patron holder. **`MSG-K` establishes the idea is already this layer's practice, not that the code lifts across.** **A property worth naming:** §XV.4 ruled that **K is the only thing that must survive a death.** Under this ruling, **a violation of that same conservation is what announces the death.** The invariant being protected and the alarm are the same quantity — no second mechanism, nothing to keep in sync. ### XXXVI.4 — Panics get no window, deliberately **A panic raises nothing and flushes nothing.** `sk_hal_panic()` prints and halts, and it should stay that way: - **The corruption that caused the panic may be in the block layer.** Flushing from a panic path risks writing corrupt data over good data — **worse than losing the dirty blocks.** - Standard practice in any kernel: do not do complex work in the panic path. **So the accepted loss is stated rather than engineered around:** if kernel-Hermes panics, dirty blocks are lost and the fleet gets no window. The self-audit exists precisely to catch the recoverable-but-doomed case *before* it becomes a panic. **It narrows the window of silent loss; it does not close it, and claiming otherwise would be the overclaiming §XXXV.6 warns about.** ### XXXVI.5 — §VII's ladder is VM-only. Stated, because §XXI.3 implied otherwise §XXI.3's table gives every rung an actor and reads as universal. **It is not.** Rung 1 is warm restart, rung 2 is a from-scratch `BIRTH` — **and `BIRTH` births VMs.** There is no birthing a kernel subsystem, and no parent to birth it. > **Kernel-Hermes has a two-state life: correct, or stopped. No rungs, no recovery, no > re-birth.** The ladder applies to Stadium patrons. Kernel-Hermes is not one. This does not weaken §VII — it bounds it, and the bound was always implicit in "authority flows down the birth graph" (§IX.2): a thing that was never born has no place on that graph. ### XXXVI.6 — Is a self-audit a supervisor? No Worth testing, given how much of this document defends against exactly that (§XIV.5, §XV.3, §XXI.2): - It watches **only itself** — no other component's liveness, no other component's fate. - It decides **nothing about anyone else**. It publishes one fact about its own state; VMs act on their own shutdown routines (§XXIII.4, §IX.1). - It is **not a timer over the fleet**. The natural cadence is per-allocation and per-release — the moments its own bookkeeping changes — not a scan of anything. **A component checking its own invariants is not a supervisor.** It is the same thing `vm_physics_conserved()` already does, one scope down. ### XXXVI.7 — Punch list - ✅ **Item 31 — SETTLED** (§XXXVI): §VIII kept; the sinking condition is a failed self-audit of kernel-Hermes's own heat accounting; panics deliberately get no window; §VII's ladder is VM-only. - ⬜ **Item 36, NEW** — choose the audit's epsilon and cadence. `VM_PHYSICS_EPSILON_Q48` (5% of `Q48_ONE`, per §25.7's own note) is the existing precedent but is **far too loose for this purpose** — it was sized for fleet drift, not for detecting a corrupt ledger. A tighter bound is wanted; a wrong one either cries wolf or never fires. - ⬜ **Item 37, NEW** — Stage B (§XXXIV.3) should prove the **audit** as well as the allocator. They are the same arithmetic seen from two sides, and proving them together costs nothing extra. - Items 32–35 unchanged (§XXXV.7). --- ## XXXVII. Item 36 SETTLED: the audit epsilon is zero — and §XXXVI's invariant needed correcting first ### XXXVII.1 — Correction to §XXXVI.3: the invariant as written is wrong §XXXVI.3 proposed `sum(heat held in live messages) == total_pulled - total_returned`. **Traced 2026-09-19, that cannot hold, and the reason is by design:** ```forth : MSG-COOL-ONE ( m -- ) DUP MSG-HEAT@ Q-DECAY Q.* SWAP MSG-HEAT! ; ``` `MSG-COOL-ONE` multiplies a message's heat by `Q-DECAY` (65208/65536 ≈ 0.99499) and **writes it back. The difference is returned to nothing — it is destroyed.** So a message allocated at `Q.SLOT` and cooled a few times holds less than was pulled for it, and the two sides of the invariant diverge **without any bug at all.** This is Loop #3's decay doing its job. **§XXXVI.3 is corrected here rather than rewritten**, per this series' own rule. The ruling it made — that the sinking condition is a failed self-audit of kernel-Hermes's own heat accounting — **stands.** The arithmetic it proposed does not. ### XXXVII.2 — The numbers, which settle the epsilon question on their own | Quantity | Value | |---|---| | `Q48_ONE` | **65536** (`q48_16.h:67`) | | `VM_PHYSICS_EPSILON_Q48` | **3277** (`capsule_vm_physics.c:57`) — 5% of `Q48_ONE` | | `Q.SLOT` today = `(Q.1 − Q.1/3) / (32 + 16 − 1)` | ≈ **929** | | `Q.SLOT` after retiring channels (§XXXIII) = `/32` | ≈ **1365** | **The existing epsilon is 3.5 messages' worth of heat** at today's `Q.SLOT`, or 2.4 after §XXXIII. **Kernel-Hermes could lose track of three entire messages and the audit would not fire.** That is not "too loose" as §XXXVI.7 put it — it is loose enough to hide the exact failure the audit exists to catch. `VM_PHYSICS_EPSILON_Q48` is **not wrong**; it was sized for a different quantity — fleet heat, which is genuinely noisy. **It must not be reused here.** ### XXXVII.3 — RULING: epsilon is zero, made achievable by ledgering the decay The corrected invariant: ``` sum(heat held in live messages) == total_pulled − total_returned − total_decayed ``` **All four terms are exact integers.** `Q.SLOT` is a compile-time constant, pulls and returns are symmetric, and decay's per-application delta is *computable at the moment it is applied* — `heat_before − heat_after`, already in hand. **Nothing here is a measurement. There is no noise to tolerate.** > **Epsilon is zero. Any divergence is a bug, and the audit fires on the first unit.** **The one change required is small and is the whole trick:** today decay is an invisible overwrite — `MSG-HEAT!` destroys heat and records nothing. **Kernel-Hermes must record what it destroys.** One counter, incremented by the delta on every decay application. That converts an unauditable quantity into an exact one, and it is why the epsilon can be zero rather than guessed. **This also answers §XXXVI.7's "wrong either way" worry directly:** a guessed epsilon either cries wolf or never fires. A zero epsilon over an exact ledger does neither — it is a correctness check, not a threshold. ### XXXVII.4 — Cadence: O(1) counters, never a scan `MSG-TOTAL-HEAT` (`messaging.4th:267`) computes its sum by **scanning the whole arena** — `MSG-MAX 0 DO … LOOP`. Fine for 32 slots per VM. **As a fleet-wide kernel structure it is §XXII's O(N)-per-beat wall again**, the mistake this reshuffle has now identified three times (the pump, `SK_SWITCH_MAX_SLOTS`, this). **So the audit maintains running counters rather than recomputing a sum:** `held`, `pulled`, `returned`, `decayed`, each updated at the three moments they change — allocate, release, decay. **The check is then one comparison of four integers, O(1), and can run on every mutation** without a cadence decision at all. **A scan remains useful as a *verification of the counters themselves*** — run it in Stage B and in diagnostics, not on the hot path. That is the same split `vm_physics_conserved()` already uses: cheap invariant continuously, expensive truth occasionally. ### XXXVII.5 — What this does not change **Fleet-level `vm_physics_conserved()` keeps its 5% band.** Different quantity, genuinely noisy, and outside this reshuffle. §XXXIV.6's tripwire still uses it as written — the two epsilons answer different questions and should not be unified. ### XXXVII.6 — Flagged, not resolved: decay means fleet heat is not strictly conserved **A finding this section surfaced and deliberately does not settle.** If message heat decays and is destroyed, and `MSG-FREE-NODE`/`STADIUM-EVICT` return only what remains, then heat pulled from a VM's reservoir **does not fully return to it.** Fleet heat would drift downward over a long enough run — which is precisely what `FABRIC-0.md` §25.7 warned of for a *different* cause (`vm_physics_touch()` share truncation, since fixed per `FABRIC-1.md:468`): "a long enough run would trip `VM-CONSERVED?`. **Nobody has measured the rate.**" **Limit of this trace, stated honestly:** `STADIUM-EVICT`'s return semantics were **not** read end to end for this section, so whether the decayed portion is genuinely lost or accounted elsewhere is **not established here.** It may be intended — decay *is* the physics — in which case "conservation" means "conserved modulo decay" and the 5% band is doing exactly that job. **Punch item 38: establish whether decayed message heat returns to the reservoir.** It bears on §XV.4's "nothing survives but K" and on §XXXIV.6's tripwire, and it is cheap to settle by reading one function. **Reported, not fixed.** ### XXXVII.7 — Punch list - ✅ **Item 36 — SETTLED** (§XXXVII): epsilon **zero**; achieved by ledgering decay as an explicit counter; `VM_PHYSICS_EPSILON_Q48` explicitly **not** reused. §XXXVI.3's arithmetic corrected in place. - ⬜ **Item 38, NEW** — does decayed message heat return to the reservoir? (§XXXVII.6) - ⬜ **Item 37** unchanged — Stage B proves the audit alongside the allocator, and should use the **scan** to verify the counters (§XXXVII.4). - Items 32–35 unchanged. --- ## XXXVIII. Item 38 SETTLED: decayed heat does not return — and a reaped message returns nothing at all §XXXVII.6 flagged this as unestablished and named the one function to read. Read now, end to end. **The answer is worse than §XXXVII.6 estimated, and the cause is a design conflation rather than a bug.** ### XXXVIII.1 — The trace Four facts, each from source: 1. **Message heat *is* Stadium cell heat.** `MSG-HEAT@/!` are not local fields — `messaging.4th:222-223`: `: MSG-HEAT@ ( m -- q ) MSG-STADIUM-CELL@ STADIUM-HEAT@ ;`. So `MSG-COOL-ONE` decays `header->heat` **in the Stadium cell itself.** 2. **Eviction returns whatever remains**, and the code knows exactly why (`stadium.c:459-466`): *"the departing patron's remaining heat must flow back to its owner's reservoir before the cell returns to the free list, **or every reap leaks heat and Σ(resident) + reservoir drifts below `Q48_ONE`**."* → `stadium_quotas[slot].reservoir += header->heat;` 3. **Reaping fires exactly when heat reaches zero** (`messaging.4th:302-305`): `MSG-REAP` → `MSG-SCAN @ MSG-HEAT@ 0 = IF … MSG-FREE-NODE`. 4. Therefore, at the moment a naturally-aged message is evicted, `header->heat` is **0**. > **A message that ages out returns `reservoir += 0`. The entire `Q.SLOT` pulled to send it is > destroyed.** **This is not the partial loss §XXXVII.6 anticipated.** The eviction path's heat-return — which exists specifically to stop reaps leaking — **is bypassed completely for every aged-out message**, because decay has already drained the field to zero before eviction reads it. The guard works; decay walks around it. ### XXXVIII.2 — The root cause: one field carrying two incompatible meanings `header->heat` is used as **both**: - **An activity metric** — Loop #3's "quiescent words lose heat over time." Decaying this is correct and is the physics. - **A reservation currency** — `Q.SLOT` is described in `messaging.4th:44` as "per-item **admission heat** for `MSG-SEND`/`CH-ACCEPT`," pulled from a reservoir and expected back. **Decaying a budget token destroys budget.** A message sitting in a queue quietly becomes cheaper, and on reap refunds nothing. The two meanings are irreconcilable in one field, and the leak is the visible consequence. **Whether it is intentional is genuinely arguable.** "You pay to send; if nobody collects, you forfeit" is a coherent backpressure economy. But nothing in the tree replenishes reservoirs — every VM starts at `Q48_ONE` and the system only ever conserves or loses — so **the forfeit is permanent and the pool is finite.** A long-lived VM that sends messages nobody collects eventually cannot send at all. ### XXXVIII.3 — A precision boundary, stated rather than glossed **There are two distinct heat accountings in this system, and this finding lands in one of them:** - **Stadium accounting** — patron-cell `heat` plus `stadium_quotas[].reservoir`, audited by `MSG-K` (`MSG-TOTAL-HEAT + CH-TOTAL-HEAT + STADIUM-RES@ + STADIUM-WORD-HEAT`). **This is what leaks.** - **VM-physics accounting** — `vm_physics_fleet_heat_sum()` over `execution_heat_q48`, which is what `vm_physics_conserved()` checks against `Q48_ONE` and what `FABRIC-3.md` §XVIII verified live as K. **Whether the two are coupled was not established here**, so this section does **not** claim the message leak trips `VM-CONSERVED?`. It may be invisible to it entirely. **What it does claim is narrower and certain: Stadium reservoir heat is destroyed, permanently, at `Q.SLOT` per reaped message.** Establishing the coupling is worth doing before §XXXIV.6's tripwire is relied on — **punch item 39.** ### XXXVIII.4 — RECOMMENDATION: separate the reservation from the age Greenfield C is the moment this costs nothing to fix, because kernel-Hermes is writing the structure anyway (§XXXIII). **Give a message two fields instead of one:** | Field | Semantics | Decays? | Returned at free? | |---|---|---|---| | **reservation** | the `Q.SLOT` pulled from the sender | **No** | **In full, always** | | **age** | drives reaping when it expires | **Yes** | n/a | **The leak disappears**, reaping keeps working (it reads `age`, not the budget), and the backpressure economy is preserved in its honest form: **sending costs you the reservation for as long as the message lives, and you get it back when it dies** — rather than being silently taxed for messages nobody collected. **Captain Bob's call**, because it is an economic behaviour change, not a repair. Option (a) — keep the forfeit, document it as intentional — remains legitimate, but should then be *stated* as a designed sink rather than left looking like the leak `stadium.c:460` was written to prevent. ### XXXVIII.5 — This makes §XXXVII's ledger simpler, not harder A pleasing consequence. §XXXVII had to correct §XXXVI's invariant because decay broke it, and introduced a `decayed` counter to restore exactness: ``` held == pulled − returned − decayed (§XXXVII, with decay in the budget) held == pulled − returned (with reservation separated from age) ``` **Separating the fields restores §XXXVI.3's original invariant exactly, and deletes the counter §XXXVII.3 had to invent.** The epsilon stays zero either way — but under this recommendation it is zero over a *simpler* ledger, with one fewer thing to get wrong. **Both §XXXVI.3 and §XXXVII.3 were right about the destination and wrong about the obstacle.** The obstacle was never decay itself; it was decay being applied to the wrong field. ### XXXVIII.6 — Punch list - ✅ **Item 38 — SETTLED** (§XXXVIII): **no.** Decayed heat does not return, and an aged-out message returns nothing at all — the eviction guard is bypassed because decay zeroes the field first. - ⬜ **Item 39, NEW** — establish whether Stadium accounting and VM-physics accounting are coupled, i.e. whether this leak is visible to `VM-CONSERVED?` (§XXXVIII.3). **Do this before relying on §XXXIV.6's tripwire.** - ⬜ **Item 40, NEW** — rule on §XXXVIII.4: separate reservation from age, or keep the forfeit and document it as a designed sink. - Items 32–35, 37 unchanged. --- ## XXXIX. Item 39 SETTLED: the two accountings are not coupled — and §XXXIV.6's tripwire watches the wrong one §XXXVIII.3 declined to claim the message-reap leak was visible to `VM-CONSERVED?`, and filed this to settle before §XXXIV.6's tripwire was relied on. **Settled: they are entirely separate systems, and the tripwire as written would have stayed green through exactly the failure it was meant to catch.** ### XXXIX.1 — The trace: no coupling, in either direction | Check | Result | |---|---| | `capsule_vm_physics.c` references to `stadium` | **0** — and it does not include `stadium.h` (only `starforth_config.h`, for the unrelated `STADIUM_CAPACITY_TICK`) | | `capsule_vm_physics.c` references to `reservoir` | **0** | | `stadium.c` references to `vm_physics` | **1 — a comment**, not code | | `stadium_words.c` references to `vm_physics` | **0** | **`vm_physics_fleet_heat_sum()` sums `n->physics.execution_heat_q48`** over the physics node list. Every writer of that field is internal: initialise (`is_root ? Q48_ONE : 0`), transfer between two VMs, redistribute on death. **Nothing reads or writes a Stadium reservoir or patron cell.** Message allocation cannot move it. ### XXXIX.2 — They are not even the same *shape* of invariant `StadiumVMQuota.reservoir`'s own doc comment (`stadium.c:105-109`): > "Invariant: **Σ(resident patron heat) + reservoir == `Q48_ONE`**, checked the same way > `vm_physics_conserved()` checks the fleet sum." …and `stadium.h:484` confirms "**conservation is per-VM**." | | Scope | Quantity | Target | |---|---|---|---| | **Stadium** | **Per-VM** | patron-cell heat + that VM's reservoir | `Q48_ONE` **each** | | **VM-physics ("K")** | **Fleet-wide** | Σ `execution_heat_q48` over live VMs | `Q48_ONE` **total** | Two invariants, two scopes, two disjoint data sets — sharing only the Q48.16 format, the constant `Q48_ONE`, and the word "heat." **That shared vocabulary is what made them look like one system**, and is why §XV.4 and §XXXIV.6 conflated them. ### XXXIX.3 — Only one of them has a checker **`vm_physics_conserved()` is the only `*_conserved()` function in the kernel.** Confirmed by exhaustive grep: the sole other match is `mama_word_vm_conserved()`, its FORTH wrapper. The Stadium side has: - a **documented** invariant (the comment above), - a **diagnostic print** — `stadium_words.c:328`, `"Stadium conservation: resident_sum="` — a human-readable dump, not a boolean, - `MSG-K` (`messaging.4th:364`), a FORTH word covering only the messaging slice, which someone must call. **There is no automated, continuous verification of Stadium conservation anywhere.** Which means §XXXVIII's leak — `Q.SLOT` destroyed per reaped message — **is invisible to every automated check in the system.** It is not that the checks disagree; it is that nothing is looking. ### XXXIX.4 — CORRECTION to §XXXIV.6: the tripwire monitors a quantity the failure cannot move §XXXIV.6 states: *"Stage B fails — `fleet_conserved` will not hold across a bare alloc/free cycle under the new allocator."* **That is wrong, and wrong in the dangerous direction.** `fleet_conserved` reports `vm_physics_conserved()`, which sums `execution_heat_q48`. **Message allocation never touches that field.** A kernel-Hermes allocator that leaked every reservation it took would leave `fleet_conserved` reporting a serene `1`. **This is precisely §XXXV.0's signature** — a check that passes while the thing it is supposed to guard is broken — and I built one into the plan. **Item 39 existed to catch it before it mattered, and did.** **Corrected tripwire for Stage B:** > Stage B verifies **(a)** kernel-Hermes's own ledger (§XXXVII.3, epsilon zero) and **(b)** the > **Stadium per-VM invariant** for the sending VM — Σ(resident patron heat) + reservoir == > `Q48_ONE` — **before and after** the alloc/free cycle. **`fleet_conserved` is not evidence for > this stage** and should not be cited as such. Since §XXXIX.3 shows no boolean Stadium checker exists, Stage B either calls `MSG-K`, or reads `stadium_words.c:328`'s dump, or — better, and cheap — **adds the missing `stadium_conserved()` as its first act.** That function is a five-line analogue of `vm_physics_conserved()` and the system has wanted one since the invariant was written down. ### XXXIX.5 — Consequences for two earlier rulings **§XV.4 ("nothing survives except K") needs its K qualified.** The heat a dying arbiter holds is **Stadium** heat. The K that `FABRIC-3.md` §XVIII verified live, and that §XV.4 cited, is **VM-physics** K. **The arbiter's heat was never in the verified quantity.** The ruling's substance stands — a dying arbiter must return what it holds — but it must say *Stadium conservation*, not K. **§XXXVI/§XXXVII's self-audit is promoted from safety net to sole instrument.** With no continuous Stadium checker, kernel-Hermes auditing its own ledger would be **the only automated guard on the messaging pool's conservation.** That raises the value of §XXXVII.3's zero epsilon considerably — and argues for §XXXIX.4's `stadium_conserved()` as a second, independent check rather than relying on the arbiter to police itself alone. ### XXXIX.6 — A naming hazard worth fixing in the sweep Two conserved quantities, both Q48.16, both targeting `Q48_ONE`, both called "heat," differing only in scope — and the FABRIC series calls one of them "K" without qualification. **Every future reader will conflate them exactly as §XV.4 and §XXXIV.6 did.** **Added to item 13's documentation sweep:** wherever "K" or "conservation" appears, say which — **fleet K** (`vm_physics`) or **quota conservation** (Stadium, per-VM). ### XXXIX.7 — Punch list - ✅ **Item 39 — SETTLED** (§XXXIX): **not coupled.** Disjoint data, disjoint code, different invariant scopes. - ✅ **§XXXIV.6 corrected** (§XXXIX.4): Stage B verifies the ledger and Stadium per-VM conservation; `fleet_conserved` is not evidence for it. - ⬜ **Item 41, NEW** — add `stadium_conserved()`, the missing boolean analogue of `vm_physics_conserved()`. Cheap, and the invariant has been documented-but-unverified since it was written. - ⬜ **Item 42, NEW** — qualify "K"/"conservation" throughout the docs (§XXXIX.6). Folds into item 13. - ⬜ **Item 40** unchanged — separate reservation from age (§XXXVIII.4). --- ## XL. Item 40 SETTLED: preserve the consumption model, ledger the sink — and a correction to §XXXVIII's framing §XXXVIII.4 recommended separating reservation from age to remove what it called a leak. **Further evidence found while settling this says the behaviour is a deliberate model, not a defect — and the recommendation is revised accordingly.** ### XL.1 — The authors knew. It is documented at the reap site. `messaging.4th`, block 5025, immediately above `MSG-REAP`: > `( K reap only fires at heat=0: freed K contribution is 0. )` > `( Force-reap not yet implemented. If added: explicit )` > `( K redistribution will be required here. )` **That is not an oversight; it is a design note.** It states outright that reaping returns zero, and anticipates that a *force*-reap — freeing a message before it has cooled — would need "explicit K redistribution," i.e. handing the remaining heat back. **The zero-return at natural reap is intended: the heat is modelled as consumed over the message's life, not held as a refundable deposit.** ### XL.2 — CORRECTION to §XXXVIII §XXXVIII called it "a real, structural leak" and framed the two-meanings conflation as an error. **That framing was wrong.** The trace in §XXXVIII stands exactly — decay drains the field, reap returns nothing, the eviction guard is bypassed — **but the interpretation does not.** It is a consumption economy, documented where it happens, and §XXXVIII did not find that comment. **What survives from §XXXVIII:** the mechanism, the arithmetic, and the observation that nothing replenishes. **What is withdrawn:** the word "leak," and the §XXXVIII.4 recommendation to change the economics as part of this reshuffle. ### XL.3 — Two further roles for message heat, both consistent with the model - **`MSG-NACK-LAST` halves it** (`messaging.4th:432`): `DUP MSG-HEAT@ 2 / OVER MSG-HEAT!`. A rejected message ages **twice as fast** — a penalty expressed in the same currency. This only makes sense if heat is a time-to-live, reinforcing §XL.1. - **Delivery order does *not* use it.** `MSG-DELIVER-ALL` scans the arena in slot order and delivers anything not already delivered or nacked — **no heat comparison anywhere.** So heat plays no scheduling role, which is consistent with §XV.3 (nothing owns turn order) and means changing it cannot perturb delivery sequencing. ### XL.4 — RULING: keep the model; make the sink explicit > **Kernel-Hermes preserves the consumption economy exactly — heat decays, natural reap returns > nothing — and additionally *records what it consumes*.** The Stadium per-VM invariant then reads: ``` Σ(resident patron heat) + reservoir + consumed == Q48_ONE ``` **This is what makes item 41's `stadium_conserved()` possible at all.** Without a `consumed` term, a checker over a system with a designed sink reports non-conservation as *normal operation* — which forces a tolerance that hides real bugs, or makes the checker useless. **With it, the invariant is exact and the epsilon stays zero.** **A pleasing arc worth recording:** §XXXVII.3 invented a `decayed` counter to keep the ledger exact. §XXXVIII.5 proposed deleting it by separating the fields. **It turns out to be the thing that makes Stadium conservation checkable** — the counter was right, and for a better reason than the one it was introduced for. ### XL.5 — Why not separate the fields now, despite it being cleaner §XXXVIII.4's design is still the better *architecture*. It is rejected for **this** reshuffle on three grounds, none of them about elegance: 1. **Scope.** This document's own discipline is "a reorganization, not an invention." Changing the fleet's economic model is an invention, and it would be smuggled in under a relocation. 2. **Measurement comparability.** Every DoE campaign and the patent-supporting figures were taken under the current economics. §XXV.1 relieves some of that by rewriting the DoE's invocative paths, but **it does not make a behavioural change to the economy free.** 3. **The economics are not understood well enough to change safely.** §XL.3 found two additional roles for message heat *while settling a question about a third*. That is a subsystem still yielding surprises, and changing its semantics mid-reshuffle is how a relocation becomes an outage. ### XL.6 — The question that is real and is deliberately not answered here **Nothing replenishes a Stadium reservoir.** Each VM's quota starts at `Q48_ONE` and thereafter only moves within that VM or is consumed. So a long-lived VM's capacity to send **declines monotonically**, and the decline accelerates with fleet size — batched pumping (§XXII's `SK_MSG_PUMP_BATCH`) means longer queue residency, more decay per message, more consumption. **This is the fourth instance of this reshuffle's recurring pattern:** latent at Tripod scale, visible at fleet scale — after the pump's O(N) walk, `SK_SWITCH_MAX_SLOTS`, and `MSG-TOTAL-HEAT`'s arena scan. **Whether a reservoir should ever replenish is a genuine open design question about the compudynamics**, not a reshuffle decision. **Punch item 43, explicitly outside this work**, and properly `FABRIC-3.md`'s or a successor's topic. Recorded here so it is not lost — and so that whoever meets a VM that has quietly stopped being able to send knows where it was first named. ### XL.7 — Punch list - ✅ **Item 40 — SETTLED** (§XL): preserve the consumption model; kernel-Hermes ledgers `consumed`; §XXXVIII's "leak" framing withdrawn and its recommendation revised. - ✅ **Item 41 clarified** — `stadium_conserved()` checks `Σ patron + reservoir + consumed == Q48_ONE`, exactly, epsilon zero. - ✅ **§XXXVII.3's counter vindicated** — kept, and now load-bearing for §XL.4. - ⬜ **Item 43, NEW, outside this reshuffle** — should a Stadium reservoir ever replenish? (§XL.6) - Items 32–35, 37, 42 unchanged. --- ## XLI. The build punchlist: phases and testable tasks **Asked: is it now possible to break this into phases and small testable tasks? Answer: for Phases 0–2, yes. For Phase 3, no — and the blockers are three named things, not general caution.** Each task below carries its own check, which also discharges item 34 ("name the evidence before the stage runs"). **Standing rules for every task:** one task per commit; three-architecture QEMU acceptance (`clean qemu`, amd64/aarch64/riscv64, foreground, one at a time) before the next; logs committed; `dict_hash` **identical across architectures** (changing is expected, diverging is the stop condition, §XXXIV.6). **No task starts without explicit authorization.** ### XLI.0 — Phase 0: preparation, no behaviour change | # | Task | Check | |---|---|---| | 0.1 | Establish Category A reachability per §XXII.2's three routes — boot path, tooling, baked capsule directory. **Never by grep alone.** | A written list with the route that proves each entry dead | | 0.2 | Strip `common/msg.4th` | 3-arch boot; `mkcapsule --lint` clean | | 0.3 | Strip `process.4th` (takes `EVENT-EMIT/WAIT/DRAIN` with it, §XXXIII.3) | 3-arch boot; lint clean | | 0.4 | Strip `SPAWN-EVENT` | 3-arch boot; lint clean | | 0.5 | Correct `MANIFEST.md` blocks 4055 and 2049 **as their files are stripped** (§XXII.5) | Manifest describes no file that no longer exists | | 0.6 | Return freed block ranges to `capsule-reserved.txt` | lint clean | | 0.7 | **Add `stadium_conserved()`** (item 41) — `Σ patron + reservoir + consumed == Q48_ONE`, epsilon zero, the five-line analogue of `vm_physics_conserved()` | Returns true on a clean boot, all three arches | | 0.8 | Verify `PLOT`/`FB-WIDTH`/`FB-HEIGHT` are registered **nowhere but** the table Hestia will own (item 33) | Read-only audit; a second registration site is a defect to report | **Phase 0 gate:** all three architectures boot to `zuse)ok>`, `stadium_conserved()` true, no `UNKNOWN WORD`. ### XLI.1 — Phase 1: Hestia, with messaging untouched | # | Task | Check | |---|---|---| | 1.1 | Allocate Hestia's block range against `capsule-reserved.txt`, avoiding 4997 | `mkcapsule --lint` clean | | 1.2 | Create `capsules/hestia/init.4th` — messaging load, `MSG-CD-INIT`, banner. **No fabric yet** | Boots; Hestia absent from fleet (not yet birthed) | | 1.3 | Add Hestia to `is_fleet_foundation` — **four names, Hermes retained** (§XXXIV.4) | 3-arch boot; Hermes still live | | 1.4 | Birth Hestia at `kernel_main.c`, **alongside** Hermes's existing birth | Registry shows both; `dict_hash` identical across arches | | 1.5 | Register Hestia for switch signals (`:1007` pattern) | Boot clean; no switch storm (§XXVIII.2's failure shape) | | 1.6 | Move `fabric.4th` from `init.4th` to `hestia/init.4th` | Hera's dict shrinks, Hestia's grows; cross-arch identity holds | | 1.7 | Move `font.4th` likewise | Same | | 1.8 | Move `PLOT`/`FB-WIDTH`/`FB-HEIGHT` registration to Hestia's table only | A non-Hestia VM calling `PLOT` gets `UNKNOWN WORD` — **verify positively** | | 1.9 | Assert §XVIII.6's headless invariant in code: Hestia's birth sets no `g_wirebind_attached_username`, mints no proxy | Boot headless with no thumbdrive; no prompt appears | **Phase 1 gate:** Tripod is Hera/Artemis/Hestia **plus** Hermes; drawing works from Hestia and only Hestia; headless policy intact. ### XLI.2 — Phase 2: the allocator and its audit (Stage A/B, inert) **This is the phase that matters most and the one §XXXIII named as the only genuinely hard part.** | # | Task | Check | |---|---|---| | 2.1 | Kernel-Hermes message/membership structures. **Wired to nothing; draws no heat** | Boot byte-identical; `dict_hash` unmoved (no FORTH edit) | | 2.2 | Heat-coupled allocate: pull `Q.SLOT` from the caller's reservoir, roll back on refusal | Unit path: N allocs against a VM with known reservoir; refusal at the right count | | 2.3 | Release: return remaining heat via the eviction path | Reservoir restored exactly for an undecayed message | | 2.4 | The four counters — `held`, `pulled`, `returned`, `consumed` (§XL.4) | Each increments at exactly one site | | 2.5 | Decay: apply, and **record the delta into `consumed`** (§XXXVII.3) | `consumed` grows by exactly `heat_before − heat_after` | | 2.6 | Self-audit: `held == pulled − returned − consumed`, **epsilon zero** (§XXXVII.3) | Holds across the cycle; deliberately corrupt a counter → audit fires on the first unit | | 2.7 | **Stage B proof** (§XXXIV.3, corrected by §XXXIX.4): alloc/free cycle verifying **(a)** the ledger and **(b)** `stadium_conserved()` before and after. **`fleet_conserved` is not evidence here** | Both true before and after, all three arches | | 2.8 | Scan-based cross-check of the counters, for diagnostics only, off the hot path (§XXXVII.4) | Scan agrees with counters | **Phase 2 gate — and the project's real go/no-go:** if 2.7 fails, **stop and re-plan. Do not proceed to Phase 3.** §XXXIII said if K holds across alloc/free/evict under the new arbiter, the rest is protocol plumbing; this is where that is established or disproved, cheaply, with nothing else built on top. ### XLI.3 — Phase 3: cutover — NOT YET BUILDABLE **Three specific blockers, each a ruling or a design, not a task:** 1. **Item 27 — channels: negotiation or one broadcast membership?** §XXXIII.5 recommends one membership and keeping the FORTH channel words until something asks for them. **Unruled**, and it changes what Phase 3 builds. 2. **Item 32 — `SK_SWITCH_MAX_SLOTS` is 16.** §XXXII made the switcher the sole mover of control, so 16 becomes a fleet ceiling. **Whether this is a constant bump or a table redesign changes Phase 3's shape**, and cannot be estimated until it is decided. 3. **§XXXII.5.1 — the delivery hand-off is undesigned.** Delivery must cause the payload to *execute inside* the target (`messaging.4th` block 5041). Under §XXXII the switcher resumes the target on its own stack, so kernel-Hermes must leave the payload somewhere the target consumes on resume. **That mechanism does not exist on paper.** It is the last genuine design question in the reshuffle, and it is on the critical path. **What Phase 3 will look like once unblocked** (shape only, per §XXXIV.3): cut over `BLK-ATTACH-EVENT` alone — one request/ack pair, the narrowest live type — under §XXXIV.2's partition rule, one message type owned by exactly one layer; then the remaining types one at a time; gate each on `stadium_conserved()` plus the ledger, and on no new allocation refusals against the FORTH-only baseline (§XXXIV.6). ### XLI.4 — Phase 4: Category B strip Only after every live type is cut over. `hermes/init.4th`, the routing table, the slot-3 pairing convention, then `messaging.4th` itself. **Hermes leaves `is_fleet_foundation` and `kernel_main.c` here** — not earlier (§XXXIV.4). One commit per item; 3-arch boot each. ### XLI.5 — Phase 5: close-out Isabelle/HOL pass, deliverable being the **restated boundary** including §XXV.4's coverage loss, not a green build (§XXV.3) · documentation sweep, grepping **by exclusion** (§XXVIII.3), with CLAUDE.md's four errors, the `TRIPOD.md`/0.1 contradiction, and item 42's K-qualification · `make sbom`, checking `Created:` and `DocumentName` · `LITHOS_VERSION = 2.1.0`, engine `VERSION` per §XXX.6's rule · resolve the stray `v2.0.1` branch and PR #1 · merge to `master` · tag `v2.1.0` · **archival close of this document.** ### XLI.6 — The honest assessment **Buildable now: Phases 0, 1, 2 — 25 tasks, each with a check, each one commit.** That is roughly half the work and includes the hard part (Phase 2), which is deliberate: the riskiest piece is reachable early and fails cheaply. **Not buildable: Phase 3**, pending two rulings and one design. **Phases 4 and 5 are shape-complete but depend on 3.** **A caution worth stating.** Phases 0–2 can be executed without answering the Phase 3 blockers, and that is a feature — but it means arriving at a working, audited, inert allocator with the cutover still undesigned. **Better to settle §XXXII.5.1's hand-off before Phase 2 finishes**, so Phase 3 starts with momentum rather than a pause. It is the last real design question, and it is the natural next iteration. --- ## XLII. The punchlist moves to `FABRIC-3.6.md` (2026-09-19) **Captain Bob:** "If the punchlist is sufficiently long, maybe it goes to a 3.6 document to work from and annotate as we work." **Agreed, and not on preference — this series has a stated rule and this document has just met it.** ### XLII.1 — The threshold is the series' own, and it was crossed while writing §XLI `FABRIC-1.md`'s close header gives the rule verbatim: > **"At ~4,400 lines and 51 open items** scattered among hundreds of resolved ones, continuing > to append here **made the still-open work hard to find** — the same reasoning `FABRIC-0.md` > itself was closed for at 7,595 lines." **This document stands at 4,452 lines with 40+ open punch items** scattered among hundreds of settled rulings. **Same threshold, same shape, same reason.** Annotating §XLI's 25 tasks here, commit by commit, would bury the design record this document exists to be. ### XLII.2 — The split, and why it is stated so firmly | | `FABRIC-3.5.md` (this) | `FABRIC-3.6.md` | |---|---|---| | Holds | The design record — rulings, reasoning, evidence | The work — tasks, results, dates, commits | | State | Design phase closed; archival close at `v2.1.0` (§XXVI.5) | Living until the work is done | | On conflict | **Authoritative** | Defers, and records the discrepancy | **Design rulings are never restated in `FABRIC-3.6.md`, only cited.** That is deliberate: §XXXI found this series' documents losing track of each other, and duplication is how two documents begin to disagree. **If executing a task proves a ruling wrong, `FABRIC-3.6.md` records the finding and this document is amended** — never a silent divergence. ### XLII.3 — The split repairs the defect §XXXI.2 found, in the successor §XXXI.2's central finding was that the carry-forward discipline was **mechanically auditable** (`grep -c '\- \[ \]'`) through `FABRIC-2.md`, and **broke at `FABRIC-3.md`, which has no checkboxes at all** — after which "is anything still open?" stopped being a `grep` and became a reading exercise. That is how §XXXI.3's six design items went quiet without anyone deciding to drop them. **`FABRIC-3.6.md` restores the checkbox convention.** Every task, every blocker, every close-out step is a `- [ ]`. **The gap analysis found a structural defect in how this series tracks work, and the next document in it does not repeat that defect** — which is a better outcome than the findings list alone. ### XLII.4 — What did not move **§XLI stays here**, as the source §XXXVI.6 instantiates: the phase structure, the reasoning for Phase 2's early placement, and the three Phase 3 blockers with their justification. **What moved is the checklist, not the argument.** `FABRIC-3.6.md` also carries an explicit **out-of-scope** section — item 43, §XXXI's items 23–26, and `src/*.c.bak` — so that work recorded here as deliberately excluded is not quietly absorbed by an executing session. §XXXI.3's six orphans are exactly what happens when that is left implicit. ### XLII.5 — Punch list - ✅ **`FABRIC-3.6.md` opened** — execution log, checkbox convention, Phases 0–5, findings log, out-of-scope carry. - ⬜ **B1/B2/B3 (items 27, 32, §XXXII.5.1)** — the Phase 3 blockers, tracked in both documents. **§XXXII.5.1, the delivery hand-off, is the last genuine design question in the reshuffle.** - Design work continues **here**; execution is tracked **there**. --- ## XLIII. B3 SETTLED: the target drains its own queue, at its own outermost checkpoint The last genuine design question in the reshuffle (§XXXII.5.1, Phase 3 blocker B3). **Settled, and once again the mechanism already exists — built for the structurally identical problem and corrected twice in the field.** ### XLIII.1 — What the hand-off has to satisfy Delivery must cause the payload to **execute inside the target** — `messaging.4th` block 5041: "Payload is FORTH text, executed by the receiver, same as every other message here." But §XXXII retired the pump, so **kernel-Hermes may not `VM-EXEC` into anyone.** The payload must therefore wait somewhere the target consumes under its own power, at a moment when doing so is safe. "Safe" is the hard part: interpreting a payload while the target is mid-word, mid-unwind, or running nested inside someone else's dispatch is the reentrancy class this codebase has been bitten by repeatedly (§XX, §XXVIII.2). ### XLIII.2 — The safe-boundary machinery is already there Traced 2026-09-19 in `vm_core.c`: - **`sk_vm_at_outermost_interpret()`** — `return g_vm_interpret_depth <= 1;` (`:1164`), with `g_vm_interpret_depth` incremented and decremented by `vm_interpret()` itself (`:1173`, `:1206`). - **A per-word checkpoint** in `execute_colon_word()` (`:946`), gated on that predicate. - **A defer-don't-lose discipline**, in the checkpoint's own words (`:925-935`): if a word is "executing nested inside a `VM-EXEC`/`VM-CALL` dispatch… `vm` here **does not own the physical stack it's currently running on**," so acting would corrupt the enclosing VM's call chain. It is therefore **"left pending… so the next checkpoint at the outermost frame picks it up instead of losing it."** - And it runs **before** the error/abort/exit-colon checks, "deliberately, so a switch never happens mid-unwind" — acting only when the word left the VM "in a clean, resumable state." **That is the exact predicate, the exact placement, and the exact deferral semantics delivery needs.** It exists because the switcher needed to answer the same question — *when is it safe to act on this VM?* — and it has already been corrected twice in the field (2026-09-13 ×2, 2026-09-14). ### XLIII.3 — RULING > **Kernel-Hermes enqueues the payload onto the target's own pending queue and publishes the > fact. It dispatches nothing. The target drains its own queue, in its own context, at its own > outermost interpret checkpoint — the same checkpoint, the same `sk_vm_at_outermost_interpret()` > gate, and the same defer-if-nested discipline the switcher already uses.** **One published fact, two independent consumers** — which is precisely the shape §XV.3 and §XXXII established: | Consumer | Uses the fact for | |---|---| | **The switcher** | eligibility — whether to resume this VM (§XXXII) | | **The target VM itself** | drain — whether to interpret a pending payload at its next safe boundary | **Neither dispatches into anyone.** Kernel-Hermes publishes; the switcher moves control; the VM does its own work. Three roles, no overlap, no second owner of anything. ### XLIII.4 — This is §XX's proven-safe pattern, generalized to the whole fleet `FABRIC-3.md` §XX already established the safe shape, for Hera specifically. With the pump skipping her, she drains her own queue via "a plain word dispatch **in her own dictionary**, not a `VM-EXEC` dispatch into anyone else's input buffer — **no reentrancy risk.**" **The pump's defect was never draining; it was draining *from outside*.** Hera was special-cased into safety. Retire the pump (§XXXII) and **every VM does what Hera already does** — the special case disappears by becoming universal. That is a relocation of a proven pattern, not an invention, and it keeps this document's scope discipline intact at the last design question. ### XLIII.5 — Recursive drain is prevented for free Interpreting a payload calls `vm_interpret()`, which **increments `g_vm_interpret_depth`** — so any checkpoint reached *during* the drain sees `depth > 1` and will not drain again. **The same counter that defines the safe boundary also makes the drain non-reentrant**, with no flag, no lock and no new state. **Amendment, found while building task 3.4 (2026-09-22): this claim is true and covers a different hazard than the one that actually mattered.** Non-reentrant drain (the same message cannot drain twice) is real, but `g_vm_interpret_depth` was built for *cross-VM* nesting — the checkpoint's own comment frames it as "nested inside a `VM-EXEC`/`VM-CALL` dispatch **from another VM's own `vm_interpret()` call**." A different VM means a different `VM` struct, so `input_buffer`/`input_length`/`input_pos` never collide in that case. **Same-VM reentrancy — draining calls `vm_interpret()` on the very `vm` whose own `vm_interpret()` call is still paused on the C stack, mid-word, at the checkpoint that triggered the drain — is a genuinely separate hazard §XLIII.5 never named.** `FABRIC-3.md` §XX had already flagged this exact class for Hera specifically: the old messaging pump skipped her because "self-targeting `VM-EXEC` would hit the same reentrancy class… (`input_buffer`/`input_pos` not being saved by `vm_state_push`/`pop`)." Read directly: `VMCallState` (`vm_state_push()`/`vm_state_pop()`, `mama_forth_words.c`) saves only `rsp`/`exit_colon`/`ecw_nesting` — never `input_buffer`, `input_length`, or `input_pos`. Left unaddressed, the enclosing `vm_interpret()` call's own while loop would silently lose the rest of its input line or block the moment a drain fires mid-line — the same failure shape as the `INPUT_BUFFER_SIZE` 256 defect `.claude/CLAUDE.md` calls non-negotiable, and trap #1 in `FABRIC-3.6.md`'s own START HERE ("a green boot is weak evidence"). **Not a divergence from this section's ruling — the mechanism is exactly as ruled.** Cursor preservation is the implementer's own obligation *inside* the ruled mechanism, the same way §XLIV.1 found `mkcapsule`'s "1024-byte limit" was arithmetically right and operationally incomplete: fixing one thing while leaving a real gap unnamed. `FABRIC-3.6.md` task 3.4's own implementation snapshots `vm->input_buffer`/`input_length`/`input_pos`, plus `vm->mode` (a checkpoint reached mid-colon-definition must not let a drained payload's words compile into the enclosing definition) and `vm->error`/`abort_requested` (a bad message must not abort the enclosing execution), around the `vm_interpret()` call, restoring all six afterward. ### XLIII.6 — Three constraints, named rather than discovered later 1. **`INPUT_BUFFER_SIZE` is 1025** — 1024 content bytes plus NUL, and `.claude/CLAUDE.md` calls this non-negotiable because `vm_interpret()` is the shared dispatch path for both REPL lines and `LOAD`ed block content. **A payload above 1024 bytes cannot be interpreted in one drain.** Today's `MSG-PADDR`/`MSG-PLEN` are out-of-line and carry no such bound. **Kernel- Hermes needs either a payload bound at send time or a chunking story — decide at build time, do not discover it at Stage C.** 2. **Starvation is possible and bounded.** A VM in a tight loop never returns to outermost depth, so never drains. The switcher can still preempt it, but preemption does not create a boundary — the VM must still reach one. **This is the switcher's existing risk, not a new one**, and it should be recorded as shared rather than solved twice. 3. **Drain one message per checkpoint, not all.** Draining the whole queue at one boundary runs unbounded work in a single checkpoint — the §XXII wall pattern in miniature. One per checkpoint is bounded and fair. **Recommended, not ruled.** ### XLIII.7 — Punch list - ✅ **B3 — SETTLED** (§XLIII): the target drains its own queue at its own outermost checkpoint; kernel-Hermes publishes and never dispatches. - ⬜ **Item 44, NEW** — payload bound or chunking, against `INPUT_BUFFER_SIZE` 1025 (§XLIII.6.1). **Decide before Phase 3.** - ⬜ **B1 (item 27)** and **B2 (item 32)** remain — both are rulings, not designs. **Phase 3's design blocker is cleared. What remains before it is buildable are two decisions, neither of which requires further investigation:** channels (one membership or negotiation), and the switch-slot ceiling. **The reshuffle has no undesigned mechanism left.** --- ## XLIV. `.claude/CLAUDE.md` corrected (authorized 2026-09-19) Item 20's four known-stale claims, plus a discoverability pointer, applied to `.claude/CLAUDE.md` under explicit authorization. **Each was verified against source immediately before editing, not from this document's own notes.** | Claim | Was | Now | Evidence | |---|---|---|---| | Theory files | 23 (18 core + 5 ACL) | **52** (47 `StarForth_*` + 5 `ACL_*`), all in `proof/ROOT` | direct count; `COVERAGE.md` agreed all along | | `LITHOS_VERSION` | `2.0.1` | **`2.0.0`** | `Makefile.starkernel:83`; §I.2 rolled it back 2026-09-04 | | ACL pinning | "never in C… no `vm_find_word` + field assignment", *and separately* "pin it in a kernel-specific capsule" | **the rule, plus its one live exception** | `kernel_main.c:771-782` does exactly that, deliberately; `ACL.4th` block 4005 explains why | | `mkcapsule` blocks | "1024-byte limit… verify with `wc -c`" | **64 chars × 16 lines, range `[2048,5120)`; verify with `--lint`** | `mkcapsule.c:344-345`, `:408-409`, `:430` | **Also added:** a `WORK IN PROGRESS` pointer at the top directing a fresh session to `FABRIC-3.6.md`'s `START HERE`, since `.claude/CLAUDE.md` is what auto-loads and this document is not. It states explicitly that no reshuffle code exists yet, and that the file's existing Tripod descriptions remain correct until the reshuffle lands — **deliberately not pre-writing the post-reshuffle state as though it were current.** ### XLIV.1 — A finding: §XXXII.2's own correction was incomplete `FABRIC-3.md` §XXXII.2 corrected the `mkcapsule` framing to "block range `[2048, 5120)` and a hard 16-content-line-per-block cap." **Reading the tool directly shows a third constraint it missed: line length ≤ 64 characters** (`mkcapsule.c:344-345`, `:430` — `validate_forth_blocks` enforces a "64-char × 16-line block format"). **And it explains where the wrong figure came from: 64 × 16 = 1024.** The old "1024-byte limit" was arithmetically right and operationally wrong — **8 lines of 128 characters is 1024 bytes and still fails.** A correction that fixes a number while leaving the wrong *kind* of rule in place is the subtler version of the same error, and §XXXII.2 made it. **Recorded rather than propagated:** `FABRIC-3.md` is a living document for another topic and is not edited from here. `.claude/CLAUDE.md` now carries the full 64×16 rule. ### XLIV.2 — Punch list - ✅ **Item 20 — CLOSED**: all four `.claude/CLAUDE.md` errors corrected, with the pointer added. - ⬜ **Item 42** (qualify "K"/"conservation", §XXXIX.6) and the `MANIFEST.md` corrections (§XIII.2) remain — they belong to the files they describe, not to `CLAUDE.md`, and `MANIFEST.md`'s ride the strip per §XXII.5. - ⬜ **Item 45, NEW** — carry §XLIV.1's 64-char line limit into the `experiments/bare_metal/README.md` capsule guidance if it repeats the byte framing. **Not checked; flagged.** --- ## XLV. B1, B2, B4 SETTLED by Captain Bob (2026-09-21) — channels negotiate, the switch table is dynamic, payloads are one block Three of Phase 3's blockers (`FABRIC-3.6.md` B1/B2/B4) ruled in one session. **These are rulings, recorded as given; the reasoning below is the design consequence, not new design.** ### XLV.1 — B1 (item 27): negotiated channels, pub/sub, ACK/NACK > **Messaging is publish/subscribe. There is one common channel every VM listens to. A private > channel is created by request → grant/deny over the common channel, and the granted channel > is a new topic with its own membership. ACK/NACK negotiation runs throughout.** This **overrules §XXXIII.5's recommendation** (one flat broadcast membership) in favour of the negotiation that `messaging.4th`'s CH-REQUEST/ACCEPT/CONFIRM/CLOSE already sketched. Consequences: - **`SkHermesMembership` (task 2.1) is per-channel.** The flat list is right *per topic*; there is now one per channel, held in a **dynamic** table with no fixed channel maximum (same reasoning as XLV.2). - **The common channel is a permanent topic**; every VM is subscribed from birth. - **A deny is a NACK.** ACK/NACK are ordinary message types on the existing heat-coupled allocator (tasks 2.2–2.7), so they draw and decay heat like any message. - **Advised, not ruled — open for Captain Bob:** ACK the private-channel open and delivery, not every common-channel message, since ACK-everything roughly doubles message heat draw against the consumption economy (§XL.4). - **Advised, not ruled — open:** who may open a channel with whom is policy and belongs in `ACL.4th` (CLAUDE.md: policy in FORTH, never gated on `zuse_session`); kernel-Hermes should ask, not decide. Where that hook lives must be settled before the grant task is written. ### XLV.2 — B2 (item 32): the switch table is dynamic, not a constant > **`SK_SWITCH_MAX_SLOTS` (16) is not to be fixed and static; it becomes a dynamic component.** Precedent already in the tree: Stadium sizes its VM quota table from RAM at boot (`stadium_max_vm_count_val`, kmalloc'd, 2026-08-15). The switch table follows the same pattern. **The concrete sizing rule is not yet specified** and is a task-writing prerequisite. ### XLV.3 — B4 (item 44): payload bound is one block; larger payloads chunk > **Follow the block convention the `*.4th` files use.** Read as (confirmed by Captain Bob 2026-09-21): a message payload is bounded to **one block = 1024 bytes** (64×16, §XLIV.1), which matches §XLIII.6.1's `INPUT_BUFFER_SIZE − 1` drain limit; anything larger is **chunked across messages**. Chunk framing (sequence/last-chunk marking) is a Phase 3 task-writing prerequisite. Note the 64-char line rule (§XLIV.1) governs *capsule source blocks*; whether it also constrains message payload content is **not** ruled here. ### XLV.4 — Punch list - ✅ **Items 27, 32, 44 — SETTLED** (§XLV.1–.3). - ✅ **NEW** — ACK cadence (XLV.1); ACL hook for channel-open policy (XLV.1); dynamic switch-table sizing rule (XLV.2); chunk framing (XLV.3). **SETTLED §XLVI.** - ✅ **Phase 3 task breakdown** — drafted `FABRIC-3.6.md` tasks 3.0–3.11, 2026-09-21. ## XLVI. §XLV.4 sub-items SETTLED by Captain Bob (2026-09-21) — task 3.0 closed All five `FABRIC-3.6.md` task 3.0 sub-items, plus task 3.3's heat-cost design point, ruled in one session (all recommended defaults accepted): - **3.0(a) ACK cadence:** channel-open + delivery only, not every common-channel message — confirms §XLV.1's own advised default. - **3.0(b) Channel-open ACL hook location:** a new word in `ACL.4th` itself (e.g. `HERMES-CHANNEL-OPEN?`), extending its scope from per-word ACL to also cover channel-open policy, rather than a separate policy file. Keeps "all policy in `ACL.4th`" as one rule with no exceptions. - **3.0(c) Dynamic switch-table sizing:** mirrors Stadium's `stadium_max_vm_count_val` pattern — RAM-derived at boot, no separate sizing formula. - **3.0(d) Chunk framing:** each chunk carries `(msg_id, seq, is_last)` ahead of up to 1024 bytes of payload; receiver reassembles by `msg_id`+`seq` order. - **3.0(e) Drain cadence:** one message per outermost-interpret checkpoint (§XLIII.6.3's own recommendation) — a backlog drains across several checkpoint hits, not in one burst. - **Task 3.3 heat cost per publish:** one message per subscriber (separate heat draw each), matching the existing heat-coupled allocator 1:1 — no refcount machinery to add to the allocator/ledger. `FABRIC-3.6.md` task 3.0 is closed by this ruling; tasks 3.1–3.7 may now be written precisely.