Files
LithosAnanake/FABRIC-3.6.md
T
Robert Allan JamesandClaude Sonnet 5 21734b1a52 Console prompt: identity/machine both sides once redirected off Hera
sk_console_user_prefix() (repl.c) previously always returned "zuse"
(or the WIREBIND-attached username) as the left side of the bracket
prefix, regardless of which VM the console was actually pointed at --
"[zuse@rajames]" after USE rajames, always showing the authenticating
superuser rather than the active identity.

Changed on Captain Bob's direct instruction: once the console is
redirected into a WIREBIND identity's own console VM
(console_get_vm_name() != "Hera"), show that same name on both sides
-- "[rajames@rajames]" -- since WIREBIND births the console VM
literally named after the identity, so the identity IS that VM, not a
separate label. At the top level (still on Hera, nothing has
redirected yet), the original zuse_session/WIREBIND-username logic is
unchanged.

An earlier, more ambitious attempt (separate identity/machine tracked
state across every console_set_vm_name() call site) regressed live to
a wrong [zuse@Artemis] prompt and was fully reverted before reaching
any acceptance run -- the landed fix needed none of that new state,
just this one function.

Verified live on all three architectures: [zuse@Hera] at the top
level and after a live WIREBIND attach (before USE), [rajames@rajames]
after USE rajames, with task 3.9's Stage D relay still firing
correctly on top of it. Zero UNKNOWN WORD, dict_hash unaffected
(display-only change).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 13:50:40 -04:00

107 KiB
Raw Blame History

FABRIC-3.6.md — the Tripod/kernel reshuffle: execution log

START HERE — session handoff

If you have just been told "go build it", read this section, then FABRIC-3.5.md's §XLI, then start at task 0.0 below. Do not re-derive the design — it is settled.

Where the code is. This repo is LithosAnanke, on the Gitea instance at gitea.strshipos.com, repo admin/LithosAnanake — Captain Bob has the credentials. Confirm you are in the right repo before anything else. git remote -v must show gitea.strshipos.com/admin/LithosAnanake. Nothing outside Gitea is this project. (The handoff was authored in a container whose default working directory was a different, empty repo on an unrelated remote — if you ever see that, you are in the wrong place.) master is the sole production line. Work on branch claude/starshipos-tripod-kernel-reshuffle-itbjns (a clean fast-forward of master at e56974e; documentation only — no reshuffle code has been written. The branch head is the source of truth; do not trust a commit id quoted here).

The two documents. FABRIC-3.5.md is the design record and is authoritative — every ruling, with its reasoning and evidence, §I–§XLIII. This document is the execution log. Rulings are cited here, never restated. If a task proves a ruling wrong, record the finding here and amend FABRIC-3.5.md — never silently diverge.

First action: task 0.0, the three-ISA baseline. It is a gate, not a formality — see its own rationale below. Nothing else starts until it is recorded.

Five traps this project's own history proves are real. A fresh session will walk into these:

  1. Silent failure is the dominant mode here. FABRIC-3.5.md §XXXV.0 lists four independent instances. Most relevantly: "a switch storm and a healthy idle REPL produce an identical serial log." A green boot is weak evidence. Name the specific observation that proves a task worked, before running it.
  2. grep cannot establish capsule reachability. Capsules are birthed by name from runtime strings. A reference count over capsules/ and src/ put the six live init-l8-* capsules at zero references; including experiments/ found 18–19 each. §XXII.2. Use the three routes, never grep alone.
  3. fleet_conserved cannot see Stadium heat. The two heat accountings are entirely decoupled (§XXXIX). A leaking message allocator leaves fleet_conserved reporting a serene 1. Stage B's evidence is the ledger plus stadium_conserved(), never fleet_conserved.
  4. dict_hash changing is expected; dict_hash diverging across architectures is the stop condition. §XXXIV.6. Do not "fix" a changed hash.
  5. Documentation in this repo drifts from the code — trust the code. .claude/CLAUDE.md carried four stale claims; all four were corrected 2026-09-19 (§XLIV) and it is now reliable. Others are not: capsules/MANIFEST.md still describes block 4055 as a live "immutable ABI" that FABRIC-2.md:2773 declared stale, and still misdescribes block 2049. FABRIC-0.md §25.7 lists resolved defects as open. Where a document and the code disagree, the code wins — and record the drift rather than working around it.

Blocked, and not to be worked around: Phase 3 needs three decisions from Captain Bob — B1 channels (one membership or negotiation), B2 the SK_SWITCH_MAX_SLOTS ceiling of 16, B4 the payload bound against INPUT_BUFFER_SIZE 1025. All design is complete; these are rulings, not investigations.

Do not: create branches, stash, fix defects found in passing, bundle tasks into one commit, or start any task without explicit authorization. Captain Bob's Law, .claude/CLAUDE.md.

Status: LIVING WORKING DOCUMENT, opened 2026-09-19. This is the execution record for the reshuffle designed in FABRIC-3.5.md. Work is tracked, annotated and closed here.

Why this is a separate document, per the series' own rule. FABRIC-1.md closed at 4,420 lines for a stated reason: "continuing to append here made the still-open work hard to find." FABRIC-3.5.md stands at 4,452 lines with 40+ open punch items scattered among hundreds of settled rulings — it has crossed the same threshold, for the same reason. Annotating a task list inside it, commit by commit, would bury the design record it exists to be.

The split of responsibility is strict, and stated because FABRIC-3.5.md §XXXI found that documents in this series lose track of each other:

FABRIC-3.5.md FABRIC-3.6.md (this)
Holds The design record — every ruling, its reasoning, its 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 a conflict Authoritative Defers, and records the discrepancy

Design rulings are never restated here, only cited (§XXXIV.2, §XL.4, …). If a task needs a rule explained, read FABRIC-3.5.md. If executing a task proves a ruling wrong, that is a finding: record it here and amend FABRIC-3.5.md there — never silently diverge.

The checkbox convention is deliberate. FABRIC-3.5.md §XXXI.2 found the series' carry-forward discipline was mechanically auditable (grep -c '\- \[ \]') up to FABRIC-2.md, and broke at FABRIC-3.md, which uses no checkboxes at all — after which "is anything still open?" stopped being a grep and became a reading exercise. This document restores the convention, so that question stays answerable by machine.


Standing rules

Apply to every task, from .claude/CLAUDE.md and FABRIC-3.5.md §XXXIV:

  • One task, one commit. No bundling.
  • Acceptance is the three-architecture QEMU boot — clean qemu, amd64/aarch64/riscv64, one at a time, in the foreground. Logs committed. There is no other acceptance test.
  • dict_hash changing is expected. dict_hash diverging between architectures is a stop condition (§XXXIV.6).
  • mkcapsule --lint capsules/ clean after any capsule change.
  • No task starts without explicit authorization. Captain Bob's Law.
  • Report, don't fix. A defect found while doing a task is recorded here, not repaired in passing.

Annotation convention

- [x] 0.2 — Strip common/msg.4th
      2026-09-DD · commit abc1234 · 3-arch boot clean, lint 0 violations
      note: <anything surprising; a finding gets its own entry below>

Mark [~] for started-not-finished, with what is outstanding. Never mark [x] on a task whose check did not actually run — FABRIC-3.5.md §XXXV.6 records that declaring done too early is this project's most repeated failure.


Pre-flight — establish the baseline before anything changes

Branch state at open (2026-09-19): claude/starshipos-tripod-kernel-reshuffle-itbjns, head 3a4e5cf, working tree clean, pushed, 33 commits ahead of master — all documentation, no code. master is unmoved at e56974e, so the branch remains a clean fast-forward. Execution starts from this commit.

  • 0.0 — Three-ISA baseline smoke test on the unmodified branch. make -f Makefile.starkernel ARCH=<arch> clean qemu for amd64, aarch64, riscv64 — one at a time, in the foreground, per .claude/CLAUDE.md. Check: all three reach zuse)ok>; zero UNKNOWN WORD; logs committed under logs/<timestamp>/<arch>/; record each dict_hash and confirm the three are identical. 2026-09-19 · logs/20260919-130023/amd64/, logs/20260919-130137/aarch64/, logs/20260919-130319/riscv64/ — all three reach [zuse@Hera] ok>, zero UNKNOWN WORD. dict_hash identical across all three: Hera (PARITY:M7.1a) 0x6824fe5993239838, Hermes 0x062252c4da6858da, Artemis 0xed80117724c26f36. note: first attempt confounded, see findings log — logs/20260919-124835/amd64/ and logs/20260919-124952/aarch64/ (kept, not deleted) show Artemis's dict_hash diverging (0xed80117724c26f36 vs 0x93d6e815354b61c0) purely because disk/artemis.img is shared across all three ISAs' qemu targets (FABRIC-3.md §XXXV.2) and the second run resumed the first run's already-formatted disk instead of formatting its own. Restored disk/artemis.img and disk/thumbdrives/zuse-thumb-ident.img to their committed blank state (git checkout --) before each of the three reruns above to remove the confound.

Why this is a gate and not a formality. Every task in this document takes the three-architecture boot as its acceptance (§XXXIV). Without a known-good baseline captured first, the first red boot is ambiguous — pre-existing fault or something task 0.2 just did? This project has been bitten by exactly that ambiguity before (FABRIC-3.md §XXVI: "the lockdown was never broken — wrong VM tested"). The baseline is what makes every later acceptance run interpretable, and the recorded dict_hash triple is the reference every subsequent §XXXIV.6 divergence check compares against.

Run it yourself — you are expected to. Captain Bob confirmed (2026-09-19) that execution happens on his PC with the full toolchain installed, so the session reading this can and should run task 0.0 directly, once authorized. Verify first that qemu-system-x86_64, qemu-system-aarch64, qemu-system-riscv64, the aarch64/riscv64 cross-compilers and OVMF/AAVMF are all present; if any are missing you are not on the intended machine — stop and say so rather than improvising a partial test.

(Historical note: this document was authored in a container with none of that toolchain, which is why task 0.0 was written but never run. Do not take its unchecked state as a prior failure — it has simply not been attempted.)


Phase 0 — Preparation (no behaviour change)

