FABRIC-3.5.md's provisional "design phase closed, not yet archival" header replaced with the real CLOSED/ARCHIVAL form per its own §XXVI.5 spec, naming v2.1.0 -- prior status headers kept underneath, not deleted. FABRIC-3.6.md gets the same treatment: a CLOSED/ARCHIVAL banner above the START HERE section, which stays as historical record rather than being removed. Neither closure triggers the FABRIC-0 -> -1 -> -2 -> -3 carry-forward chain (both are standalone topic documents) and neither touches FABRIC-3.md, which remains open for its own topic. The Tripod/kernel reshuffle is complete: Hermes moved into the kernel as kernel-Hermes, the Tripod is Hera/Artemis/Hestia, tagged v2.1.0. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1813 lines
142 KiB
Markdown
1813 lines
142 KiB
Markdown
# FABRIC-3.6.md — the Tripod/kernel reshuffle: execution log
|
||
|
||
**Status: CLOSED/ARCHIVAL as of 2026-09-22, at tag `v2.1.0`.** All five phases (0–5) closed —
|
||
Category A strip, Hestia relocation/birth, kernel-Hermes build (allocator, arena, message
|
||
types), Stage-cutover of every FORTH-owned message type, the Category B strip (Hermes VM +
|
||
`messaging.4th` removed), the Isabelle pass, the documentation sweep, `make sbom`, the version
|
||
bump, and the merge to `master` + tag. **This document is closed; the reshuffle it tracked is
|
||
done, not merely planned.** Per §XXVI.5 (cited here, ruled in `FABRIC-3.5.md`, also closed at
|
||
this same tag): closing this document hands nothing to a successor and does not touch
|
||
`FABRIC-3.md`, which remains open and authoritative for its own topic (bare metal boot).
|
||
|
||
**The `START HERE` section immediately below is kept as historical record of how this
|
||
execution began — it describes a session about to start work, not the current state.** Do not
|
||
follow its "first action: task 0.0" instruction; every task it points at is closed. If future
|
||
work touches this fleet again (a Phase 8 PKI elevation entrypoint, further hardware bring-up,
|
||
etc.), it gets its own new document, not a reopening of this one — matching the discipline
|
||
`FABRIC-3.5.md`'s own close just followed.
|
||
|
||
---
|
||
|
||
> ## START HERE — session handoff (historical — reshuffle complete, see status above)
|
||
>
|
||
> **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.**
|
||
|
||
- [x] **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`.
|
||
|
||
- [x] **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.
|
||
- [x] **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.
|
||
- [x] **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).
|
||
- [x] **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.
|
||
- [x] **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.
|
||
- [x] **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.
|
||
- [x] **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).
|
||
- [x] **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.
|
||
|
||
- [x] **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.
|
||
- [x] **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).
|
||
- [x] **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.
|
||
- [x] **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.
|
||
- [x] **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.
|
||
- [x] **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.
|
||
- [x] **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.
|
||
- [x] **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.**
|
||
|
||
- [x] **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.
|
||
- [x] **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.
|
||
- [x] **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.
|
||
- [x] **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.
|
||
- [x] **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.
|
||
- [x] **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.
|
||
- [x] **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).
|
||
- [x] **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 — **COMPLETE, gate (3.11) passed 2026-09-22**
|
||
|
||
**Blockers cleared 2026-09-21 (§XLV).** 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.** Never triggered across
|
||
any of tasks 3.1–3.10's own acceptance runs. See task 3.11's own entry below for the closing
|
||
account.
|
||
|
||
- [x] **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.
|
||
- [x] **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.
|
||
- [x] **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.
|
||
- [x] **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.
|
||
|
||
- [x] **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.
|
||
- [x] **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.
|
||
- [x] **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).
|
||
- [x] **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).
|
||
- [x] **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).
|
||
- [x] **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).
|
||
- [x] **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.
|
||
- [x] **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.
|
||
- [x] **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.
|
||
- [x] **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.
|
||
- [x] **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).
|
||
2026-09-22 · **Closed.** Final acceptance: `logs/20260922-165122/amd64/`,
|
||
`logs/20260922-165333/aarch64/`, `logs/20260922-165635/riscv64/` — 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**
|
||
(`0x25052a7b8d02b81e` Hera / `0x7011e083c3259321` Hermes / `0x26317a2a0d37e4b0` Hestia /
|
||
`0xc14d3370d46fcf08` Artemis) — changed from every prior acceptance run in this document
|
||
(`word_count` 529→530, one new C word registered) but consistent across all three
|
||
architectures, matching §XXXIV.6's own rule that a *changing* hash is expected and only
|
||
*divergence* between architectures is the stop condition.
|
||
|
||
**Reachability verified live before writing any code, per `advisor()`'s own steer** — not
|
||
grep alone (`FABRIC-3.5.md §XXXV.0`/trap #2): `grep -rn "SEND-ELEVATE" capsules/
|
||
experiments/ docs/` found zero callers of `SEND-ELEVATE-REQUEST` anywhere (matching the
|
||
`init-l8-*` zero-reference precedent, §XXII.2), so this task genuinely has no automatic
|
||
trigger — but `FIND SEND-ELEVATE-REQUEST`, `FIND ELEVATE-GRANT`, and `FIND CH-REQUEST`
|
||
(correct usage: `FIND` parses the next word from input directly, not `S" ..." FIND` — the
|
||
latter faulted live, corrected mid-check) all resolved to real, nonzero dictionary
|
||
addresses on a live Hera boot. **Correction to a prior finding, found live in the course of
|
||
this check:** task 3.8's own write-up claimed "Hera's own pre-existing inability to load
|
||
`common:messaging.4th`" — false. `capsules/init.4th` (Hera's own `MAMA_INIT` capsule)
|
||
loads `common:messaging.4th` at line 21, and `SEND-ELEVATE-REQUEST` lives and works in her
|
||
dictionary right now. Task 3.8's own actual scope (only the reply leg was real FORTH
|
||
messaging traffic) stands unaffected by this correction — the false claim was about *why*,
|
||
not about *what* task 3.8 did. `SEND-ELEVATE-REQUEST` is a real, complete, directly-callable
|
||
entrypoint (H.5/H.8's own design: any VM wanting word-ACL elevation calls it with its own
|
||
pubkey and the target word name) with no current automatic trigger, not dead/unreachable
|
||
code — verified live by calling it directly from Hera's own console on all three
|
||
architectures: `0 0 0 0 S" DUP" SEND-ELEVATE-REQUEST` (a deliberately-invalid all-zero
|
||
pubkey, so `ELEVATE-GRANT` correctly refuses without granting anything real — the check is
|
||
that the full pipeline runs, not that a grant succeeds; confirmed `DUP` itself unaffected
|
||
afterward). Evidence line fires immediately, no idle-pump delay needed here (unlike task
|
||
3.9's WIREBIND-identity target): Hera is both sender and receiver in this test, and she is
|
||
always being actively interpreted, matching task 3.8's own Hera-target precedent exactly.
|
||
|
||
**What actually cut over.** `SEND-ELEVATE-REQUEST` (`messaging.4th`, block 5040) no longer
|
||
ends in `ELEVATE-REQUEST MY-CH-ID @ 0 ELEVATE-REQ-BUF ELEVATE-REQ-LEN @ CH-REQUEST`; it now
|
||
ends in `ELEVATE-REQ-BUF ELEVATE-REQ-LEN @ KH-ELEVATE-SEND DROP`, a new C word (`repl.c`)
|
||
wrapping `sk_hermes_send_one()`, registered unconditionally for every VM
|
||
(`sk_repl_register_words()`, same site `KH-BLK-ATTACH-SEND` already uses). `from`/`to` are
|
||
derived from the calling VM and `sk_get_mama_vm()` directly in C, never taken from the
|
||
stack — a real correctness improvement over `CH-REQUEST`'s own "initiator-only gate...
|
||
refuses if `from` doesn't match `MY-CH-ID`", which existed only because a caller *could*
|
||
pass the wrong stack value; deriving `from` from the C-level calling VM's own identity makes
|
||
that spoof structurally impossible rather than merely gated. `SK_HERMES_MSG_TYPE_ELEVATE_
|
||
REQUEST` (`kernel_hermes.h`) deliberately reuses `ELEVATE-REQUEST`'s own value (8), same
|
||
partition-rule reasoning as tasks 3.8/3.9. Delivery is unchanged task 3.4 machinery
|
||
(`sk_hermes_drain_checkpoint()` → `vm_interpret()` on Hera with the payload text, which
|
||
calls `ELEVATE-GRANT` exactly as before). No new static-buffer lifetime caveat this time —
|
||
the payload-aliasing fix landed before this task started, so `g_kh_elevate_buf` needed no
|
||
`g_kh_blk_attach_buf`/`g_kh_console_cmd_buf`-style deferred-defect note.
|
||
|
||
**Found, not fixed (Captain Bob's Law): `CH-REQUEST` (`messaging.4th` block 5031) is now
|
||
dead code.** It had exactly one real caller (`SEND-ELEVATE-REQUEST`'s own old tail, just
|
||
removed); nothing else in `capsules/` calls it. Left in place, not removed — orphaning it is
|
||
a side effect of this cutover, not this task's own scope to clean up.
|
||
- [x] **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.**
|
||
2026-09-22 · **Passes.** Tasks 3.8 (`BLK-ATTACH-EVENT`), 3.9 (`CONSOLE-CMD-EVENT`), and 3.10
|
||
(`ELEVATE-REQUEST`) — messaging.4th's entire live/reserved type space except the two
|
||
already-dead sentinels (`MSG-NACKED`/`MSG-DELIVERED`, never live to begin with per task
|
||
0.1/0.4) and `PAUSE-EVENT`/`RESUME-EVENT`/`KILL-EVENT` (Category A/dead, same source) — are
|
||
all real kernel-Hermes cutovers now, each independently verified live on all three
|
||
architectures with real evidence. Stage B's own conservation evidence (`stadium_conserved()`
|
||
holding across every cutover's own acceptance run, task 2.7's four-term form) has held
|
||
through every one of tasks 3.8/3.9/3.10's acceptance logs, cited in their own entries above.
|
||
**Phase 4 may begin.**
|
||
|
||
## Phase 4 — Category B strip — **COMPLETE 2026-09-22**
|
||
|
||
Only after every live type is cut over. **Hermes leaves `is_fleet_foundation` and
|
||
`kernel_main.c` here, not earlier** (§XXXIV.4). Per FABRIC-3.5.md §XXXIV.3, Stage E is one
|
||
atomic change set — 4.1–4.4 are checked off together, not independently bootable
|
||
intermediate states.
|
||
|
||
- [x] **4.1** — Strip `capsules/hermes/init.4th`. Deleted. `is_fleet_foundation` no longer
|
||
needs it — nothing else referenced this file.
|
||
- [x] **4.2** — Strip the FORTH routing table and slot-3 pairing convention.
|
||
`capsule_wirebind.c`'s slot-3 `VM-NAME-REG` registration call removed (already dead
|
||
since task 3.9's cutover moved name resolution to `sk_console_user_prefix()`).
|
||
- [x] **4.3** — Strip `capsules/common/messaging.4th`. Deleted. Two C-side dependents found
|
||
by full-repo grep (not just `capsules/`) and fixed as part of this same commit:
|
||
`capsule_console.c`'s `CONSOLE_IDENTITY_SRC` (dropped its `S" common:messaging.4th"
|
||
EXEC` / `MSG-CD-INIT` lines, kept `Block 4997`) and `capsule_mint.c`'s
|
||
`MINT_DEFAULT_PERSONALITY` (same two lines dropped, kept `WELCOME`). Every capsule that
|
||
loaded the file (`capsules/init.4th` block 2049, `capsules/artemis/init.4th` block 4854,
|
||
`capsules/hestia/init.4th` block 4987) had its load line + `MSG-CD-INIT` +
|
||
`MY-CH-ID`/`CH-ADD-MBR` setup removed; Artemis and Hestia kept their own
|
||
`STARTUP-BANNER`/`WELCOME` tails. `capsules/doe-campaign.4th` (`SETUP-HERMES`/
|
||
`SETUP-VMS`/`PHASE1-DOE`/`CD-TICK`/`SMOKE-CAMPAIGN`, blocks 4060–4064) reduced to
|
||
Artemis-only per explicit instruction (fold `SETUP-HERMES` strip into this same commit).
|
||
`capsules/zuse-eligibility.4th`'s header comment updated to point at the kernel-Hermes
|
||
drain checkpoint instead of the removed `MSG-DELIVER` path.
|
||
- [x] **4.4** — Remove Hermes from `is_fleet_foundation` and its birth from `kernel_main.c`.
|
||
`capsule_birth.c`'s `is_fleet_foundation` OR-chain drops the `"Hermes"` prefix check.
|
||
`kernel_main.c`: Hermes's own birth block removed, the Artemis-birth comment referencing
|
||
it fixed, `sk_vm_switch_signal_register(hermes_entry.vm_id)` + its guard removed, and the
|
||
"Kernel-Hermes common-channel fleet self-test" fixed to drop its unconditional
|
||
Hermes-liveness requirement (was about to false-FAIL post-strip). The drain and
|
||
channel-open-policy self-tests already had graceful `SKIPPED (Hermes not live)` /
|
||
`SKIPPED (Hera/Hermes not available)` fallbacks and needed no change — confirmed live
|
||
below, not just by inspection.
|
||
|
||
2026-09-22 · **Closed.** `mkcapsule --lint capsules/` clean: 36 files (was 38 — the two
|
||
Category B deletions), 0 violations. Full three-architecture acceptance, each run the
|
||
same sequence: clean boot → self-test block → hot-attach bob's blank identity thumbdrive
|
||
via QMP → `MINT` → detach/reattach to trigger `WIREBIND` → `USE rajames` → `1 2 + .`
|
||
relay round-trip.
|
||
- amd64: `logs/20260922-181141/amd64/qemu-amd64-20260922-181141.log`
|
||
- aarch64: `logs/20260922-181753/aarch64/qemu-aarch64-20260922-181753.log`
|
||
- riscv64: `logs/20260922-182136/riscv64/qemu-riscv64-20260922-182136.log`
|
||
|
||
Zero `UNKNOWN WORD` on all three. `PARITY:` lines carry only Hera/Hestia/Artemis — Hermes
|
||
genuinely absent, not just silent. **`dict_hash` identical across all three
|
||
architectures for every VM**: `0x0a0ad2497d33c3c0` (Hera, `MAMA_INIT`), `0xb1256603
|
||
f848e2b9` (Hestia), `0xc791409ac1715690` (Artemis) — matching §XXXIV.6's rule (a
|
||
*changing* hash is expected; cross-architecture *divergence* is the stop condition; none
|
||
occurred). Self-tests on all three: "Kernel-Hermes common-channel fleet self-test: PASS"
|
||
(confirming the 4.4 fix), "Kernel-Hermes drain self-test: SKIPPED (Hermes not live)" and
|
||
"Kernel-Hermes channel-open policy self-test: SKIPPED (Hera/Hermes not available)" both
|
||
graceful as predicted, every other self-test PASS. `mint → attach → USE → relay` verified
|
||
live and byte-identical across all three architectures: prompt correctly shows
|
||
`[rajames@rajames]` (task-3.9-era prompt fix holding), single `ok`/`ok>` cycle (CRLF fix
|
||
holding), `1 2 + .` → `3` delivered via `Kernel-Hermes CONSOLE-CMD-EVENT (real, Stage D)`
|
||
with `stadium_conserved(self)=true` on every run.
|
||
|
||
**Found, not fixed (Captain Bob's Law): `capsules/zuse-eligibility.4th`'s only policy
|
||
word, `ELEVATE-GRANT`, is now unreachable — and this file is still loaded at boot
|
||
(`capsules/init.4th:18`, `S" zuse-eligibility.4th" EXEC`), not dead/orphaned code.** Task
|
||
3.10 made `SEND-ELEVATE-REQUEST` (which called `KH-ELEVATE-SEND`, `repl.c`) live in
|
||
`messaging.4th`; this task deletes that entire file. `grep -rn "SEND-ELEVATE-REQUEST"
|
||
capsules/ src/ include/` after the strip: zero hits outside comments — the word no longer
|
||
exists anywhere, so nothing can reach `KH-ELEVATE-SEND` or `ELEVATE-GRANT` any more. This
|
||
is not stray dead code left over from a cutover; it is **Phase 8 PKI's own named-open-item
|
||
elevation entrypoint** (see this document's own ACL section / `docs/03-architecture/
|
||
word-acl/DESIGN.md` Phase 8) deleted as collateral of the Category B strip. A live capsule
|
||
is now shipping with a policy word nothing can call. Left as-is per this task's own scope
|
||
— **this needs a decision from Captain Bob before Phase 5 close-out, not just a cleanup
|
||
note**: either give `SEND-ELEVATE-REQUEST` a new home now (e.g. inline in
|
||
`zuse-eligibility.4th` itself, which already depends on nothing from the deleted file), or
|
||
explicitly accept that Phase 8 PKI work will need to build its own entrypoint from
|
||
scratch rather than resuming this one.
|
||
|
||
**Decision (Captain Bob, 2026-09-22): leave it unreachable.** `ELEVATE-GRANT` and
|
||
`KH-ELEVATE-SEND` stay in place, uncalled, until Phase 8 PKI begins; that milestone will
|
||
build its own entrypoint rather than resuming `SEND-ELEVATE-REQUEST`. No further action
|
||
this phase.
|
||
|
||
## Phase 5 — Close-out — **COMPLETE, tag `v2.1.0` cut 2026-09-22**
|
||
|
||
**Note on commit granularity:** tasks 5.1–5.4 landed as one commit (`a4ad14a`), a deliberate
|
||
deviation from the per-task-commit discipline this document has otherwise used since Phase 0.
|
||
Reason: all four tasks' write-ups accumulated in `FABRIC-3.6.md` across one continuous work
|
||
pass before any commit was made, so splitting the commit would have meant hand-splitting one
|
||
file's hunks after the fact rather than committing as each task actually finished — the
|
||
opposite of what per-task commits are for. Tasks 5.5–5.7 return to one-commit-per-task.
|
||
|
||
- [x] **5.1** — Isabelle/HOL pass. **Deliverable is the restated boundary**, explicitly
|
||
including §XXV.4's coverage loss — *not* a green build (§XXV.3).
|
||
2026-09-22 · **Closed.** `/home/rajames/CLionProjects/Isabelle2011-1/bin/isabelle build
|
||
-o threads=1 -D proof/` (memory-safe single-threaded flag per this session's own standing
|
||
rule) — exit 0, "Finished StarForth" in 41s (warm heap cache), zero `FAILED`/`error`/
|
||
`sorry`/`warning` across all 52 theories. This is the green build, included for
|
||
completeness, but per `proof/COVERAGE.md`'s own framing it isn't the deliverable.
|
||
|
||
**The restated boundary.** `proof/COVERAGE.md` (generated 2026-08-14, commit `346c793`,
|
||
unchanged since) scopes the entire suite to `src/word_source/*.c` — the 34 vendored
|
||
FORTH-word-implementation files, the 7 physics loops, Q48.16, and the 5 word-level ACL
|
||
policy theories. **None of Phases 0–4 of this reshuffle touched anything in that scope.**
|
||
Every file this reshuffle changed lives in `src/starkernel/` (kernel-only C, `kernel_
|
||
hermes.c`/`.h`, `repl.c`, `capsule_*.c`, `kernel_main.c`) or `capsules/*.4th` (Hera/
|
||
Artemis/Hestia init capsules, the deleted `messaging.4th`/`hermes/init.4th`) — both
|
||
entirely outside `proof/`'s own stated boundary from the start. **The clean pass above is
|
||
not regression evidence — nothing in `proof/`'s scope changed, so there was nothing this
|
||
reshuffle could have regressed there in the first place**; the 41s runtime is a warm
|
||
heap-cache replay, not a fresh re-verification, so it shouldn't be read as re-checking
|
||
anything either. It is included for completeness (task 5.1 asks for the build), not as
|
||
support for a no-regression claim the boundary argument above already makes on its own.
|
||
Separately, and for the same reason: none of the reshuffle's *own* new code
|
||
(kernel-Hermes's C implementation, the WIREBIND/MINT identity flow, the capsule
|
||
birth/switch-signal changes) is formally verified — none of that was ever in scope for
|
||
this suite, and still isn't. The suite's already-documented gaps (block-window
|
||
cache, TIB scan-shape variants beyond `forth_parse_word`, vocabulary-chain file-scope
|
||
statics, raw-pointer DictEntry navigation, the three divergent L8 mode-selector
|
||
representations — full list in `proof/COVERAGE.md`'s own "structurally NOT provable"
|
||
section) are unaffected by this reshuffle either way, since none of them intersect
|
||
`src/starkernel/` or `capsules/` at all. **Net effect on the boundary: none — restated,
|
||
not moved.**
|
||
- [x] **5.2** — Documentation sweep, grepping **by exclusion** (§XXVIII.3).
|
||
**CLAUDE.md's four errors: DONE 2026-09-19 (§XLIV), pointer to this document added.**
|
||
2026-09-22 · **Closed.** Every outstanding item resolved or explicitly settled:
|
||
|
||
- **`.claude/CLAUDE.md` itself, further updated** (beyond the four-errors pass): now
|
||
that Phase 4 has actually landed, its WIP banner claiming "no reshuffle code has been
|
||
written yet" was false — corrected to reflect Phases 0–4 complete. The "current fleet"
|
||
Tripod description (Hera/Hermes/Artemis, plus a stray "two Hermes instances" claim
|
||
that was stale even before this reshuffle) updated to Hera/Artemis/Hestia. Mama
|
||
dictionary word count corrected `453` → `530` (current `PARITY:M7.1a word_count=`),
|
||
with a caveat to verify against a live line rather than cite the number statically.
|
||
- **`MANIFEST.md` rides the strip (§XXII.5) — done.** `hermes/init.4th`'s full
|
||
block-by-block table (4100–4153) replaced with a deletion note (detail recoverable
|
||
from git history, not duplicated); `common/messaging.4th`'s deletion recorded in
|
||
"Deleted capsules"; `init.4th` block 2049, `doe-campaign.4th` blocks 4060–4062, and
|
||
`hestia/init.4th` block 4987 (its `COMMON-CH` join no longer exists) all updated to
|
||
match the post-strip live files, verified by direct read. Former Hermes range marked
|
||
UNASSIGNED. `mkcapsule --manifest capsules` reports zero conflicts, 36 capsules,
|
||
after the edit.
|
||
- **The `TRIPOD.md`/0.1 contradiction (punch item 26) — confirmed already resolved.**
|
||
`.claude/TRIPOD.md`'s "Immediate Goal" section carries its own 2026-08-13 correction
|
||
header stating the contradiction (Hera auto-spawning Hermes/Artemis at boot) was fixed
|
||
then, before the document was even marked superseded (2026-08-15). No further action
|
||
needed; the punch item was asking to confirm a fix already in place, not make one.
|
||
- **Item 42 (K-qualification) — checked against the currently-living corpus, clean; full
|
||
retroactive sweep of the closed archival corpus explicitly declined.** `.claude/
|
||
CLAUDE.md` and `FABRIC-3.6.md` (the two documents this reshuffle actually reads/writes
|
||
day to day) both already use qualified terms throughout (`fleet K`, `stadium_
|
||
conserved()`, `quota conservation`) with zero ambiguous bare-`K` hits. The closed
|
||
archival documents (`FABRIC-0.md`/`FABRIC-1.md`/`FABRIC-2.md`/`FABRIC-3.5.md`) carry
|
||
110 combined raw `\bK\b` regex hits — the large majority already correctly qualified
|
||
in context (the regex can't distinguish "fleet K" from a genuinely bare "K"), scattered
|
||
across ~20,000 combined lines of design record. A full manual audit is disproportionate
|
||
to the residual risk and contradicts this series' own stated discipline (`FABRIC-3.5.md`
|
||
§XLII.2: "design rulings are never restated... corrections are recorded, not silently
|
||
rewritten into archival history"). Declined, not silently dropped — recorded here as
|
||
the explicit decision.
|
||
- **The superseded subsystem docs' update-or-archive call — settled: archive, do not
|
||
update.** Recorded directly in `.claude/CLAUDE.md`'s own superseded-docs note: `TRIPOD.
|
||
md`/`HERMES.md`/`ARTEMIS.md`/`CONSOLE.md` stay frozen at their 2026-08-15 state.
|
||
`HERMES.md` in particular now describes a VM that no longer exists in this codebase at
|
||
all (not merely a superseded design) — noted explicitly rather than silently left
|
||
stale, but not rewritten, since rewriting a historical record to track a codebase it no
|
||
longer describes defeats its purpose as a record.
|
||
- **Item 45 — `experiments/bare_metal/README.md` block framing — was real, fixed.**
|
||
Line 149 still said "each block can hold up to 1024 bytes of source text," the same
|
||
wrong-kind-of-rule `FABRIC-3.5.md` §XLIV.1 found and corrected in `.claude/CLAUDE.md`
|
||
on 2026-09-19 (8 lines of 128 characters is 1024 bytes and still fails — the real
|
||
limit is per-line/per-line-count, not a byte total). Corrected to the real rule (64
|
||
chars × 16 lines, block range `[2048,5120)`, verify with `mkcapsule --lint`, not
|
||
`wc -c`) — this file had never received that correction.
|
||
- [x] **5.3** — `make sbom`; check `Created:` and `DocumentName` (§XXVI.2).
|
||
2026-09-22 · **Closed.** `syft` was not installed on this machine — installed to
|
||
`~/.local/bin` (user, not root; explicit approval obtained first). First regen fixed
|
||
`Created:` (`2026-07-24` → live) but `DocumentName` stayed wrong (`StarForth`) — traced to
|
||
a genuine Makefile bug, not a stale artifact: `Makefile:747` hardcodes `--source-name
|
||
StarForth` in the `sbom` target. Fixed (`--source-name LithosAnanke`, explicit approval
|
||
obtained first) and regenerated. Both fields now correct: `DocumentName: LithosAnanke`,
|
||
`Created: 2026-09-22T23:21:56Z`.
|
||
|
||
**Found and fixed, an ordering bug against 5.4:** this regen ran before the version bump
|
||
(5.4, below), so `sbom.spdx`'s `PackageVersion` came out `3.1.0` — correct at the moment
|
||
of generation, but stale the moment 5.4 landed. Root cause is a second, independent
|
||
finding: the hosted `Makefile` (which `sbom` actually runs from — `Makefile.starkernel`
|
||
is the kernel build, a separate file) carries its **own** hardcoded `VERSION ?= 3.1.0`
|
||
at `Makefile:17`, never mentioned by `.claude/CLAUDE.md`'s own "two independently tracked
|
||
version strings" note, which only documents `Makefile.starkernel`'s pair. Two hardcoded
|
||
copies of the same semantic value (the embedded StarForth engine version) in two build
|
||
files is real duplication-drift risk, not intentional independence like `LITHOS_VERSION`/
|
||
`VERSION`'s documented split. Bumped `Makefile:17` to `3.2.0` to match 5.4's engine-version
|
||
rule and re-ran `make sbom`: `PackageVersion: 3.2.0`, `Created` refreshed again. `sbom.spdx`/
|
||
`sbom.spdx.json`, the `Makefile:747` `--source-name` fix, and this `Makefile:17` fix all
|
||
ride this phase's commit.
|
||
- [x] **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).
|
||
2026-09-22 · **Closed.** `Makefile.starkernel`: `LITHOS_VERSION` `2.0.0` → `2.1.0`;
|
||
engine `VERSION` `3.1.0` → `3.2.0` (minor per §XXX.6's rule — dictionary-visible changes
|
||
only: `BIRTH`'s registration widened, the messaging layer replaced by kernel-Hermes,
|
||
`PLOT`/`FB-*` relocated to Hestia; no FORTH-79 word semantics changed). The stale
|
||
version-comment block in `Makefile.starkernel` (which still said "even major = LTS" —
|
||
backwards relative to the ratified §XXX.1 odd/even rule — and had no `v2.1.0` entry)
|
||
replaced with the real ladder. `docs/lithosananke/ROADMAP.md`'s own "Release Versioning
|
||
Policy" section (the second of the two live places) was still the full retired
|
||
2026-08-28 policy, not just a stale table — replaced with the ratified §XXX policy text,
|
||
the old text kept struck-through in a `<details>` block for traceability, and the
|
||
board-by-board rollout section's "each cut is an LTS point-in-time cut" line corrected
|
||
(v2.x is the even/working line under the ratified policy, not LTS).
|
||
|
||
Verified the bump flows through the real build: `include/version.h` shows
|
||
`STARFORTH_VERSION "3.2.0"` / `LITHOS_VERSION "2.1.0"` after a clean amd64 rebuild.
|
||
Full three-architecture acceptance re-run (version bump is a `Makefile.starkernel`
|
||
change, so it gets the same three-arch boot as any kernel change): amd64
|
||
(`logs/20260922-192400/amd64/`) and aarch64 (`logs/20260922-192931/aarch64/`) both clean
|
||
on the first pass — zero `UNKNOWN WORD`, banner correctly reads `LithosAnanke v2.1.0` /
|
||
`StarForth Version 3.2.0`. **riscv64's first pass, `logs/20260922-193504/riscv64/` —
|
||
FAILED, committed as the audit record of the failure, not a fourth passing run** — hit a
|
||
`virtio_blk: vblk_io timed out` during the boot-time Zuse genesis mint (fence marker
|
||
write failed, not persistent); reached `[zuse@Hera] ok>` regardless, but flagged rather
|
||
than accepted on a degraded run. **riscv64's accepted run is the immediate retry,
|
||
`logs/20260922-194020/riscv64/`**, no code change in between: clean pass, genesis mint
|
||
succeeded, zero `UNKNOWN WORD`. Assessed as transient host I/O contention under TCG
|
||
(single qemu process, no
|
||
leaked processes, host memory tight but not exhausted at the time) rather than a
|
||
regression — this change touched only `Makefile.starkernel` version strings/comments and
|
||
two markdown files, no runtime code — and the clean retry on identical disk state/binary
|
||
confirms it. Reported here rather than silently retried-and-forgotten, per Captain Bob's
|
||
Law.
|
||
- [x] **5.5** — Resolve the stray `refs/heads/v2.0.1` and PR #1 (§XXVI.4). **Investigate, do
|
||
not delete.**
|
||
2026-09-22 · **Closed — investigated, nothing destructive done, nothing needs Captain
|
||
Bob's sign-off to become safe.**
|
||
- **`refs/heads/v2.0.1`** — `git log origin/master..origin/v2.0.1` is empty (0 commits);
|
||
`git log origin/v2.0.1..origin/master` shows 128. `origin/v2.0.1`'s tip
|
||
(`fcba528`) is the exact merge-base of the two branches — it is a strict ancestor of
|
||
current `master`, fully subsumed, not a divergent line. Its own tip commit message
|
||
confirms this by construction: "FABRIC-3.md: Task 1 closed -- master fast-forwarded to
|
||
v2.0.1, verified" (2026-09-04). Stale branch-ref hygiene debt, exactly as flagged —
|
||
safe to delete, **not deleted**, since that needs Captain Bob's explicit go-ahead per
|
||
Captain Bob's Law, not this task's own scope.
|
||
- **PR #1** — queried via the Gitea API directly (`gh` isn't installed and wouldn't talk
|
||
to this Gitea instance anyway). Not a stray reference at all: it's a PR from **this
|
||
same branch** (`claude/starshipos-tripod-kernel-reshuffle-itbjns`) against `master`,
|
||
`state: closed`, `merged: false`, `merged_at: null`, opened `2026-09-18T21:28:06Z` and
|
||
closed `2026-09-18T21:28:26Z` — 20 seconds later, before any of this reshuffle's actual
|
||
work existed on the branch. Already closed, already not merged, blocks nothing. Nothing
|
||
to resolve beyond recording what it is.
|
||
- [x] **5.6** — Merge to `master`; tag `v2.1.0`.
|
||
2026-09-22 · **Closed, explicit approval obtained first (this is a shared-branch
|
||
operation on the sole production line — the plan authorizing it is not a standing
|
||
grant, per `.claude/CLAUDE.md`'s own scope rule).** `master` was a strict ancestor of
|
||
this branch (`git merge-base --is-ancestor origin/master HEAD` — true, 0 commits unique
|
||
to `master`, 85 unique to this branch), so this was a clean fast-forward, not a merge
|
||
commit: `git push origin HEAD:refs/heads/master` (`e56974e..8edbd3c`), never checking out
|
||
`master` locally, so the uncommitted `FABRIC-3.md` WIP on this branch's working tree was
|
||
never touched. Tagged `v2.1.0` (annotated, `8edbd3c`) and pushed. `refs/heads/v2.0.1`
|
||
(task 5.5's finding — fully merged, safe to delete) also deleted this same pass, explicit
|
||
approval obtained first.
|
||
- [x] **5.7** — Archival close of `FABRIC-3.5.md` (§XXVI.5) and of this document.
|
||
2026-09-22 · **Closed.** `FABRIC-3.5.md`'s provisional "design phase closed, not yet
|
||
archival" header replaced with the real CLOSED/ARCHIVAL form, naming `v2.1.0`, per its
|
||
own §XXVI.5 spec — prior status headers kept underneath, not deleted, matching this
|
||
series' own no-silent-rewrite rule. This document (`FABRIC-3.6.md`) closed the same way:
|
||
a CLOSED/ARCHIVAL status banner added above the `START HERE` section, which is kept as
|
||
historical record of how the session began rather than removed. Neither closure triggers
|
||
the `FABRIC-0 → -1 → -2 → -3` carry-forward chain (both documents are standalone topic
|
||
documents, not part of it) and neither touches `FABRIC-3.md`, which stays open for its
|
||
own topic. **Phase 5, and the Tripod/kernel reshuffle itself, are complete.**
|
||
|
||
---
|
||
|
||
## 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 — CLOSED.** Captain Bob's own instruction, after 3.9/prompt-format both landed:
|
||
"finish the work" before cleanup/3.10. Fixed exactly as scoped above — `SkHermesMessage`
|
||
(`kernel_hermes.h`) gained an inline `payload_buf[SK_HERMES_CHUNK_MAX_PAYLOAD]` field;
|
||
`sk_hermes_send_one()` (`kernel_hermes.c`, the single funnel every sender/`sk_hermes_publish()`
|
||
already goes through) now `memcpy()`s the caller's payload into it and points `payload_addr` at
|
||
that copy, instead of storing the caller's own pointer. No sender or reader call site needed to
|
||
change — every existing reader (`sk_hermes_drain_checkpoint()`, `sk_hermes_reassemble()`) already
|
||
only ever reads through `payload_addr`, which still points at valid bytes of the same length,
|
||
now message-owned. `g_kh_blk_attach_buf`/`g_kh_console_cmd_buf` (`repl.c`, tasks 3.8/3.9) no
|
||
longer need to survive past their own send call — their doc comments corrected accordingly;
|
||
left `static` as a harmless convenience, not changed to stack-local (out of this fix's own
|
||
scope).
|
||
|
||
**Verified the original bug is actually gone, not just no-longer-observed.** Reproduced the exact
|
||
original scenario — `S" Captain Bob"...MINT`'d a real `rajames` identity sequentially first
|
||
(so both devices are genuinely eligible, not one blank/one real), then rebooted with `rajames`'s
|
||
now-minted drive attached at launch alongside Zuse's own (`QEMU_EXTRA`, port 2), reproducing two
|
||
simultaneous `BLK-ATTACH-EVENT` sends in one idle-loop pass before either drains
|
||
(`logs/20260922-140405/amd64/`: `ledger held=4096`, the same two-admission signature the original
|
||
bug's own log showed). **Before this fix, that scenario left `rajames`'s own WIREBIND pairing
|
||
silently never happening** (`FABRIC-3.6.md`'s own earlier finding: `S" bob" USE` returned `USE:
|
||
bob not found`). **After this fix, in the identical scenario, `WIREBIND: rajames attached and
|
||
ready` fires, `USE rajames` succeeds, and the Stage D relay still works on top of it** — confirmed
|
||
live, interactively, in that same log. `dict_hash` for Artemis differs from this document's other
|
||
acceptance runs in that one log only, expected and already-documented (task 0.0's own finding:
|
||
`disk/artemis.img` was deliberately left unreset across the two boots this reproduction needed,
|
||
so Artemis took the `resuming` branch, not `blank -- formatting`) — not a cross-architecture
|
||
divergence, since this reproduction was amd64-only by design.
|
||
|
||
Standard three-ISA acceptance (single-drive boot, disks reset before each arch, matching every
|
||
other task in this document): `logs/20260922-140634/amd64/`, `logs/20260922-140908/aarch64/`,
|
||
`logs/20260922-141236/riscv64/` — all three reach `[zuse@Hera] ok>`, zero `UNKNOWN WORD`,
|
||
`dict_hash` identical across all three and matching every prior acceptance run in this document
|
||
exactly (unaffected — the fix changes message storage, not dictionary content). Both `USE
|
||
rajames` (task 3.9's earlier finding that the documented `FABRIC-3.md §XXX` USE/BINDSTEP crash
|
||
did not reproduce, confirmed again here) and the Stage D relay verified working on all three.
|
||
|
||
**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.
|
||
|
||
**2026-09-22 — CLOSED**, after the task 3.8 payload-aliasing fix landed. The noise turned out to
|
||
be exactly three sources, found by tracing each visible line back to its actual call site rather
|
||
than auditing `console_println()` broadly:
|
||
|
||
1. **`INFERENCE: Output validation failed, ignoring results`** (`vm_runtime.c`, kernel; `vm_time.c`,
|
||
hosted mirror) — already `log_message(LOG_WARN, ...)`, level-gated, just visible at the default
|
||
runtime `LOG_WARN` level (`kernel_main.c` sets it there after POST). Fires routinely during
|
||
normal operation with the runtime self-recovering every time (the message says so). Downgraded
|
||
to `LOG_INFO` in both files (kept in sync, same duplicated-logic pattern this codebase already
|
||
uses elsewhere for the hosted/kernel split).
|
||
2. **`xhci: CSW status = FAILED`** (was `LOG_ERROR`) and **`xhci: unit not ready -- retrying TEST
|
||
UNIT READY`** (was `LOG_WARN`), both `xhci.c` — also already level-gated, same visibility
|
||
reason. `xhci.c`'s own existing comment right there confirms these are "a fresh SCSI target's
|
||
standard first-command UNIT ATTENTION behavior, not a driver defect" — the bounded retry
|
||
resolves it routinely. Downgraded both to `LOG_INFO`. The genuine terminal failure ("`xhci: unit
|
||
still not ready -- giving up`", after `XHCI_BOT_TUR_MAX_RETRIES`) was left at `LOG_ERROR`,
|
||
unchanged — that one is a real failure, not routine noise.
|
||
3. **`Stadium: dispatch cell=... behaviour=...`** (`stadium.c`'s `stadium_dispatch()`) — the one
|
||
genuine defect, not just a severity judgment call: an unconditional `console_puts()`/
|
||
`console_println()` sequence with **no level gating at all**, printing on every single Stadium
|
||
dispatch regardless of log level — confirmed live, interleaving visibly with ordinary
|
||
interactive console sessions (a live QMP `screendump` Captain Bob asked for caught it directly,
|
||
`logs/20260922-134049/aarch64/`'s own screendump before this fix). Rewritten to route through
|
||
`log_message(LOG_DEBUG, ...)`, one call per `StadiumBehaviour` case, matching every other
|
||
diagnostic trace in this codebase — silent at the default level, available with
|
||
`--log-level=debug`.
|
||
|
||
No sender/reader/call-site changes needed anywhere else — this was purely a severity/level
|
||
question for two sources and a genuine gating gap for the third, not an API or behavior change.
|
||
Verified live: a fresh screendump after the fix (`logs/20260922-142449/amd64/`) shows a clean
|
||
interactive session with none of the three noise sources present, confirmed against the exact
|
||
same mint→WIREBIND→`USE`→relay sequence that produced the noisy screendump earlier. Standard
|
||
three-ISA acceptance clean: `logs/20260922-142449/amd64/`, `logs/20260922-142835/aarch64/`,
|
||
`logs/20260922-143229/riscv64/` — zero `UNKNOWN WORD`, `dict_hash` identical across all three and
|
||
matching every prior acceptance run in this document (unaffected — display-only change). Also
|
||
confirmed the hosted build (`make`, no `ARCH=`) still compiles clean after the `vm_time.c` change.
|
||
|
||
**2026-09-22, real defect found and fixed while investigating a separate complaint (Captain Bob:
|
||
the interactive Stage D exchange "looks nothing like a normal single-VM session" — extra `ok`/
|
||
`ok>` cycles with no command typed in between).** Root cause traced via a live QMP `screendump`,
|
||
not guessed at: `sk_console_readline()` (`repl.c`) breaks its read loop on **either** `'\r'` or
|
||
`'\n'` as an independent terminator — a line sent as both bytes (a real terminal in CRLF mode, or
|
||
any scripted sender writing `"\r\n"`, which every QMP/socat-driven verification session in this
|
||
whole document has been doing) submits **twice**: once for the real line on the first byte, then
|
||
an immediate empty-line submit on the second byte the very next time the function is called, each
|
||
producing its own `" ok"`. Confirmed this pre-dates Stage D entirely — the very first `ok`/`ok>`
|
||
pair after `MINT` (Hera's own console, not `rajames`'s, not an async relay at all) already showed
|
||
doubled. **Not a Stage D async-relay artifact as first suspected** — every prior screendump and
|
||
every prior acceptance log in this document that shows doubled `ok`s is this same bug, present
|
||
since before this document opened.
|
||
|
||
Fixed at the source (`sk_console_readline()`, `repl.c`): after breaking on `'\r'` or `'\n'`, a
|
||
single non-blocking peek for the paired byte (the other one of the two) — discard it if present,
|
||
push it back via the existing `g_console_pending_key` single-slot buffer (the same mechanism
|
||
`sk_console_key_available()` already uses for "peeked but not this call's to consume") if it
|
||
turns out to be unrelated input for the next line. A byte that hasn't arrived yet is not waited
|
||
for, matching every other non-blocking read in this function.
|
||
|
||
Verified live: a fresh screendump after the fix (`logs/20260922-162219/amd64/`) shows exactly one
|
||
`ok`/`ok>` pair per typed line, matching normal single-VM interaction, with Stage D's own async
|
||
delivery timing unchanged (Captain Bob's own explicit choice: clean up the prompt noise, keep the
|
||
real delivery latency — the evidence line and result still land on a later idle-pump beat, just
|
||
without the doubled `ok` cluttering the wait). Standard three-ISA acceptance also clean on all
|
||
three architectures: `logs/20260922-162219/amd64/`, `logs/20260922-162531/aarch64/`,
|
||
`logs/20260922-162932/riscv64/` — zero `UNKNOWN WORD`, `dict_hash` identical to every prior run in
|
||
this document (unaffected).
|
||
|
||
---
|
||
|
||
## 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).
|