FABRIC-3.5.md §XXIII: the sinking latch settled -- design phase complete

The last open design question. The signal is a plain scalar in
kernel-Hermes's own translation unit, written only by Hermes, read by
every VM through a registered primitive. That invents nothing: it is the
same registration path §XX already established when register_child_vm_
words() hands the eight STADIUM-* primitives to every VM and Hera's two
sites mirror them. Reading a scalar needs no queue, channel, routing
table or working arbiter, which is precisely the requirement -- the
transport is the thing that failed.

Names it accurately. "Semaphore" was the original framing but the
mechanism is a one-way latch, and the distinction carries correctness.
Per §VII.1 a VM attempts its own recovery before announcing anything, so
raising the latch means recovery already failed and there is no path back
to not-sinking. Single writer, many readers, one irreversible transition,
both observable values valid -- which is the discipline heartbeat.c:33-34
already states verbatim and §XXVIII Stage 3 already reused once rather
than inventing new locking. This is the third such variable and takes the
same route for the same reason. Calling it a semaphore in code would
imply counting and blocking semantics it must not acquire.

Settles who reads it, which is the part §XXVIII's shared C stack
constrains: a VM has no thread and cannot poll. So the check happens at
the dispatch point, and the split is the design -- the kernel delivers
the fact, the VM runs its own flush-and-BYE in its own context. The
kernel publishes, never decides, so §XV.3 and §XIV.5 hold, and each VM
running its own shutdown is what §IX.1 already ruled. The window needs no
deadline because Hera's halt is physically terminal.

Completes §VIII.2's two-mechanism rule with no exceptions list: floor VMs
send SOS through routing, the arbiter beneath raises the latch, and which
one applies is decided entirely by which side of the routing layer the
sender sits on.

Every design question this document opened is now ruled. What remains is
build sequencing plus two small local decisions, items 17 and 18. No code
is authorized.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VkM1zHGvBerLF6aqkHPweP
This commit is contained in:
Claude
2026-09-19 10:24:43 +00:00
parent 3f65a86993
commit d0c4f60194
+136 -2
View File
@@ -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.