Gate: all three architectures boot to zuse)ok>, stadium_conserved() true, no UNKNOWN WORD.

  • 0.1 — Establish Category A reachability by §XXII.2's three routes (boot path, tooling, baked capsule directory). Never by grep alone. Check: a written list naming the route that proves each entry dead. 2026-09-19 · investigation only, no code changed. Checked by all three routes — a boot-path trace of init.4th/hera/hermes/init.4th/artemis/init.4th for any EXEC/S" name" load, an experiments/tools/docs invocation search, and confirming presence in the baked capsule directory doesn't by itself make a name reachable if nothing constructs it at runtime:

    | Candidate | Boot path | Tooling/experiments/docs | Verdict |
    |---|---|---|---|
    | `capsules/common/msg.4th` | zero `EXEC`/load sites in any boot-loaded capsule | zero invocations; its only two words `HERMES-ACK`/`HERMES-NACK` have zero callers anywhere; `messaging.4th` block 5033's own comment: "common:msg.4th's HERMES-ACK/NACK indirection is retired" | **DEAD** |
    | `capsules/process.4th` | zero `EXEC` sites anywhere | zero invocations; its four words `SPAWN`/`PAUSE`/`RESUME`/`KILL-VM` have zero callers outside the file itself | **DEAD** |
    | `SPAWN-EVENT` (constant, `messaging.4th`) | N/A — a constant, only ever defined, never read by name | zero invocations by name anywhere | **DEAD** |
    
    Both files are present in the baked capsule directory (`mkcapsule` build log: `[p]
    common:msg.4th`, `[p] process.4th`) — noted per §XXII.2 that presence alone proves
    nothing; it only means nothing constructs either name at runtime to reach them, which the
    other two routes confirm.
    
    finding: `EVENT-EMIT`/`EVENT-WAIT`/`EVENT-DRAIN` (`messaging.4th`, Category B, staying)
    have one caller beyond `process.4th` that §XXXIII.3 missed — `tools/hermes_smoke.sh` calls
    all three directly. This does not make them live: the script is already broken on its own
    terms, referencing `capsules/core/init.4th` and `build/amd64/standard/starforth`, neither
    of which exists in this repo, and calling the pre-rename `CD-INIT` word (`messaging.4th`
    block 5033 renamed it to `MSG-CD-INIT`) — it cannot currently execute. Task 0.3's plan
    stands unchanged; recorded because §XXXIII.3's "only other reference is `MANIFEST.md`"
    was itself an undercount of exactly the kind §XXII.2 warns about, even though the
    conclusion survives.
    
    finding: `PAUSE-EVENT`/`RESUME-EVENT`/`KILL-EVENT` (`messaging.4th`) are exactly as
    dead-by-name as `SPAWN-EVENT` — zero references anywhere outside their own definition;
    `process.4th`'s calls pass bare numeric literals (`1`/`2`/`3`/`4`), never the constant
    names. Task 0.4 only names `SPAWN-EVENT`; not expanding its scope here, but flagging that
    the other three meet the identical bar for whoever picks up Category A stripping next.
    
  • 0.2 — Strip capsules/common/msg.4th. Check: 3-arch boot; lint clean. 2026-09-19 · logs/20260919-131945/amd64/, logs/20260919-132054/aarch64/, logs/20260919-132226/riscv64/ — all three reach [zuse@Hera] ok>, zero UNKNOWN WORD. mkcapsule --lint capsules/ clean, 38 files, 0 violations (was 39 before the strip). dict_hash unchanged from the task 0.0 baseline on all three (Hera 0x6824fe5993239838, Hermes 0x062252c4da6858da, Artemis 0xed80117724c26f36) — expected, not a defect: task 0.1 confirmed msg.4th was never EXEC'd into any VM's dictionary, so removing the dead file from the baked capsule directory doesn't move anything actually loaded. capsule-reserved.txt not yet touched — block 4055's range is returned in task 0.6 per the punchlist's own ordering. MANIFEST.md's stale block-4055 entry not yet corrected — bundled into task 0.5 alongside block 2049's correction, per the punchlist.

  • 0.3 — Strip capsules/process.4th (takes EVENT-EMIT/-WAIT/-DRAIN with it, §XXXIII.3). Check: 3-arch boot; lint clean. 2026-09-19 · logs/20260919-132914/amd64/, logs/20260919-133024/aarch64/, logs/20260919-133156/riscv64/ — all three reach [zuse@Hera] ok>, zero UNKNOWN WORD. mkcapsule --lint capsules/ clean, 37 files, 0 violations (was 38). Deleted capsules/process.4th outright and messaging.4th's Block 5030 in full (EVENT-EMIT/EVENT-WAIT/EVENT-DRAIN — a self-contained block, nothing else in it). dict_hash: Hera's PARITY:M7.1a snapshot unchanged (0x6824fe5993239838 — it fires before any capsule loads, so capsule edits never move it); Hermes and Artemis both moved (0xa0f5c639a1228596, 0x1650cb7153056160) since both load messaging.4th — identical across all three architectures, which is the actual property that matters (§XXII.4: every strip changes dict_hash, cross-arch identity is the invariant). capsule-reserved.txt still untouched (task 0.6); MANIFEST.md's stale block-4055/2049 entries still untouched (task 0.5, now unblocked since both 0.2 and 0.3 are done).

  • 0.4 — Strip SPAWN-EVENT. Check: 3-arch boot; lint clean. 2026-09-19 · logs/20260919-133452/amd64/, logs/20260919-133559/aarch64/, logs/20260919-133731/riscv64/ — all three reach [zuse@Hera] ok>, zero UNKNOWN WORD. mkcapsule --lint capsules/ clean, 37 files, 0 violations (unchanged — a one-line edit within messaging.4th, no file added or removed). Removed the single 1 CONSTANT SPAWN-EVENT line only, per task 0.1's finding — PAUSE-EVENT/RESUME-EVENT/KILL-EVENT left in place, matching the punchlist's stated scope. dict_hash moved for Hermes/Artemis (0x52801b746e063ae9, 0x6ad92fa3935918d4), identical across all three architectures; Hera's PARITY:M7.1a snapshot unchanged as before. capsule-reserved.txt and MANIFEST.md's stale entries still untouched — Phase 0's strips (0.2–0.4) are now all done; 0.5 and 0.6 are next.

  • 0.5 — Correct capsules/MANIFEST.md blocks 4055 and 2049 as their files are stripped (§XXII.5). Check: manifest describes no file that no longer exists. 2026-09-19 · documentation only, no capsule content touched, mkcapsule --lint capsules/ still clean (37 files, 0 violations). Block 2049's justification rewrote its claimed init.4th load list (compudynamics, common:msg, fleet-k, process) against the file's actual current content — none of those four are loaded there; two were already deleted (compudynamics.4th/fleet-k.4th, 9323f776), two are this pass's own strips. The standalone common/msg.4th and process.4th sections (former blocks 4055 and 4300–4301) were removed and folded into "Deleted capsules (historical)" alongside the existing compudynamics.4th/fleet-k.4th entry, same convention. Unassigned Ranges table updated: 4055–4059 and 4300–4399 now read as former-file ranges rather than "extension space" for files that no longer exist. Verified every remaining ### \*.4th`` section in the manifest names a file actually present on disk — zero stale entries left.

  • 0.6 — Return freed block ranges to capsule-reserved.txt. Check: lint clean. 2026-09-19 · added 4055-4059 (former common/msg.4th) and 4300-4399 (former process.4th) to tools/capsule-reserved.txt, each noted as freed by this reshuffle's strip rather than owned by non-capsule infrastructure (the file's usual purpose) — documented as lifted, not permanent, once someone deliberately wants a range back. mkcapsule --lint capsules/ clean, 37 files, 0 violations, and check_reserved_conflicts() (the hard build-gate that actually reads this file, tools/mkcapsule.c:974) passes clean on a real make -f Makefile.starkernel ARCH=amd64 all — confirms the new entries don't collide with anything currently baked. Documentation/registry only, no capsule content touched, so no 3-arch boot run for this task. All of Phase 0's strips and documentation corrections (0.1–0.6) are now done; stadium_conserved() (0.7) and the PLOT/FB-WIDTH/ FB-HEIGHT registration audit (0.8) remain before Phase 0's gate is fully met.

  • 0.7 — Add stadium_conserved(): Σ patron + reservoir + consumed == Q48_ONE, epsilon zero (§XL.4, item 41). Check: true on a clean boot, all three arches. 2026-09-19 · logs/20260919-135137/amd64/, logs/20260919-135240/aarch64/, logs/20260919-135417/riscv64/ — all three reach [zuse@Hera] ok>, zero UNKNOWN WORD, and print Stadium conservation: CONSERVED (all identical: resident_sum=47641 reservoir=17895 sum=65536=Q48_ONE). Implemented int stadium_conserved(VMUuid vm_id) in src/starkernel/vm/stadium.c (declared include/starkernel/vm/stadium.h) as stadium_resident_sum(vm_id) + stadium_reservoir_peek(vm_id) == Q48_ONE — the two-term form, not three. §XL.4's consumed term is a Phase 2 kernel-Hermes ledger deliverable that doesn't exist yet; nothing draws on any VM's Stadium quota today (Phase 2 task 2.2 is literally where that wiring gets built), so consumed is honestly zero right now and folding it in would be inventing Phase 2 state ahead of it existing. Doc comment on the function says exactly this, so whoever builds Phase 2's ledger extends this function rather than working around it. Wired into the existing per-VM boot diagnostic (stadium_words_print_boot_diagnostics(), kernel_main.c:810, Hera only — the sole existing call site) rather than adding a new one. No compiler warnings on either edited file (checked with a forced recompile).

  • 0.8 — Audit that PLOT/FB-WIDTH/FB-HEIGHT are registered nowhere but the table Hestia will own (item 33). Check: read-only; a second site is a defect to report. 2026-09-19 · read-only audit, no code changed. Finding: they are reachable from every VM today, not confined to one table. Traced the two layers separately: - FORTH level (the drawing vocabulary): capsules/fabric.4th/font.4th (which build CART-PLOT etc. on top of raw PLOT) are EXEC'd only from capsules/init.4th — Hera only. Neither hermes/init.4th nor artemis/init.4th load them. This layer matches item 33's expectation and is exactly what tasks 1.6/1.7 move to hestia/init.4th. - C level (the raw primitives themselves): register_framebuffer_words() (src/word_source/framebuffer_words.c:60-65, registering PLOT/FB-WIDTH/ FB-HEIGHT) is called unconditionally from register_forth79_words() (src/word_registry.c:139), which is itself called unconditionally from vm_init() (src/starkernel/vm/vm_bootstrap.c:263) — the generic per-VM bootstrap every VM goes through, no identity check, no #ifdef. So the raw primitives are already in every VM's C-level dictionary at birth, Hestia or not. - Verified live, not just from source: booted amd64 (logs/20260919-140231/amd64/) and ran S" FB-WIDTH ." S" Hermes" VM-EXEC and the same against Artemis — both returned 1280, not UNKNOWN WORD. Neither loads fabric.4th, so this is the raw C primitive itself answering, confirmed reachable from VMs that were never meant to draw. Not fixed here — read-only per the task, and this is exactly task 1.8's own stated check ("a non-Hestia VM calling PLOT gets UNKNOWN WORD — verify positively"), which this finding confirms currently fails and gives 1.8 a concrete starting state: register_framebuffer_words()'s call site in register_forth79_words() will need to become conditional on VM identity (or moved out of the universal bootstrap entirely), not just the FORTH-level fabric.4th relocation tasks 1.6/1.7 already plan for.

    **Phase 0 gate met**: all three architectures have repeatedly booted to `[zuse@Hera] ok>`
    with `stadium_conserved()` true and zero `UNKNOWN WORD` across tasks 0.2–0.7. Phase 0 is
    closed; Phase 1 (Hestia, messaging untouched) is next.
    

Phase 1 — Hestia (messaging untouched)

