diff --git a/FABRIC-3.5.md b/FABRIC-3.5.md index 3f088688..c269aaa7 100644 --- a/FABRIC-3.5.md +++ b/FABRIC-3.5.md @@ -487,6 +487,11 @@ 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 @@ -607,8 +612,9 @@ structural remains; what follows are small local decisions, one placement questi 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** — where the sinking semaphore mechanically lives. The rest of - §VIII.3 is settled by §XVI. +- ~~**§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 (§XVI.2)** — does Hera's suicide replace `BYE`'s current cold-restart, or become a @@ -2073,3 +2079,131 @@ Then, unchanged in content but now downstream of it: 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.