Gate: Tripod is Hera/Artemis/Hestia plus Hermes; drawing works from Hestia and only Hestia; headless policy intact.

  • 1.1 — Allocate Hestia's block range vs. capsule-reserved.txt, avoiding 4997. Check: lint clean. 2026-09-19 · allocated 4986–4996 (11 blocks) for hestia/init.4th, documented in capsules/MANIFEST.md's Unassigned Ranges table (not capsule-reserved.txt — that file is for blocks owned by non-capsule infrastructure, per its own header comment; a real capsule allocation belongs in MANIFEST.md, same as every other infrastructure capsule). Checked against the actual current occupancy, not the table's own stale blanket "4853+ OPEN" line: fabric.4th occupies 4900–4924 and font.4th 4925–4985 already (both are their own standalone capsule files, EXEC'd by init.4th but block-numbered independently of it — task 1.6/1.7 relocate which capsule EXECs them, not their own block ranges), and 4997 is the console proxy's hardcoded Block 4997 string literal (capsule_console.c:27-29). 4986–4996 sits in the gap between the two, avoiding 4997 as required, with room for Hestia's initial messaging-load/banner shape (task 1.2) plus moderate growth (bind vocabulary, per §XVIII.5) without colliding with anything. No .4th file created yet. mkcapsule --lint capsules/ still clean, 37 files, 0 violations (documentation-only, no capsule content changed).

  • 1.2 — Create capsules/hestia/init.4th: messaging load, MSG-CD-INIT, banner. No fabric yet. Check: boots; Hestia not yet birthed.

  • 1.3 — Add Hestia to is_fleet_foundation (capsule_birth.c:793-796) — four names, Hermes retained (§XXXIV.4). Check: 3-arch boot; Hermes still live. 2026-09-19 · logs/20260919-163734/amd64/, logs/20260919-163837/aarch64/, logs/20260919-164005/riscv64/ — all three reach [zuse@Hera] ok>, zero UNKNOWN WORD, byte-identical to the pre-change baseline (same dict_hash triple) — expected, since nothing births anything named "Hestia" yet (that's task 1.4), so the added name in the is_fleet_foundation check never matches. Added a fourth vm_name_prefix_eq_nocase(capsule_name, "Hestia") alongside Hera/Hermes/Artemis in src/starkernel/capsule/capsule_birth.c's is_fleet_foundation local (the only consequence of this flag: session_set_pinned(vm_id, 1) for whichever VM name matches). Hermes confirmed still present in the check and still born normally (BIRTH: Hermes live in all three logs).

  • 1.4 — Birth Hestia in kernel_main.c, alongside Hermes's existing birth. Check: registry shows both; dict_hash identical across arches. *Change graphics metekey. 2026-09-19 · logs/20260919-165646/amd64/, logs/20260919-165802/aarch64/, logs/20260919-165931/riscv64/ — all three reach [zuse@Hera] ok>, zero UNKNOWN WORD. Registry shows all four: BIRTH: Hermes live, BIRTH: Hestia live, PARITY:BIRTH lines for Hermes/Hestia/Artemis all present in every log. Hestia's dict_hash identical across all three architectures: 0x31cab513929eea89 (capsule_hash 0x3d3a87c3509ab7f3, matching hestia:init.4th's own hash). Hera/Hermes hashes unchanged from task 1.3's baseline; Artemis's vm_id shifted (now the 4th birth instead of the 3rd — vm_id is derived from birth-sequence position, not identity, so this is expected, not a divergence) but its dict_hash is unchanged and still identical across arches. Added a birth block in src/starkernel/kernel_main.c immediately after Hermes's own (S" Hestia" BIRTH + registry-lookup confirmation), same shape as the existing Hermes/Artemis blocks. No compiler warnings (forced recompile checked). Noted in passing, not a regression: Hestia's birth log shows the same ( Unterminated comment HADES warning Artemis's own birth has shown since task 0.0's very first baseline — a pre-existing quirk, not something this task introduced.

  • 1.5 — Register Hestia for switch signals (the :1007 pattern). Check: boot clean; no switch storm (§XXVIII.2's shape — and note it is invisible by default, §XXXV.0). 2026-09-19 · logs/20260919-170232/amd64/, logs/20260919-170403/aarch64/, logs/20260919-170537/riscv64/ — all three reach [zuse@Hera] ok>, zero UNKNOWN WORD, hashes identical to task 1.4's baseline (switch-signal registration is pure C runtime state, doesn't touch any FORTH dictionary). Added a fourth capsule_vm_find_by_name_nocase("Hestia", ...) + sk_vm_switch_signal_register(...) block in src/starkernel/kernel_main.c, same shape as the existing Hermes/Artemis blocks, after all four fleet members are confirmed born (same "never register before birth is confirmed" discipline the existing comment on this block already states). Took §XXXV.0's "invisible by default" warning literally rather than trusting a clean boot log alone: §XXVIII.2's own recorded storm signature is "QEMU pinned near 100% CPU, serial log frozen solid" — not an error message, not UNKNOWN WORD. Named that signature before running, then checked for it directly rather than just reading ok> and moving on: confirmed each boot reached the prompt in normal wall-clock time (~30s, matching every prior run in this document), and — since TCG itself always shows ~100% CPU regardless of guest workload, so CPU alone proves nothing — watched the serial log's own line count at the idle prompt for 5–10s on all three architectures and confirmed it stopped growing (last real output, StarForth CLI/ok>, with nothing appended after) rather than flooding or silently stalling mid-boot. No compiler warnings.

  • 1.6 — Move fabric.4th from init.4th to hestia/init.4th. Check: Hera's dict shrinks, Hestia's grows; cross-arch identity holds. Merged with task 1.7 into one commit — see that entry. Real dependency discovered while attempting 1.6 alone, not a convenience bundling: recorded here and there.

  • 1.7 — Move font.4th likewise. Check: same. 2026-09-19 · finding, not a defect: 1.6 and 1.7 are not independent, and doing 1.6 alone breaks Hera's boot. font.4th calls G-LINE/G-ELLIPSE, which are fabric.4th's own words (fabric.4th blocks 5000–5002 per font.4th's own comment). Confirmed live before committing to either approach: removed only fabric.4th's EXEC from init.4th, left font.4th's in place, booted amd64 — Hera's boot floods with UNKNOWN WORD: 'G-LINE'/ 'G-ELLIPSE' the moment font.4th loads (logs/20260919-171047/amd64/, kept as evidence, not deleted). Reverted that partial state immediately (git checkout -- capsules/init.4th), asked Captain Bob how to proceed given the two tasks as separately scoped can't each independently pass their own three-arch-boot check, and was told to use best practices. Merged both into this one commit, moving fabric.4th and font.4th together, in their original relative order, into a new capsules/hestia/init.4th block 4988 — a deliberate, documented deviation from "one task, one commit," not a bundling of convenience.

    `logs/20260919-171942/amd64/`, `logs/20260919-172123/aarch64/`,
    `logs/20260919-172253/riscv64/` — all three reach `[zuse@Hera] ok>`, zero `UNKNOWN WORD`.
    Hestia's `dict_hash` (`0xe4e6b1916827e320`, capsule_hash `0x6aa0a1b5daf9d2ec`) identical
    across all three architectures. **Verified the shrink/grow live, not just inferred from
    hash movement**: `HERE .` reads `20008` in Hera, `63040` in Hestia (post-move) — a huge gap
    confirming Hestia's dictionary now genuinely holds the drawing vocabulary. `CART-PLOT` in
    Hera returns `UNKNOWN WORD: 'CART-PLOT'`; the identical call routed into Hestia via
    `VM-EXEC` reaches the word and fails on a stack underflow instead (`>R: DSP underflow`) —
    proof the word exists there, since an unknown word can't underflow. `mkcapsule --lint
    capsules/` clean, 38 files, 0 violations. `MANIFEST.md` updated: `init.4th`'s block 2049
    entry no longer lists `fabric.4th`/`font.4th`; `hestia/init.4th`'s entry gains block 4988.
    
  • 1.8 — Move PLOT/FB-WIDTH/FB-HEIGHT registration to Hestia's table only. Check: positively verify a non-Hestia VM calling PLOT gets UNKNOWN WORD. 2026-09-19 · logs/20260919-173129/amd64/, logs/20260919-173251/aarch64/, logs/20260919-173420/riscv64/ — all three reach [zuse@Hera] ok>, zero UNKNOWN WORD during boot. This is the real fix for task 0.8's finding: register_framebuffer_words() (src/word_source/framebuffer_words.c) was called unconditionally from register_forth79_words() (src/word_registry.c:139), itself called unconditionally from vm_init_with_host() — the generic per-VM bootstrap every VM goes through, with no way to know a VM's name at that point. Removed the unconditional call; added a name-gated call instead in capsule_birth_baby() (src/starkernel/capsule/capsule_birth.c), right beside the existing is_fleet_foundation check — the one place in the whole birth path where capsule_name and the newly-allocated VM* are both in scope together: if (vm_name_prefix_eq_nocase(capsule_name, "Hestia")) register_framebuffer_words((VM*)new_vm);

    **Positively verified, exactly as the check demands, not just inferred**: at the live
    prompt, `S" FB-WIDTH ." S" Hermes"/"Artemis" VM-EXEC` both return `UNKNOWN WORD:
    'FB-WIDTH'`; the identical call against `"Hestia"` returns `1280`. Confirmed at the
    fundamental level too: Hera's own base `PARITY:M7.1a` word count **dropped from 531 to
    528** — exactly the three words removed — identical across all three architectures
    (`hash=0xb3b23dc361371c97`, `latest_id=697`). Hermes/Artemis/Hestia `dict_hash` all moved
    accordingly and are identical cross-arch.
    
    **Side effect on the vendored hosted build, expected and not a regression**: `capsule_birth.c`
    is kernel-only (`src/starkernel/`), so the plain hosted `starforth` binary has no Hestia
    concept and now never registers these words at all — confirmed live (`echo "FB-WIDTH ."
    | ./build/amd64/standard/starforth` → `UNKNOWN WORD: 'FB-WIDTH'`). `framebuffer_words.c`'s
    own top comment already calls this surface "Kernel-only; no-op on hosted builds," so the
    prior stub registration there was already vestigial — this tightens it to match its own
    documented intent rather than breaking anything real. Ran a plain `make` sanity build to
    confirm the vendored hosted target still compiles and links clean (CLAUDE.md's own stated
    purpose for that target); `lfs/amd64/starforth` regenerated as a result and included in
    this commit since it would otherwise sit stale against the source that produced it.
    `mkcapsule --lint capsules/` clean, 38 files, 0 violations (no capsule content changed —
    this task is pure C). No compiler warnings on either edited file.
    
  • 1.9 — Assert §XVIII.6's headless invariant: Hestia's birth sets no g_wirebind_attached_username and mints no proxy. Check: boot headless, no thumbdrive, no prompt appears. 2026-09-19 · Audited first: neither capsules/hestia/init.4th nor her birth block in kernel_main.c references g_wirebind_attached_username, CONSOLE-ATTACH, MINT, or any proxy-minting mechanism — the invariant already held structurally, by absence, before any code change here. Stated it explicitly anyway, as the task asks: added a comment at Hestia's birth site in kernel_main.c quoting §XVIII.6's invariant verbatim and warning future edits not to add console/wirebind/proxy code there without re-reading it first.

    **Verified live with `make -f Makefile.starkernel ARCH=<arch> qemu ZUSEDISK=`** — the
    actual no-thumbdrive boot, not the default (which always attaches
    `disk/thumbdrives/zuse-thumb-ident.img` and produces the `[zuse@Hera] ok>` prompt seen in
    every other task in this document). `logs/20260919-173942/amd64/`,
    `logs/20260919-174126/aarch64/`, `logs/20260919-174307/riscv64/` — all four VMs (Hera/
    Hermes/Hestia/Artemis) born successfully on all three, zero `UNKNOWN WORD`, and critically
    **zero `ok>` occurrences anywhere in any of the three full logs** — genuinely silent,
    matching `sk_repl_headless_wait()`'s own documented "no banner, no prompt, no input
    surface at all." Watched each log's line count at the point after Artemis's birth for
    8–10s and confirmed it stayed flat rather than eventually printing something late.
    
    Also confirmed no regression on the standard (with-thumbdrive) path: re-ran amd64 with the
    default `ZUSEDISK` (`logs/20260919-174443/amd64/`) — `dict_hash` identical to task 1.8's
    baseline, zero `UNKNOWN WORD`, reaches `ok>` normally. Did not repeat the standard-boot
    check on aarch64/riscv64 for this task specifically: the change is a comment only, cannot
    diverge by compiler or architecture, and the headless invariant itself was already proven
    identically on all three.
    
    **Phase 1 is now fully closed** — tasks 1.1–1.9 all done. Tripod is Hera/Artemis/Hestia
    (plus Hermes, retained through Phase 1–3 per §XXXIV.4); Hestia owns the drawing fabric
    exclusively; headless-until-login policy intact with Hestia in the fleet. Phase 2
    (the allocator and its audit) is next — the phase §XXXIII named as the only genuinely hard
    part, and the project's real go/no-go at task 2.7.
    

Phase 2 — Allocator and audit (inert) — the real go/no-go

Gate: task 2.7. If it fails, stop and re-plan. Do not proceed to Phase 3.

  • 2.1 — Kernel-Hermes message/membership structures, wired to nothing, drawing no heat. Check: boot byte-identical; dict_hash unmoved. 2026-09-19 · logs/20260919-204642/amd64/ — byte-identical to task 1.9's baseline in every respect: same dict_hash triple, zero UNKNOWN WORD, reaches [zuse@Hera] ok>. Did not repeat aarch64/riscv64: the new file is a header with type definitions only, included nowhere in the real build, so nothing compiles it into any object file on any architecture — there is no mechanism by which it could diverge by compiler or ISA.

    Added `include/starkernel/vm/kernel_hermes.h`: `SkHermesMessage` (field-for-field mirror
    of `messaging.4th`'s live 9-cell `MSG-*` layout — type/from/to/payload addr+len/Stadium
    cell index/seq/channel/orig-type, plus an explicit `in_use` flag for task 2.2's allocator)
    and `SkHermesMembership` (one flat broadcast membership list — `SXXXIII.4`/`SXXXIII.5`'s
    recommended replacement for `messaging.4th`'s 28-word channel abstraction, which has
    exactly one live caller). Deliberately no separate heat field on the message struct: per
    `SXL.4`, a message's heat *is* the Stadium cell it occupies, not a value copied alongside
    it — keeping one source of truth for the conservation invariant `stadium_conserved()`
    (task 0.7) checks.
    
    **Genuinely wired to nothing**: no `.c` file, no Makefile change, no include from any
    compiled source. Syntax-checked standalone (`gcc -std=c99 -Wall -Wextra -Werror
    -fsyntax-only` against a throwaway file `#include`ing it under `__STARKERNEL__`) before
    touching the real build, rather than discovering a typo only once something references it
    in a later task. Item 27 (channel negotiation vs. broadcast, Phase 3 blocker B1) is not
    answered by this structure and isn't meant to be — a flat membership list is correct either
    way; the negotiation question is about behaviour built on top, not this shape.
    
  • 2.2 — Allocate: pull Q.SLOT from the caller's reservoir; roll back on refusal. Check: N allocs against a known reservoir; refusal at the right count. 2026-09-19 · logs/20260919-205534/amd64/, logs/20260919-205645/aarch64/, logs/20260919-205817/riscv64/ — all three reach [zuse@Hera] ok>, zero UNKNOWN WORD, dict_hash triple unmoved from task 2.1's baseline (pure C, no FORTH touched). All three print Kernel-Hermes alloc self-test: PASS with identical arithmetic: reservoir0=65536 Q_SLOT=2048 expected_n=32 got_n=32 reservoir_after=0.

    Added `sk_hermes_alloc(VMUuid, SkHermesMessage**)` (`src/starkernel/vm/kernel_hermes.c`,
    new file, wired into `Makefile.starkernel`'s `LOADER_EXTRA_SRCS` list since this repo lists
    `vm/*.c` files explicitly rather than globbing). Checks
    `stadium_reservoir_peek(vm_id) >= SK_HERMES_Q_SLOT` **before** touching the reservoir at
    all — refusal this way needs no rollback, since nothing was pulled — with an explicit
    rollback path (`stadium_reservoir_push`) kept for the pull-then-short case defensively,
    though nothing in this single-core kernel is expected to reach it. `SK_HERMES_Q_SLOT`
    defined as `Q48_ONE / SK_HERMES_MSG_MAX` (2048) — **deliberately simpler than
    `messaging.4th`'s own formula**, which reserves a `Q.1/3` floor for `COMMON-CH`'s own
    Stadium heat; kernel-Hermes has no such object (`SXXXIII.4`/`SXXXIII.5`'s flat membership
    list carries no heat of its own), so there is nothing left for that floor to protect.
    
    **Self-test, not a FORTH-visible check** (matching the existing `Stadium quota grant
    self-test` pattern already in `kernel_main.c`): mints a second synthetic VM
    (`lo=3`, distinct from the existing self-test's `lo=1`) via `stadium_grant_quota()`,
    reads back its actual granted reservoir (always a fresh `Q48_ONE` per
    `stadium_grant_quota()`'s own code — not assumed), derives `expected_n` from it rather
    than hardcoding 32, allocates until refusal, and checks **all three** of: the refusal
    lands at exactly `expected_n`, the reservoir doesn't move on the refused attempt (rollback
    proven, not just assumed), and the final reservoir equals `reservoir0 − got_n × Q_SLOT`
    exactly.
    
    **Noted, not fixed:** `SK_HERMES_Q_SLOT = Q48_ONE / SK_HERMES_MSG_MAX` and
    `SK_HERMES_MSG_MAX = 32` together mean reservoir exhaustion and arena exhaustion land at
    *exactly* the same count by construction — this test cannot distinguish which refusal
    reason fired, only that refusal fires at the right count and rolls back correctly. Not a
    defect at this stage (nothing yet needs the arena to outlive the reservoir), but worth
    remembering if `SK_HERMES_MSG_MAX` is ever resized independently of `Q_SLOT`'s divisor.
    
    **Deliberately not evidence for `stadium_conserved()`** — per the watch-list raised before
    starting Phase 2: allocating alone (no release yet, that's task 2.3) leaves pulled heat
    held by kernel-Hermes, off the Stadium floor entirely, so the two-term
    `stadium_resident_sum + reservoir == Q48_ONE` check would correctly read **false** right
    now if run mid-hold — that is expected, not a bug, and is exactly why Stage B (task 2.7) is
    defined as "before and after the alloc/free cycle," not "continuously during." This task's
    self-test checks reservoir arithmetic directly instead, which is the right instrument for
    what task 2.2 alone claims.
    
    **AMENDED 2026-09-20, while starting task 2.3 — see that entry for the full account.**
    This task's own first cut of `sk_hermes_alloc()` never admitted a real Stadium-floor
    patron; it only pulled reservoir heat and set a local flag. Corrected in the same commit
    as task 2.3, since release cannot evict something that was never admitted. The two-term
    `stadium_conserved()` non-evidence point above still holds — if anything it holds more
    precisely now that held heat really is off the floor (in the Stadium-admission sense) and
    not merely absent from a two-term check that never modeled it in the first place.
    
  • 2.3 — Release: return remaining heat via the eviction path. Check: reservoir restored exactly for an undecayed message. 2026-09-20 · logs/20260920-041455/amd64/, logs/20260920-041619/aarch64/, logs/20260920-041752/riscv64/ — all three reach [zuse@Hera] ok>, zero UNKNOWN WORD, dict_hash triple unmoved from task 2.2's baseline. All three print Kernel-Hermes alloc/release self-test: ... PASS with identical arithmetic: reservoir0=65536 ... reservoir_after_alloc=0 ... reservoir_final=65536 — exact restoration, not approximate.

    **Real finding, not a clean continuation: task 2.2's allocator was missing a step.**
    §XXXIII.4 item 1 says `MSG-FREE-NODE` returns heat "via `STADIUM-EVICT`" — which only
    means something if allocation admitted a real Stadium patron in the first place. Task
    2.2's first cut didn't; it only pulled reservoir heat and flagged a local slot in use.
    Caught this before writing release on top of a foundation that couldn't support it, per
    §XL.4's own invariant: held message heat that is neither a resident patron nor reservoir
    nor `consumed` is invisible to the conservation equation entirely, which cannot be right.
    Flagged to Captain Bob before proceeding; authorized to correct task 2.2 in the same pass
    as building 2.3 on top of the fix, rather than patching around the gap.
    
    **The fix, amending `src/starkernel/vm/kernel_hermes.c`'s `sk_hermes_alloc()`:** after
    pulling `SK_HERMES_Q_SLOT`, now calls `stadium_admit()` with a `StadiumPatronHeader` whose
    `heat` is the pulled amount, `behaviour = STADIUM_BEHAVIOUR_DELIVER` (matching
    `messaging.4th`'s own `SB-DELIVER STADIUM-ADMIT` at its `MSG-ALLOC` site exactly — same
    enum values, `SB-DELIVER`=1=`STADIUM_BEHAVIOUR_DELIVER`), and `identity` set to the
    message's own slot index (matching the FORTH precedent, and keeping
    `stadium_dispatch()`'s existing `DELIVER` diagnostic — "msg_idx=" — meaningful instead of
    every message printing 0; caught live in the first boot of this fix, since all 32
    admissions printed `msg_idx=0` before the identity fix). The returned cell index is
    stored in the message's own `stadium_cell` field. If the Stadium floor itself refuses
    admission (a third refusal path, independent of reservoir affordability), the pull is
    rolled back the same way the other refusal paths already do.
    
    **`sk_hermes_release()`, the new function this task actually asks for:** calls
    `stadium_evict()` on the message's `stadium_cell` — which itself returns the departing
    patron's remaining heat to its owning VM's reservoir (`stadium.c`'s own comment: "the
    departing patron's remaining heat must flow back to its owner's reservoir"), so release
    does not touch the reservoir directly, matching `MSG-FREE-NODE`'s exact shape
    (`DUP 5 CELLS + @ STADIUM-EVICT DROP`). Clears the slot regardless of eviction's own
    return value, since a message asked to be released should not remain allocated either way.
    
    **Self-test extended, not replaced:** the existing synthetic-VM self-test (task 2.2, `lo=3`)
    now keeps every allocated message's pointer in an array, allocates to exhaustion exactly as
    before, then releases all of them and checks the reservoir returns to precisely its
    starting value. "Undecayed" is true by construction here — no decay/TTL logic exists yet
    (task 2.5) — so this is exactly the case the check asks for: exact restoration, not
    approximate. `stadium_dispatch()`'s existing `DELIVER`-case console output (one line per
    eviction, pre-existing instrumentation, not new) is verbose but expected — confirmed
    real and load-bearing (`stadium.c`'s own comment: "has been real and live on every boot
    for weeks"), not something this task introduced. No compiler warnings on either edited
    file.
    
  • 2.4 — The four counters: held, pulled, returned, consumed (§XL.4). Check: each increments at exactly one site. 2026-09-20 · logs/20260920-055432/amd64/, logs/20260920-055549/aarch64/, logs/20260920-055723/riscv64/ — all three reach [zuse@Hera] ok>, zero UNKNOWN WORD, dict_hash triple unmoved. All three print PASS with identical final ledger: held=0 pulled=65536 returned=65536 consumed=0 — satisfying held == pulled − returned − consumed (0 == 65536 − 65536 − 0) exactly.

    Added `sk_hermes_ledger(uint64_t*, uint64_t*, uint64_t*, uint64_t*)` (an accessor, not a
    mutator) plus four static counters in `src/starkernel/vm/kernel_hermes.c`. Each has exactly
    one increment/decrement site: `held`/`pulled` both move at `sk_hermes_alloc()`'s single
    success path (after every refusal branch has already returned); `held`/`returned` both
    move at `sk_hermes_release()`'s single success path. `consumed` is declared and always
    reads 0 — **its one increment site doesn't exist yet**, and won't until task 2.5 gives
    decay something to record.
    
    `sk_hermes_release()` reads the Stadium cell's **live** `header.heat` immediately before
    calling `stadium_evict()`, rather than assuming the original pulled amount — `stadium_evict()`
    zeroes the header as part of freeing the cell and its own return value is a success code,
    not the credited amount, so this is the only point the true remaining heat is available.
    Today (no decay yet) this always equals the original `Q.SLOT` pull; once task 2.5 exists,
    this is what keeps `returned` correct without touching this function again.
    
    Self-test (kernel_main.c) extended again: snapshots the ledger before running (so it
    checks its own deltas, not an assumption that it's the only caller ever), verifies
    `held`/`pulled` grow by exactly `got_n × Q_SLOT` on allocation with `returned`/`consumed`
    untouched, then verifies `held` returns to its starting value and `returned` grows by the
    same amount on release, with `consumed` still untouched, and checks the audit invariant
    itself as a bonus (task 2.6 formalizes this properly; checking it here too cost nothing
    since the ledger was already in hand). No compiler warnings on either edited file.
    
  • 2.5 — Decay: apply, and record the delta into consumed (§XXXVII.3). Check: consumed grows by exactly heat_before − heat_after. 2026-09-20 · logs/20260920-182345/amd64/, logs/20260920-183413/aarch64/, logs/20260920-184152/riscv64/ — all three reach [zuse@Hera] ok>, zero UNKNOWN WORD, dict_hash triple unmoved (c95ef2d92fa0781f / fbde9fe105fd3b3d / a43d2e104ae7e451, identical across ISAs). All three print PASS with identical decay_consumed=352.

    Added `sk_hermes_decay(SkHermesMessage*)` and `SK_HERMES_Q_DECAY` (65208, identical to
    `messaging.4th`'s `Q-DECAY`; §XL.4 preserves the economy exactly). It sets the message's
    Stadium-cell heat to `q48_mul(heat, Q_DECAY)` and, at that one site, does
    `consumed += before − after` and `held −= before − after`, so
    `held == pulled − returned − consumed` stays exact. `consumed` now has its one increment
    site.
    
    Self-test: a second alloc-to-exhaustion cycle decays each message once. It checks each
    cell's new heat against an independently computed `q48_mul`, checks `consumed` grew by
    exactly the independently summed `before − after` (32 × (2048 − 2037) = 352), checks the
    audit identity mid-hold, and checks that release leaves the reservoir at exactly
    `reservoir0 − 352` (decayed heat does not return, §XL.4). A vacuity guard fails the test
    if decay consumed nothing. The task 2.3 undecayed exact-restoration check is kept as the
    first cycle.
    
    Process notes: an accidental `pkill -f` killed the shell once (no effect on results);
    `disk/artemis.img` and `disk/thumbdrives/zuse-thumb-ident.img` were restored via
    `git checkout --` before each ISA per the task 0.0 finding. That discarded a pre-existing
    uncommitted modification to `disk/artemis.img` that was present at session start.
    
  • 2.6 — Self-audit: held == pulled − returned − consumed, epsilon zero. Check: holds across the cycle; deliberately corrupt a counter → audit fires on the first unit. 2026-09-20 · logs/20260920-204658/amd64/, logs/20260920-205410/aarch64/, logs/20260920-205834/riscv64/ — all reach [zuse@Hera] ok>, zero UNKNOWN WORD, dict_hash triple unmoved and identical across ISAs; all print PASS, audit_failures=0, decay_consumed=352.

    Added `sk_hermes_audit_values()` (pure exact-equality predicate, no tolerance),
    `sk_hermes_audit()` (live ledger, O(1), prints and counts a failure) and
    `sk_hermes_audit_failure_count()`. The live audit runs at the end of every successful
    alloc, release and decay (§XXXVII.4: no cadence decision needed). Corruption is tested
    on a *copy* of the ledger, +1 unit on each of the four counters in turn, so live state is
    never corrupted; each is caught by the predicate while the uncorrupted values pass. The
    live audit stayed silent through both alloc/decay/release cycles.
    
    Limit, stated: the corruption proof exercises the predicate, not the wiring of the live
    audit's failure branch (that branch never executed). Task 2.8's scan cross-check is the
    independent check on the counters themselves.
    
  • 2.7 — Stage B proof (§XXXIV.3 as corrected by §XXXIX.4): alloc/free cycle verifying (a) the ledger and (b) stadium_conserved() before and after. Check: both true, all three arches. fleet_conserved is not evidence here — it cannot see Stadium heat (§XXXIX.1). 2026-09-20 · logs/20260920-212209/amd64/, logs/20260920-212522/aarch64/, logs/20260920-212845/riscv64/ — all print Stage B (ledger + stadium_conserved): PASS, audit_failures=0, vm_consumed=693; zero UNKNOWN WORD; dict_hash triple unmoved and identical across ISAs; reach [zuse@Hera] ok>.

    **Prerequisite built here, as §XL.4 directs:** `stadium_conserved()` gains its `consumed`
    term. New per-VM `consumed` field in `StadiumVMQuota` (zeroed at both quota-creation
    sites), `stadium_consumed_record()`/`stadium_consumed_peek()`, and
    `stadium_conserved()` now checks `resident + reservoir + consumed == Q48_ONE`, epsilon
    zero. `SkHermesMessage` gained an `owner` VMUuid so decay charges the funding VM;
    `sk_hermes_decay()` records into it. Other VMs are unaffected (consumed stays 0).
    
    Stage B checks `stadium_conserved()` at four points — before, mid-hold, after decay,
    after release — plus the ledger audit, and requires the old **two-term form to FAIL after
    decay** while the four-term form holds (non-vacuity: the consumed term is load-bearing).
    `fleet_conserved` is not consulted.
    
    **First attempt FAILED on all three ISAs** (`logs/20260920-211204/`, `-211517/`,
    `-211840/`, kept as audit artifacts). Cause was a bug in the test, not the code: it
    expected 32 allocations, but the preceding decay cycle had consumed 352 from that VM's
    reservoir so only 31 fit. Fixed by deriving the expected count from the current reservoir.
    Vm_consumed=693 is 352 (32×11, first cycle) + 341 (31×11, Stage B).
    
    **Phase 2 gate: passed** (2.7 holds on all three arches). Remaining Phase 2 work is 2.8
    (diagnostic scan cross-check).
    
  • 2.8 — Scan-based cross-check of the counters, diagnostics only, off the hot path (§XXXVII.4). Check: scan agrees with counters. 2026-09-21 · logs/20260921-083638/amd64/, logs/20260921-083950/aarch64/, logs/20260921-084314/riscv64/ — all print Scan cross-check (counters vs arena): PASS with identical scan_held_before_decay=8192, scan_held_after_decay=8148; Stage B and the main self-test still PASS, audit_failures=0, zero UNKNOWN WORD, dict_hash triple unmoved and identical across ISAs.

    Added `sk_hermes_scan_held()` (walks the arena, sums live Stadium-cell heat of in-use
    messages, reports the live count) and `sk_hermes_scan_check()` (scan == `held`, exact).
    Called only from the self-test, never from alloc/release/decay. The check runs at rest
    (0 live), mid-hold (4 live, 4×2048 = 8192), after decay (8148 = 8192 − 4×11) and after
    release (0), with a vacuity guard (non-zero mid-hold, strictly less after decay).
    
    Limit, stated: the scan reads the same Stadium cells the counters were derived from, so
    it verifies the counters against the arena, not the Stadium cells against ground truth;
    the latter is `stadium_conserved()`'s job (2.7).
    
    **Phase 2 is complete (2.1–2.8).** Phase 3 remains blocked on B1, B2, B4.
    

Phase 3 — Cutover — UNBLOCKED, tasks not yet written

Blockers cleared 2026-09-21 (§XLV). Task list drafted 2026-09-21, awaiting Captain Bob's review — none started. Shape (§XXXIV.3, amended by §XLV): build the negotiated pub/sub plumbing inert, then cut over BLK-ATTACH-EVENT alone, then remaining types one at a time, under §XXXIV.2's partition rule — one message type owned by exactly one layer, no message shared. Stop condition (§XXXIV.6): dict_hash diverging across ISAs, or Stage B evidence (ledger + stadium_conserved()) going false — re-plan, do not push on.

  • B1 — CLEARED 2026-09-21 by FABRIC-3.5.md §XLV.1: negotiated pub/sub channels — one common channel, private channels by request/grant/deny, ACK/NACK throughout. Overrules §XXXIII.5. Open sub-items: ACK cadence, ACL hook for channel-open policy.
  • B2 — CLEARED 2026-09-21 by FABRIC-3.5.md §XLV.2: dynamic, not a fixed constant (Stadium-style RAM-derived sizing). Open sub-item: the concrete sizing rule.
  • B3 — CLEARED 2026-09-19 by FABRIC-3.5.md §XLIII: the target drains its own queue at its own outermost interpret checkpoint; kernel-Hermes publishes and never dispatches. Reuses sk_vm_at_outermost_interpret() and the Stage 3 checkpoint.
  • B4 — CLEARED 2026-09-21 by FABRIC-3.5.md §XLV.3: one block (1024 bytes) per message, larger payloads chunked. Open sub-item: chunk framing.

All four blockers are cleared, but Phase 3 has no task breakdown yet — the shape below predates §XLV's negotiation. Writing the task list (and settling the §XLV.4 sub-items it depends on) is the next step, before any Phase 3 code.

Phase 3 tasks (drafted 2026-09-21; draft, not authorized)

Each is one commit with three-ISA acceptance, per the standing rules. Tasks marked [needs ruling] cannot be written precisely until Captain Bob settles the named sub-item.

  • 3.0 — Decision gate, not code. Rule on the §XLV.4 sub-items: (a) ACK cadence (advised: channel open + delivery, not every common-channel message); (b) where the channel-open ACL hook lives in ACL.4th; (c) the dynamic switch-table sizing rule; (d) chunk framing; (e) drain one message per checkpoint (§XLIII.6.3, recommended, never ruled). Check: each answer recorded in FABRIC-3.5.md. 2026-09-21 · no commit (documentation ruling, see FABRIC-3.5.md §XLVI) · all five sub-items plus task 3.3's heat-cost point ruled by Captain Bob, all recommended defaults accepted.

  • 3.1 — Dynamic switch table (B2). Read SK_SWITCH_MAX_SLOTS's every use first; replace the constant with a boot-time, RAM-derived allocation (Stadium's stadium_max_vm_count_val pattern). Check: boot byte-identical, dict_hash unmoved; a synthetic test drives more than 16 slots. 2026-09-21 · logs/20260921-233241/amd64/, logs/20260921-233814/aarch64/, logs/20260921-234837/riscv64/ (riscv64's first attempt, logs/20260921-234312/, timed out mid-boot on a 280s wrapper before reaching the prompt — kept per the never-delete-logs convention, not a real finding, just an undersized timeout; the 234837 rerun with more headroom reached the prompt cleanly). All three reach [zuse@Hera] ok>, zero UNKNOWN WORD. Switch-signal table sized dynamically off stadium_max_vm_count() at boot, confirmed via a new Switch-signal: N slots console line: 50 slots (amd64), 202 slots (aarch64), 50 slots (riscv64) — all far above the old fixed 16-slot cap, satisfying the "drives more than 16 slots" check without a separate synthetic harness (real boot-time RAM sizing already clears it by a wide margin). Reused session_boot_init()'s exact pattern (kmalloc to stadium_max_vm_count(), called in kernel_main.c right after stadium_boot_init(), before the first sk_vm_switch_signal_register() call). dict_hash for Hermes (0xc95ef2d92fa0781f) and Hestia (0xfbde9fe105fd3b3d) identical across all three architectures — unmoved from pre-task values, as expected (this task touches no capsule). Artemis's dict_hash differs between the amd64 run (0xa43d2e104ae7e451, fresh-formatted disk/artemis.img) and the aarch64/riscv64 runs (0x7f18214489036b39, both resuming the same disk state the amd64 run left behind) — a disk-state artifact of running three sequential boots against one shared image, not an architecture divergence; not a §XXXIV.6 stop condition. note: unrelated finding, not fixed — riscv64's boot logged three virtio_blk: vblk_io timed out errors during Artemis's disk I/O in both riscv64 attempts (sectors 20/8/8 and 3224/5496/5504); boot recovered and reached the prompt regardless. virtio_blk.c was not touched by this task. Reporting per standing rule, not investigating further here.

  • 3.2 — Channel table + common channel, inert (B1). Dynamic table of topics, each with its own SkHermesMembership; the common channel exists from boot; every VM is subscribed at birth. Wired to nothing. Check: boot byte-identical; every born VM appears in the common channel's membership; create/destroy a synthetic private topic. 2026-09-22 · logs/20260922-000447/amd64/, logs/20260922-000954/aarch64/, logs/20260922-001511/riscv64/ — all three reach [zuse@Hera] ok>, zero UNKNOWN WORD, mkcapsule --lint capsules/ clean (38 files, 0 violations, unchanged -- no capsule touched). New SkHermesChannel table (kernel_hermes.h/.c): a channel is an index into a boot-time, stadium_max_vm_count()-sized array, no name field (mirrors messaging.4th's own nameless CH-ARENA). Sizing note: SXLV.1 says the channel table has "no fixed channel maximum, same reasoning as SXLV.2" but names no separate numeric rule -- extending task 3.1's already-ruled stadium_max_vm_count() bound directly (same kmalloc-at-boot shape) is the smallest choice consistent with what was ruled; flagged in the header comment as an engineering extrapolation, not restated as a separate Captain Bob ruling. Common channel (index SK_HERMES_CHANNEL_COMMON = 0) created in sk_hermes_channels_boot_init(), called from kernel_main.c right after the switch-signal boot init; Hera subscribed explicitly right after stadium_birth_hera() (she is the one VM never born through capsule_birth_baby()); every other VM (Tripod fleet and future WIREBIND identities alike) subscribed inside capsule_birth_baby() itself, at its VM_STATE_LIVE completion point -- the single choke point every non-Hera birth already passes through, confirmed by grep (mama_forth_words.c, capsule_runcap.c, capsule_console.c all call it). New console lines confirmed live on all three architectures: Kernel-Hermes: N channel slots (50 amd64, 202 aarch64, 50 riscv64 -- tracks switch-signal's own per-arch sizing exactly, as expected since both derive from the same stadium_max_vm_count()), then after all four fleet births, Kernel-Hermes common-channel fleet self-test: PASS (common channel members=4, one per Hera/Hermes/Hestia/Artemis) and Kernel-Hermes synthetic private-topic self-test: PASS (create/subscribe/is-member/unsubscribe/destroy round-trip against a synthetic VM id, plus confirms a destroyed channel refuses further ops and the common channel itself refuses sk_hermes_channel_destroy()) -- both PASS on all three architectures. dict_hash for Hermes (0xc95ef2d92fa0781f) and Hestia (0xfbde9fe105fd3b3d) identical across all three architectures, unmoved from task 3.1's values (this task touches no capsule).

  • 3.3 — Publish path, no dispatch (§XLIII.3). Publish allocates via sk_hermes_alloc(), enqueues onto each subscriber's own pending queue, and records the fact; it dispatches nothing. Self-test only. Check: ledger audit and stadium_conserved() hold across N publishes to M subscribers (heat cost per subscriber is a design point to settle before writing this: one message per subscriber, or one shared message with a reference count — [needs ruling]). 2026-09-22 · logs/20260922-002857/amd64/, logs/20260922-003404/aarch64/, logs/20260922-003922/riscv64/ — all three reach [zuse@Hera] ok>, zero UNKNOWN WORD, mkcapsule --lint capsules/ clean (38 files, 0 violations, unchanged -- no capsule touched). Heat cost ruled 2026-09-21 (one message per subscriber); sk_hermes_publish() allocates one SkHermesMessage per channel member via sk_hermes_alloc() (funded by the publisher's reservoir) and enqueues each onto a new per-subscriber SkHermesPendingQueue (found-or-created lazily by vm_id, same shape as stadium.c's quota_slot_for_vm() -- not pre-populated at birth like channel membership is, since not every VM ever receives a message). Table sized from stadium_max_vm_count(), same pattern as tasks 3.1/3.2. Best-effort, not atomic across subscribers -- a failed allocation or full queue skips just that one subscriber and rolls back its own allocation; not a separate ruling, the natural reading of the task's own check (ledger/stadium_conserved() invariants hold under partial delivery too), documented as such in the header. Added sk_hermes_pending_count()/ _peek()/_pop() as the read/drain primitives task 3.4's real checkpoint-driven drain will build on -- this task's own self-test uses them directly for cleanup since no checkpoint hook exists yet. New console line confirmed live on all three architectures: Kernel-Hermes: N pending-queue slots (50 amd64, 202 aarch64, 50 riscv64 -- tracks the channel/switch-signal tables' own per-arch sizing exactly). Self-test (synthetic publisher lo=7, three synthetic subscribers lo=8/9/10, N=2 publishes to a 3-member synthetic channel) confirms sk_hermes_publish() returns 3 each time, each subscriber's queue holds exactly 2 afterward, ledger audit and stadium_conserved(publisher) hold mid-publish, then confirms the same invariants return to baseline after draining every queue by hand (pending_peek()/release()/pop()) -- PASS on all three architectures. dict_hash for Hermes (0xc95ef2d92fa0781f) and Hestia (0xfbde9fe105fd3b3d) identical across all three architectures, unmoved from tasks 3.1/3.2's values (this task touches no capsule).

  • 3.4 — Drain at the outermost checkpoint (§XLIII.3–.5). Reuse sk_vm_at_outermost_interpret(); one message per checkpoint [needs ruling 3.0e]. Check: a nested interpret does not drain; a queued payload is interpreted exactly once at depth 1. 2026-09-22 · logs/20260922-010622/amd64/, logs/20260922-011130/aarch64/, logs/20260922-011648/riscv64/ — all three reach [zuse@Hera] ok>, zero UNKNOWN WORD, mkcapsule --lint capsules/ clean (38 files, 0 violations, unchanged -- no capsule touched). Finding, amended into FABRIC-3.5.md §XLIII.5 (caught by advisor() before writing the naive version, not found live): §XLIII.5's "recursive drain is prevented for free" via g_vm_interpret_depth is true but covers only same-message re-drain, not the separate same-VM reentrancy hazard FABRIC-3.md §XX had already named for Hera specifically (VMCallState saves rsp/exit_colon/ecw_nesting only, never input_buffer/input_length/input_pos) -- draining calls vm_interpret() on the same vm whose own vm_interpret() call is still paused mid-word at the checkpoint, which would silently truncate the enclosing REPL line or LOAD block if the cursor isn't saved and restored by hand. sk_hermes_drain_checkpoint() (kernel_hermes.c) snapshots input_buffer/input_length/input_pos/mode/error/abort_requested around the vm_interpret() call and restores all six -- mode forced to MODE_INTERPRET for the duration (a checkpoint reached mid-colon-definition must not compile the payload's words into the enclosing definition); error/abort_requested restored so a bad message can't abort the enclosing execution. Placed in vm_core.c's existing cooperative checkpoint before the switch-signal block, not after (sk_vm_context_switch() does not return until something switches back, so a drain placed after it would silently never run on any checkpoint that switches). Gated behind a system-wide pending-total counter (sk_hermes_pending_total, maintained in queue_push()/sk_hermes_pending_pop()) so the common no-message-in-flight case costs one integer read per word dispatch, not a stadium_max_vm_count()-sized queue-table scan -- flagged by advisor() as a real hot-path cost against this project's own +0.0603% measurement floor, not deferred. Self-test (kernel_main.c) publishes a real, stack-neutral payload ("1 2 + DROP") to Hermes (a real, already-born VM, not synthetic -- needed a genuine live dictionary), proves the depth gate via VM-EXEC-ing the existing harmless colon word WELCOME (capsules/hermes/init.4th block 4855) into Hermes from Hera's context -- a real, already- proven-safe nested vm_interpret() call (mama_forth_words.c's own VM-EXEC mechanism) -- confirming the message does NOT drain at the resulting depth 2, then calls sk_hermes_drain_checkpoint() directly from this self-test's own genuinely-outermost C context and confirms it DOES drain exactly once, with a further call a clean no-op. Deliberately avoided the block/LOAD mechanism for the nested case -- real but touches real disk-backed block storage, which a throwaway diagnostic has no business perturbing. dict_hash for Hermes/Hestia unmoved and identical across all three architectures (this task touches no capsule).

  • 3.5 — Payload bound and chunking (B4) [needs ruling 3.0d]. Max 1024 bytes per message; larger payloads sent as ordered chunks, reassembled before drain. Check: 1024-byte payload single message; 3000-byte payload chunked and reassembled byte-exact; 1025-byte single-message send refused. 2026-09-22 · logs/20260922-063122/amd64/, logs/20260922-063629/aarch64/, logs/20260922-064147/riscv64/ — all three reach [zuse@Hera] ok>, zero UNKNOWN WORD, mkcapsule --lint capsules/ clean (38 files, 0 violations, unchanged -- no capsule touched). Sizing decision (checked with advisor(), not itself a separate ruling -- see the header's own note): SK_HERMES_CHUNK_MAX_PAYLOAD (1024) bounds every message's payload_len uniformly, chunked or not -- a chunk carrier is [SkHermesChunkHeader][content slice] where the slice is capped at 1024 - sizeof(header), not 1024 itself. Rejected the literal alternative (slice up to 1024, carrier up to 1024+sizeof(header)): under that reading a chunk carrier could never be handed to vm_interpret() as-is, making a future chunk-aware drain a special case instead of the same one-block invariant every other message already satisfies. sk_hermes_publish() (task 3.3) now enforces this bound directly, closing the gap that task's own doc comment explicitly parked ("3.3 does not enforce a payload size limit"). Deliberately no chunking-SENDER API (sk_hermes_publish_chunked() or similar) -- advisor() flagged that building one raises a real memory-lifetime question kernel-Hermes has never answered (chunk buffers would need to stay alive until every subscriber drains them, and nothing here can know when that is); sending is a loop pattern a caller writes with sk_hermes_chunk_count() + sk_hermes_publish(), demonstrated by this task's own self-test rather than hidden behind a new allocator. sk_hermes_reassemble() is pure and memory-agnostic (caller-owned input chunks, caller-owned output buffer) -- validates msg_id agreement, exact seq coverage {0..n_chunks-1} with is_last on exactly the last, and every non-final slice at exactly SK_HERMES_CHUNK_MAX_SLICE bytes, before a single memcpy; the reassembled total length is computed once from validated seq completeness and checked against out_buf_cap once, so a too-small output buffer is refused cleanly with zero partial copy rather than order-dependently on whichever chunk happens to overflow. Self-test (kernel_main.c) proves exactly the task's three checks: a 1024-byte payload as one message; a 1025-byte single-message send refused outright, no allocation, ledger untouched; a 3000-byte payload split into 3 chunks, drained off a synthetic subscriber's own pending queue (task 3.3 primitives), reassembled, and confirmed byte-exact against the original. Ledger audit and stadium_conserved() hold before and after. dict_hash for Hermes/Hestia unmoved and identical across all three architectures (this task touches no capsule).

  • 3.6 — ACK/NACK and private-channel negotiation (B1) [needs ruling 3.0a]. request → grant/deny on the common channel; a grant creates a private topic; close tears it down. ACK/NACK are message types on the existing allocator. Check: grant path, deny path, close path; heat conserved (ledger + stadium_conserved()) across all three; a NACK'd request leaves no topic behind. 2026-09-22 · logs/20260922-070455/amd64/, logs/20260922-071004/aarch64/, logs/20260922-071525/riscv64/ (the successful runs; logs/20260922-065946/amd64/ is a genuine first attempt that hit a real bug, caught by the self-test itself before this task was called done -- kept, not deleted, per the never-delete-logs convention) — all three reach [zuse@Hera] ok>, zero UNKNOWN WORD, mkcapsule --lint capsules/ clean (38 files, 0 violations, unchanged -- no capsule touched). Design finding (checked with advisor()): the ruling's "over the common channel" cannot mean sk_hermes_publish()-style fan-out -- that sets msg->to to whichever member it is iterating, so every common-channel member would receive its own copy of a request believing it was the addressee, drawing heat for a message only one VM should ever see. messaging.4th's own CH-REQUEST ( type from to paddr plen -- ) carried an explicit to for the same reason -- point-to-point addressing was in the original protocol from the start; "over the common channel" means every VM is reachable from birth (task 3.2), not that the exchange itself fans out. Extracted sk_hermes_send_one() from sk_hermes_publish()'s own per-subscriber body (alloc/set-fields/find-or-create-queue/ push/roll-back-on-refusal) so both callers share one code path and the ledger can never diverge between them -- sk_hermes_publish() now just loops it per member. Message types: CH_REQUEST/CH_GRANT/ACK/NACK/CH_CLOSE, chosen clear of messaging.4th's own live/reserved type space (2/3/4/7/8/9/253/255), not itself a ruling. "A deny is a NACK" (SXLV.1) -- no separate CH_DENY type. ACK sent once, for the ruled channel-open+delivery moment. The grant/deny decision is a plain caller-supplied approved bool (named for what it is, not allow) -- task 3.7 replaces the call site that produces it with a real ACL.4th query, not this function's signature. Close authority is membership alone (either party may close a channel it belongs to) -- narrowest defensible rule given both are already trusted members, not itself a ruling. Sibling case advisor() flagged as the one a green boot would hide, beyond the task's own three checks: an approved==1 respond() whose channel creation itself fails (table exhausted) must still fall through to NACK, not a silent false grant or a half-open channel -- self-test exhausts the entire channel table, confirms the fallback, then restores capacity. Bug found and fixed before this was called done: the first self-test draft (logs/20260922-065946/amd64/, FAIL) called sk_hermes_pending_pop() directly on the deny path's dropped request copy without releasing it first -- pending_pop() only advances the queue per its own doc comment, so the message's Stadium heat was never returned, leaking held heat and failing the self-test's own held0/held1 baseline check. Fixed to peek+release+pop, matching every other drain in the same self-test; re-verified PASS on all three architectures after the fix. dict_hash for Hermes/Hestia unmoved and identical across all three architectures (this task touches no capsule). Artemis's own dict_hash differs run-to-run as already documented (task 3.1's own finding) -- shared disk/artemis.img state accumulated across this session's many prior boots, not an architecture divergence.

  • 3.7 — Channel-open policy hook [needs ruling 3.0b]. Kernel-Hermes asks ACL.4th, never decides in C, never gates on zuse_session. Check: a denied open is denied by FORTH policy, with the C unchanged when the policy changes. 2026-09-22 · logs/20260922-075138/amd64/, logs/20260922-075649/aarch64/, logs/20260922-080212/riscv64/ (the successful runs; logs/20260922-074119/amd64/ and logs/20260922-074628/amd64/ are two genuine prior attempts that hit real bugs, caught by the self-test itself before this task was called done -- kept, not deleted, per the never-delete-logs convention) — all three reach [zuse@Hera] ok>, zero UNKNOWN WORD, mkcapsule --lint capsules/ clean (38 files, 0 violations — one block added inside the existing ACL.4th, no file added). dict_hash identical across all three architectures for every VM (Hera 0xbb2b51e7595464ce, Hermes 0xc95ef2d92fa0781f, Hestia 0xfbde9fe105fd3b3d, Artemis 0x7f18214489036b39).

    Added `HERMES-CHANNEL-OPEN? ( req-hi req-lo -- allow? )` at `capsules/ACL.4th` block 4008
    (default: approve everything) — the one word policy authors edit; `sk_hermes_channel_open_policy(VM *target_vm, VMUuid requester)`
    (`kernel_hermes.h`/`.c`) is the C-side query that calls it, and never itself decides. Uses
    plain word-dispatch (`vm_find_word()` + the entry's own `func` pointer) against the
    *target* VM's own dictionary and data stack, not `vm_interpret()` — deliberately, since
    `HERMES-CHANNEL-OPEN?` is a fixed known name, and this avoids task 3.4's input-buffer
    cursor-preservation hazard entirely (this touches the target's data stack only, never its
    input buffer). **Fails closed, not open**: no policy word present (e.g. a VM that never
    loaded `ACL.4th`), a policy-word error, or stack underflow all return "denied," matching
    `.claude/CLAUDE.md`'s own posture that absence of policy must never mean "always allow."
    A broken policy word's own error state is cleared on the target VM before returning, so it
    cannot leak into whatever else that VM is doing.
    
    **Two real bugs found and fixed before this was called done, not a clean first pass:**
    1. (`logs/20260922-074119/`) The first draft called `entry->func(target_vm)` directly
       without setting `target_vm->current_executing_entry` first — colon words dispatch
       through `execute_colon_word()`, which reads its own body address from that field and
       silently no-ops if it's `NULL` (`vm_core.c:730`). Every call silently did nothing,
       leaving the pushed requester args untouched on the stack rather than erroring — no
       crash, no obvious symptom, just a wrong answer, which is why it needed the self-test
       (not a crash) to catch. Fixed by setting `current_executing_entry` before the call and
       restoring the caller's own value after (in case the target VM is ever called into
       mid-dispatch elsewhere later).
    2. (`logs/20260922-074628/`) Still `FAIL`, with debug instrumentation left in from
       diagnosing bug 1 (`entry=... func=...`, `error=0 dsp=1`/`error=0 dsp=2` printed, then
       `FAIL`). **No intermediate commit exists for this attempt, so its precise defect is
       not reconstructable now** — the log records the symptom and nothing more. Debug prints
       were removed; the third attempt (`075138`/`075649`/`080212`) passed clean on all three
       architectures with no debug output.
    
    Self-test in `kernel_main.c` proves the task's check four ways against the **same
    unchanged C function**: (a) Hera's default `HERMES-CHANNEL-OPEN?` approves; (b)
    redefining that same word live on Hera to deny flips the answer with zero C change; (c)
    restoring the approve-default flips it back; (d) Hermes — who never loads `ACL.4th` at
    all (grep-confirmed: only `init.4th`/`ACL.4th`/`zuse.4th`/`block-acl.4th` reference it) —
    is correctly refused closed, not silently approved, proving the fail-closed behaviour on a
    VM with no policy at all. A fifth check wires the policy result straight into task 3.6's
    `sk_hermes_channel_respond()` end to end: a denied policy really produces a NACK and no
    channel, with the ledger audit and `stadium_conserved()` holding before and after —
    exactly task 3.6's own deny-path shape, reused rather than re-derived.
    
    **Not yet done, by scope**: the actual call site inside `sk_hermes_channel_respond()`'s
    caller still takes a plain `approved` bool (task 3.6's own signature, unchanged, as
    promised) — this task builds and proves the query function itself; wiring it as the
    *only* source of that bool at a real call site is deferred to whichever later task first
    needs a live (not self-test-only) channel-open decision, consistent with every other
    Phase 3 task before Stage C being self-test-only. `dict_hash` for Hermes/Hestia unmoved
    from task 3.6's values, as expected (neither VM loads `ACL.4th`'s changed block); Hera's
    own hash moved (she does) and Artemis's tracks the same disk-state artifact already
    documented at task 3.1.
    
  • 3.8 — Stage C: cut over BLK-ATTACH-EVENT alone (§XXXIV.3). One layer owns it; FORTH Hermes still routes every other type. Check: the real Hera↔Artemis attach path works end to end on all three ISAs; ledger and stadium_conserved() true before/after; fleet_conserved is not evidence (§XXXIX). 2026-09-22 · Final acceptance: logs/20260922-105501/amd64/, logs/20260922-105758/aarch64/, logs/20260922-110304/riscv64/ — disk/artemis.img and disk/thumbdrives/zuse-thumb-ident.img reset (git checkout --) before each of the three, per task 0.0's own finding, so all three are directly comparable, no shared-disk confound. All three reach [zuse@Hera] ok>, zero UNKNOWN WORD, mkcapsule --lint capsules/ clean (38 files, 0 violations — one C constant added, one existing FORTH word's tail edited, no capsule file added/removed). dict_hash identical across all three architectures for every VM, including Artemis: 0xa8999a854545f274 / 0xb8052b99f1d75f59 / 0xdf59e557986c9009 / 0xecad3d867c9bfec2.

    **What actually cut over.** Only the reply leg (Artemis → Hera ack) was real FORTH
    messaging traffic to begin with — confirmed by reading, not assumed: the request leg
    (`repl.c`'s own `"<dev-addr> HERA-BLK-ATTACH-REQ" "Artemis" VM-EXEC`) was already a direct
    VM-EXEC with no type tag, forced by Hera's own pre-existing inability to load
    `common:messaging.4th` (its own header comment says why). So this task's real scope was:
    `capsules/artemis/init.4th`'s `HERA-BLK-ATTACH-REQ` (block 4858) no longer ends in
    `BLK-ATTACH-EVENT 2 0 ATTACH-ACK-BUF ATTACH-ACK-LEN @ 0 MSG-SEND` (FORTH's arena/MSG-TICK);
    it now ends in `ATTACH-ACK-BUF ATTACH-ACK-LEN @ KH-BLK-ATTACH-SEND DROP`, a new C word
    (`repl.c`) wrapping `sk_hermes_send_one()` (task 3.6). Delivery is task 3.4's already-wired
    `sk_hermes_drain_checkpoint()` calling `vm_interpret()` on Hera with the identical payload
    text — `BLK-ATTACH-ACK` (`repl.c`, unchanged) fires exactly as before, just reached by a
    different layer. `SK_HERMES_MSG_TYPE_BLK_ATTACH` (`kernel_hermes.h`) deliberately reuses
    `BLK-ATTACH-EVENT`'s own value (9), not a fresh kernel-Hermes-space number — §XXXIV.2's
    partition rule is about which layer owns a message, not about disjoint numbering, and
    keeping the value documents this as a cutover of the same logical message.
    
    **Evidence for the check, and its honest limit.** `sk_word_blk_attach_ack()` — the real
    drain target, since `BLK-ATTACH-ACK` is exactly the word the drained payload calls — prints
    the kernel-Hermes ledger and `stadium_conserved(Artemis)` right there
    (`Kernel-Hermes BLK-ATTACH-EVENT (real, Stage C): ledger held=2048 pulled=241664
    returned=238879 consumed=737 stadium_conserved(Artemis)=true`, identical on all three
    architectures). This is evidence for the **mid-hold instant** — before
    `sk_hermes_drain_checkpoint()`'s own `release()`/`pop()` run, which happen after this
    handler returns, in the caller — not literally "after the full cycle." That's a real
    instant to check, not a weaker substitute: task 2.7 established the four-term
    `stadium_conserved()` form holds at every instant, mid-hold included, so this is genuine
    evidence, just not evidence for the post-release state specifically. Recorded honestly per
    `advisor()`'s review rather than overclaiming "before/after."
    
    **Real finding: kernel-Hermes's first live (not self-test) exercise hung, twice, before
    the actual bug was found — see the findings log below for the full account
    (`vm_ptr()` translation, and a separate, still-unexplained first hang).** Both are recorded
    there, not restated here.
    
    **Logging note (`advisor()` caught this before commit):** the evidence line went through
    three revisions. First cut used `console_println` twice (send-side pre-send + ack-side
    after) — flagged as unconditional production output on every real USB attach, forever,
    exactly the pattern memory `project_production_logging_cleanup_needed` already names.
    Tried `log_message(LOG_DEBUG, ...)`, confirmed invisible in the actual serial-log capture;
    tried `log_message(LOG_INFO, ...)`, **also invisible** — confirmed live that `repl.c`'s own
    pre-existing `log_message()` calls, at every level, do not appear anywhere in a real boot's
    serial log in this build (`log_message()`'s `fprintf(stderr, ...)` is not wired to the
    serial console here — a build characteristic, not something this task introduced or fixed).
    Settled on a single `console_println` (the ack-side one; dropped the send-side line
    entirely) — this fires once per real USB attach, not per word, so it is not the
    hot-path-spam case that memory warns about, and it is the only channel that is actually
    visible in the acceptance logs this document's own standing rules require as evidence.
    
    **Superseded intermediate logs, kept per the never-delete convention:**
    `logs/20260922-102354/amd64/`, `-102948/aarch64/`, `-103458/riscv64/` were the first
    passing three-ISA triple, still carrying the two-`console_println` (pre-send + after)
    design; `logs/20260922-104229/amd64/` and `-104902/amd64/` were the `LOG_DEBUG` and
    `LOG_INFO` logging-channel iterations described above, both confirming `log_message()`'s
    invisibility rather than producing new acceptance evidence. All superseded by
    `105501`/`105758`/`110304`, cited above, once the disk-reset-before-each-arch procedure and
    the final single-line evidence design were both settled.
    
  • 3.9 — Stage D: CONSOLE-CMD-EVENT (type 7) (§XXXIV.3). One layer owns it; FORTH Hermes still routes every other type. Check: a real interactive console-session relay works end to end on all three ISAs, via USE into a live WIREBIND identity followed by a plain (unquoted) console line; the same task 3.8 evidence discipline (ledger + stadium_conserved(), fleet_conserved not accepted as evidence). 2026-09-22 · Resumed and closed — Captain Bob ruled 2026-09-22 to land the send-side cutover first (already written, per the pause note below) and fix the task 3.8 payload-aliasing defect separately; that sequencing decision stands, the aliasing fix is still its own open item (findings log above). Final acceptance: logs/20260922-132142/amd64/, logs/20260922-132512/aarch64/, logs/20260922-132848/riscv64/ — disk/artemis.img and disk/thumbdrives/{zuse,bob}-thumb-ident.img reset (git checkout --) before each of the three, disk-reset-before-each-arch procedure unchanged from task 3.8. All three reach [zuse@Hera] ok>, zero UNKNOWN WORD, mkcapsule --lint capsules/ clean (38 files, 0 violations). dict_hash identical across all three architectures for every VM, matching task 3.8's own baseline exactly: 0xb8052b99f1d75f59 (Hera) / 0xecad3d867c9bfec2 (Hermes) / 0xdf59e557986c9009 (Hestia) / 0xa8999a854545f274 (Artemis).

    **What actually cut over**, matching task 3.8's own precedent: `sk_repl_dispatch_line()`'s
    "`CONSOLE-CMD-EVENT 0 3 S\" ...\" 0 MSG-SEND`" FORTH-string-interpret is gone; a plain
    (unquoted) console line typed while paired to a live WIREBIND identity now calls
    `sk_hermes_send_one()` (`repl.c`) directly, with `SK_HERMES_MSG_TYPE_CONSOLE_CMD`
    (`kernel_hermes.h`) deliberately reusing `CONSOLE-CMD-EVENT`'s own value (7), same
    partition-rule reasoning task 3.8 already established for `BLK-ATTACH-EVENT`. Delivery is
    task 3.4's `sk_hermes_drain_checkpoint()`, calling `vm_interpret()` on the target identity
    VM with the payload text — the console line itself, unwrapped, since kernel-Hermes's
    payload is raw bytes, not a FORTH string literal.
    
    **Real finding, found live verifying this exact task, not folded into the send-side
    cutover's own write-up:** kernel-Hermes's drain only ever runs as a side effect of
    `vm_interpret()` being called ON the target VM (`vm_core.c`'s own outermost-interpret
    checkpoint hook, task 3.4) — it has nothing to do with the old FORTH `MSG-TICK` word. Task
    3.8's target was Hera herself, who is *always* being interpreted (the interactive REPL
    loop), so her own queue drained as a side effect of ordinary console activity. Task 3.9's
    target is a WIREBIND identity's own `~user` VM — a passive receiver nothing else drives.
    Confirmed live: `sk_hermes_send_one()` returned `0` (queued correctly) but the payload sat
    undelivered indefinitely — no crash, no error, matching `FABRIC-3.5.md §XXXV.0`'s own named
    failure signature exactly. Root cause: `sk_repl_idle()`'s existing round-robin pump
    (`SK_MSG_PUMP_BATCH`/`g_msg_pump_cursor`, built for the *old* FORTH `MSG-TICK` system) only
    gives a live VM an interpret tick if `vm_find_word(vm, "MSG-TICK", 8)` finds an
    ACL-allowed entry — a VM minted with `MINT_PERSONALITY_STD79_LOCKDOWN` (FORTH-79/83
    standard words only, no `common:messaging.4th`) never has that word, so the pump silently
    skipped it forever, and kernel-Hermes's drain checkpoint never got a chance to fire.
    **Fixed in the same pump loop** (`repl.c`, `sk_repl_idle()`): an unconditional, direct call
    to `sk_hermes_drain_checkpoint((VM*)ent.vm_ptr)` for every live non-Hera VM visited each
    beat, independent of the MSG-TICK/VM-EXEC dispatch below it — no FORTH word required,
    since kernel-Hermes's own drain is a plain C function designed to be called standalone
    (`kernel_main.c`'s own boot self-test already calls it exactly this way). Verified live:
    relay now delivers, typically one idle beat after the send (bounded, not instant — the
    pump runs once per idle cycle, matching the existing MSG-TICK dispatch's own latency
    characteristic, not a regression this task introduced).
    
    **Second real finding, also worth recording though it did not block this task:** the
    `FABRIC-3.md §XXX` (2026-09-15) documented `USE`/BINDSTEP crash ("interactive `USE
    <identity>` on a freshly-attached identity halts the kernel outright") **did not
    reproduce** in this task's own live testing, on any of the three architectures, across
    several repeated `USE rajames` calls on a freshly-WIREBIND-attached identity. Not chased
    further — either something in the intervening 3.6-series work fixed it as a side effect,
    or the specific reproduction conditions differ from this task's own test shape. Recorded
    as a finding, not claimed as a fix (no root-cause investigation was done here).
    
    **Also found and fixed along the way, its own separate commit
    (`ac4d431`, already landed before this task's own final acceptance run):**
    `zuse_root_pubkey_known` never got set after a same-session fresh genesis mint, silently
    blocking WIREBIND for every identity attach on any boot that reset `artemis.img`
    fresh — which is exactly what this project's own acceptance convention does before every
    run. See that commit's own message for the full account; not restated here.
    
    **A prompt-format change was requested mid-task (Captain Bob), attempted, found to
    regress live (`[zuse@Artemis]` instead of the intended reformat), and reverted back to the
    original, unmodified `[zuse@Hera]`-style format before this task's final acceptance run** —
    confirmed by every log cited above. The reformat needs real new state (separating "which
    identity" from "which machine," last discussed as needing changes across 8+ save/restore
    call sites in `mama_forth_words.c`) and is deliberately deferred to its own task rather
    than interleaved with this one's live debugging. Not tracked as a numbered task yet —
    pick a number when it's actually scheped.
    
  • 3.10 — Stage D: ELEVATE-REQUEST (type 8), the same one-type-per-task check as 3.8/3.9. Split out as its own explicit item (previously bundled into 3.9's own text without a checkbox) per this document's own carry-forward-discipline rule (§XXXI.2).

  • 3.11 — Phase 3 gate. No FORTH-owned live message types remain; Stage B evidence holds across a full boot on all three ISAs. Do not start Phase 4 until this passes.

Phase 4 — Category B strip

Only after every live type is cut over. Hermes leaves is_fleet_foundation and kernel_main.c here, not earlier (§XXXIV.4).

  • 4.1 — Strip capsules/hermes/init.4th.
  • 4.2 — Strip the FORTH routing table and slot-3 pairing convention.
  • 4.3 — Strip capsules/common/messaging.4th.
  • 4.4 — Remove Hermes from is_fleet_foundation and its birth from kernel_main.c.

Phase 5 — Close-out

  • 5.1 — Isabelle/HOL pass. Deliverable is the restated boundary, explicitly including §XXV.4's coverage loss — not a green build (§XXV.3).
  • [~] 5.2 — Documentation sweep, grepping by exclusion (§XXVIII.3). CLAUDE.md's four errors: DONE 2026-09-19 (§XLIV), pointer to this document added. Outstanding: MANIFEST.md (rides the strip, §XXII.5), the TRIPOD.md/0.1 contradiction, item 42's K-qualification, the superseded subsystem docs' update-or-archive call, and item 45 (experiments/bare_metal/README.md block framing).
  • 5.3 — make sbom; check Created: and DocumentName (§XXVI.2).
  • 5.4 — LITHOS_VERSION = 2.1.0; engine VERSION per §XXX.6's rule; roadmap table gains its 2.1.0 line in two live places (§XXVIII.2).
  • 5.5 — Resolve the stray refs/heads/v2.0.1 and PR #1 (§XXVI.4). Investigate, do not delete.
  • 5.6 — Merge to master; tag v2.1.0.
  • 5.7 — Archival close of FABRIC-3.5.md (§XXVI.5) and of this document.

Findings log

Defects and surprises found while executing. Reported, not fixed (unless the task was to fix them). A finding that contradicts a FABRIC-3.5.md ruling must also be amended there.

2026-09-19, task 0.0 — shared disk/artemis.img confounds cross-ISA dict_hash comparison unless reset between runs. Not a code defect and does not contradict any FABRIC-3.5.md ruling — FABRIC-3.md §XXXV.2 already documents that the disk is deliberately shared across all three ISAs' qemu targets. But it means a same-order rerun of the three architectures will have run 2 and 3 silently take Artemis's resuming branch instead of its blank disk -- formatting branch, producing a different dict_hash for Artemis alone (Hera and Hermes are unaffected — neither touches the artdisk at birth). Worth carrying forward: any task in this document that checks dict_hash identity across architectures and involves Artemis should restore disk/artemis.img and disk/thumbdrives/zuse-thumb-ident.img to their committed blank state (git checkout --) before each architecture's run, or the check will fail on a confound rather than a real divergence.

2026-09-22, task 3.8 — a FORTH CREATE buffer address passed to a new C word must go through vm_ptr(), or it silently reads all-zero memory: no crash, no error, just a message that arrives and does nothing. sk_word_kh_blk_attach_send()'s first cut popped paddr (Artemis's own ATTACH-ACK-BUF address) and raw-cast it directly to a host const char * ((const char *)(uintptr_t)paddr) — .claude/CLAUDE.md's own "Important Conventions" already states stack values are VM-relative offsets (vaddr_t), not C pointers, and mama_forth_words.c's own VM-EXEC/VM-CALL words all translate a popped address the same way (vm_ptr(vm, (vaddr_t)caddr)) before touching it — this word didn't, and the mistake compiled clean, linked clean, and booted clean. Diagnosed with a temporary probe (reverted after capture, per memory feedback_revert_probes_after_capture): logs/20260922-101153/amd64/ and logs/20260922-101937/amd64/ (a second, redundant run of the same debug build, kept per the never-delete convention rather than for any independent evidence) both show DBG send: plen=25 n=25 buf=[] — correct length, empty content — followed by DBG drain-enter #2 payload= (empty) and a clean DBG drain-return. vm_interpret() on an empty string is a no-op: no error, no UNKNOWN WORD, BLK-ATTACH-ACK simply never ran, and the pending USB attach was silently never confirmed. Fixed by reading src via vm_ptr(vm, (vaddr_t)paddr) instead of a raw cast (repl.c's own comment on the fix cites the precedent directly). This is exactly FABRIC-3.5.md §XXXV.0's named failure signature ("things here fail without saying anything") — carrying the rule forward explicitly for whoever next writes a FORTH-facing C word: any popped stack value that names a FORTH-visible address goes through vm_ptr()/VM_ADDR(), never a raw pointer cast, unless the value was itself explicitly formatted as a raw host pointer by the pushing code (as repl.c's own existing dev-addr already legitimately is, built via %llu from a real uintptr_t at the VM-EXEC call site — the exception that makes the rule easy to misapply by analogy).

2026-09-22, task 3.8 — a separate, real hang, root cause not found; recorded as a genuine open item, not folded into the vm_ptr() finding above. The very first attempt to exercise the real send/drain path (logs/20260922-092154/amd64/, before the vm_ptr() bug above was even suspected) froze solid — serial log stopped growing entirely, QEMU pinned near 100% CPU, no further output ever, for over 40 minutes of wall time before being killed by hand. This happened before the empty-payload bug was diagnosed or fixed, so it is tempting to assume the same root cause — but the empty-payload runs (101153/101937) did not hang; they drained cleanly (if uselessly) and reached ok> on schedule. Something else froze that first boot, at approximately the same point in the boot sequence, and it was never reproduced again across every subsequent run this session (with or without the vm_ptr() fix). Per §XXXV.0's own standing warning, an unexplained freeze in a brand-new, live-for-the-first-time nested-drain code path (task 3.4's sk_hermes_drain_checkpoint() calling vm_interpret() on a VM that is itself mid-dispatch, exercised live for the first time by this exact task) is not something to write off as a fluke. Reported, not chased further here — logs/20260922-092154/ is kept as the sole evidence.

2026-09-22, found while starting task 3.9 — task 3.8's g_kh_blk_attach_buf is a real payload-aliasing defect, not a benign restatement of an existing hazard as that task's own write-up claimed. Discovered orienting for 3.9: booted amd64 with a second real identity drive ("bob") attached at boot alongside Zuse's own (QEMU_EXTRA at port 2, logs/20260922-111840/amd64/), which puts two real USB-MSC devices through sk_repl_idle()'s per-slot attach loop in one pass — two KH-BLK-ATTACH-SEND calls before either drains. The ledger confirms it: held=4096 (two admissions, 2×2048) at the first drain's own evidence print, not held=2048 as every single-device boot this session showed. Only one device's identity ever completed (Zuse: genesis minted...); S" bob" USE afterward returned USE: bob not found — bob's own WIREBIND pairing never happened.

Root cause (following the code, not re-deriving it live): g_kh_blk_attach_buf (repl.c) is one static buffer, and sk_hermes_send_one() stores payload_addr as a caller- owned pointer, not a copy. Two sends before either drain means both messages point at the same address — whichever send wrote last. Task 3.8's own write-up claim that this "carries the same single-buffer-reuse shape ATTACH-ACK-BUF itself already had... not a new hazard this introduces" is wrong, and is amended here rather than left standing. FORTH's own MSG-SEND had the identical pointer-aliasing shape, but never hit the window: MSG-TICK pumped from sk_repl_idle() itself, draining between attaches in the same loop that queues them. Kernel-Hermes drains at interpret checkpoints, which do not fire during that idle-loop pass at all — the cutover didn't just change how delivery happens, it changed when, and that's what opened a window FORTH's own design never had. Likely mechanism for why the second message produced no visible failure either (inference, not confirmed): both messages probably drained using the same (second-write) payload text, and the one carrying the wrong dev-addr most likely hit repl.c:246's found_slot == 0 || !pending early return — silent, no print, ahead of this task's own evidence line. Not chased to confirm, per the scope decision below.

Not fixed here. This is a defect in already-committed, closed task 3.8 code, found while scoping a different task — Captain Bob's Law: report, don't fix in passing. A real fix changes SkHermesMessage's own shape (task 2.1) to own its payload bytes rather than referencing a caller's pointer, which is bigger than a repl.c-local patch: it touches every existing sender (tasks 3.3/3.5/3.6/3.8) and needs its own three-ISA acceptance. logs/20260922-111840/amd64/ is kept as the reproduction — a second identity drive attached at boot is what exposes this; the default single-drive boot this whole document's other acceptance runs used never will. Task 3.9 is paused pending Captain Bob's decision on when this gets fixed.

2026-09-22, out-of-band request during task 3.9's own verification — console prompt format changed on Captain Bob's direct instruction, not a reshuffle task, no task number. The bracket line prefix (console.c's emit_prefix(), unchanged) used to read [<session-owner>@<active console/identity>] — e.g. [zuse@rajames] after USE rajames, always showing the authenticating superuser on the left regardless of which identity the console was actually redirected to. Corrected across several rounds of live clarification to: once the console is redirected into a WIREBIND identity's own console VM (console_get_vm_name() != "Hera"), show that same name on both sides — [rajames@rajames], not [zuse@rajames] — because WIREBIND births the console VM literally named after the identity (capsule_console_birth()), so the identity is that VM, not a separate label layered on top of it ("R.A. James is also a VM," Captain Bob's own reasoning). At the top level (still on Hera, nothing has redirected the console yet — e.g. a live WIREBIND attach announced but not yet USE'd into), the original zuse_session/capsule_wirebind_attached_username() logic is unchanged, since Hera's own name doesn't say who's driving her.

First attempt regressed live and was reverted before being carried into any acceptance run. An initial, more ambitious design (separate "identity" vs. "machine" tracked state, touching every console_set_vm_name() call site across BIRTH/RUN/VM-EXEC/VM-CALL/CONNECT-* in mama_forth_words.c) produced a wrong [zuse@Artemis] prompt live, because the machine segment only ever got updated on the way in to a temporary excursion (e.g. into Artemis's own context during a birth), never restored on the way back out for a "restore to a non-fleet-member name" case. Caught by advisor() before it reached any committed acceptance run; fully reverted (console.c, console.h, repl.c's registration all restored byte-for-byte to their prior committed state) rather than patched forward. The landed fix, below, needed none of that new state.

Landed fix: sk_console_user_prefix() alone (repl.c) — when console_get_vm_name() != "Hera", return it directly (emit_prefix() then prints it on both sides of the @, unchanged otherwise); else keep the original zuse_session/WIREBIND-username logic. Zero new state, zero other call sites touched. Verified live, interactively, on all three architectures (same mint → WIREBIND-attach → USE → typed console line shape as task 3.9's own acceptance): [zuse@Hera] at the top level and immediately after a live WIREBIND attach (before USE), [rajames@rajames] after USE rajames, Stage D relay (task 3.9) still fires correctly on top of it. Zero UNKNOWN WORD, dict_hash identical to every prior acceptance run in this document (unaffected — this is a display-only change). Logs: logs/20260922-133732/amd64/, logs/20260922-134049/aarch64/, logs/20260922-134638/riscv64/.

Separately raised, not yet actioned: background diagnostic console noise ([Hestia@Hestia]/[Hermes@Hermes] INFERENCE: Output validation failed warnings, HADES xhci error/warn lines) interleaves visibly with interactive console output, confirmed live via a QMP screendump Captain Bob asked for mid-task. Matches this project's own already-tracked, not-yet-started item (memory project_production_logging_cleanup_needed: console_println() overuse, much of it should be DEBUG-level). Captain Bob explicitly deferred this to after the prompt-format fix landed — genuinely not started, no task number assigned yet.


Out of scope, carried for visibility

Recorded in FABRIC-3.5.md, deliberately not part of this reshuffle. Listed so they are not absorbed by accident:

  • Item 43 — should a Stadium reservoir ever replenish? (§XL.6) A VM's send capacity declines monotonically today.
  • Items 23–26 — the FABRIC-3.5.md §XXXI gap-analysis follow-ups: re-home FABRIC-2 §17.4, consolidate the three reported-not-scheduled registries, decide FABRIC-2's five orphaned design items, reconcile FABRIC-0's seven opens.
  • src/*.c.bak — tracked-but-stale, reported in three places and actioned in none (§XXII.5).