Direct instruction while planning the next goal (mint zuse.img thumbdrive -> (Zuse)ok>): Hera must give birth only by message request, no coupling outside messages, applying universally to user VMs too, not just Zuse -- a sharper commitment to the standing "nothing is done until it's messaging" criterion (D.1) for this specific next flow, since messaging and storage are both genuinely live now. Traced before capturing anything further: detection ownership moving to a running Artemis VM instance is confirmed a real architecture change from what HOTPLUG/WIREBIND already built/scoped (both currently have Hera deciding and acting directly, not just detecting). Found a real, load-bearing prerequisite this direction depends on: Artemis is not persistently alive today -- every boot log this session confirms she's birthed for her own migration self-test then explicitly killed before the REPL reaches ok>. Capture only, no design commitments yet -- WIREBIND/MSGMIGRATE both need their own reconciliation pass against this.
2551 lines
184 KiB
Markdown
2551 lines
184 KiB
Markdown
# FABRIC-3.md — the Stadium, continued again
|
||
|
||
**Status:** Living working document, opened 2026-08-25 as the successor to `FABRIC-2.md`
|
||
(now closed/archival — see its own header). This document does not repeat `FABRIC-2.md`'s
|
||
design argument or history; it restates only outcomes, with pointers back to the section
|
||
that derived them. Read `FABRIC.md` for the original "why," `FABRIC-2.md` for everything
|
||
derived through 2026-08-25, this document for what's left as of that date onward.
|
||
|
||
**Provenance.** Everything in Section A below is a full, non-sampled carry-forward of every
|
||
open (`- [ ]`) item in `FABRIC-2.md` as of 2026-08-25 — 51 items, confirmed by
|
||
`grep -c '^- \[ \]' FABRIC-2.md`, none dropped (Section A itself holds 49: the other 2,
|
||
`FABRIC-2.md` §F.3's own two checkbox lines, were pure summaries cross-referencing items
|
||
already listed individually elsewhere — 4.4s/1.11/4.3/§17.4 and 5.1/ACL-RWT re-measurement,
|
||
both of which are carried forward as their own individual items above — not distinct content,
|
||
confirmed by diffing item text programmatically before writing this document, not assumed).
|
||
Extracted mechanically (a script pulling each checkbox item's own text, stopping at the first
|
||
blank line rather than the next checkbox, to avoid pulling in unrelated already-resolved
|
||
narrative that happened to sit between two open items in the source document) and spot-checked
|
||
against the original. Item numbers/labels are
|
||
carried forward unchanged, for traceability — this is not a renumbering or a re-prioritization.
|
||
Section groupings match `FABRIC-2.md`'s own (documentation debt, xHCI WRITE(10), Milestone
|
||
3–9 punch lists, etc.) — items are relocated, not reorganized.
|
||
|
||
**How to use this document going forward.** New findings, new punch-list items, and new
|
||
decisions get added here, not to `FABRIC-2.md`. Follow the same discipline `FABRIC-2.md`
|
||
§(intro) established for how work gets picked up, closed, and recorded.
|
||
|
||
---
|
||
|
||
## A. Carried forward from FABRIC-2.md (51 items, all still open as of 2026-08-25)
|
||
|
||
### From FABRIC-2.md §A — Blocked or scoped, not started
|
||
|
||
- [ ] **1.11 — Dirty-event granularity.** Leaning region-based. Blocked on item 4.3 — settled
|
||
as part of the console migration, not speculatively before it. *Refs (FABRIC.md):* §17.5,
|
||
§23.2, §23.4 #1.
|
||
|
||
- [ ] **4.3 — Console.** Umbrella item; settles 1.11 as part of the work. Nearly everything
|
||
under it (4.3.1–4.3.7f, 4.4–4.4ac) is done — the parent stays open only because 4.4s below
|
||
is still blocked and nothing has formally closed the umbrella. *Refs (FABRIC.md):* §17.5,
|
||
§27.
|
||
|
||
- [ ] **4.4s — `(user)` prompt segment.** Scoped, blocked, not started. Extends 4.4's prompt
|
||
format. *Refs (FABRIC.md):* §27.8, 4.4.
|
||
|
||
- [ ] **5.1 — Re-run the DoE on the new substrate.** A green POST suite is not evidence that
|
||
determinism holds under the Stadium migration — needs its own campaign. Not started.
|
||
|
||
### From FABRIC-2.md §D — Design questions still genuinely open
|
||
|
||
- [ ] **§17.4 — the framebuffer utility's internal heat/decay dynamics are undesigned.**
|
||
Explicitly "Open, deferred": not a Stadium patron, but what physics (if any) governs it
|
||
internally was never designed. Not blocking anything. Blocked on item 1.11 specifically
|
||
(dirty-event granularity), not "the framebuffer work" in general — see `FABRIC-2.md` §D's
|
||
own 2026-08-13 refinement of this item before assuming it's ripe.
|
||
|
||
### From FABRIC-2.md §E — Documentation debt
|
||
|
||
- [x] **Already done — stale carry-forward, closed 2026-08-26.** This item was never marked
|
||
complete when the actual work concluded. `FABRIC-2.md` Sections M through T (2026-08-19 to
|
||
2026-08-21) already re-ran this measurement — and in the process found the original
|
||
"ACL-RWT" name itself was wrong: the Rolling-Window-of-Truth mechanism it was named after
|
||
was dead code, removed 2026-07-08 (`ACL-RECHECK-RW` was never reachable — `acl_recheck()`
|
||
only ever looks up the 11-char `ACL-RECHECK`, never the 14-char `-RW` variant). What the
|
||
original campaign actually measured was the live `ACL-TTL` mechanism under a misleading
|
||
name. **Final, accepted result (`FABRIC-2.md` §T): +0.0603% ACL-TTL enforcement overhead,
|
||
architecture-independent (identical across amd64/aarch64/riscv64), fully deterministic
|
||
(CV=0.000%)** — see also memory `project_acl_ttl_overhead_final.md`. No new campaign run
|
||
here; this entry corrects the bookkeeping, not the measurement.
|
||
|
||
### From FABRIC-2.md §J — Maintainability sweep (2026-08-18)
|
||
|
||
- [ ] `docs/lithosananke/ROADMAP.md` and `M7.1.md` — stale `Branch: lithosananke` (no such
|
||
branch exists post-split), `M7.1.md`'s "Status: Design Complete" (shipped and live, not
|
||
just designed), `ROADMAP.md`'s self-contradiction (M8 marked OBSOLETE in one place, still
|
||
a live success criterion in another), and its stale "AHCI driver" claim for M9 (real
|
||
implementation is `virtio_blk.c`) — not fixed, flagged.
|
||
|
||
- [ ] Top-level `ROADMAP.md` (StarForth-era, "Phase 0 Complete... Phase 1 Starting," dated
|
||
2025-12-14) — badly stale, no historical/superseded banner to warn a reader. Not fixed.
|
||
|
||
- [ ] `docs/03-architecture/word-acl/DESIGN.md` says ACL Phase 7 (LithosAnanke kernel parity)
|
||
is still "remaining" — direct contradiction with `.claude/CLAUDE.md`, which states Phase 7
|
||
is independently verified complete. Not fixed.
|
||
|
||
- [x] Confirmed accurate, not stale (2026-08-26): `VM-FLEET-ATTRACTOR-DESIGN-20260705.md`'s
|
||
claim that `doe-campaign.4th` is "broken and being superseded." Live-ran `SMOKE-CAMPAIGN`
|
||
from the current capsule (amd64) — it completes without error and correctly conserves fleet
|
||
heat (`VM-PHYSICS: conserved=CONSERVED`, `fleet_heat_sum=65536`). Initially read that as
|
||
contradicting the doc's claim; **Captain Bob corrected this directly — it's broken.**
|
||
"Doesn't crash" and "runs" are not the same claim: the doc's actual argument is that the
|
||
capsule has no real controlled-experimental-factor mechanism (no manual heat-injection
|
||
point under the current design, so it cannot drive the fleet through controlled scenarios
|
||
the way a DoE campaign needs to), a methodological gap a clean execution trace doesn't
|
||
surface or disprove. Doc's claim stands; not touched.
|
||
|
||
- [x] Tracked (2026-08-26) — real proof-modeling gap, not just stale prose, so not fixed
|
||
here. Confirmed by reading `StarForth_Loop4_Pipeline.thy`'s own comment (lines 127–137):
|
||
`pipeline_metrics_state`'s `pm_last_accuracy_num`/`pm_last_accuracy_den` fraction pair
|
||
doesn't correspond to anything in the real C struct — `include/vm.h`'s
|
||
`PipelineGlobalMetrics` has a single `double last_checked_accuracy` field, no num/den pair
|
||
anywhere. The `.thy` file's own comment already scopes the real fix correctly: "a full
|
||
field-level pass over `pipeline_metrics_state` is its own separate task" — matches this
|
||
project's standing caution that each remaining Isabelle gap needs its own subsystem model,
|
||
not a documentation-sprint patch. The punch-list ask was tracking this outside the buried
|
||
`.thy` comment, which it now has here — the actual re-model stays unattempted, on purpose.
|
||
|
||
### From FABRIC-2.md §X, Milestone 2 — USB hardware stack
|
||
|
||
- [x] **CLOSED 2026-08-27 (FABRIC-3.md §F.3).** Decide and implement where the hotplug event
|
||
surfaces to the rest of the kernel. Re-checked directly against `sk_repl_idle()` and the
|
||
`bot_msc_attach_pending`/`bot_msc_detach_pending` doc comments in `xhci_driver.h`: Milestone
|
||
2h's flag-set-by-xHCI/flag-consumed-by-`sk_repl_idle()` pattern isn't literally a registered
|
||
callback, but its own doc comment confirms it was built deliberately to achieve the same
|
||
goal — *"keeps xhci.c decoupled from block_subsystem.c"* — and that decoupling is real,
|
||
live on all three arches. Closed as written; no further design work on this node itself.
|
||
|
||
- [x] **BUILT + VERIFIED 2026-08-28 (`FABRIC-3.md` §F.17).** CBW/data/CSW for SCSI WRITE(10),
|
||
a direct mirror of READ(10) with the data direction flipped. Verified live via a genuine
|
||
cold-reboot round-trip on all three architectures (amd64/aarch64/riscv64), not just a
|
||
same-session read. `blkio_usb.c` is read-write now; `read_only` flipped to `0`. Real hardware
|
||
(Milestone 8) remains untested — QEMU-only so far.
|
||
|
||
- [ ] Implement basic error/stall recovery (CSW failure status, endpoint stall clear) — at
|
||
minimum enough to not wedge the controller on a single bad transfer. **SCOPED 2026-08-27
|
||
(`FABRIC-3.md` §F.14)**: full BOT-spec recovery designed — new `STALL_ERROR` completion code,
|
||
new xHCI Reset Endpoint + Set TR Dequeue Pointer commands, `CLEAR_FEATURE(ENDPOINT_HALT)` via
|
||
existing control-transfer plumbing, escalating to a full Bulk-Only Mass Storage Reset on a
|
||
second stall, bounded via a new retry counter mirroring `bot_tur_retries`'s own precedent.
|
||
Real gap closed today: CSW status handling (PASS/FAILED/PHASE ERROR) already existed; nothing
|
||
distinguished a STALL specifically, and no recovery of any kind existed before this pass.
|
||
|
||
### From FABRIC-2.md §X, Milestone 3 — Block subsystem extensions
|
||
|
||
- [ ] Implement the CA-signed-cert verification path (Milestone 6 dependency — the cert chain
|
||
validator doesn't exist yet either). **Now confirmed a hard prerequisite of `WIREBIND` too
|
||
(`FABRIC-3.md` §F.5), not just an M3 item in isolation. SCOPED 2026-08-27 (§F.7)**: format
|
||
and trust root decided (Zuse-signed X.509/DER cert, not the offline capsule-PKI chain); the
|
||
actual DER signature-extraction code is new work, not yet built.
|
||
|
||
- [ ] Implement the first-touch allocation function: given a verified identity pubkey and a
|
||
requested block count, either read an existing range from the drive's map or claim a new
|
||
one at `g.total_user_lbn` and write it back. *(Single-block relocation itself — the
|
||
mechanism this would allocate ranges for — is done: `blk_subsys_relocate_block()`/
|
||
`RELOCATE-BLOCK`, `FABRIC-2.md`, commit `36d832f`. This item is about the identity→range
|
||
allocation that decides what to relocate blocks* into*, still unbuilt.)* **Scope clarified
|
||
2026-08-27 (`FABRIC-3.md` §F.6, decision 3): a user's pool is their entire thumbdrive by
|
||
default (never contested) plus any system-resident device blocks they additionally claim,
|
||
first-come-first-served — this function only governs that second, system-side extension,
|
||
not the drive itself.** **SCOPED 2026-08-27 (`FABRIC-3.md` §F.11)**: the "claim at
|
||
`g.total_user_lbn`" phrasing above is stale, predating `BMAPFMT`'s distributed-ownership
|
||
decision — claims are found by scanning Artemis's already-attached device's own `blk_meta_t`
|
||
records for unowned devblocks, not by extending a counter. Claims may be a scattered chain
|
||
(reusing `blk_meta_t`'s existing `prev_block`/`next_block`/`chain_length` fields), found via
|
||
a full linear scan each time, no cached index.
|
||
|
||
- [x] **SCOPED 2026-08-27 (FABRIC-3.md §F.4).** Design the on-drive block-map format (Section
|
||
U item 4). Resolved as: no separate table — repurpose the existing, fully-wired-but-
|
||
zero-callers `blk_meta_t`/`blk_get_meta()`/`blk_set_meta()` (`block_subsystem.c`), replacing
|
||
its stale POSIX-flavored ownership fields with a pubkey-fingerprint owner + a fast-deny ACL
|
||
bit + deliberate reserved slack. Field design done; the actual code edit is not.
|
||
|
||
- [x] **CLOSED 2026-08-27 (`FABRIC-3.md` §F.12) — already built, not a separate item.**
|
||
`blk_set_meta()`'s dirty-tracking already flows through the exact same `cache_writeback()`/
|
||
`blk_flush()` path ordinary block data uses; the whole devblock (data + metadata) already
|
||
gets written to the device, real and unstubbed. Blocked only on `WRITE(10)` itself, already
|
||
modeled in the graph — nothing else stands between this and working.
|
||
|
||
- [x] **CLOSED 2026-08-27 (`FABRIC-3.md` §F.13) — already built, not a separate item.**
|
||
`cache_load_devblock()` already unpacks and validates every devblock's metadata on load.
|
||
Tracing "on insertion" surfaced a real, separate, previously-unflagged conflict instead:
|
||
`homeblocks_sig_t` and the block-subsystem's own generic `'STFR'`/v2 header both wanted
|
||
devblock 0, which would leave a home-blocks drive permanently write-refused or overwrite its
|
||
own identity header. Fixed by relocating `homeblocks_sig_t` to devblock 1; `MINT` must also
|
||
run the ordinary format-confirm path at devblock 0.
|
||
|
||
- [ ] Design the migration state machine (Section U item 5) — states, transition triggers.
|
||
Session direction, 2026-08-25: **ACL manages *when* to relocate** (capacity pressure, or a
|
||
compudynamics heat/cold signal); migration itself is expected to be rare, not routine. The
|
||
`physics_hotwords_cache.c`-reuse question is settled differently than originally framed —
|
||
see this document's new §B below (Stadium unification), which reframes block/word placement
|
||
as a `compudynamics.c`-driven decision generically, not a `physics_hotwords_cache.c`
|
||
(`DictEntry*`-hardcoded) reuse question specifically.
|
||
|
||
- [x] **CLOSED 2026-08-27 (`FABRIC-3.md` §F.10), scoped alongside `DETACH`.** Unclean removal
|
||
runs the exact same VM-kill path `EJECT` (M5) uses, minus the flush step — skipped outright
|
||
since the device is confirmed gone by the time the existing `bot_msc_detach_pending` signal
|
||
fires, not attempted-and-ignored. Design done; the actual wiring into `sk_repl_idle()`'s
|
||
existing detach handling is not yet written.
|
||
|
||
### From FABRIC-2.md §X, Milestone 4 — Drive/credential security
|
||
|
||
- [x] **Designed (2026-08-26), not yet implemented — Phase 8 kickoff.** Mirrors two existing
|
||
precedents exactly: `CAPSULE_MAGIC_PACK`'s bit-packed magic (`include/starkernel/capsule.h`)
|
||
and `blk_volume_meta_t`'s magic+version+fields+pad-to-4096 structural convention
|
||
(`include/block_subsystem.h`). Lives at the first 4KiB devblock of the GPT metadata
|
||
partition (the ~1GB partition decided 2026-08-22) — the header format doesn't depend on the
|
||
still-missing GPT parser; it's just what gets written starting at that partition's first
|
||
devblock once something can locate it. Deliberately narrow: identifies/authenticates the
|
||
drive only, does **not** invent the block-map or credential/cert formats (both separate,
|
||
still-open items below) — reserves offset/size pointers to where they'll live instead of
|
||
embedding them.
|
||
|
||
```c
|
||
#define HOMEBLOCKS_SIG_MAGIC 0x4248414CULL /* 'LAHB' -- LithosAnanke Home Blocks,
|
||
* same little-endian ASCII packing as
|
||
* CAPSULE_DESC_MAGIC's 'CAPS' */
|
||
#define HOMEBLOCKS_SIG_VERSION_0 0
|
||
|
||
#define HOMEBLOCKS_SIG_PACK(ver) \
|
||
(HOMEBLOCKS_SIG_MAGIC | ((uint64_t)(ver) << 32))
|
||
#define HOMEBLOCKS_SIG_GET_MAGIC(m) ((uint32_t)((m) & 0xFFFFFFFFULL))
|
||
#define HOMEBLOCKS_SIG_GET_VERSION(m) ((uint8_t)(((m) >> 32) & 0xFF))
|
||
|
||
typedef struct {
|
||
uint64_t magic; /* HOMEBLOCKS_SIG_PACK(...) */
|
||
uint8_t drive_uuid[16]; /* unique per-mint instance id -- Phase 8 mints multiple
|
||
* distinct drives, needs something to tell them apart */
|
||
uint64_t minted_time_ns;
|
||
uint64_t metadata_devblocks; /* size of this GPT metadata partition, in 4KiB devblocks
|
||
* -- sanity/bounds check against the GPT entry once a
|
||
* parser exists */
|
||
uint32_t cert_offset; /* devblock offset within this partition where the
|
||
* CA-signed cert blob starts; 0 = not yet minted */
|
||
uint32_t cert_devblocks;
|
||
uint32_t blockmap_offset; /* devblock offset where the block-map (Milestone 3,
|
||
* format still undesigned) starts */
|
||
uint32_t blockmap_devblocks;
|
||
uint64_t hdr_crc; /* REAL from day one, not a placeholder like
|
||
* blk_volume_meta_t's "unused yet" hdr_crc -- this
|
||
* header's whole job is gating a warn-and-refuse
|
||
* security check below, so the crc has to actually work */
|
||
uint8_t _pad[4096 - 64]; /* pad to one devblock; 64 = sum of the fields above */
|
||
} homeblocks_sig_t;
|
||
```
|
||
|
||
`cert_offset`/`cert_devblocks` are 0 on first design pass — the real CA hasn't been
|
||
generated yet (Milestone 6), so this reserves the *shape* of where a cert will attach
|
||
without committing to a cert format that doesn't exist. Same reasoning for
|
||
`blockmap_offset`/`blockmap_devblocks` against Milestone 3's still-open block-map design.
|
||
**Implemented (2026-08-26):** `include/starkernel/homeblocks_sig.h` — `homeblocks_sig_t` +
|
||
`HOMEBLOCKS_SIG_PACK`/`_GET_MAGIC`/`_GET_VERSION` macros, a C99 compile-time size assertion
|
||
(same discipline `stadium.h`'s own header-size checks use), and the identical field layout
|
||
shown above. Verified standalone: `sizeof(homeblocks_sig_t) == 4096`, compiles clean under
|
||
`-std=c99 -Wall -Wextra -Werror`. Not yet consumed by any code — nothing in the block
|
||
subsystem or xHCI driver reads or writes it yet, so no functional kernel change and no
|
||
3-arch acceptance boot needed for this step; that starts with the signature-check
|
||
implementation, the next punch-list item below.
|
||
|
||
- [x] **Implemented (2026-08-26): the check function itself, real and complete — not yet
|
||
wired to any write path.** `include/starkernel/homeblocks_sig.h` +
|
||
`src/starkernel/homeblocks_sig.c`: `homeblocks_sig_check(dev, sig_start_fblock, out_sig)`
|
||
reads the 4 consecutive 1KB `blkio` forth-blocks the 4KB header spans, verifies magic →
|
||
version → CRC-64 in order, returns one of `HOMEBLOCKS_SIG_OK`/`_BLANK`/`_BAD_VERSION`/
|
||
`_BAD_CRC`/`_READ_ERROR`. Reuses `block_subsystem.c`'s existing CRC-64/ISO
|
||
(`compute_crc64`, previously `static`/file-local, now exposed) rather than a second CRC
|
||
implementation — same algorithm already proven via per-block checksums. Takes the header's
|
||
starting block as a plain parameter rather than resolving it internally: this function
|
||
verifies a signature given a location; finding that location (GPT-partition-relative,
|
||
once a parser exists) stays the caller's job, not invented here.
|
||
|
||
**Verified against the actual shipped code**, not a reimplementation: a standalone host
|
||
test links the real `homeblocks_sig.c` against a fake in-memory `blkio_dev` and exercises
|
||
all four outcomes — blank media → `BLANK`, a correctly-minted header → `OK` (round-trips
|
||
`drive_uuid`/`minted_time_ns` correctly), a flipped CRC → `BAD_CRC`, an unrecognized
|
||
version → `BAD_VERSION`. All four pass. A full QEMU-hotplug live test isn't proportionate
|
||
yet — nothing calls this function from the live kernel path (deliberately; wiring it into
|
||
the attach path is the next item below), so a live boot check has nothing to exercise.
|
||
Clean zero-warning compile and clean boot on all three architectures confirms no
|
||
build/link regression from exposing `compute_crc64` and adding the new source file to
|
||
every kernel build.
|
||
|
||
- [x] **Implemented (2026-08-26): the "warn" half, live and wired.** Wired into
|
||
`sk_repl_idle()`'s USB hotplug attach handler (`repl.c`), right between
|
||
`blkio_usb_open_msc()` succeeding and `blk_subsys_attach_device()` — calls
|
||
`homeblocks_sig_check(&usb_blk_dev, 0, &sig)` and logs a distinct message per outcome
|
||
(recognized / blank-or-foreign / bad-version / bad-crc / read-error).
|
||
|
||
**The "refuse" half is deliberately not implemented — there is nothing real to gate yet.**
|
||
`blkio_usb.c` has no SCSI `WRITE(10)` support at all (Milestone 2's biggest open item), so
|
||
there is no write path today to refuse; the only thing attach currently enables is
|
||
read-only access, which is also the general-purpose USB block I/O path this repo already
|
||
relies on for unrelated testing, not exclusively a home-blocks identity workflow. Refusing
|
||
attach on blank media would have broken that legitimate use without protecting anything
|
||
real — building that gate now would be enforcement with no live consumer, the same
|
||
"don't build ahead of a real caller" reasoning `EXPIRE`'s deferral used. Refuse belongs on
|
||
the write path once `WRITE(10)` exists to give it something to gate against.
|
||
|
||
`sig_start_fblock` is hardcoded to `0` at the call site — correct for today's unpartitioned
|
||
raw test/real media (no GPT parser exists yet), explicitly flagged in the code comment as
|
||
the one place that will need to change to a real GPT-partition-relative lookup once that
|
||
parser lands, isolated from `homeblocks_sig.c`'s own location-agnostic check logic.
|
||
|
||
**Verified live**, not just compiled: hot-attached `disk/usb-thumbdrive-test.img` (blank
|
||
media, no `LAHB` magic) through a running amd64 instance's QMP socket (`blockdev-add` +
|
||
`device_add usb-storage,bus=xhci0.0`) — captured exactly 4 real TUR+READ10 BOT cycles
|
||
(matching the header's 4 forth-block span) followed by `xhci: USB drive not recognized
|
||
(blank or foreign media) -- read-only general use only`, then normal attach completing
|
||
successfully afterward (no regression — no `MSC block-subsystem attach failed`).
|
||
Conservation intact, no panic. Clean zero-warning compile and clean boot on all three
|
||
architectures.
|
||
|
||
- [x] **Resolved (2026-08-26): reuse `acl_pinned` directly, no new flag needed.** The open
|
||
question assumed credential data "isn't a dictionary word" — but the design already chosen
|
||
for it (`ZUSE-CERT-LO`/`HI`, `ACL-CA-KEY-LO`/`HI`) are `CONSTANT` words, i.e. real
|
||
`DictEntry`s. `acl_pinned`'s enforcement is more general than assumed: `vm_create_word()`
|
||
(`dictionary_management.c:394-404`) — the single choke point every word-defining construct
|
||
goes through — unconditionally refuses to let *anything* shadow a pinned name ("Pin is
|
||
permanent: no word may shadow a pinned entry — ever, by anyone"), a real general
|
||
redefinition guard, not just an ACL-mode-change lock. `zuse.4th`'s `ACL-ZUSE-BOOT` already
|
||
pins both cert constants today — the mechanism is already wired for this.
|
||
|
||
**Real gap surfaced along the way, not yet fixed:** `ACL-ZUSE-BOOT` self-activates and pins
|
||
`ZUSE-CERT-LO`/`HI` unconditionally on *every* boot — before any legitimate minting step
|
||
could ever run, permanently locking in the `0` placeholder on the very first boot. The boot
|
||
sequence needs to distinguish "already minted, pin it" from "not yet minted, don't pin yet"
|
||
before minting can work at all.
|
||
|
||
**This directly shaped the next design pass (2026-08-26, Captain Bob):** a dedicated
|
||
`disk/zuse.img` QEMU test thumbdrive, "bleachable" back to pristine/unminted state for
|
||
repeated first-boot testing; a one-time first-boot mint-Zuse flow (mint → write real cert →
|
||
blow the fuse → *then* pin, resolving the gap above); a separate, ongoing `S" name" MINT`
|
||
word for an authenticated Zuse session to mint additional regular users; and a Zuse recovery
|
||
path, explicitly flagged as unresolved and risky if rushed — not to be designed casually,
|
||
since the earlier "no software recovery, mint a new one" rule existed specifically to close
|
||
a hole a careless recovery path could reopen.
|
||
|
||
**First piece implemented (2026-08-26): the `zuse.img` bleach mechanism.**
|
||
`disk/zuse.img` (64MB, blank, matching the existing USB-fixture convention exactly — see
|
||
`disk/README.md`) + `scripts/bleach_zuse_img.sh` (idempotent reset back to blank). Verified
|
||
live: hot-attached via QMP (same method as the warn-and-refuse verification above) — reads
|
||
back as `HOMEBLOCKS_SIG_BLANK` (`xhci: USB drive not recognized (blank or foreign media)`),
|
||
correctly simulating a genuine first boot. Deliberately flat/raw, not GPT-partitioned,
|
||
matching `homeblocks_sig_check()`'s current `sig_start_fblock=0` call site — both move to a
|
||
real GPT-partition-relative offset together once a parser exists, not attempted here. No
|
||
kernel code touched this step (host-side test tooling only), so no 3-arch acceptance boot
|
||
needed — single live amd64 QMP-hotplug confirmation is the right verification tier.
|
||
|
||
**Correction (Captain Bob, 2026-08-26): drives are not bound to any particular size.**
|
||
64MB was only ever this fixture's arbitrary test-convenience size, matching
|
||
`usb-thumbdrive-test.img`'s existing BOT-driver-testing precedent — never a real-world
|
||
constraint. Audited for anywhere this might have implied otherwise: the actual format
|
||
(`homeblocks_sig_t`) already carries its own `metadata_devblocks` field and hardcodes no
|
||
size anywhere, confirmed clean. `scripts/bleach_zuse_img.sh` gained a `--size-mb` override
|
||
so this was never a hidden assumption baked into the tooling either. The earlier
|
||
`project_usb_thumbdrive_gpt_layout.md` memory's "16GB reference size" phrasing (already
|
||
hedged as tentative, but risked reading as a target) corrected to state the point
|
||
explicitly — the GPT layout's design point is the *proportions* (small metadata partition,
|
||
everything else block storage), not any absolute size.
|
||
|
||
**Still open, not attempted:** the mint-then-pin boot-sequence fix itself, the `MINT` word,
|
||
and the Zuse recovery path.
|
||
|
||
**Correction, supersedes the "reuse `acl_pinned`" resolution above (2026-08-26): a pinned
|
||
`CONSTANT` is not actually tamper-proof.** `ACL-PIN`/`acl_pinned` only guards against
|
||
*redefinition* — `vm_create_word()`'s pin check blocks a second `: ZUSE-CERT-LO ... ;`, but
|
||
nothing stops `' ZUSE-CERT-LO >BODY !` from overwriting the same word's data field in place.
|
||
A `CONSTANT`'s value lives in its data field, so the earlier design left the cert mutable
|
||
from FORTH despite being "pinned." Found while starting the mint-then-pin boot-sequence fix
|
||
itself; fixing that gap came first since building a real mint flow on top of a tamperable
|
||
store would just re-open the hole later.
|
||
|
||
**Fixed (2026-08-26): moved cert storage out of the dictionary entirely.** New
|
||
`VM` struct fields (`include/vm.h`): `zuse_cert_lo`/`zuse_cert_hi` (the cert value) +
|
||
`zuse_cert_installed` (one-time fuse bit). New `vm_zuse_cert_install(vm, lo, hi)`
|
||
(`src/vm.c`) — C-only, no FORTH word wraps it, returns `-1` on a second call rather than
|
||
silently re-installing (a second call is a caller bug, not a runtime condition to recover
|
||
from). No FORTH store word exists or should exist for these fields, closing the `>BODY`
|
||
path structurally rather than by convention. Three new read-only C primitives
|
||
(`src/word_source/starforth_words.c`, same shape as the existing `HEARTBEAT-TICKS@`):
|
||
`ZUSE-CERT-LO@`, `ZUSE-CERT-HI@`, `ZUSE-CERT-INSTALLED?`. `capsules/zuse.4th`'s old
|
||
`ZUSE-CERT-LO`/`HI` `CONSTANT` words (and `ACL-ZUSE-BOOT`'s two now-pointless `ACL-PIN`
|
||
calls on them) deleted outright rather than left as dead/insecure scaffolding — `mkcapsule
|
||
--lint` clean (31/31) after the edit. `vm_zuse_cert_install()` has no caller yet: the real
|
||
mint flow still doesn't exist (Milestone 6 CA + the `MINT` word are both still open), and
|
||
calling it with a placeholder value would just be a stub wearing the shape of a fix — so
|
||
this stays an honest, complete slice (storage + read accessors) with the actual mint-then-
|
||
pin sequence still explicitly open, not faked. **Verified:** hosted `make` build clean,
|
||
zero warnings; clean boot to `ok>` on all three architectures (amd64/aarch64/riscv64),
|
||
Stadium conservation intact (43691/21845/65536) on all three, no panics or guest errors.
|
||
ACL is opt-in (`init.4th`'s `S" ACL.4th" EXEC` commented out by default) so the new words
|
||
weren't exercised live from the REPL this pass — compile/lint/boot verification only.
|
||
|
||
**MINT word design, picked up 2026-08-26.** Before scoping `MINT` itself, found a real
|
||
conflict with an existing, deliberate decision: `include/starkernel/ed25519.h` is verify-
|
||
only by design — "this kernel never signs or generates keys (no entropy source to do so
|
||
safely anyway); signing happens in the host-side build tool" (`FABRIC-2.md`, Milestone 6,
|
||
2026-08-22). But the vision for `MINT` (§D below) is an *interactive*, on-device
|
||
`S" name" MINT` word — an authenticated Zuse session signing a new user's cert live, at
|
||
runtime. A kernel that structurally never signs can't do that as envisioned. Raised
|
||
directly; **decided (Captain Bob, 2026-08-26): give the kernel a real signing capability**
|
||
rather than reshape `MINT` around verify-only. This reopens the prior "no entropy source"
|
||
constraint deliberately, not by accident.
|
||
|
||
**Phase A — `virtio-rng`, done 2026-08-26.** Checked what entropy is actually available
|
||
before choosing a design: `include/starkernel/vm_uuid.h` already found, for VM UUIDs, that
|
||
amd64 has RDRAND and riscv64 has the Zkr extension, but QEMU's aarch64 CPU models (including
|
||
`max`) expose neither RNDR nor any RNG property at all — confirmed directly against QEMU
|
||
10.2.1. That's why VM UUIDs use a deterministic PRNG uniformly instead of a per-arch split;
|
||
that same choice is **not safe for Ed25519 keygen** — a seed drawn from a known value makes
|
||
the private key predictable. Decided: add a `virtio-rng` device instead of a per-arch
|
||
RDRAND/Zkr split with a weaker aarch64 fallback — QEMU supplies real host entropy
|
||
identically on all three arches, closing the aarch64 gap directly (QEMU-only; real hardware
|
||
at Milestone 8 needs a real per-arch RNG driver, a separate later problem).
|
||
|
||
New `include/starkernel/virtio_rng.h` + `src/starkernel/virtio/virtio_rng.c`, transport
|
||
plumbing (PCI capability walk, common-cfg feature negotiation, split virtqueue) mirroring
|
||
the existing `virtio_blk.c` exactly — same device family, same quirks. Simpler shape than
|
||
block: one virtqueue, one device-writable descriptor, no request header or status byte (the
|
||
entropy device has none); `virtio_rng_get_bytes()` loops internally since the device may
|
||
return fewer bytes than requested per round. `-object rng-random,id=rng0,filename=/dev/urandom`
|
||
+ `-device virtio-rng-pci` added to all three arches' QEMU invocations
|
||
(`Makefile.starkernel`). Wired into boot (`kernel_main.c`, right after the existing
|
||
`virtio_blk_find_artemis()` call site, same graceful-noop-on-absence precedent).
|
||
|
||
**Verified live, not just compiled:** a temporary probe (written, run once, captured,
|
||
reverted — per this project's standing probe convention) pulled 16 real bytes through the
|
||
full request/notify/poll/used-ring round trip on all three architectures and printed them:
|
||
amd64 `be9223909b86a8ccbfff705ccae2caa6`, aarch64 `861487df65a6d7a26b2c9c34f5ff96ee`, riscv64
|
||
`fec51d80e8c169a35aad9d908eab2f39` — three different values, confirming real entropy, not a
|
||
stale or repeated buffer. Probe reverted; permanent code is just the driver + init call. A
|
||
second, final 3-arch acceptance boot ran against that reverted code (not the probe build) to
|
||
confirm the shipped state itself is clean. Clean zero-warning compile and clean boot to
|
||
`ok>` on all three architectures, Stadium conservation intact (43691/21845/65536), no panics
|
||
or guest errors on either pass.
|
||
|
||
**Still open: Phase B (real Ed25519 keygen/signing, seeded from this entropy) and Phase C
|
||
(the `MINT` word itself, cert format, and whether Zuse's own keypair needs to chain to the
|
||
Milestone 6 offline root CA or is a self-sovereign instance-local root of trust).**
|
||
|
||
**Phase B — real Ed25519 keygen/signing, done 2026-08-26.** Extended
|
||
`include/starkernel/ed25519.h`/`src/starkernel/crypto/ed25519.c` (previously verify-only)
|
||
with `ed25519_keygen(seed, pubkey_out)` and `ed25519_sign(seed, msg, msg_len, sig_out)`, per
|
||
RFC 8032 §5.1.5/5.1.6, reusing every point-arithmetic primitive verify already had
|
||
(`scalar_mult`, `point_compress`, the base-point constants) — no new curve code, only the
|
||
seed-expansion/clamping and per-message nonce derivation verify never needed. Signing is
|
||
deterministic (nonce derived from seed+message, not fresh randomness): only keygen ever
|
||
touches entropy, via a caller-supplied seed (`virtio_rng_get_bytes()`, Phase A) — keygen
|
||
itself still generates nothing and trusts the caller for randomness quality, matching this
|
||
file's original design philosophy exactly.
|
||
|
||
New `scalar_muladd()` (`scalar25519.c`/`.h`) for signing's `S = (k*a + r) mod L` step, the
|
||
one piece of scalar arithmetic verify never needed (verify only ever reduced or compared,
|
||
never multiplied scalars). Schoolbook 256×256-bit multiply into a `u128` wide accumulator
|
||
with exactly one final carry-propagation pass — deliberately the same shape as `fe25519.c`'s
|
||
existing field multiply, because that file's own history records a real bug from trying to
|
||
carry mid-accumulation instead of in one final pass; structurally can't repeat that mistake
|
||
this way. Reduces the result via the existing, already-proven `scalar_reduce512()` rather
|
||
than writing new modular-reduction logic.
|
||
|
||
**Verified against an independent implementation, not self-consistency** — this project's
|
||
own standing lesson (two real, invisible-by-inspection bugs in the original from-scratch
|
||
field arithmetic, an off-by-one-hex-digit hand-transcribed SHA-512 vector) means a passing
|
||
self-check proves nothing on its own. Built a throwaway host test harness (compiled, run,
|
||
discarded — the crypto files have no `__STARKERNEL__` gate, so they link as an ordinary
|
||
Linux binary) against Python's `cryptography` library (OpenSSL-backed). Six trials — five
|
||
random seed/message pairs (message lengths 1, 32, 255, 1000 bytes) plus the empty-message
|
||
case — every one produced a byte-for-byte identical public key and signature to the
|
||
independent implementation, not just a signature this codebase's own verify accepted.
|
||
**Verified on-target too:** clean zero-warning compile of the crypto files on all three
|
||
architectures, and a full 3-arch QEMU acceptance boot (amd64/aarch64/riscv64) — all clean to
|
||
`ok>`, Stadium conservation intact, no panics or guest errors. Nothing calls
|
||
`ed25519_keygen()`/`ed25519_sign()` from the live kernel path yet (Phase C's job); this pass
|
||
is compile/link/boot-regression verification for the crypto library itself.
|
||
|
||
**Still open: Phase C** — the `MINT` word itself, cert format, and whether Zuse's own
|
||
keypair needs to chain to the Milestone 6 offline root CA or is a self-sovereign
|
||
instance-local root of trust.
|
||
|
||
**Phase C scoping, 2026-08-26.** Three findings before any code:
|
||
|
||
1. **Resolved, not a real conflict: Zuse doesn't need Milestone 6's CA.** That CA chain is
|
||
specifically for *capsule/code signing* (root → snakeoil intermediate → per-capsule
|
||
Ed25519 signatures verified at capsule-load time) — a different trust domain from *user
|
||
identity*. The vision's own framing ("we mint one and only one Zuse user and blow a fuse
|
||
... the only way around is a new system") already implies Zuse's authority comes from
|
||
being the unique first-boot mint on *this instance*, not from an external chain.
|
||
**Decided: Zuse is a self-sovereign, instance-local root of trust**, keypair generated
|
||
on-device from real entropy (Phase A+B). Regular users, minted later via `MINT`, get
|
||
certs signed by *Zuse's* key, not the Milestone 6 CA — two independent PKI domains.
|
||
2. **A real gap in this session's own earlier work:** `vm_zuse_cert_install(vm, lo, hi)`
|
||
(the very first change this session made, before Phase A existed) only holds two
|
||
`uint64_t` (16 bytes) — sized against the old placeholder `ZUSE-CERT-LO`/`HI` FORTH-cell
|
||
design, not against what a real Ed25519 keypair needs (32-byte pubkey alone, well over
|
||
100 bytes for a full cert). Needs expanding before Phase C can store anything real.
|
||
3. **A genuine blocker, found by asking where the cert would actually live:** "mint once,
|
||
ever" requires surviving reboots, but `Makefile.starkernel`'s `qemu` target copied a
|
||
fresh, pristine `OVMF_VARS.fd` on *every* invocation (not just after `clean`) — so a
|
||
UEFI-NVRAM-based cert (the real-hardware-compatible option, and this codebase already has
|
||
a live `SetVariable`/`GetVariable` precedent via `SF_VAR_REBOOT_TRIES`/`SF_VAR_BOOT_ARGS`)
|
||
would never actually persist under this project's own normal test workflow. Digging
|
||
further: aarch64's `qemu` recipe had no persistent NVRAM store *at all* — a single
|
||
combined `-bios $AAVMF_CODE` argument, no separate writable VARS pflash drive like
|
||
amd64/riscv64 have. **Decided (on request): fix the harness rather than switch
|
||
substrates.** amd64/riscv64: the VARS-template copy is now conditional on the destination
|
||
not already existing, so `clean` (which deletes the whole `build/$(ARCH)/kernel` tree,
|
||
`OVMF_VARS.fd`/`RISCV_VARS.fd` included) is the bleach step, and a bare `make qemu` now
|
||
preserves NVRAM across runs — exactly matching the existing "always pass `clean` before
|
||
`qemu`" acceptance convention, no new bleach script needed. aarch64: restructured to
|
||
split CODE(ro)/VARS(rw) pflash drives matching the other two (host has
|
||
`/usr/share/AAVMF/AAVMF_VARS.fd` alongside the existing `AAVMF_CODE.fd`), with a
|
||
graceful fallback to the old single-`-bios` mode (and a console note) on a host that only
|
||
has non-split firmware packaged, so this doesn't regress environments without one.
|
||
**Verified live:** all three architectures still boot clean to `ok>` with the new
|
||
pflash arrangement, Stadium conservation intact, no panics or guest errors — this is
|
||
infrastructure-only (no cert code yet), so a plain boot-regression check is the right
|
||
verification tier.
|
||
|
||
**Still open:** the actual cert struct (expanding past the 16-byte placeholder), the
|
||
first-boot mint-vs-already-minted boot sequence using `SetVariable`/`GetVariable`, and the
|
||
`MINT` word itself.
|
||
|
||
**Cert struct expanded (2026-08-26):** `vm_zuse_cert_install()` (both `src/vm.c`'s hosted
|
||
copy and a new kernel-side duplicate in `src/starkernel/vm/vm_core.c` -- the kernel build's
|
||
`VM_EXCLUDE` list drops `src/vm.c` entirely, same reason `vm_set_base()` already has two
|
||
independent copies) now takes a real 32-byte seed + 32-byte pubkey instead of the old
|
||
16-byte placeholder. FORTH-side `ZUSE-CERT-LO@`/`HI@` replaced with `ZUSE-PUBKEY@ ( i -- u )`
|
||
(8-byte LE chunk `i`, 0..3, of the public half only -- the seed has no FORTH access at all).
|
||
`ACL-ZUSE-BOOT` now checks `ZUSE-CERT-INSTALLED?` before authenticating rather than
|
||
authenticating unconditionally. Verified: clean compile and clean boot on all three
|
||
architectures.
|
||
|
||
**NVRAM persistence attempt: crashed, root-caused, reverted -- do not retry as designed.**
|
||
First attempt placed the mint-or-load `GetVariable`/`SetVariable` logic right after
|
||
`virtio_rng_init()` (before `capsule_birth_mama()`); it page-faulted (`CR2` inside the OVMF
|
||
flash MMIO window, a supervisor write to a not-present page) partway through boot. Moved the
|
||
same logic to the one place in this codebase already calling `SetVariable` post-
|
||
`ExitBootServices` successfully (`SF_VAR_REBOOT_TRIES`, much later in boot) — **identical
|
||
crash, same RIP and CR2** — which disproved the "too early in boot" theory outright: it isn't
|
||
a timing issue.
|
||
|
||
**Localized precisely (advisor-directed, one boot, debug markers around each call):**
|
||
`GetVariable` returns fine. `SetVariable` **with real 64-byte data** never returns — that's
|
||
the exact fault site. The pre-existing `SF_VAR_REBOOT_TRIES` call that looked like a working
|
||
precedent is actually a **delete of a variable that's never existed** (`size=0, data=NULL`) —
|
||
a fundamentally different, much cheaper internal path than a real data write, so it proved
|
||
nothing about real persistence being safe. **Root cause: this kernel's VMM never maps
|
||
whatever memory region OVMF's variable service needs to actually write flash-backed variable
|
||
data** — a real gap in UEFI runtime-services support, not specific to Zuse. Fixing it for
|
||
real means walking the UEFI memory map for the relevant regions and mapping them into the
|
||
kernel's own page tables, and per Section U's own note, the flash window's location is
|
||
firmware/arch-specific (OVMF's differs from AAVMF's and EDK2-riscv64's), so "walk the map and
|
||
map everything" is not guaranteed 3-arch-uniform even once attempted.
|
||
|
||
**Second, independent finding (not a bug, a design flaw in the persistence choice): storing
|
||
the raw 32-byte seed in NVRAM was a real defect regardless of the crash.** `SetVariable` was
|
||
called with `EFI_VARIABLE_RUNTIME_ACCESS`, meaning any later-loaded UEFI application or the
|
||
booted OS itself could read Zuse's private key straight out of NVRAM. For an irrevocable
|
||
"one and only one Zuse, ever" root of trust, that undermines the property the design exists
|
||
to provide — this would have needed fixing even had the crash not happened.
|
||
|
||
**Decision needed, not yet made:** given virtio-blk writes are already proven working on all
|
||
three architectures in this repo (`vblk_write`, Artemis's own persistence across runs), a
|
||
dedicated file-backed system-identity disk (mirroring `disk/artemis.img`'s existing pattern,
|
||
separate from Artemis's internal storage and separate from home-blocks USB thumbdrives) is
|
||
the substrate with no open unknowns today — recommended over either fixing the UEFI
|
||
flash-mapping gap (real but large, unscoped VMM work) or accepting the NVRAM approach as
|
||
originally designed (has the exposed-seed defect regardless). Not decided or built yet.
|
||
**Reverted to a known-safe state:** all Zuse mint/NVRAM code removed from `kernel_main.c`
|
||
(only two harmless includes remain), `init.4th`'s `ACL.4th` line back to its documented
|
||
commented-out default. Verified clean compile and clean boot on all three architectures in
|
||
this reverted state.
|
||
|
||
**Substrate corrected (Captain Bob, 2026-08-26): no files, ever — this OS's entire reason
|
||
for being is anti-POSIX, anti-file.** The "dedicated system-identity disk" recommendation
|
||
above was framed in file/filesystem language by mistake; corrected before any code was
|
||
written. The only real persistence primitives here are content-addressed capsules and raw
|
||
LBN-numbered blocks (`block_subsystem.c`) — never a filesystem, never file paths. Saved as
|
||
`feedback_no_files_anti_posix.md` so this isn't re-learned next session.
|
||
|
||
**Design, agreed on request: a growable metadata fence at the TOP of a device's block
|
||
space, mirroring the bottom BAM reservation from the opposite end.** `block_subsystem.c`'s
|
||
BAM already reserves the bottom `BLK_DISK_SYS_RESERVED` (32) blocks of every attached
|
||
device, invisible to FORTH's `BLOCK`/`BUFFER`. Zuse's cert (and future system metadata)
|
||
gets a second reservation at the *top* of the same device, starting at
|
||
`BLK_META_FENCE_INIT` (128) blocks and growing downward as needed — the two reservations
|
||
grow toward each other from opposite ends, never colliding, same shape as a stack/heap.
|
||
Explicitly never RAM-backed (the fast-RAM/ramdrive LBN ranges are documented as volatile in
|
||
this same file's own header comment — losing Zuse's identity to a RAM eviction is exactly
|
||
the failure this is designed against). Reuses Artemis's own already-attached, already-proven
|
||
virtio-blk device — no new device attachment. Rejected reusing BAM's own bottom-reserved
|
||
zone directly: those 32 blocks are fully claimed by BAM/volume-metadata bookkeeping, not
|
||
free space.
|
||
|
||
**Step 1 (field round-trip) implemented and verified 2026-08-26, allocator not yet
|
||
touched.** New `meta_fence_blocks` field in `blk_volume_meta_t`, appended after
|
||
`reloc_devblocks` and carved from `_pad[]` — identical graceful-default technique the
|
||
`reloc_start`/`reloc_devblocks` fields already established (a pre-existing formatted volume
|
||
reads the field back as 0 via its zeroed former padding, not a format-breaking change).
|
||
Added a compile-time `_Static_assert(sizeof(blk_volume_meta_t) == 4096, ...)`, same
|
||
discipline `homeblocks_sig.h` already uses — caught a real bug immediately: the hand-summed
|
||
`_pad[]` size formula was off by 4 bytes (a compiler-inserted alignment gap before
|
||
`tracked_blocks` that the manual byte-count missed), found via `offsetof()` rather than by
|
||
re-deriving the arithmetic by hand again, consistent with this project's standing rule to
|
||
never trust a hand-derived numeric claim in this class of code.
|
||
|
||
Worked against disposable clones throughout, never the real `disk/artemis.img`
|
||
(`ARTDISK=...` is `?=`-overridable) — `disk/artemis-metafence-fresh.img` (blank, exercises
|
||
the fresh-format path) and `disk/artemis-metafence-test.img` (a copy of the pre-existing
|
||
`artemis.img`, exercises the graceful-default-on-reload path), both kept as regression
|
||
fixtures per `disk/README.md`'s existing convention (mirrors `artemis-reloc-test.img`
|
||
exactly). **Verified independently via direct byte reads of the disk image, not the
|
||
kernel's own self-report** (`log_message(LOG_INFO, ...)` turned out not to reach serial
|
||
output at all in this build — an unrelated, pre-existing log-level gap, not a regression):
|
||
fresh format writes `meta_fence_blocks=128` at header byte offset 184; a second boot without
|
||
reformatting reads it back unchanged; the pre-existing old-format image correctly reads back
|
||
0. Full 3-arch acceptance boot against the real, untouched `disk/artemis.img` also clean —
|
||
conservation intact, no panics.
|
||
|
||
**Step 2 (allocator + read/write accessors), done 2026-08-26.** Units corrected from "Forth
|
||
1 KiB blocks" to 4 KiB devblocks (matching `bam_devblocks`/`reloc_devblocks`) before anything
|
||
depended on the original meaning — a clean fix, not a migration, since nothing consumed the
|
||
field yet. This let the fence fold directly into `compute_totals_from_B()`'s existing
|
||
`payload4k` calculation (`total_devblocks - 1 - B - R - F`, F = `meta_fence_blocks`) instead
|
||
of needing a second, separate subtraction against `user_blocks` — `total_blocks`,
|
||
`user_blocks`, and `free_blocks` all shrink correctly for free, in both the fresh-format and
|
||
reload code paths, from this one formula change.
|
||
|
||
New `blk_meta_zone_read()`/`blk_meta_zone_write()` (`block_subsystem.c`/`.h`) — raw, unpacked
|
||
4 KiB devblock I/O (no Forth-block packing, same shape as the header/BAM/reloc-table
|
||
regions), addressed by `devblock_from_top` counting down from the device's last physical
|
||
devblock, refusing (not silently clamping) if the index isn't within the on-disk
|
||
`meta_fence_blocks`. No FORTH word wraps either — C-only, same discipline as
|
||
`vm_zuse_cert_install()` itself, which will be this zone's first real tenant.
|
||
|
||
**Verified independently at every step, never trusting the kernel's own report:**
|
||
- Capacity math: read a freshly-formatted image's header bytes directly and independently
|
||
recomputed the expected `total_blocks` in a separate Python script using the same formula
|
||
— exact match (22647, down from what it would have been without the fence).
|
||
- Accessor correctness: a temporary probe (written, run, captured, reverted) wrote a known
|
||
256-byte-repeating pattern via `blk_meta_zone_write(0, ...)`, read it back via
|
||
`blk_meta_zone_read(0, ...)`, and compared in-memory (`PASS`) — then, independently,
|
||
read the raw image file at the exact expected physical byte offset
|
||
(`(total_devblocks-1)*4096`) and confirmed the pattern landed there byte-for-byte.
|
||
- `log_message(LOG_INFO, ...)` still doesn't reach serial output in this build (same
|
||
pre-existing gap noted in Step 1) — all verification here used `console_println` (which
|
||
does reach serial) for the temporary probe, and direct file reads for everything else.
|
||
|
||
Full 3-arch acceptance boot (real, untouched `disk/artemis.img`, probe code fully reverted)
|
||
clean on all three architectures — conservation intact, no panics.
|
||
|
||
**Still open:** wiring `vm_zuse_cert_install()`'s seed+pubkey to actually persist through
|
||
these new accessors (the zone exists and works; nothing writes Zuse's cert into it yet), and
|
||
the `MINT` word itself.
|
||
|
||
**Step 3 (Zuse's cert wired to the fence), done 2026-08-26 -- first-boot mint-then-load is
|
||
real, end to end.** New `include/starkernel/zuse_cert_devblock.h`: a small, standalone
|
||
on-disk record format (`zuse_cert_devblock_t` -- magic + version + 32-byte seed + 32-byte
|
||
pubkey + a real CRC-64/ISO from day one, same "real from day one" discipline
|
||
`homeblocks_sig_t` already established, since this gates a real security check) occupying
|
||
devblock_from_top=0 of the fence. Deliberately its own header, not inlined at the boot-time
|
||
call site: the still-open `MINT` word will be a second consumer of this exact format later.
|
||
|
||
`kernel_main.c`'s mint-or-load logic moved from the crashed NVRAM approach to this: read
|
||
devblock 0 of the fence, and if magic/version/CRC all check out, install the existing
|
||
cert; otherwise, if `virtio_rng` is ready, mint a fresh one (Phase A+B) and write it. Runs
|
||
right after `virtio_rng_init()`, well before `capsule_birth_mama()` -- unlike the crashed
|
||
NVRAM attempt, raw block I/O against Artemis's already-proven virtio-blk device has no
|
||
boot-timing risk at all, so the earlier "re-invoke `ACL-ZUSE-BOOT` after Mama birth"
|
||
workaround is no longer needed; `ACL.4th`/`zuse.4th`'s self-activating `ACL-ZUSE-BOOT` sees
|
||
a correctly-populated cert on its one, ordinary first pass.
|
||
|
||
**Verified live, independently, across every real scenario, never trusting the kernel's own
|
||
report:**
|
||
- **Fresh mint** (blank `disk/artemis-metafence-fresh.img`): boot logs `Zuse: minted, fuse
|
||
blown`; the on-disk record at the exact expected physical offset independently decodes to
|
||
magic bytes `b'ZUSE'`, version 1, a real 32-byte seed and pubkey, and a CRC that an
|
||
independent from-scratch Python re-implementation of the exact CRC-64/ISO algorithm
|
||
(table generation included, not just the check) confirms byte-for-byte.
|
||
- **Reload** (reboot the same now-minted image, no reformat): boot logs `Zuse: cert loaded
|
||
from block fence`; the on-disk seed and pubkey are byte-for-byte identical to the first
|
||
boot's -- genuinely "mint once, ever," not a silent re-mint.
|
||
- **Graceful refusal on a pre-fence volume** (`disk/artemis-metafence-test.img`,
|
||
`meta_fence_blocks=0`): `blk_meta_zone_read`/`write` both correctly refuse (no space to
|
||
read or write), so the kernel mints a cert for RAM/this-boot-only use and honestly reports
|
||
`Zuse: minted but fence write FAILED (not persistent)` -- no crash, no silent data loss,
|
||
no corruption of a device with no fence at all.
|
||
- **Real disk regression check:** the same graceful-refusal path exercised identically
|
||
against the real, untouched `disk/artemis.img` (which has no fence yet either) on all
|
||
three architectures -- clean boot, conservation intact, no panics, `disk/artemis.img`
|
||
itself reverted afterward (no committed churn).
|
||
|
||
**Phase 8's core arc is now functionally complete:** real entropy (Phase A) → real signing
|
||
(Phase B) → real, anti-file, block-native persistence (Phase C) → a working first-boot mint
|
||
that survives reboots. **Still open:** the ongoing `S" name" MINT` word for an authenticated
|
||
Zuse session to mint additional regular users (needs `zuse_cert_devblock_t`-format certs
|
||
signed by Zuse's own key, not just installed) -- the real remaining piece of the original
|
||
vision.
|
||
|
||
### From FABRIC-2.md §X, Milestone 5 — Console/VM key-match binding
|
||
|
||
- [ ] Settle the still-open question: reuse `ACL-PIN`/`acl_allow` directly, or build a
|
||
separate key-matching primitive — `ACL-PIN` gates word execution specifically and nothing
|
||
today gates console-session-to-VM ownership, so this decision needs to happen before any
|
||
code gets written here.
|
||
|
||
- [ ] Design the key/lock data shape (what the console presents, what the VM carries, how
|
||
they're compared).
|
||
|
||
- [ ] Wire drive insertion (Milestone 2e's hotplug signal, post-identity-authentication) to a
|
||
call into `capsule_birth_baby()` (confirmed a real, callable, on-demand birth path already)
|
||
to spin up or re-attach that identity's VM. **Traced 2026-08-27 (`FABRIC-3.md` §F.5): this
|
||
has no mechanism behind it yet — depends on both `CERTVERIFY` (identity-authentication) and
|
||
a new `RUNCAP` mechanism (§F.6, scoped) for per-identity VM content, since
|
||
`capsule_birth_baby()` today only births fixed, build-time-baked capsules.**
|
||
|
||
- [ ] Implement the actual attach/bind step — extending `sk_repl_set_active_vm()` (confirmed
|
||
to exist, currently an unguarded raw pointer-set) with the key-match check from above, so a
|
||
console can only bind to the one VM whose lock matches its key. **SCOPED 2026-08-27
|
||
(`FABRIC-3.md` §F.9)**: real gate is `USE` (`mama_forth_words.c:430-480`), the live,
|
||
already-working, currently-unguarded retarget word — check re-verifies the attached drive
|
||
live on every call; `installed=0` VMs stay freely targetable.
|
||
|
||
- [ ] Implement detach behavior on console disconnect or VM teardown. **SCOPED 2026-08-27
|
||
(`FABRIC-3.md` §F.10)**: a new `EJECT` word — flush via the existing `blk_vm_flush_all()`,
|
||
reset the console's active VM if bound to the departing VM, then `capsule_vm_kill()` (already
|
||
complete, no new teardown mechanism needed). Needs one small new piece of state `WIREBIND`
|
||
must own: which `VMUuid` is currently attached via the home-blocks USB path.
|
||
|
||
### From FABRIC-2.md §X, Milestone 6 — Kernel/capsule PKI signing chain
|
||
|
||
- [ ] Generate (offline, outside the kernel/repo entirely) the real root CA keypair — "stays
|
||
unrevocable," never embedded, never loaded by any kernel code.
|
||
|
||
- [ ] Generate the "snakeoil" intermediate certificate, signed by that real root CA (this is
|
||
a real CA-signed intermediate, not a self-signed/untrusted cert despite the name —
|
||
"snakeoil" names its informal/private-project status).
|
||
|
||
- [ ] Embed the already-CA-signed snakeoil intermediate as a capsule blob at build time
|
||
(mechanically proven already via the font-capsule precedent — no new embedding
|
||
infrastructure needed, just a new payload). **Bootstrapping resolved: no kernel-boot-time
|
||
verification of a hardcoded CA public key is needed at all** — trust is established once,
|
||
at build time, by whoever holds the real root CA and produces the build.
|
||
|
||
- [ ] Add a signing step to the `mkcapsule` build tool (or a separate signing tool) that
|
||
produces a signature alongside each capsule's existing xxHash64.
|
||
|
||
- [ ] Extend `MANIFEST_AUTO.md`'s generation to add a signature-status column, matching the
|
||
existing xxHash64 column's generation pattern.
|
||
|
||
- [ ] Implement magic-number-based content-type detection (Section U item 14) — a shared
|
||
primitive, also usable for Milestone 4's foreign-drive check.
|
||
|
||
- [x] **Root CA + snakeoil intermediate generated 2026-08-26**, entirely offline, in a sibling
|
||
directory outside this repo (`/home/rajames/CLionProjects/lithosananke-ca/`, not tracked by
|
||
git here). Ed25519, OpenSSL 3.5.5. Root: self-signed, 20-year validity (2026–2046),
|
||
`CN=LithosAnanke Root CA`. Intermediate: a real CA-signed cert (not self-signed despite the
|
||
name), 10-year validity, `CA:TRUE, pathlen:0` (can sign capsules, can't mint further
|
||
intermediates), chain verified (`openssl verify` returns `OK`). Both private keys `chmod
|
||
600`. The root key never touches this repo or any kernel code, per the design's own
|
||
requirement.
|
||
|
||
- [x] **Snakeoil intermediate embedded as a capsule, 2026-08-26.** Exported to DER
|
||
(`capsules/pki/snakeoil-intermediate.der`, 418 bytes) and dropped under `capsules/` —
|
||
confirmed the font-capsule precedent needed zero new infrastructure: `mkcapsule`'s
|
||
`process_file()` embeds any non-`.4th` file verbatim already. Shows up as capsule
|
||
`pki:snakeoil-intermediate.der` in the generated capsule table (38 capsules total, up from
|
||
37) — retrieve via `capsule_find_by_name()` + `capsule_get_payload()`, never
|
||
`capsule_exec_payload()` (it's a passive data blob, not executable capsule code).
|
||
|
||
- [x] **Minimal DER/X.509 parser written and independently verified, 2026-08-26.** New
|
||
`include/starkernel/x509_ed25519.h` + `src/starkernel/crypto/x509_ed25519.c`:
|
||
`x509_extract_ed25519_pubkey()`, a from-scratch, narrow DER walker (not a general ASN.1/
|
||
X.509 parser, per this milestone's own design decision) — walks `Certificate →
|
||
TBSCertificate → SubjectPublicKeyInfo`, handles the optional `[0] EXPLICIT Version` field
|
||
(present on v3 certs), verifies the `AlgorithmIdentifier` OID is exactly `1.3.101.112`
|
||
(RFC 8410 Ed25519) rather than assuming, and extracts the raw 32-byte key from the trailing
|
||
`BIT STRING`. Handles both short-form and long-form DER lengths (a real cert with v3
|
||
extensions routinely exceeds the 127-byte short-form limit). Every step bounds-checked
|
||
against the buffer end — refuses malformed input, never faults.
|
||
|
||
**Verified against ground truth, not self-consistency:** run against the real embedded
|
||
`snakeoil-intermediate.der`, the extracted 32-byte key matched `openssl pkey -pubin -text`'s
|
||
own reported public key byte-for-byte. Refusal path verified too: truncated input, 10 random
|
||
garbage bytes, an empty file, and a real RSA certificate (algorithm-mismatch case, not just
|
||
structural malformation) all correctly return failure rather than misreading or crashing.
|
||
Compiles clean on all three kernel architectures (no `__STARKERNEL__` guard needed — same
|
||
freestanding-safe shape as the other crypto files).
|
||
|
||
**Still open:** the signing step in `mkcapsule` (needs a new `sig[64]` field on
|
||
`CapsuleEntry` and a parallel emitted array in `capsule_generated.c`, since `CapsuleDesc`
|
||
itself has no spare bytes — confirmed exactly 64 bytes, every field used), wiring
|
||
`ed25519_verify()` into the three `capsule_validate()` call sites in `capsule_birth.c`
|
||
(**decided: land as WARN-only first, prove correct on all three architectures against both a
|
||
valid and a deliberately-corrupted capsule, then flip to hard-refuse in a separate step** —
|
||
a bug here has a much larger blast radius than anything else in Phase 8, since a false
|
||
refusal on Mama's own capsule means no `ok>` at all, on any architecture), and the
|
||
signature-status column on `capsules/BLOCK_MAP.md` (confirmed the real, live manifest target
|
||
— `capsules/MANIFEST_AUTO.md` is stale/dead, not regenerated since 2026-07-05, flag as docs
|
||
drift rather than a real target).
|
||
|
||
**mkcapsule signing + capsule_birth.c wiring, done 2026-08-26 -- landed WARN-only exactly as
|
||
decided.** First attempt shelled out to `openssl pkeyutl -sign` (`fork`/`execlp`, not
|
||
`system()` -- interpolating the key path into a shell string would have been a real, if
|
||
narrow, injection vector). **Corrected on request: no new external host binary dependency
|
||
when the repo's own code can do the job** — this project's standing preference, same theme
|
||
as the earlier anti-file correction. Rewritten to link `ed25519_sign()`
|
||
(`src/starkernel/crypto/ed25519.c`, already independently verified against OpenSSL in Phase
|
||
B) directly into `mkcapsule`. This needed one more small piece: `tools/pkcs8_ed25519.c`, a
|
||
narrow DER walker (same shape as `x509_ed25519.c`, deliberately not shared code — small
|
||
enough that duplicating a few TLV-walking lines was simpler than threading a header between
|
||
the kernel crypto tree and host tooling) extracting the raw 32-byte seed from the
|
||
intermediate's PKCS#8 private key, plus a minimal self-written base64 decoder (PEM is
|
||
`openssl genpkey`'s default output, not raw DER — checked the repo for an existing decoder
|
||
first, found none). **Verified end-to-end before wiring anything in:** the extracted seed,
|
||
run through this project's own `ed25519_keygen()`, reproduces the exact pubkey embedded in
|
||
the cert; a full self-contained sign+verify round-trip (zero openssl involvement) passes.
|
||
|
||
`CapsuleDesc` had no spare bytes (confirmed exactly 64, every field used), so signatures live
|
||
in a new parallel array, `CapsuleSigEntry capsule_signatures[]` (`{ sig[64], has_sig }`,
|
||
mirroring `capsule_names[]`'s existing shape), emitted by `mkcapsule --sign-key <path>` (new
|
||
optional CLI flag; omitted or the key file missing → every capsule gets `has_sig=0`,
|
||
graceful, not a build failure -- CI has no access to the offline key). New
|
||
`Makefile.starkernel` variable `SIGN_KEY` (defaults to this machine's actual key path,
|
||
`?=`-overridable, gated by `$(wildcard ...)` so a fresh checkout or CI never fails for
|
||
lacking it).
|
||
|
||
New `capsule_sig.c`/`.h`: `capsule_verify_signature()`, deliberately a separate, additive
|
||
function, not folded into the already-tested `capsule_validate()` (whose signature and
|
||
behavior stay untouched). Finds and parses the embedded `pki:snakeoil-intermediate.der`
|
||
capsule's pubkey once per boot (cached — every later capsule birth reuses it, the cert can't
|
||
change mid-boot), then `ed25519_verify()`s the requested capsule's payload against its stored
|
||
signature. Wired into all three `capsule_validate()` call sites in `capsule_birth.c`
|
||
(`capsule_birth_mama`, `capsule_birth_baby`, `capsule_run_experiment`) via
|
||
`log_message(LOG_WARN, ...)` on anything other than `CAPSULE_SIG_OK` — never refuses.
|
||
(Hit one unrelated build snag wiring this in: `vm.h` and `log.h` both define `LOG_LINE_MAX`
|
||
for two completely unrelated concepts — vm.h's own is the persistent block-log line size,
|
||
unconditional `#define`; log.h's is `#ifndef`-guarded. `log.h` has to be included *after*
|
||
`vm.h` in any TU that needs both, or the build fails under `-Werror`; fixed and commented in
|
||
`capsule_birth.c` so the next person touching includes there doesn't rediscover it the hard
|
||
way.)
|
||
|
||
**Verified independently, both directions, live in the real kernel:** a full clean build (38
|
||
real capsules, all signed) boots clean on all three architectures with zero signature
|
||
warnings — the expected outcome when every signature is genuinely valid, not merely an
|
||
absence of testing. Separately, hand-corrupted one byte of Mama's own `init.4th` capsule's
|
||
*stored signature* (not its payload/hash, which `capsule_validate()` already checks
|
||
independently and would have masked the test) directly in the generated
|
||
`capsule_generated.c`, without regenerating from source, and rebuilt only the changed object:
|
||
produced exactly `capsule sig: init.4th: INVALID -- signature does not verify` on boot, and
|
||
**the kernel still reached `ok>`** — proving warn-only genuinely doesn't refuse anything yet,
|
||
precisely as decided. Reverted the tampered build before the final acceptance pass. Final,
|
||
untampered state: clean 3-arch acceptance boot, conservation intact, zero warnings, real disk
|
||
untouched.
|
||
|
||
**Still open:** flipping WARN to hard-refuse (a separate, deliberate step once this has run
|
||
clean for a while, not bundled into this pass) and the `BLOCK_MAP.md` signature-status
|
||
column.
|
||
|
||
**`BLOCK_MAP.md` signature-status column, done 2026-08-26.** `--manifest` mode's file scan
|
||
(`manifest_file()`) is a completely separate code path from build mode's `process_file()` —
|
||
confirmed it only ever walks `.4th` files (never the embedded PKI cert or font capsule), and
|
||
the Makefile invokes `mkcapsule` twice per build for two different purposes (once to emit
|
||
`capsule_generated.c`, once for the manifest). Extended `--manifest` to accept the same
|
||
optional `--sign-key <path>` prefix as build mode (factored the key-loading code, previously
|
||
inline in build mode's arg parsing, into a shared `load_sign_key()` used by both) so the
|
||
manifest can report real per-capsule signing status without touching or requiring a rebuild
|
||
of `capsule_generated.c`. New `Signed` column on the capsule summary table: `yes`/`no` when
|
||
`--sign-key` was given, `n/a` (with an explanatory footnote) when it wasn't — never a bare
|
||
blank that could be misread as "unsigned." `Makefile.starkernel`'s manifest-generation call
|
||
site updated to pass the same `$(SIGN_KEY_ARGS)` the real build already uses, so
|
||
`capsules/BLOCK_MAP.md` reflects this machine's actual signed state by default. Verified:
|
||
clean compile, `capsules/BLOCK_MAP.md` correctly shows `yes` for all 31 tracked `.4th`
|
||
capsules on a real signed build; a quick amd64 boot (no kernel code touched by this step,
|
||
only host tooling) confirmed no regression, zero signature warnings, conservation intact.
|
||
|
||
**This closes every open item from FABRIC-2.md's original Milestone 6 punch list except
|
||
magic-number content-type detection (Section U item 14, shared with Milestone 4, not
|
||
started) and the hard-refuse flip (deliberately deferred).**
|
||
|
||
**Hard-refuse flip, done 2026-08-26 -- and it landed narrower than the original plan
|
||
assumed.** Before flipping, found that `CAPSULE_SIG_MISSING` (no signature at all) is the
|
||
*normal* state on every machine except this one -- CI and any other checkout have no access
|
||
to the offline key, by design. Refusing on `MISSING` the same as `INVALID` would brick boot
|
||
everywhere but here, not catch anything real. **Decided (on request): enforce ONLY on
|
||
`CAPSULE_SIG_INVALID`** (a signature that IS present but doesn't verify -- unambiguous
|
||
tampering/corruption evidence, safe to refuse on regardless of who's building).
|
||
`MISSING`/`NO_ROOT_KEY` stay WARN-only permanently, not as a transitional state.
|
||
|
||
All three `capsule_birth.c` call sites now `return CAPSULE_RUN_ERR_INVALID` on
|
||
`CAPSULE_SIG_INVALID`, after logging the same WARN as before. **Verified on all three
|
||
architectures, both directions, exactly per the original rollout commitment:** the positive
|
||
case (real signed capsules) reboots clean with zero warnings on amd64/aarch64/riscv64,
|
||
identical to before this flip. The negative case (the same one-byte signature corruption
|
||
technique used for the WARN-only proof, on Mama's own `init.4th`) now genuinely refuses --
|
||
`capsule_birth_mama()` returns the error -- on all three architectures identically:
|
||
`capsule sig: init.4th: INVALID` followed by `Init: Mama birth FAILED`. **The feared "no
|
||
ok> at all" blast radius didn't materialize**: `kernel_main.c` already had graceful
|
||
error handling for a failed Mama birth (log and continue, pre-existing code, not written for
|
||
this feature) -- the kernel reaches a degraded `ok>` rather than crashing or hanging, on all
|
||
three architectures. Final acceptance pass (real, correctly-signed capsules, all tampering
|
||
reverted) clean on all three, zero warnings, conservation intact, real disk untouched.
|
||
|
||
**Milestone 6 is now fully closed** except magic-number content-type detection (shared with
|
||
Milestone 4, genuinely separate scope, not started).
|
||
|
||
### From FABRIC-2.md §X, Milestone 7 — Contributor capsules / trust tiers
|
||
|
||
- [ ] Create the `capsules/contrib/` directory (mechanically trivial, matches existing
|
||
subdirectory convention — the directory itself is not the work).
|
||
|
||
- [ ] Add a `FLAG_CONTRIB` bit to `mkcapsule.c`'s flag system, assigned by path match
|
||
(`contrib/` prefix), same pattern as how `init.4th` already gets `FLAG_MAMA_INIT`.
|
||
|
||
- [ ] Decide and implement one of the four spitballed trust-tier directions (signature-
|
||
authority tiers / block-namespace sandboxing / QEMU-vs-real-hardware conditional
|
||
enforcement) — none chosen yet, this is a real decision point, not just an implementation
|
||
task.
|
||
|
||
- [ ] If block-namespace sandboxing is chosen: extend `mkcapsule`'s existing conflict-
|
||
detection logic to also reject a `contrib/`-path capsule claiming blocks outside its
|
||
reserved range.
|
||
|
||
### From FABRIC-2.md §X, Milestone 8 — Bare-metal boot from physical USB
|
||
|
||
- [ ] Build a fresh `starkernel.iso` via `make -f Makefile.starkernel ARCH=amd64 clean` + the
|
||
ISO-build step.
|
||
- [ ] Identify the exact block device path for the target USB drive on the host doing the
|
||
flashing (`lsblk`/`dmesg` after insertion — care needed, wrong device = data loss).
|
||
- [ ] `dd if=build/amd64/kernel/starkernel.iso of=/dev/sdX bs=4M status=progress` (or
|
||
equivalent) — confirm `dd` is the right tool for an El Torito ISO vs. needing `isohybrid`
|
||
first (open question, not yet verified).
|
||
- [ ] Physically boot the real machine from the flashed drive (BIOS/UEFI boot-order menu,
|
||
Secure Boot may need disabling — unknown until tried).
|
||
- [ ] Capture what happens with no serial-socket log available (real hardware has no
|
||
`qemu-serial-*.sock` to `socat` into) — decide the observation method.
|
||
- [ ] Confirm POST reaches the same 1012/0/0 result on real hardware as every QEMU acceptance
|
||
run.
|
||
- [ ] Confirm `ok>` prompt is reachable and a basic command (e.g. `HEARTBEAT-TICKS@ .`) works
|
||
identically to QEMU.
|
||
- [ ] Document the result (pass/fail, and if fail, what diverged from QEMU) — first real
|
||
external validation this project has ever had outside QEMU TCG emulation.
|
||
|
||
### From FABRIC-2.md §X, Milestone 9 — Networking / capsule distribution server
|
||
|
||
- [ ] (Deferred) Revisit and punch-list this milestone once Milestone 7 closes, not before.
|
||
|
||
---
|
||
|
||
## B. Stadium unification — words/VMs/blocks/messages on the same engine
|
||
|
||
Raised 2026-08-25: "words are stadium patrons, VMs are patrons, blocks are patrons, messages
|
||
are stadium patrons, all should be operated on by THE SAME ENGINE." Investigated before
|
||
designing anything — the real state is more nuanced than "everything's a stub," verified via
|
||
direct reads and `git log`, not assumed:
|
||
|
||
**`FABRIC.md` §18.3 already decided the mapping** (not invented here): blocks → `MIGRATE`,
|
||
messages → `DELIVER`, ACLs → `EXPIRE`, words and VMs both → `COOL`. `stadium_evict()`
|
||
(`src/starkernel/vm/stadium.c`) — real, tested infrastructure: bitmap tracking, pin/`contains`
|
||
refusal, the Hera-patron-zero panic guard, heat-conservation back to the owner's reservoir on
|
||
every reap — calls `stadium_dispatch()` for the actual payload action when a patron departs.
|
||
|
||
**Per-behaviour status, as of 2026-08-25:**
|
||
|
||
- **`MIGRATE` (blocks)** — zero consumer, genuinely stub (`stadium_dispatch()`'s case prints
|
||
`"MIGRATE (stub)"` and returns). This session already built the real mechanical primitive
|
||
it needs: `blk_subsys_relocate_block()`/`RELOCATE-BLOCK` (`FABRIC-2.md`, commit `36d832f`),
|
||
live-verified (redirect + content survive an abrupt kill and reboot) but never wired to
|
||
`stadium_dispatch()` — it's a separate, parallel, already-working mechanism today, not
|
||
routed through Stadium at all.
|
||
- **`COOL` (words *and* VMs, same tag)** — half real. **Words are fully live**, but via a
|
||
*separate, bespoke* mechanism, `stadium_word_dispatch()` (`stadium_words.c`, item 4.1),
|
||
wired directly into the real VM word-execution hot path (`vm_core.c:690,885,896`) — it does
|
||
**not** go through the generic `stadium_dispatch()` switch at all. `ONTOLOGY.md` §IX
|
||
claiming words are "not yet migrated" is itself stale documentation drift (same class of
|
||
bug as the "glibc" misattribution corrected earlier this session — flagged as a small,
|
||
separate fix below, not blocking). **VM cooling has no evidence of ever being wired
|
||
anywhere** — still genuinely stub.
|
||
- **`DELIVER` (Hermes messages)** — `FABRIC.md` (~line 3452) records this explicitly as
|
||
"Open, surfaced not resolved": Hermes's message/channel heat already integrates with
|
||
Stadium's reservoir accounting (`STADIUM-HEAT@`, `STADIUM-RES-PULL/PUSH`), but which Hermes
|
||
lifecycle event maps to `DELIVER` vs. `EXPIRE` was never decided, let alone wired. Real,
|
||
substantial, Hermes-specific integration work.
|
||
- **`EXPIRE` (ACL TTL expiry)** — no evidence of any wiring anywhere; `ACL.4th`/
|
||
`acl_recheck()` has zero Stadium involvement today. Also substantial, separate work.
|
||
|
||
**Why `DELIVER`/`EXPIRE` aren't being resolved in the same pass as `MIGRATE`:** each is a
|
||
full subsystem integration (Hermes lifecycle mapping; ACL-to-Stadium wiring where none has
|
||
ever existed) in its own right — attempting all four stubs at once risks exactly the rushed,
|
||
shipped-but-incomplete outcome the no-stubs rule (below) exists to prevent. `MIGRATE` gets
|
||
resolved for real because this session already has a tested primitive underneath it; the
|
||
other three become honest, explicit punch-list items instead of being touched speculatively.
|
||
|
||
**Punch list:**
|
||
|
||
- [x] **Investigated (2026-08-25): block-patron admission does not exist yet, but is
|
||
architecturally straightforward, not blocked.** Confirmed via `stadium_admit()`'s own doc
|
||
(`stadium.h`) that it REFUSES any candidate with `mass != 1`, and that a `mass > 1`
|
||
multi-cell patron would need a continuation chain nothing has ever designed — this looked
|
||
at first like a hard blocker for a 1024-byte block. It isn't: confirmed via
|
||
`stadium_word_dispatch()`'s real candidate construction (`stadium_words.c:245-252`) that
|
||
Stadium cells carry pure identity/heat/bookkeeping only — `candidate.identity = word_id`,
|
||
`payload[32]` unused — the actual word content stays in the dictionary; Stadium never holds
|
||
it. By the same pattern, a block patron's cell would carry `identity = LBN`, `mass = 1`,
|
||
`payload` unused — the actual 1024 bytes of block content stays exactly where it already
|
||
lives (block cache / disk via `block_subsystem.c`), unmoved. So `mass != 1` is a non-issue;
|
||
the real gap is just that nothing has ever built the LBN→cell_index residency map (the
|
||
block-patron analogue of `stadium_word_dispatch()`'s `resolve_resident_cell()`) or the
|
||
touch-on-access hook (the analogue of `vm_core.c`'s three `stadium_word_dispatch()` call
|
||
sites). Not yet built — this is real, scoped, buildable work, not a stub-around candidate.
|
||
**Still open**, plan to be presented before implementation per the no-stubs/no-early-coding
|
||
conventions.
|
||
- [x] **Resolved (2026-08-25): real block-patron admission + real `MIGRATE` dispatch, both
|
||
live.** New `stadium_blocks.h`/`stadium_blocks.c` mirror `stadium_words.c`'s shape (Option B
|
||
starter-grant admission, redirected Loop #3 cooling, self-healing stale-entry detection) but
|
||
key residency by `(quota_slot, lbn)` in a fixed-capacity open-addressing hash table sized off
|
||
`stadium_cell_count()` (tombstone-based deletion, since LBN space isn't densely bounded like
|
||
`word_id`), not a dense array. Wired into `block_word_block()`/`block_word_buffer()`/
|
||
`block_word_update()` (`block_words.c`), `#ifdef __STARKERNEL__`-guarded. `stadium_dispatch()`'s
|
||
`MIGRATE` case now calls `blk_flush(lbn)` for real (confirmed `blk_flush()`, not
|
||
`blk_subsys_relocate_block()`, is the right primitive — the latter is for compudynamics-driven
|
||
relocation to a *different* LBN mid-residency, not ordinary reap write-back). Three new Kconfig
|
||
tuning constants (`STADIUM_BLOCK_HEAT_QUANTUM`/`STADIUM_BLOCK_COOL_RATE_Q48`/
|
||
`STADIUM_BLOCK_TRACK_CAP_MULT`) mirror the word-patron ones exactly, same three-layer wiring.
|
||
**Verified:** clean compile, zero warnings, on all three architectures; clean boot to
|
||
`zuse)ok>`/`ok>` REPL on all three, conservation (`resident_sum + reservoir == Q48_ONE`) intact
|
||
identically across all three; `BLOCK`/`BUFFER` touches exercised live from the REPL on amd64 and
|
||
riscv64 with no crash; a 22,000-distinct-block flood loop (amd64, artificially shrunk to a
|
||
20,971-cell Stadium via a one-off smaller `-m` to make quota pressure reachable) ran clean under
|
||
heavy admission-path load with no corruption. **Live `MIGRATE` fire confirmed (2026-08-26):**
|
||
interactive flooding alone never triggered it — Hera's reservoir was already sitting exactly at
|
||
the `Q48_ONE / 3` floor from the boot-time self-tests, so every block-touch candidate pulled 0
|
||
heat, and a 0-heat candidate can never be *strictly denser* than an existing resident, so
|
||
`stadium_admit()`'s eviction fallback correctly refuses rather than evicts once the free list is
|
||
exhausted (a pre-existing reservoir-floor/density-eviction interaction, applies equally to word
|
||
patrons, not introduced by this pass). Closed deterministically instead with a temporary boot
|
||
probe (`kernel_main.c`, inserted into the existing Artemis 4.6 self-test block, reverted
|
||
immediately after capture — no code left behind): `100 65536 0 STADIUM-ADMIT ... STADIUM-EVICT`
|
||
against the live Artemis VM. Captured live: `Stadium: dispatch cell=63257 behaviour=MIGRATE
|
||
lbn=100`, followed by `DBG err after MIGRATE probe=0` — `blk_flush(100)` fired for real, with
|
||
the correct LBN threaded through from the departing patron's `identity` field exactly as
|
||
designed. (Artemis's own reservoir went briefly out of Q48_ONE-balance during the probe — raw
|
||
`STADIUM-ADMIT` doesn't debit the reservoir on its own, by its own doc, so a heat value handed
|
||
to it directly is invented, not pulled; harmless here since Artemis is killed and her whole
|
||
economy discarded immediately after, and Hera's own conservation was independently confirmed
|
||
back at the normal 43691/21845/65536 baseline afterward.)
|
||
- [x] **Bug found, reported, then fixed on request (2026-08-25/26): `capsules/lib.4th:13-14`
|
||
shadowed the C primitives `USE`/`RUN`.** While chasing the live-MIGRATE test above,
|
||
`S" Artemis" USE` (meant to redirect the REPL into Artemis's own vocabulary,
|
||
`mama_word_use()`) instead printed `EXEC: failed: Artemis`. Root cause: `capsules/lib.4th:13`
|
||
defined `: USE ( addr u -- ) EXEC ;` — a FORTH word with the same name but a completely
|
||
different meaning ("load/exec a capsule"), which shadowed the C-registered `USE` in
|
||
dictionary search order since `lib.4th` loads after primitive registration. `lib.4th:14` did
|
||
the identical thing to `RUN`, which CLAUDE.md also names as an untouchable C primitive
|
||
("BIRTH, RUN, USE are primitives — registered in C exactly like DUP, BYE, EXEC"). Same bug
|
||
class as the K-PUSH dictionary-shadowing issue
|
||
(`docs/working/architecture/K-PUSH-DICTIONARY-SHADOWING-BUG-20260704.md`). Reported first
|
||
per CLAUDE.md's rule against unrequested fixes; Captain Bob then explicitly asked for the fix.
|
||
**Fix:** traced every real caller before touching anything — `RUN`'s alias was dead (never
|
||
called anywhere as bare `RUN`); `USE`'s alias had exactly one real caller,
|
||
`capsules/hermes/init.4th:397` (`S" common:msg.4th" USE`, intentionally exploiting the shadow
|
||
to load that capsule right after `lib.4th` itself loaded). Both aliases were pure
|
||
`EXEC` wrappers with zero added behavior, so the fix deleted both definitions from `lib.4th`
|
||
outright and changed the one real call site plus its matching doc comment
|
||
(`capsules/common/msg.4th:4`) to call `EXEC` directly — no new names invented, the C
|
||
primitives untouched, `mkcapsule --lint` clean (31/31 pass). **Verified live:** rebuilt and
|
||
booted amd64 — Hermes still births and her `COMMON-CH`-eviction self-test (which depends on
|
||
`common:msg.4th` having loaded) still passes exactly as before; interactively, `S" Artemis"
|
||
USE` now correctly prints `USE: now using Artemis` and switches the REPL's console-name
|
||
coloring, confirming the C primitive runs unshadowed. Clean compile and clean boot with
|
||
conservation intact on all three architectures (amd64/aarch64/riscv64).
|
||
- [x] **Resolved (2026-08-26): real VM-patron admission + real explicit-KILL eviction, both
|
||
live.** Re-scoped on request: confirmed `capsule_vm_kill()` had zero Stadium involvement
|
||
(`vm_cleanup()`/`sf_free()` only) and child-VM birth only ever called
|
||
`stadium_grant_quota()` (a resource pool for the VM's *own* future word/block patrons) —
|
||
never `stadium_admit()` for the VM *itself*. The only precedent, `stadium_birth_hera()`,
|
||
admits Hera into her own quota as a permanently pinned cell 0, which can never reach
|
||
`stadium_evict()` — not a working example of `COOL` firing for a VM. On closer look this
|
||
turned out NOT to be entangled with the still-iterating Tripod/Zuse/messaging vision after
|
||
all (§D) — birth and kill already funnel through two single choke points, so the earlier
|
||
2026-08-25 deferral was overcautious. **Design:** added `size_t stadium_patron_cell` to
|
||
`VMRegistryEntry` (`capsule_run.h`). At birth, right after the existing
|
||
`stadium_grant_quota()` call (`capsule_birth.c`), admit a candidate into the new VM's own
|
||
quota mirroring `stadium_birth_hera()`'s shape (`identity=0`, `mass=1`, `behaviour=COOL`)
|
||
but deliberately **unpinned** — pinning would need a new "unpin" primitive (none exists) to
|
||
ever evict it later, and adding a pin-bypass to `stadium_evict()`'s refusal logic isn't
|
||
something to do casually; unpinned costs nothing since nothing wires `COOL`'s dispatch body
|
||
to actually kill anything, so the worst case of an unrelated natural eviction is
|
||
`stadium_patron_cell` going stale, which is tolerated the same way `stadium_word_forget()`
|
||
already tolerates staleness. At `capsule_vm_kill()` and `capsule_vm_kill_all_nonmama()`:
|
||
`stadium_evict()` the tracked cell if still resident, silently tolerating refusal (already
|
||
gone). `stadium_dispatch()`'s `COOL` case needed no new payload body — same as it already is
|
||
for words, where `COOL` has no defined extra action beyond `stadium_evict()`'s own universal
|
||
reservoir credit; the missing piece was admission and a genuine trigger, not dispatch-body
|
||
logic. **Verified live:** a second, new `Stadium: dispatch cell=... behaviour=COOL` now
|
||
fires immediately before every `PARITY:KILL` line, for both Hermes and Artemis, confirmed on
|
||
amd64 (distinct from the pre-existing `COMMON-CH` word-eviction self-test's own COOL print).
|
||
Conservation (`resident_sum + reservoir == Q48_ONE`) intact throughout. Clean zero-warning
|
||
compile and clean boot on all three architectures (amd64/aarch64/riscv64).
|
||
- [x] **Re-scoped and resolved (2026-08-26): `DELIVER` (Hermes) was never actually a gap —
|
||
the earlier "zero consumer, needs substantial Hermes lifecycle mapping" framing above was
|
||
wrong, carried over unverified from `FABRIC.md`'s old "open, not resolved" note about
|
||
*which* Hermes event maps to `DELIVER` vs. `EXPIRE`. Item 4.2 already answered that in code
|
||
(messages → `DELIVER`, channels → `COOL`) without the prose ever catching up — same
|
||
documentation-drift class as the stale `ONTOLOGY.md` words note and the earlier glibc
|
||
misattribution. Confirmed live: `capsules/hermes/init.4th`'s `MSG-ALLOC` already admits
|
||
every message with `SB-DELIVER`, and `MSG-FREE-NODE` (called from both `MSG-ACK-LAST` and
|
||
heat-driven `MSG-REAP`) already evicts it — `behaviour=DELIVER (stub)` has been printing on
|
||
boot logs since at least 2026-08-05. Checked whether the dispatch body needed a real payload
|
||
action the way `MIGRATE` did: `MSG-DELIVER` (the FORTH word) already runs the actual
|
||
delivery (`VM-EXEC` of the payload) *before* eviction, decoupled from Stadium reap — so by
|
||
dispatch time delivery is already done, same shape as `COOL`, which needs no extra action
|
||
beyond `stadium_evict()`'s own universal reservoir credit. **Fix:** `stadium_dispatch()`'s
|
||
`DELIVER` case now prints the departing message's real identity (`DELIVER msg_idx=N`, same
|
||
shape as `MIGRATE`'s `lbn=` print) instead of a misleading `(stub)` label — confirmed live
|
||
via a forced `MSG-SEND`/`MSG-DELIVER-ALL`/`MSG-ACK-LAST` sequence from Hermes's own REPL
|
||
context (`Stadium: dispatch cell=73653 behaviour=DELIVER msg_idx=1`). `COOL`'s case was in
|
||
the identical situation (real for both words and VMs, no extra action needed) and, on
|
||
request, got the same fix (2026-08-26): now prints `COOL identity=N` (word_id for a word, 0
|
||
— the patron-zero convention — for a VM) instead of `(stub)`. Confirmed live: both shapes
|
||
fired correctly on the same boot — `COOL identity=0` at Hermes's/Artemis's own explicit
|
||
channel-eviction self-test and again at their VM-patron eviction at `PARITY:KILL`,
|
||
`COOL identity=1` at a second channel eviction — conservation intact throughout. Clean
|
||
zero-warning compile and clean boot on all three architectures for both fixes.
|
||
- [ ] `EXPIRE` (ACL) — confirmed genuinely unscoped (2026-08-26), not a case of stale
|
||
documentation like `DELIVER` turned out to be. Two findings below, then four open questions
|
||
— **decided directly on request (2026-08-26)**, decision recorded after the questions, no
|
||
code written (a decision isn't a green light to build, per this session's own convention):
|
||
|
||
**Finding 1 — `acl_ttl` and Stadium's `ttl` are different things wearing the same name.**
|
||
`DictEntry.acl_ttl` (`vm_core.c:756`) is a per-word countdown that batches how often
|
||
`ACL-RECHECK` runs — when it hits 0, `acl_recheck()` calls the FORTH word `ACL-RECHECK`
|
||
(`ACL.4th:50`), which **always renews**: STRICT mode sets `allow=1, ttl=0` (recheck every
|
||
time); TTL mode computes a fresh heat-based TTL and sets `allow=1`. Denial isn't a live path
|
||
anywhere in current policy. This is a *renewal* cycle, not a *residency-ending* event —
|
||
nothing about it resembles "leaving the Stadium floor."
|
||
|
||
**Finding 2 — `StadiumPatronHeader.ttl` is completely inert.** Every candidate constructor
|
||
across the whole codebase (`stadium.c`, `stadium_words.c`, `stadium_blocks.c`, Hermes's
|
||
`MSG-ALLOC`/`CH-ALLOC`) sets `candidate.ttl = 0`, and nothing anywhere ever reads,
|
||
decrements, or reaps on it. The generic TTL-expiry *mechanism* `EXPIRE` would need to fire
|
||
from doesn't exist in Stadium's own engine at all — a gap one level deeper than "ACL isn't
|
||
wired to Stadium."
|
||
|
||
**Open questions:**
|
||
1. Is "ACL patron" even the right model, or was `FABRIC.md`'s original "ACLs → EXPIRE"
|
||
mapping a category mismatch from the start — conflating `acl_ttl`'s recheck-amortization
|
||
counter with Stadium's residency `ttl`?
|
||
2. If it is the right model: what gets admitted as a patron? One per ACL-guarded word would
|
||
be redundant with the word's own patron cell item 4.1 already tracks. A different unit
|
||
(e.g. per zuse session) might fit better once PKI/session auth lands (Phase 8, still
|
||
open per `.claude/CLAUDE.md`'s ACL section).
|
||
3. Building this for real means building Stadium's generic ttl-decrement/reap-on-zero
|
||
mechanism first — nothing to hook `EXPIRE` into today. Is that in scope here, or its own
|
||
separate item?
|
||
4. What should the reap action actually *do*, given current ACL policy never revokes — would
|
||
`EXPIRE` force an `ACL-RECHECK`, or something else entirely?
|
||
|
||
**Decision:**
|
||
1. Not at the per-word level. `acl_ttl`-hits-zero always renews, never revokes — forcing
|
||
`EXPIRE`'s residency-ending tag onto it would misuse the tag. But "ACLs → EXPIRE" isn't
|
||
wrong in spirit, just aimed at the wrong unit: the one place in this system where
|
||
something ACL-related genuinely has a lifetime and should be revoked is a **zuse
|
||
superuser session** (Phase 8, not yet built) — authenticate, hold elevated privilege for
|
||
a bounded time, then actually drop back to non-zuse. That's a real residency-ending
|
||
event; per-word recheck isn't.
|
||
2. A zuse session, not a per-word ACL entry — a session is the thing with a genuine
|
||
start/lifetime/end. A word already has its own patron via item 4.1; a second one for ACL
|
||
purposes would be redundant bookkeeping, not a new concept.
|
||
3. **Not in scope now.** This is the decision that actually resolves the item: Phase 8
|
||
doesn't exist yet, so there is no session to admit as a patron regardless of any other
|
||
choice made here. Building the generic ttl-decrement/reap mechanism now, with nothing
|
||
real to feed it, would be speculative infrastructure ahead of its only consumer — close
|
||
in spirit to what the no-stubs standard exists to prevent, just inverted (a real
|
||
mechanism with no real caller, instead of a fake mechanism with a real caller).
|
||
4. Revoke the session's elevated privilege and drop the console back to its non-zuse state —
|
||
a real action, unlike `DELIVER`/`COOL`, which needed none.
|
||
|
||
**`EXPIRE` stays explicitly deferred until Phase 8 (PKI/zuse session minting) lands** — not
|
||
abandoned, not left ambiguous: revisit it as part of that work, admitting the session itself
|
||
(not a word) as the patron, once there is something real for it to represent.
|
||
- [x] Fixed (2026-08-26): `ONTOLOGY.md` §IX's "words (dictionary, warehouse-resident today,
|
||
not yet migrated)" line was stale — corrected to state words are fully migrated and live
|
||
via `stadium_word_dispatch()`. Doc-only, no build/boot verification needed.
|
||
|
||
---
|
||
|
||
## C. Standing rule: no stubs or TODOs, ever
|
||
|
||
Stated directly, 2026-08-25, after the `stadium_dispatch()` stub investigation above:
|
||
**"I've never allowed stubs before."** Saved as a persistent memory
|
||
(`feedback_no_stubs_or_todos.md`) so this applies across sessions, not just this one. Full
|
||
statement: no stub function that prints a placeholder and returns, no `TODO`-and-move-on
|
||
comment in place of real logic, in any language, ever committed as if it were finished work.
|
||
Small, honest increments are still fine and encouraged — each increment just has to be a
|
||
complete, real implementation of whatever slice it covers, never a placeholder for a later
|
||
slice. A pre-existing stub found while working nearby (as here) gets flagged and resolved,
|
||
not built on top of or left in place.
|
||
|
||
---
|
||
|
||
## D. Tripod final shape — minting, one-time Zuse fuse, messaging-only (vision, substantially expanded 2026-08-27)
|
||
|
||
Stated directly by Captain Bob, 2026-08-25: "we're going to have to iterate because I know
|
||
what the final shape of the tripod will be." Originally captured as a forward-looking vision,
|
||
not yet broken into implementable items. **2026-08-27: expanded into a much fuller picture**
|
||
after Phase 8/Milestone 6 closed and unblocked this design pass — captured here in full before
|
||
any of it gets scoped into implementable items, per direct request ("capture EVERYTHING first
|
||
then we'll build a plan"). Full detail also in memory `project_tripod_final_shape_vision.md`.
|
||
|
||
### D.1 — Original vision (2026-08-25), still accurate
|
||
|
||
- **Thumbdrive presentation → legality check → mint.** A newly presented thumbdrive is
|
||
checked for legality (against the CA-root-derived identity/certificate scheme). If not
|
||
legal, it is "minted" — formatted for system use — which (1) spins a new user VM and (2)
|
||
attaches the console VM to it.
|
||
- **One-time Zuse mint + fuse-blow on first install.** A brand-new system instance
|
||
("Install"/"Try It") mints exactly one Zuse superuser, then irreversibly "blows a fuse":
|
||
direct quote, "we mint one and only one Zuse user and blow a fuse. The only way around is a
|
||
new system." Post-fuse, no further Zuse can ever be minted on that instance — but the
|
||
system is NOT bricked: the existing Zuse superuser keeps working, and ordinary users can
|
||
still "thumb in" via regular thumbdrives. **This half is now actually built** — Phase 8's
|
||
entropy→signing→block-fence persistence work (2026-08-26, `project_phase8_milestone6_complete.md`)
|
||
implements exactly this, minus the physical thumbdrive itself (see D.4, WRITE(10) gap).
|
||
- **Messaging-only once Tripod is fully live.** All inter-VM interaction becomes Hermes
|
||
messaging, not direct calls/shared state — a stated end-state, not the current
|
||
implementation. **Reaffirmed and sharpened 2026-08-27: hardwired/direct-call
|
||
implementations are acceptable *during development only* — no piece of this design is
|
||
considered actually finished until its hardwired calls are replaced with real Hermes
|
||
messages.** This is a standing completion criterion for everything in this section, not a
|
||
separate future milestone.
|
||
- **Polymorphic block-boundary behavior for user VMs.** Still just one sentence, still not
|
||
elaborated — genuinely unresolved since 2026-08-25, needs its own follow-up conversation.
|
||
**CLOSED 2026-08-27, see `D.4`/§F.16** — that follow-up happened; already true of the live
|
||
block-subsystem code.
|
||
- **`ClaudeEXPORT/`** — a prior Claude data export, "concepts and thoughts as guidelines,"
|
||
explicitly flagged as possibly containing superseded/conflicting ideas, not authoritative.
|
||
See memory `project_claude_export_archive.md`.
|
||
|
||
### D.2 — The session model (2026-08-27): a session IS a VM
|
||
|
||
Direct correction to how this document and the earlier `EXPIRE` framing (§B) had been
|
||
thinking about "sessions": **a session is any VM client running in the Stadium fabric — Zuse
|
||
is no different from any other VM.** The Stadium/FABRIC metaphor restated by Captain Bob:
|
||
the fabric floats inside the Stadium; VMs actually doing work are "players on the field"
|
||
(system perturbances); user interaction is "fans in the stands" providing patron-driven noise.
|
||
Zuse is a player like any other, not a special boolean flag on Hera.
|
||
|
||
**This directly invalidates the `EXPIRE`-as-TTL-on-`zuse_session`-boolean plan this session
|
||
had started scoping (§B's `EXPIRE` decision, and the implementation research begun
|
||
2026-08-27 before this correction landed).** Today's `zuse_session` field on the VM struct is
|
||
not what a "session" actually is in this model — a session ending is a **VM detaching**, far
|
||
closer to the existing, real, working `COOL`/`capsule_vm_kill()` path than to a new
|
||
Stadium-behaviour-tag TTL sweep. **`EXPIRE` as originally scoped is superseded, not merely
|
||
deferred** — when Stadium/ACL session-lifetime work is picked up again, it should be framed
|
||
as "what happens when a user-VM's owning thumbdrive detaches," reusing `COOL`, not as a
|
||
timeout on a flag.
|
||
|
||
When Hera has no VM attached beyond fleet-servicing strata, she idles, watching services and
|
||
maintaining a serviced fleet foundation — her normal steady state, not a special case.
|
||
|
||
**Validated against the original Stadium foundation, not just today's intuition (mined
|
||
2026-08-27 from `FABRIC.md` §17.1/§20, the oldest, most foundational layer of this whole
|
||
design — see D.2b below for the full mining pass).** `FABRIC.md` §17.1's own patron/departure
|
||
table already says VMs depart via heat decay (`COOL`) and only messages/ACLs ever used TTL —
|
||
meaning the `EXPIRE`-as-TTL-on-a-flag plan this session had started scoping didn't just become
|
||
inconvenient once "session = VM" landed, **it contradicted the original design from the
|
||
start**: a VM was never one of the two patron kinds TTL governs. §20's outer/inner Stadium
|
||
nesting (VMs as outer-Stadium patrons; words/blocks/ACLs/messages as inner-per-VM patrons)
|
||
already anticipated exactly "attach = admit a VM patron" as the outer-Stadium case — no new
|
||
Stadium theory is needed for this, only a new *admission trigger* (thumbdrive attach) feeding
|
||
the existing admit/evict machinery.
|
||
|
||
### D.2b — The pentagon: five VMs, minimum, to service one attached user (2026-08-27)
|
||
|
||
Stated directly: **"Just to get a user, 5 VMs are needed — if we draw every connection to
|
||
every node they can make a pentagon."** The five: **Hera, Hermes, Artemis, the user's own VM,
|
||
and the Console.** Drawing every pairwise connection among five nodes (a complete graph, K5)
|
||
draws a pentagon with all its diagonals — visually a pentagram. Noted in passing, and worth
|
||
keeping since this project's naming is already steeped in Greek mythology (Hera, Hermes,
|
||
Artemis, Ananke/Lithos, Zuse for Zeus): a pentagram at the foundation of a "pagan" system fits
|
||
the theme, not an accident to explain away.
|
||
|
||
**The Console is a full peer node, not a passive terminal that merely "attaches to" a user
|
||
VM** — D.3 step 4 undersells this; the Console is architecturally one of the five, with (at
|
||
minimum) its own binding relationship to the user's VM and, per the messaging end-state,
|
||
eventually its own message traffic to Hera/Hermes/Artemis too, not just a one-way relay.
|
||
Reconciling D.3's "console attaches to the new user VM" phrasing against this five-node,
|
||
fully-connected picture is real remaining work, not done here — captured as a gap in D.4.
|
||
|
||
Why each of the five is load-bearing, best understanding as of this capture (not all edges
|
||
independently confirmed — see D.4's gaps):
|
||
- **Hera** — governs the fleet, births/kills the user VM (existing `capsule_birth_baby()`/
|
||
`capsule_vm_kill()` path), holds the outer-Stadium quota the new VM draws from.
|
||
- **Hermes** — the messaging fabric itself; per the standing "nothing is done until it's
|
||
messaging" criterion, every other edge in this pentagon is ultimately meant to route through
|
||
Hermes, making Hermes both a node in the graph and (eventually) the medium connecting every
|
||
other edge — worth being explicit that this is a two-level relationship (Hermes as a peer VM
|
||
*and* as the substrate other peers communicate over), not a design tension to smooth over
|
||
by treating it as an ordinary fifth node.
|
||
- **Artemis** — persistent block storage; the identity/system-metadata store (Phase 8's
|
||
block-fence) and, per D.3, wherever a user's own working data actually lives during a
|
||
session, live here.
|
||
- **The user's own VM** — where the user plays (D.3 step 5).
|
||
- **Console** — the human-facing binding point (D.3 step 4, Milestone 5).
|
||
|
||
### D.3 — The attach/mint flow, end to end
|
||
|
||
1. **Idle loop watches for thumbdrive connects** (`sk_repl_idle()`, already the right hook —
|
||
confirmed 2026-08-27 during the (superseded) `EXPIRE` research: ~1s cadence, already used
|
||
for `xhci_poll_events()`/MSC attach).
|
||
2. **Cert-only authentication — no password, no username.** A presented thumbdrive's identity
|
||
is *entirely* the cert on it. Verification means checking the cert's fields directly (chain
|
||
of trust, most likely signed by *Zuse's own key* — the same `x509_ed25519.c`/
|
||
`ed25519_verify()` primitives Milestone 6 already built and proved work end to end, just
|
||
verifying against Zuse's pubkey instead of the snakeoil intermediate's). **The system has
|
||
no requirement to maintain any single identity itself** — no user directory, no central
|
||
registry of who exists. Each thumbdrive is a fully self-contained, portable identity
|
||
record.
|
||
3. **A dedicated user VM is born** from the identity's own data — "the users/agents/services
|
||
metadata can reside in their capsule and [get] encoded directly into their dictionary when
|
||
they attach and their init.4th executes." This is a *runtime*-constructed capsule from
|
||
drive content, not a build-time-embedded one like `pki:snakeoil-intermediate.der` — a
|
||
materially different mechanism from anything `mkcapsule` does today (gap, see D.4).
|
||
4. **The console VM attaches to the new user VM** — this is exactly Milestone 5's still-open
|
||
"Console/VM key-match binding" item, now with its open question *resolved*: "the bumps on
|
||
a thumbdrive have to fit the holes on the VM... (ACL territory)" — **reuse the existing
|
||
ACL primitive (`acl_mode`/`acl_allow`/`acl_pinned`) for the key-match check, don't build a
|
||
separate mechanism.** Exact comparison semantics still undesigned (see D.4).
|
||
5. **The user plays entirely inside their own VM** — all their FORTH activity happens in
|
||
their own isolated VM context, never Hera's or anyone else's.
|
||
6. **Ongoing minting**: while Zuse is attached/active, a second blank thumb presented can be
|
||
minted with a new named identity (worked example given: `S" CaptBob" MINT`-shaped flow)
|
||
and ejected — this is the still-open `MINT` word from Phase 8's own punch list, now with a
|
||
concrete attach-flow context to design it against.
|
||
7. **Detach**: working state is copied back to the user's own thumb and flushed before eject.
|
||
**Explicitly deferred — stated directly as "going to be a maintenance operation later,"
|
||
not needed now.**
|
||
|
||
### D.4 — Gaps identified 2026-08-27, sorted by whether they're answered
|
||
|
||
**Answered (resolved during capture, not deferred):**
|
||
- Milestone 5's "reuse `ACL-PIN` vs. build a new key-match primitive" question — reuse ACL.
|
||
**Now grounded, not just intuited** (mined 2026-08-27 from `FABRIC-2.md` §C/§D,
|
||
lines ~2884-2918): `acl_pinned`'s one-way-ratchet (`include/vm.h:346`, C-only mutation, no
|
||
FORTH clear path) was *already* identified as the right-shaped precedent for exactly two
|
||
things — Zuse's own one-way "burn" and console-session ownership — but in both cases only
|
||
the *pattern* was precedented, never actually built past word-execution gating. The
|
||
"still genuinely open" framing in `FABRIC-2.md` is accurate as of that writing; today's
|
||
decision closes it, the comparison *semantics* remain the real gap (below).
|
||
- `EXPIRE`'s framing — superseded by "session end = VM detach," see D.2. **Now shown to be
|
||
required, not just cleaner**, by `FABRIC.md` §17.1's own patron/departure table (see D.2's
|
||
addendum above) — TTL was never a valid mechanism for a VM patron in the first place.
|
||
- Regular-user cert chain-of-trust mechanism — signed by Zuse's own key, verified with
|
||
already-built, already-proven Ed25519/X.509 primitives (Milestone 6). No new crypto work
|
||
needed, only a new verification *call site* using existing functions. **Correction,
|
||
2026-08-27 (`FABRIC-3.md` §F.7): partially wrong.** `x509_extract_ed25519_pubkey()`
|
||
deliberately stops at `SubjectPublicKeyInfo` — no signature extraction, no TBS-byte-range
|
||
capture, no chain/issuer check of any kind (`x509_ed25519.h`'s own doc comment). Verifying a
|
||
*signed* cert (not just reading a pubkey out of one) is new DER-walking work, not a pure
|
||
reuse. Trust root is also confirmed separate from the capsule-PKI chain: Zuse's own
|
||
on-device key (`zuse_cert_devblock_t`), never the offline root CA / snakeoil intermediate.
|
||
- Whether the system needs a central user directory — no, by design; each thumbdrive is
|
||
self-contained (reinforces D.3 point 2, was implicit, now explicit).
|
||
- **The concrete target shape for message-bus migration** (mined 2026-08-27 from
|
||
`FABRIC-2.md`, cross-checked against live `capsules/hermes/init.4th`): a message is
|
||
`MSG-CELLS` = 9 cells (`TYPE, FROM, TO, PADDR, PLEN, STADIUM-CELL, SEQ, CH, ORIG-TYPE`), 72
|
||
bytes, payload **out-of-line** (a pointer+length to caller-owned bytes — there was never a
|
||
fixed inline payload field, an earlier open question in `FABRIC-2.md` §23.4 that its own
|
||
design already closed as moot). Live API: `MSG-ALLOC ( heat -- addr|0 )`,
|
||
`CH-ALLOC ( heat -- addr|0 )`, `MSG-DELIVER ( m -- )`, `MSG-DELIVER-ALL ( -- )`. A future
|
||
"thumbdrive attach event" or "cert verify request" message type follows this exact shape —
|
||
allocate, fill `TYPE`/`FROM`/`TO`/`CH`, point `PADDR`/`PLEN` at an out-of-line event struct,
|
||
send. Not built, but no longer an abstract "use messaging someday" — there's a real API to
|
||
target.
|
||
|
||
**Real, unanswered gaps — deferred on purpose (per "answer what we can, defer what we
|
||
can't"):**
|
||
- **How does thumbdrive content actually become a capsule at runtime?** `mkcapsule` only
|
||
ever builds capsules at build time, baked into `capsule_generated.c` — nothing today
|
||
constructs a capsule from data read off a live-attached device. This is new mechanism, not
|
||
a variation on anything that exists. **SCOPED 2026-08-27, `FABRIC-3.md` §F.6**: no new
|
||
birth machinery needed — `capsule_birth_baby()` is already generic; construct a
|
||
self-contained heap-allocated single-entry directory (mirroring `kernel_main.c`'s own
|
||
heap-copy of the compile-time directory) and feed it straight in. Source-location-on-drive
|
||
and read-path details still open.
|
||
- **Exact ACL "bumps and holes" comparison semantics.** Which specific ACL fields, compared
|
||
how, constitute a console-to-VM match? Direction is settled (reuse ACL), the actual
|
||
comparison logic is not designed.
|
||
- **Message-bus migration scope.** Which specific interactions (attach detection, cert
|
||
verify, console bind) move to Hermes messages, and what do those message shapes look like?
|
||
Entirely unscoped, explicitly deferred until after a hardwired version exists to migrate
|
||
*from*. **SCOPED 2026-08-27 (`FABRIC-3.md` §F.15)**: `MSG-DELIVER` already executes arbitrary
|
||
FORTH text on the target VM (`VM-EXEC`), so no new dispatch mechanism is needed. Real target
|
||
is a genuine Console VM (already real: serial+framebuffer+PS2), two-hop flow (Hera→Console
|
||
reports attach; Console→Hera requests the privileged operation with literal arguments).
|
||
Surfaced a substantial new vision detail along the way: blank-media minting is a Console-
|
||
driven interactive onboarding form, not a bare word call (§D.6).
|
||
- **Polymorphic block-boundary behavior for user VMs** — still just the one original sentence
|
||
from 2026-08-25, never elaborated. **CLOSED 2026-08-27 (`FABRIC-3.md` §F.16)**: clarified by
|
||
the user as an architectural principle (the block layer should have no idea what backs it),
|
||
then confirmed already true of `blk_get_buffer()`'s live RAM/raw/disk-cache abstraction —
|
||
not a missing feature.
|
||
- **SSD identity-store scope** — does the system-resident store (the block-fence built in
|
||
Phase 8) ever hold anything about *regular* users, or is it purely Zuse/system bookkeeping
|
||
with regular users staying purely thumbdrive-resident? Best read of what was said: the
|
||
latter, but not stated explicitly enough to treat as settled. **CLOSED 2026-08-27
|
||
(`FABRIC-3.md` §F.16)**: confirmed the latter, both by D.3's own explicit "no central
|
||
registry" statement and structurally by where this session's own scoping put every piece of
|
||
user identity data (§F.6-F.8, all on the user's own drive).
|
||
- **Does a user's VM/session state round-trip across multiple attaches** (unplug, come back
|
||
later, resume exactly where you left off), given detach explicitly copies state back to the
|
||
drive? Implied yes by the detach description, not confirmed directly. **CLOSED 2026-08-27
|
||
(`FABRIC-3.md` §F.16)**: confirmed as a structural consequence of decisions already made —
|
||
the user's pool being their own drive plus `EJECT`'s flush-before-kill means the data simply
|
||
never leaves; no separate resume mechanism needed. **Checked against
|
||
`FABRIC.md` §24's mutation/identity-stability rules (mined 2026-08-27) — no conflict either
|
||
way**: identity is only pinned stable *within one residency*, so a thumbdrive re-attaching
|
||
later and being admitted as a fresh VM patron (possibly reusing an identity value derived
|
||
from its cert/pubkey) is simply a new residency each time. Nothing in the foundational
|
||
theory blocks or answers round-tripping; it stays a pure implementation decision.
|
||
- **Reconcile D.2b's five-node, fully-connected pentagon against D.3 step 4's "console
|
||
attaches to the user VM" phrasing.** The Console is a full peer node (Hera/Hermes/Artemis/
|
||
User-VM/Console, K5), not just a passive relay that binds to one other node — D.3's
|
||
wording undersells this. Which of the ten pentagon edges are real *today* versus aspirational
|
||
once messaging lands is not worked out; only Hera↔UserVM (birth/kill) and
|
||
UserVM↔Artemis-via-Hera's-quota (existing Stadium quota mechanics) are clearly already
|
||
real in some form.
|
||
- **Confirmed baseline before any message-bus migration can start** (mined 2026-08-27,
|
||
`FABRIC-2.md` cross-checked against live `src/starkernel/repl.c` and the xHCI/USB driver):
|
||
today's hotplug→MSC-attach→`homeblocks_sig_check()`→`blk_subsys_attach_device()` chain is
|
||
**100% hardwired, direct C calls, zero messaging at any point** — confirmed empirically, not
|
||
assumed. This is the exact, real starting point "hardwire now, message later" describes;
|
||
there is no partial messaging implementation anywhere in this path to build on yet.
|
||
- **The single biggest hard blocker underneath all of it: SCSI `WRITE(10)` still doesn't
|
||
exist** (`blkio_usb.c` is read-only — Milestone 2's biggest open item, unchanged). Minting
|
||
a blank thumb, and flushing state back on detach, both need real writes to physical USB
|
||
media. None of D.3's flow can run on real hardware until this lands; QEMU testing would need
|
||
to proxy through a writable virtio-blk stand-in the same way Phase 8's cert persistence did
|
||
for Zuse's own identity.
|
||
|
||
**Small, unrelated, real bug found along the way:** the prompt shows `zuse)ok>` (a stray
|
||
trailing paren, "never corrected typo") — should be `(Zuse)ok>`, both parens around the
|
||
identity name, generalizing to any attached identity (e.g. `(CaptBob)ok>`). Independent of
|
||
everything else in this section; safe to fix any time. **CLOSED 2026-08-27 (`FABRIC-3.md`
|
||
§F.16)**: traced for the literal string — it doesn't exist in any live code path today
|
||
(`SK_PROMPT_TEXT` is unconditionally `"ok> "`); the typo lives only in a doc comment
|
||
describing not-yet-implemented behavior. Correct intended form recorded for whenever it's
|
||
actually built.
|
||
|
||
**Why this affects §B:** VM-`COOL` (and any inter-VM Stadium wiring) sits close to this
|
||
design — see D.2's `EXPIRE` supersession above, the concrete example of exactly this risk
|
||
materializing.
|
||
|
||
**Next step:** no implementation yet — this section is capture only, per direct request. Next
|
||
session should turn D.3/D.4 into an actual scoped, ordered plan (the user's own framing:
|
||
"identify gaps, answer what we can, defer what we can't... push the pile out in front of us").
|
||
|
||
---
|
||
|
||
## E. The pile as a graph (2026-08-27)
|
||
|
||
Stated directly: "if this is all documented, scoped and correctly interwoven we can begin
|
||
iterating... I see no reason to not create this as a graph, it might better handle the way I
|
||
think." Auditing D.1-D.4/D.2b against the older Milestone 2/3/5 punch lists (§X in this
|
||
document) confirms the instinct is right, not just stylistic — several items across
|
||
*different* milestones are, on inspection, **literally the same open question**, and one
|
||
single blocker (`WRITE(10)`) gates work in at least four unrelated-looking places. A flat
|
||
punch list hides that; a graph doesn't.
|
||
|
||
```mermaid
|
||
graph TD
|
||
W10["✅ WRITE(10) SCSI support<br/>BUILT + VERIFIED 2026-08-28 (§F.17), all 3 arches"]
|
||
HOTPLUG["✅ Hotplug event surfacing<br/>CLOSED 2026-08-27, M2 (§F.3)"]
|
||
STALL["❌ USB error/stall recovery (M2)<br/>SCOPED 2026-08-27 (§F.14) — full BOT-spec recovery designed"]
|
||
|
||
M6["✅ Milestone 6 — capsule PKI<br/>DONE 2026-08-26"]
|
||
PH8["✅ Phase 8 — Zuse identity<br/>+ block-fence, DONE 2026-08-26"]
|
||
|
||
CERTVERIFY["❌ Zuse-signed user-cert verification (M3)<br/>SCOPED 2026-08-27 (§F.7) — X.509/DER, own trust root"]
|
||
FIRSTTOUCH["❌ First-touch identity→block-range<br/>allocation (M3) — SCOPED 2026-08-27 (§F.11)"]
|
||
BMAPFMT["❌ On-drive block-map format (M3)<br/>SCOPED 2026-08-27 (§F.4) — repurpose blk_meta_t"]
|
||
BMAPWRITE["✅ Write block-map to drive (M3)<br/>CLOSED 2026-08-27 (§F.12) — already built, blocked only on W10"]
|
||
BMAPREAD["✅ Read/validate block-map on insert (M3)<br/>CLOSED 2026-08-27 (§F.13) — already built"]
|
||
MIGSM["🟡 Migration state machine (M3)<br/>partially answered: ACL decides *when*"]
|
||
UNCLEAN["❌ Unclean-removal handling (M3)<br/>DESIGN CLOSED 2026-08-27 (§F.10), code not yet written"]
|
||
|
||
ACLKEY["❌ ACL 'bumps and holes' comparison<br/>semantics — SCOPED 2026-08-27 (§F.2)"]
|
||
WIREBIND["❌ Wire hotplug → capsule_birth_baby() (M5)"]
|
||
BINDSTEP["❌ Extend sk_repl_set_active_vm()<br/>with key-match (M5) — SCOPED 2026-08-27 (§F.9)"]
|
||
DETACH["❌ Detach behavior / new EJECT word (M5)<br/>SCOPED 2026-08-27 (§F.10)"]
|
||
|
||
EXPIRE["✅ EXPIRE reframed: session end<br/>= VM detach via COOL (§B + D.2)"]
|
||
RUNCAP["❌ Runtime capsule construction<br/>from thumbdrive content — SCOPED 2026-08-27 (§F.6)"]
|
||
MINT["❌ Ongoing MINT word (Phase 8 + D.3)<br/>SCOPED 2026-08-27 (§F.8) — GPT dropped, single-device confirmed OK"]
|
||
PENTAGON["📍 Pentagon: Hera/Hermes/Artemis/<br/>User-VM/Console, K5 (D.2b)"]
|
||
MSGSHAPE["✅ Hermes message shape known<br/>(MSG-CELLS, MSG-ALLOC/DELIVER)"]
|
||
MSGMIGRATE["❌ Message-bus migration of<br/>attach/verify/bind (D.4) — SCOPED 2026-08-27 (§F.15)"]
|
||
|
||
SSDSCOPE["✅ SSD identity-store scope<br/>for regular users — CLOSED 2026-08-27 (§F.16)"]
|
||
ROUNDTRIP["✅ Session state round-trip<br/>across attaches — CLOSED 2026-08-27 (§F.16)"]
|
||
POLYBLOCK["✅ Polymorphic block-boundary<br/>behavior — CLOSED 2026-08-27 (§F.16), already true"]
|
||
PROMPTBUG["🐛 (Zuse)ok> prompt typo<br/>CLOSED 2026-08-27 (§F.16) — doc-comment only, not live code"]
|
||
EMPTYBUFBUG["🐛 EMPTY-BUFFERS mass-zeros every<br/>block on every device (§F.17) — found 2026-08-28, NOT FIXED"]
|
||
|
||
W10 -->|unblocked, §F.17| BMAPWRITE
|
||
W10 -->|unblocked, §F.17| MINT
|
||
W10 -->|unblocked, §F.17| DETACH
|
||
W10 -.->|QEMU-verified; real hardware still untested| M8["Milestone 8 — bare metal"]
|
||
|
||
M6 -->|unblocks| CERTVERIFY
|
||
PH8 -->|supplies crypto primitives| CERTVERIFY
|
||
PH8 -->|supplies mint-then-pin precedent| MINT
|
||
|
||
BMAPFMT --> BMAPWRITE
|
||
BMAPFMT --> BMAPREAD
|
||
BMAPFMT --> FIRSTTOUCH
|
||
BMAPREAD -.->|found: sig must relocate off devblock 0, §F.13| MINT
|
||
|
||
CERTVERIFY --> RUNCAP
|
||
RUNCAP --> MINT
|
||
|
||
HOTPLUG --> WIREBIND
|
||
CERTVERIFY -->|"post-identity-authentication" per M5 wording, SCOPED 2026-08-27 §F.5| WIREBIND
|
||
RUNCAP -->|no per-identity VM content until this exists, SCOPED 2026-08-27 §F.5| WIREBIND
|
||
ACLKEY --> BINDSTEP
|
||
WIREBIND --> BINDSTEP
|
||
BINDSTEP --> DETACH
|
||
UNCLEAN -.->|same kill path, no flush| DETACH
|
||
DETACH -->|triggers| EXPIRE
|
||
|
||
MSGSHAPE --> MSGMIGRATE
|
||
WIREBIND -.->|hardwired version must exist first| MSGMIGRATE
|
||
BINDSTEP -.->|hardwired version must exist first| MSGMIGRATE
|
||
CERTVERIFY -.->|hardwired version must exist first| MSGMIGRATE
|
||
PENTAGON -->|which of the 10 edges get messaged| MSGMIGRATE
|
||
|
||
classDef done fill:#1a3,stroke:#333,color:#fff
|
||
classDef blocked fill:#a33,stroke:#333,color:#fff
|
||
classDef unblocked fill:#a83,stroke:#333,color:#fff
|
||
classDef open fill:#666,stroke:#333,color:#fff
|
||
classDef partial fill:#883,stroke:#333,color:#fff
|
||
class M6,PH8,EXPIRE,MSGSHAPE,HOTPLUG,BMAPWRITE,BMAPREAD,SSDSCOPE,ROUNDTRIP,POLYBLOCK,W10 done
|
||
class STALL,FIRSTTOUCH,WIREBIND,BINDSTEP,DETACH,MINT,BMAPFMT,CERTVERIFY,RUNCAP,UNCLEAN,MSGMIGRATE,ACLKEY blocked
|
||
class MIGSM partial
|
||
```
|
||
|
||
**Legend:** ✅ done · 🔓 unblocked-but-not-built · ❌ not started, no open design question left
|
||
(just needs building) · ❓ real open design question, not just missing implementation ·
|
||
🟡 partially addressed · 📍 topology/context node, not a work item itself · 🐛 trivial,
|
||
independent bug. Solid arrows = hard dependency (A must exist before B can be built or
|
||
finished). Dashed arrows = softer "gates/informs" relationships.
|
||
|
||
**What the graph makes visible that the flat lists didn't:**
|
||
- **`WRITE(10)` is a true hub**, not just "the M2 punch list's biggest item" — it independently
|
||
gates the block-map write path, `MINT`, detach/flush-back, *and* all real-hardware testing.
|
||
Landing it once unblocks four separate-looking fronts at once.
|
||
- **`ACLKEY` (the exact ACL comparison semantics) is one node, not two** — Milestone 5's "key
|
||
and lock data shape" and D.4's "bumps and holes" gap are the literal same open question,
|
||
asked in two different sessions/documents. Answering it once closes both.
|
||
- **`RUNCAP` (runtime capsule construction) sits on the critical path to `MINT`** — a gap that
|
||
read as "D.4 leftover detail" is actually load-bearing for the ongoing-minting flow, not a
|
||
side note.
|
||
- **`MSGMIGRATE` has three separate hardwired prerequisites**, all currently unbuilt — the
|
||
message-bus migration can't even start until `WIREBIND`/`BINDSTEP`/`CERTVERIFY` exist to
|
||
migrate *from*, confirming this is correctly the last thing to tackle in this whole area, not
|
||
a parallel track.
|
||
- **Only `SSDSCOPE`, `ROUNDTRIP`, `POLYBLOCK`, and `PROMPTBUG` are genuinely standalone** —
|
||
everything else in the pile connects to at least one other node.
|
||
- **`WIREBIND` was missing two real incoming edges** (found scoping it, §F.5): `CERTVERIFY`
|
||
and `RUNCAP` both gate it, not just `HOTPLUG`. Without a verified identity there's nothing
|
||
to bind to, and without `RUNCAP` there's no per-identity VM content to birth — today's
|
||
`capsule_birth_baby()` only runs fixed, build-time-baked capsules by name, so "spin up that
|
||
identity's VM" had no real mechanism behind it at all until this pass traced it.
|
||
- **`CERTVERIFY` and the capsule-PKI chain are two separate trust roots, not one** (§F.7) —
|
||
easy to conflate since both use Ed25519/X.509, but user certs are signed by Zuse's own
|
||
on-device key while capsules are signed by the offline root CA's snakeoil intermediate.
|
||
Neither chain validates the other; a future audit pass should keep this distinction explicit
|
||
rather than let "PKI" get treated as one undifferentiated mechanism.
|
||
- **`MINT` closed out the GPT question for the whole arc, not just itself** (§F.8) — GPT was
|
||
never actually load-bearing anywhere in this design; every node that referenced it
|
||
(`CERTVERIFY`, `WIREBIND`, `RUNCAP`, `HOTPLUG`) was already treating raw devblock-0 as the
|
||
working format. Dropping GPT converts all of those from "interim, revisit later" to simply
|
||
"the design," retroactively.
|
||
- **`BINDSTEP` already has a live target to gate, not new plumbing** (§F.9) — `USE`
|
||
(`mama_forth_words.c`) is a real, working, currently-unguarded retarget word. The only
|
||
genuinely new code this node needs is a small cross-file accessor for the currently-attached
|
||
device (mirroring `sk_repl_get_active_vm()`'s own precedent) plus the comparison itself.
|
||
- **`UNCLEAN` (M3) and `DETACH` (M5) were the same node wearing two names, in a different way
|
||
than `ACLKEY` was** (§F.10) — not identical questions asked twice, but one shared teardown
|
||
path (`capsule_vm_kill()`, already complete) forked by whether a flush is physically possible.
|
||
Scoping them together closed a long-open M3 item as a side effect of an M5 pass.
|
||
- **Found a real, pre-existing, independent bug while tracing `DETACH`** (§F.10): plain `KILL`
|
||
today never resets `g_repl_active_vm`, so killing the VM a console is currently `USE`'d onto
|
||
leaves a dangling pointer. Reported, not fixed — the new `EJECT`/hot-unplug call sites handle
|
||
this correctly, but the existing `KILL` word's own call sites do not.
|
||
- **`FIRSTTOUCH`'s own punch-list wording was stale** (§F.11) — "claim at `g.total_user_lbn`"
|
||
predates `BMAPFMT`'s distributed-ownership decision and doesn't describe a workable
|
||
mechanism against the current design. A second, unrelated discovery in the same pass —
|
||
`blk_meta_t`'s existing chain fields — turned what looked like a fragmentation problem into
|
||
a non-issue for free.
|
||
- **`BMAPWRITE` was already done** (§F.12) — `BMAPFMT`'s decision to repurpose the existing
|
||
`blk_meta_t` accessors instead of a new table meant "write the map" and "write a block" were
|
||
never actually two operations. The only reason this hasn't been seen working is `WRITE(10)`
|
||
itself, already the graph's known hub.
|
||
- **`BMAPREAD` closed the same way, but tracing it surfaced a real, load-bearing bug that had
|
||
nothing to do with reading at all** (§F.13) — `homeblocks_sig_t` and the block-subsystem's
|
||
own generic disk header physically collide at devblock 0. Every earlier node that touched
|
||
`homeblocks_sig_t` (`CERTVERIFY`, `WIREBIND`, `RUNCAP`, `MINT`) inherited this without anyone
|
||
noticing, because each only traced `homeblocks_sig_check()` itself, never the ordinary attach
|
||
path running alongside it. Fixed now (relocate to devblock 1) rather than left to surface
|
||
later as a real, confusing runtime failure.
|
||
- **`STALL` closes out Milestone 2 entirely** (§F.14) — the last item on that punch list.
|
||
Unlike most nodes this session, this one designs genuinely new protocol machinery rather than
|
||
finding existing infrastructure already covers it: no completion-code distinction and no
|
||
recovery of any kind existed before this pass, only a bounded-timeout safety net.
|
||
- **`MSGMIGRATE` needed no new mechanism, only a routing decision** (§F.15) — `MSG-DELIVER`
|
||
already executes arbitrary FORTH text on the target VM. Scoping it also surfaced a real,
|
||
substantial vision expansion (§D.6): blank-media minting is meant to be an interactive,
|
||
Console-driven onboarding form, connecting forward into both `RUNCAP`'s deferred
|
||
"default personality content" question and `MINT`'s own scope.
|
||
- **`WRITE(10)` (§F.17), the graph's original highest-leverage node, is built and verified —
|
||
the whole arc's single hard blocker is gone.** `BMAPWRITE`, `MINT`, and `DETACH`/`EJECT` all
|
||
move from "scoped, blocked" to "scoped, genuinely buildable." Validating it surfaced a real,
|
||
separate, serious bug (`EMPTYBUFBUG`) — a stark reminder that this pile still has more
|
||
surprises in it than the scoping pass alone can find; the "full codebase audit pass" already
|
||
flagged as deferred (§D.5) has real work waiting for it.
|
||
|
||
**Not yet done:** an ordered plan (which node to attack first, given the graph). Per Captain
|
||
Bob's own framing, that's the next pass — "start asking and answering questions iteratively
|
||
until we've planned as much as possible."
|
||
|
||
## F. Scoped plans (iterative Q&A pass, started 2026-08-27)
|
||
|
||
One node at a time: surface the concrete decisions the node's own doc comments/code don't
|
||
already answer, get a ruling, record it here. This section grows as the pass continues — it's
|
||
the output of "the punch list of punch lists," not a duplicate of §E.
|
||
|
||
### F.1 — `WRITE(10)` (the graph's highest-leverage node)
|
||
|
||
Traced against the live READ(10) implementation in `src/starkernel/usb/xhci.c`
|
||
(`xhci_bot_read_block()` / `xhci_bot_send_read10()` / the `XHCI_XFER_*`/`BOT_CMD_*`/
|
||
`XHCI_NEXT_ACTION_*` deferred-dispatch state machine) before asking anything — the mechanism
|
||
already exists for READ10 and WRITE10 is a direct mirror of it, not new protocol work:
|
||
|
||
- **Data direction flips.** READ10's Data-In stage rides `bulk_in_ring`; WRITE10's Data-Out
|
||
stage must ride `bulk_out_ring` instead (new `XHCI_XFER_BOT_DATA_OUT` /
|
||
`XHCI_NEXT_ACTION_BOT_DATA_OUT`, reusing the existing `xhci_bulk_out_enqueue_and_ring()`
|
||
against `bot_data_buf` rather than the CBW). CBW's `bmCBWFlags` clears the DATA_IN bit
|
||
(host→device) instead of setting it.
|
||
- **New SCSI opcode** — `SCSI_CMD_WRITE10` (0x2A) — plus `BOT_CMD_WRITE10` /
|
||
`BOT_TUR_CHAIN_WRITE10`, mirroring READ10's TUR-preflight chain exactly (same first-command
|
||
UNIT ATTENTION handling on a fresh target).
|
||
- **`blkio_usb.c`'s `usb_blk_write()`** stops being the `BLKIO_ENOSUP` stub: `memcpy` the
|
||
caller's `src` into `xdev->bot_data_buf`, then call the new `xhci_bot_write_block()` — same
|
||
shape as `usb_blk_read()`.
|
||
|
||
**Decisions made 2026-08-27 (iterative Q&A pass):**
|
||
|
||
1. **`read_only` flag in `blkio_info()`** — flips to 0 immediately once `xhci_bot_write_block()`
|
||
is proven in QEMU. No separate "bring-up mode" gate to design or later retire.
|
||
2. **Validation target** — a new disposable QEMU-only test image (e.g.
|
||
`disk/usbwrite-test.img`, following the same disposable-clone convention as
|
||
`disk/artemis-metafence-*.img`), attached via `-drive` + `-device usb-storage,drive=...`.
|
||
Never `disk/artemis.img` itself, matching [[project_disk_images_in_repo]]'s standing rule
|
||
and how Phase 8's block-fence work was validated
|
||
([[project_metadata_fence_design]]'s verification method).
|
||
3. **Scope boundary** — `WRITE(10)` alone is the unit of work. `MINT`'s write-the-identity step
|
||
and `DETACH`'s flush-back-to-thumb step are separate graph nodes; each gets its own scoping
|
||
pass once `WRITE(10)` lands, rather than being pulled into this one preemptively.
|
||
|
||
**Not yet scoped (deferred within this node):** exact CSW failure/residue handling for a
|
||
partial write (READ10's PASS/FAILED logic likely transfers unchanged, not independently
|
||
verified); whether a write needs a follow-up `TEST UNIT READY` or cache-flush SCSI command
|
||
before the transfer is considered durable on real media (QEMU's `usb-storage` backend may mask
|
||
this — a real-hardware-only question, revisit at Milestone 8).
|
||
|
||
### F.2 — `ACLKEY` (the "bumps and holes" comparison semantics)
|
||
|
||
Traced against the actual code before asking anything: `acl_mode`/`acl_allow`/`acl_pinned`
|
||
(`include/vm.h:343-346`, `src/word_source/acl_words.c`) are **`DictEntry` fields — per word,
|
||
not per VM.** There is no VM-level ACL structure anywhere today. So D.3/D.4's "reuse the
|
||
existing ACL primitive" resolution can only ever have meant reuse the *pattern* (a cached
|
||
decision plus a C-enforced one-way ratchet — `if (e->acl_pinned) return;` at every mutating
|
||
primitive) — the actual data has to live somewhere new. `sk_repl_set_active_vm()`
|
||
(`starkernel/repl.c:64`) is confirmed to be exactly what its own doc comment says: an
|
||
unguarded raw pointer swap, no credential concept at all.
|
||
|
||
**Decisions made 2026-08-27 (iterative Q&A pass):**
|
||
|
||
1. **Where the lock lives.** A new dedicated type in its own header —
|
||
`include/starkernel/vm_identity.h`, a `VMIdentity { uint8_t owner_pubkey[32]; uint8_t
|
||
installed; }` — embedded by value as a field on `struct VM`, exactly the way `VMUuid
|
||
stadium_vm_id` (`vm_uuid.h`, its own header, embedded field) already does. **Explicit
|
||
standing instruction from Captain Bob**: don't grow `struct VM`/`vm.h` feature-by-feature
|
||
with more inline fields — when a feature's data has real shape, it gets its own header and
|
||
gets *integrated* as a field, not inlined ad hoc. `VMUuid` was already the right precedent;
|
||
`VMIdentity` follows it. Deliberately holds only the public key, not a seed — unlike
|
||
Zuse's own `zuse_cert_seed`/`zuse_cert_pubkey` pair, a regular VM's lock never needs to
|
||
sign anything itself, so no private material belongs on it at all.
|
||
2. **Zuse override.** Zuse keeps an always-allowed override to attach to any VM (consistent
|
||
with the existing `emergency_console`/`zuse_session` bypass precedent, and avoids the
|
||
bricking risk of Zuse ever being locked out of a VM by its own cert state) — **but the
|
||
override must be a distinct, explicit call path** (e.g. a separate function/word, not a
|
||
silently-taken branch inside the normal bind check), so invoking it requires a deliberate
|
||
acknowledgment rather than happening as a side effect of an ordinary bind attempt. Exact
|
||
acknowledgment UX (a confirm prompt? a two-step word?) is not designed yet — the
|
||
architectural commitment is only that it can't be silent.
|
||
3. **Binding pin.** No pin. `sk_repl_set_active_vm()` stays freely retargetable — matches
|
||
today's Tripod-fleet `USE`-word behavior, and is needed for real cases where a single
|
||
console legitimately moves between VMs mid-session (Zuse doing maintenance work in another
|
||
service VM; a user's console retargeting to update something in a second VM they own). The
|
||
credential check runs at each bind call, not just once at first attach.
|
||
|
||
**Not yet scoped (deferred within this node):** the exact acknowledgment UX for the Zuse
|
||
override path; whether `VMIdentity.installed` needs its own one-way fuse semantics (probably
|
||
yes — the *lock itself* being immutable once set at a VM's birth is a different axis from
|
||
"binding stays retargetable," and shouldn't be conflated with decision 3 above) — worth a
|
||
dedicated look when this is actually built, not decided in this pass.
|
||
|
||
### F.4 — `BMAPFMT` (on-drive block-map format)
|
||
|
||
Worked through conversationally, one step at a time, rather than dumped as a finished design —
|
||
each step below only followed once the previous one was grounded in real code, not assumption.
|
||
|
||
**Step 1 — does a map cell need a state field, not just ownership?** Yes, confirmed against
|
||
existing precedent before answering: the internal Artemis BAM entry
|
||
(`blk_bam_entry_t{allocated, dirty}`, `block_subsystem.h:257-261`) already conflates ownership
|
||
and state in the simplest map this codebase has. More importantly, `MIGSM` and `UNCLEAN` (two
|
||
already-identified, currently-blocked graph nodes) have nowhere else to record "this range is
|
||
mid-migration" or "this range's flush was interrupted" without a state field — this node was
|
||
quietly upstream of both.
|
||
|
||
**Step 2 — should a cell also carry an ACL?** Yes, and it can't reuse `acl_mode`/`acl_allow`/
|
||
`acl_pinned` directly for the same reason `ACLKEY` (F.2) couldn't — those are `DictEntry`
|
||
fields. Decided: the owner field and any future ACL grant should share **the same identity
|
||
representation** `VMIdentity` already established (a pubkey), not a second encoding — keeps
|
||
"identity is one common primitive" (D.5) actually true in practice, not just in name.
|
||
|
||
**Step 3 — ACL check ordering.** "ACL denial is the fast exit path" — grounded directly
|
||
against `vm.c:611-624`'s live word-execution ACL check: a cached `acl_allow` bit is
|
||
checked *first*, before any other work, and denial short-circuits immediately. A block cell's
|
||
ACL needs the identical shape at its top: one cheap cached allow bit, checked before range/
|
||
state/owner logic runs.
|
||
|
||
**Step 4 — buffering/granularity, and a major discovery.** Confirmed: this kernel already
|
||
buffers a whole 4 KiB devblock on any block touch (`LOAD` and friends), and
|
||
`BLK_META_PER_BLOCK` (`block_subsystem.h:83`, `341u /* 341×3 ~= 1023, padded to 1024 */`)
|
||
already packs **exactly** 3×1 KiB Forth-block metadata slices into that same devblock's spare
|
||
1 KiB — the "3-block cluster + 1 KiB metadata" shape volunteered in conversation *already
|
||
exists as `blk_meta_t`*, with real, wired, non-stub accessors (`blk_get_meta()`/
|
||
`blk_set_meta()`, cached, dirty-tracked, `block_subsystem.c:1175-1207`). **Grepped and
|
||
confirmed it has zero callers anywhere in the codebase** — fully built, fully unused. Its
|
||
existing "Security & ownership" 40 bytes (`owner_id`/`permissions`/`acl_block`/`signature[2]`)
|
||
predate and directly conflict with both the anti-POSIX principle and `VMIdentity`'s
|
||
pubkey-based model — flagged, not silently reused.
|
||
|
||
**Decisions made 2026-08-27:**
|
||
|
||
1. **`BMAPFMT` is not a new structure — it's repurposing `blk_meta_t`.** No separate on-drive
|
||
block-map table gets built. Ownership/ACL/state travel *with* the block itself
|
||
(distributed), not in a centralized table (the shape Milestone 3's original wording
|
||
pictured). One I/O gets payload and map info together; nothing separate to keep in sync.
|
||
2. **Consequence flagged, not silently absorbed:** `homeblocks_sig_t`'s reserved
|
||
`blockmap_offset`/`blockmap_devblocks` fields (`homeblocks_sig.h:93-95`) become unnecessary
|
||
under this decision — that header reserved space for a centralized table this design no
|
||
longer needs. Needs a comment update in that file once this is actually built, not left
|
||
silently stale.
|
||
3. **Replacement for the 40-byte "Security & ownership" block** (same budget the old
|
||
`owner_id`/`permissions`/`acl_block`/`signature[2]` occupied; nothing else in `blk_meta_t`
|
||
moves):
|
||
```
|
||
uint8_t owner_fp[8]; /* truncated fingerprint of owner's VMIdentity pubkey, not the
|
||
full 32 bytes -- keeps this cheap per-block; full pubkey
|
||
resolves via the drive's own identity record. */
|
||
uint8_t acl_allow; /* cached fast-deny bit, checked first -- vm.c:611-624's exact
|
||
pattern, applied to a block instead of a word. */
|
||
uint8_t acl_reserved[7]; /* explicitly undecided -- deliberate slack per "flexibility
|
||
until we understand the recipe," not a placeholder to fill
|
||
reflexively. */
|
||
uint64_t reserved_future; /* untouched budget, same reasoning. */
|
||
```
|
||
`flags` (already existing, already generic) does double duty as the **state** field from
|
||
Step 1 — no new field, just future-defined bit values (`CLAIMED`/`MIGRATING`/`STALE`/etc.).
|
||
Everything else in `blk_meta_t` (`checksum`, timestamps, `content_type`, hash, chain links,
|
||
`app_data[15]`) is untouched.
|
||
4. **Allocation granularity**: claims quantize to whole devblocks (3 Forth blocks), matching
|
||
the existing packing — a cell never needs to describe partial-devblock ranges.
|
||
|
||
**Not yet scoped (deferred within this node):** the actual allow-list/grant shape beyond the
|
||
single fast-deny bit (lands in `acl_reserved`, once designed); the specific `flags` bit
|
||
values for each state; whether `blk_get_meta()`/`blk_set_meta()` need new FORTH word wrappers
|
||
or stay C-only like `zuse_cert_seed`'s "no FORTH access" precedent; the actual repurposing
|
||
edit to `block_subsystem.h`/`.c` itself (this pass produced the field design, not the code
|
||
change).
|
||
|
||
### F.5 — `WIREBIND` (breadcrumb only — followed into `RUNCAP` instead, 2026-08-27)
|
||
|
||
Traced against the live code before asking anything: `capsule_birth_baby()`
|
||
(`capsule_birth.c:473-614`) only ever births a fixed, build-time-baked `(p)` capsule looked up
|
||
by name — `BIRTH`'s own mapping is literally `S" Artemis" → "artemis:init.4th"`
|
||
(`mama_forth_words.c:216`). The PERSONALITY stage — where a baby would load *its own*
|
||
per-identity content — is a confirmed no-op today: `dispatch_init_forth(new_vm)`
|
||
(`capsule_birth.c:605`) does nothing, with its own comment stating "per-VM block storage is
|
||
M9 scope; no-op until then."
|
||
|
||
**Finding: the M5 punch-list wording — "spin up or re-attach *that identity's VM*" — has no
|
||
mechanism behind it yet.** Two real prerequisites were missing from the §E graph and are now
|
||
added:
|
||
- **`CERTVERIFY`** — M5's own wording calls this step "post-identity-authentication"; there's
|
||
nothing to bind an identity to without a verified identity first. **Decision: required, not
|
||
optional** — matches the literal M5 wording and avoids birthing VMs for unauthenticated
|
||
drives before any lock/key concept (`ACLKEY`/`BINDSTEP`) exists to protect them.
|
||
- **`RUNCAP`** — without it, "that identity's VM" can only ever mean the one generic,
|
||
same-for-everyone capsule that happens to exist at build time. Real per-identity content is
|
||
blocked on `RUNCAP` (or M9's per-VM block storage, a larger and further-out mechanism).
|
||
|
||
**Working assumption recorded for whenever this node is actually scoped:** a `WIREBIND`-
|
||
triggered birth always creates a fresh VM (matches `capsule_birth_baby()`'s existing shape).
|
||
Recognizing a *returning* identity and re-attaching to a prior VM/session is `ROUNDTRIP`'s
|
||
question, not `WIREBIND`'s — deliberately not folded in here.
|
||
|
||
**Not scoped further — by design.** Rather than decide `WIREBIND`'s generic-capsule shape in
|
||
the abstract, the pass followed the real blocker down into `RUNCAP` itself (§F.6). Revisit
|
||
`WIREBIND` once `CERTVERIFY` and `RUNCAP` both exist.
|
||
|
||
### F.6 — `RUNCAP` (runtime capsule construction from thumbdrive content)
|
||
|
||
Traced against `capsule.h`/`capsule_birth.h` before assuming new birth machinery was needed.
|
||
Two things, read directly from the code, changed the shape of this node:
|
||
|
||
1. **`capsule_birth_baby()` is already generic** — it takes `dir`/`descs`/`names`/`arena` as
|
||
plain parameters; it is not wired to the compile-time `capsule_descriptors[]` global in any
|
||
way. `mkcapsule` sets `.desc_capacity = capsule_count` always (`mkcapsule.c:955`) — despite
|
||
`capsule.h`'s own comment calling this "fixed at compile time for **Phase A**" (implying
|
||
headroom for a later phase), there is zero spare capacity reserved today, and no
|
||
runtime-append mechanism exists into the baked-in directory. Not a blocker, though —
|
||
2. **...because `RUNCAP` doesn't need to touch the baked-in directory at all.** It can
|
||
construct its own self-contained, heap-allocated `CapsuleDirHeader` + one `CapsuleDesc` +
|
||
one `CapsuleNameEntry` + a small arena — the exact same shape `kernel_main.c:729-750`
|
||
already builds when it heap-copies the compile-time directory before Mama's own birth, just
|
||
sized for a single entry instead of the whole set — fill the arena with raw FORTH source
|
||
text read off the thumbdrive, and hand that straight to the existing, unmodified
|
||
`capsule_birth_baby()`. No directory-merge logic, no changes to the birth path itself.
|
||
|
||
**Decisions made 2026-08-27:**
|
||
|
||
1. **Mechanism: heap-built single-entry capsule directory, not baked-in-directory extension.**
|
||
Confirmed safe and minimal per the trace above.
|
||
2. **Location of the source content on the drive.** `homeblocks_sig_t`'s `blockmap_offset`/
|
||
`blockmap_devblocks` fields are already dead weight per `BMAPFMT`'s decision (§F.4) that
|
||
ownership/ACL/state travel with `blk_meta_t`, not a centralized on-drive table. **Repurpose
|
||
those two fields** (rename to `identity_src_offset`/`identity_src_devblocks`) to point at
|
||
this identity's raw FORTH init/personality source instead of adding new fields — reuses
|
||
already-reserved header space, keeps `homeblocks_sig_t`'s byte budget untouched. (Comment
|
||
update in `homeblocks_sig.h` still owed when this is actually built, same as `F.4` already
|
||
flagged.)
|
||
3. **Scope of a user's pool — clarified 2026-08-27, corrects the framing this node started
|
||
from:** a user's pool is **their entire thumbdrive by default**, plus any system-resident
|
||
device blocks they additionally claim, first-come-first-served, plus the required metadata.
|
||
The thumbdrive itself is never contested — it's already theirs, no claiming logic needed
|
||
there. `FIRSTTOUCH`'s "claim a new range at `g.total_user_lbn`" logic (M3) applies only to
|
||
the *system*-device extension, not the drive itself. This simplifies the personality-source
|
||
question in decision 2 above: no allocation contention to resolve, just a pointer into the
|
||
drive's own metadata partition.
|
||
|
||
**Not yet scoped (deferred within this node):** the exact read path (which forth-blocks within
|
||
the metadata partition, how `identity_src_devblocks` bounds a variable-length FORTH source);
|
||
whether the source is plain FORTH text authored once at mint time or something a user can edit
|
||
and have re-read on next attach; the interaction with `CERTVERIFY` (does verification gate
|
||
reading this content, or only gate the resulting birth). **Partially settled by `MINT`'s own
|
||
scoping pass (§F.8, decision 2):** this region's first devblock holds a new
|
||
`user_identity_seed_t` record, and the FORTH source itself occupies the remaining devblocks —
|
||
narrows "the exact read path" question above, though the read-side code is still unwritten.
|
||
|
||
### F.7 — `CERTVERIFY` (Zuse-signed user-cert verification)
|
||
|
||
Traced against the actual crypto/cert code before assuming D.4's "no new crypto work needed"
|
||
framing was still accurate. Two files settle the trust-root question outright:
|
||
`x509_ed25519.h`'s own doc comment ("no signature verification, no chain validation, no
|
||
extension parsing — this only answers *what public key does this cert claim to hold*") and
|
||
`zuse_cert_devblock_h`'s design (Zuse's own on-device Ed25519 keypair, persisted in the system
|
||
block-fence, `FABRIC-3.md` §C). **Confirmed: a regular user's cert has a completely separate
|
||
trust root from the capsule-PKI chain** — it's signed by Zuse's own locally-resident key, never
|
||
the offline root CA / snakeoil intermediate. No chain walk is needed at verify time; it's one
|
||
signature check against a pubkey the kernel already holds.
|
||
|
||
**Decisions made 2026-08-27 (iterative Q&A pass):**
|
||
|
||
1. **Format: X.509/DER**, not a simple custom struct (the `zuse_cert_devblock_t` precedent
|
||
would have been simpler, but explicitly not chosen). The cert's `SubjectPublicKeyInfo`
|
||
holds the owner's Ed25519 pubkey (same field `x509_extract_ed25519_pubkey()` already reads).
|
||
2. **Drive binding: the cert's `serialNumber` field holds the 16-byte `drive_uuid`** (matching
|
||
`homeblocks_sig_t.drive_uuid`) rather than adding a new X.509v3 extension — `serialNumber`
|
||
is an early, shallow `SEQUENCE` member (actually simpler to reach than
|
||
`SubjectPublicKeyInfo`, which the existing parser already walks past), so this avoids
|
||
needing any extension-parsing support at all. Binding to the drive means a valid cert's
|
||
bytes alone, copied onto a different physical drive, will not verify there.
|
||
3. **Revocation: explicitly deferred.** Matches this project's incremental-scope precedent
|
||
elsewhere (capsules have a `REVOKED` flag; user-cert revocation is separate, unbuilt
|
||
surface). `CERTVERIFY` v1 only checks magic/structure, the embedded algorithm OID
|
||
(Ed25519, rejecting anything else — same check `x509_extract_ed25519_pubkey()` already
|
||
does for the pubkey field), the `serialNumber`-vs-`drive_uuid` match, and the signature
|
||
itself.
|
||
4. **Real added scope, flagged honestly rather than assumed away:** verifying (not just
|
||
reading) this cert requires genuinely new DER-walking code beyond
|
||
`x509_extract_ed25519_pubkey()` — that function stops at `SubjectPublicKeyInfo` by design.
|
||
`CERTVERIFY` additionally needs: the raw `TBSCertificate` byte range (the exact bytes
|
||
Zuse's signature covers), the outer `signatureValue` `BIT STRING` contents, and the
|
||
`serialNumber` `INTEGER` bytes. Verification itself is then one call:
|
||
`ed25519_verify(zuse_pubkey, tbs_bytes, tbs_len, signature)`, where `zuse_pubkey` comes
|
||
from this system's own `zuse_cert_devblock_t`, not from anything read off the user's drive.
|
||
5. **Location on drive:** `homeblocks_sig_t.cert_offset`/`cert_devblocks` — already reserved
|
||
for exactly this ("the CA-signed cert blob") since that header was first designed. No
|
||
header changes needed here, unlike `RUNCAP`'s repurposing of the block-map fields.
|
||
|
||
**Not yet scoped (deferred within this node):** the actual new DER TBS/signature-extraction
|
||
function's design (this pass decided *what* it must extract, not its implementation); whether
|
||
`x509_ed25519.c` grows this capability in place or a sibling file holds it; how `MINT` (which
|
||
shares this exact cert format on the signing side) constructs the DER bytes in the first place
|
||
— DER *encoding* has never been needed in this kernel before, only decoding.
|
||
|
||
### F.8 — `MINT` (the last piece of the original Tripod vision)
|
||
|
||
Two real dependencies surfaced while scoping this node that weren't in the §E graph, both
|
||
resolved before any design decision was needed:
|
||
|
||
1. **GPT.** The earlier-decided drive layout (`project_usb_thumbdrive_gpt_layout`:
|
||
~1GB GPT metadata partition + remainder) has no parser or writer anywhere in kernel code.
|
||
**Dropped entirely, per direct instruction (2026-08-27) — not deferred, decided against.**
|
||
The raw `homeblocks_sig_t`-at-devblock-0 layout every other node in this arc (`CERTVERIFY`,
|
||
`WIREBIND`, `RUNCAP`, `HOTPLUG`'s own `sig_start_fblock=0`) already treats as an interim
|
||
stand-in is now the **permanent** format. The "small metadata region + remainder is user
|
||
storage" *proportion* from the old GPT design still holds — it's just a plain devblock-count
|
||
convention within one raw device now (metadata = devblocks `0..N` covering the sig header +
|
||
cert + identity-source regions; everything past that is the user's own general block-storage
|
||
pool directly, consistent with `FIRSTTOUCH`'s pool clarification in §F.6). No GPT-parser
|
||
node needed in the graph at all — this closes that thread rather than deferring it.
|
||
**Correction, 2026-08-27 (§F.13): `homeblocks_sig_t` itself moved off devblock 0** to
|
||
devblock 1 (forth-block 4) once a real collision with the block-subsystem's own generic
|
||
disk header was found — the "devblocks `0..N`" metadata-region framing above still holds,
|
||
it just now starts one devblock later than stated here.
|
||
2. **Single-device USB driver.** `xhci_dev_t`'s BOT/MSC state (`device_ctx`, `bulk_in_ring`,
|
||
`bulk_out_ring`, `bot_data_buf`) are singular fields, not per-slot arrays — only one USB MSC
|
||
device is usable at a time today. This looked like it might conflict with the vision's
|
||
"while Zuse is active, mint a second blank thumb" (implying two drives attached at once).
|
||
**Confirmed no conflict**: Zuse's own identity is system-resident, loaded from Artemis's own
|
||
top-of-device block-fence at boot (`kernel_main.c:634-687`), never from a removable thumb.
|
||
`MINT` only ever needs the one target blank drive attached — today's single-device path is
|
||
sufficient, no driver work needed for this node.
|
||
|
||
**Decisions made 2026-08-27:**
|
||
|
||
1. **The minted identity gets a real keypair**, not just a pubkey badge. `virtio_rng` +
|
||
`ed25519_keygen()` generate a fresh seed/pubkey pair for the new identity, stored on *their*
|
||
thumbdrive (never system-resident — matches `SSDSCOPE`'s "regular users stay
|
||
thumbdrive-resident" read). Keeps the door open for a user VM to sign something itself
|
||
later, per `D.5`'s "identity is common to every VM" direction, without redesigning the cert
|
||
format again.
|
||
2. **Storage layout** (direct extension of `RUNCAP`'s already-decided `identity_src_offset`/
|
||
`identity_src_devblocks` region, §F.6 — no further header growth needed): that region's
|
||
*first* devblock holds a new `user_identity_seed_t` record (`magic`/`version`/`seed[32]`/
|
||
`pubkey[32]`/`crc` — same shape as `zuse_cert_devblock_t`, a new type rather than reusing
|
||
that Zuse-specific one, per this project's own "dedicated header per real-shaped feature"
|
||
convention); the *remaining* devblocks in that region hold the raw FORTH personality/init
|
||
source `RUNCAP` reads at birth.
|
||
3. **`drive_uuid`**: a fresh, independent 16 random bytes from `virtio_rng` at mint time (not
|
||
derived from the identity seed — two separate random draws, matching `homeblocks_sig_t`'s
|
||
own field being logically "which physical drive," not "whose identity").
|
||
4. **Cert construction**: builds the exact `CERTVERIFY` (§F.7) format — `SubjectPublicKeyInfo`
|
||
= the new pubkey, `serialNumber` = `drive_uuid`, signed with **Zuse's own seed** via
|
||
`ed25519_sign()` (real signing capability already exists, Phase 8). This is the first
|
||
confirmed caller that needs DER *encoding*, not just the decoding `x509_ed25519.c` already
|
||
has — flagged in `F.7` already, restated here since `MINT` is where it's actually needed.
|
||
5. **Header assembly**: `hdr_crc` via the existing `homeblocks_sig_compute_crc()` — no new
|
||
checksum logic, this function already does exactly what's needed.
|
||
|
||
**Not yet scoped (deferred within this node):** the default personality/init FORTH content a
|
||
freshly minted identity starts with (shared open question with `RUNCAP`, §F.6: mint-time-fixed
|
||
vs. user-editable); a monotonic-ns source for `minted_time_ns` — checked, and there is no
|
||
working precedent anywhere in this codebase today (`CapsuleDesc.created_ns` is hardcoded to `0`
|
||
at generation time, not a real example to follow); the `S" name" MINT` FORTH word's own
|
||
stack/argument shape; whether `MINT` refuses to overwrite a drive that already reads as a
|
||
recognized home-blocks drive (`HOMEBLOCKS_SIG_OK`), mirroring `WRITE(10)`'s
|
||
refuse-on-non-blank-media posture — reasonable by analogy, not decided here. The hard blocker
|
||
is unchanged and already modeled in the graph: `WRITE(10)` still doesn't exist, so `MINT`
|
||
cannot write anything to real or QEMU-emulated USB media yet regardless of the above.
|
||
|
||
### F.9 — `BINDSTEP` (extending `sk_repl_set_active_vm()` with the `ACLKEY` check)
|
||
|
||
Traced against the live retarget path before assuming new plumbing was needed:
|
||
`USE` (`mama_forth_words.c:430-480`) is the real, already-working console-retarget word —
|
||
`S" name" USE` looks a VM up by name (case-insensitive) and calls `sk_repl_set_active_vm()`
|
||
directly, completely unguarded today. `BINDSTEP` is concretely "add the `ACLKEY` (§F.2)
|
||
comparison to this one call site," not a new mechanism.
|
||
|
||
**Real gap found, not just a policy question:** the "re-verify live" approach (decided below)
|
||
needs access to whatever drive is currently attached, but `usb_blk_dev`/`xdev`
|
||
(`repl.c:107-108`) are function-static inside `sk_repl_idle()` — invisible to `USE`, which
|
||
lives in `mama_forth_words.c`. `BINDSTEP` needs a small new accessor exposing "the currently
|
||
attached home-blocks device," mirroring the existing `sk_repl_get_active_vm()` precedent
|
||
(`repl.h:67-69`) for exactly this kind of cross-file REPL-state access.
|
||
|
||
**Decisions made 2026-08-27 (iterative Q&A pass):**
|
||
|
||
1. **Check freshness: re-verify live, not cached.** Every `USE` call re-runs the
|
||
drive-check/`CERTVERIFY` path against whatever is currently attached, rather than trusting a
|
||
pubkey cached at original attach time. `USE` is a rare, human-triggered, interactive
|
||
operation — the repeated DER-parse + `ed25519_verify()` cost is a non-issue, and this avoids
|
||
any staleness question if a drive was swapped without a clean detach.
|
||
2. **VMs with no `VMIdentity` installed yet (`installed=0`) — Hera/Hermes/Artemis today, until
|
||
`D.5`'s per-VM-identity work lands — stay freely targetable, no check applied.** Preserves
|
||
`USE`'s current working behavior exactly; refusing on `installed=0` would be a real
|
||
regression today for VMs that were never in scope for this lock in the first place.
|
||
3. **For an `installed=1` target:** `BINDSTEP` refuses (matching `USE`'s existing clean
|
||
early-refusal style for not-found/dead-VM cases) whenever either no drive is currently
|
||
attached, or the freshly re-verified cert's owner pubkey doesn't match the target's
|
||
`VMIdentity.owner_pubkey`. No drive attached is a plain refusal, not a special case — it
|
||
simply means "no identity presented," which can never match.
|
||
4. **Zuse's override UX: deferred**, per direct instruction — this pass confirms the
|
||
architectural constraint from `F.2` still stands (a distinct, explicit call path, not a
|
||
branch folded into `USE`'s own logic) without committing to the exact word/interaction yet.
|
||
|
||
**Not yet scoped (deferred within this node):** the new accessor's exact name/signature; the
|
||
refusal message text (matching `USE`'s existing console-message style); whether a refused
|
||
`BINDSTEP` attempt should produce an audit-log record (this codebase has a real precedent for
|
||
this — `capsule_parity_log_birth_failed()` and friends, `parity.c` — but nothing says a
|
||
security-relevant refusal here needs the same treatment; not decided).
|
||
|
||
### F.10 — `DETACH` (the last M5 node) — and `UNCLEAN` closed alongside it
|
||
|
||
Traced `capsule_vm_kill()` (`capsule_birth.c:295-364`) fully before scoping, per D.2's own
|
||
framing that detach should reuse it: it's real and complete — evicts the Stadium patron cell
|
||
via `stadium_evict()`, `vm_cleanup()`+`sf_free()`s the VM, marks it `DEAD`, logs via
|
||
`capsule_parity_log_kill()`, is idempotent, and refuses to kill Hera. `DETACH` doesn't need a
|
||
new teardown mechanism, only a caller for this one plus the flush/bookkeeping around it.
|
||
|
||
**Real finding: D.3's "flush before eject" only makes physical sense as a deliberate
|
||
pre-removal step — once a drive is physically pulled, there's nothing left to write to.**
|
||
That splits this into two genuinely different paths, and per direct instruction this pass
|
||
scopes **both**, closing the previously-separate, long-open `UNCLEAN` node (M3) at the same
|
||
time rather than leaving it for later:
|
||
|
||
- **Graceful path (new `EJECT` word, no name conflict found):** the drive is still physically
|
||
present. Flush, then kill, then it's safe to remove.
|
||
- **Abrupt path (the existing `bot_msc_detach_pending` hot-unplug signal, already wired and
|
||
consumed in `sk_repl_idle()` for `blk_subsys_detach_device()`):** the drive is already gone.
|
||
No flush is attempted — data since the last flush is lost, which *is* correct unclean-removal
|
||
semantics, matching what every real OS does on a surprise USB yank. This is `UNCLEAN`'s
|
||
entire remaining scope, now closed as "same kill path as `EJECT`, minus the flush attempt."
|
||
|
||
**One genuinely new, small piece of state required for both paths** (not a new mechanism, a
|
||
missing link): a way to know *which VM* corresponds to the currently home-blocks-attached
|
||
drive. `BINDSTEP`'s live re-verify-and-compare approach (§F.9) only works while the drive is
|
||
still physically present and readable — useless for the abrupt path, where the device is
|
||
already gone by the time the signal fires. Decided: `WIREBIND` (§F.5, itself not yet built)
|
||
must set a small tracked `VMUuid` — "the VM currently attached via the home-blocks USB path" —
|
||
at successful birth; both `EJECT` and the hot-unplug handler read and clear it. No new registry
|
||
machinery needed beyond this: `capsule_vm_registry_get(vm_id, &entry)` (already exists) resolves
|
||
the `VMUuid` to a live entry, and `capsule_vm_kill(entry.name)` (already exists, takes a name
|
||
not an ID — chain the two rather than adding a kill-by-id variant) does the rest.
|
||
|
||
**Decisions made 2026-08-27:**
|
||
|
||
1. **`EJECT` (graceful):** resolve the tracked attached-VM id → entry; `blk_vm_flush_all(vm)`
|
||
(already exists, same function `sk_repl_idle()`'s own periodic sync already calls) while the
|
||
VM is still alive; if the console's active VM (`sk_repl_get_active_vm()`) is this same VM,
|
||
reset it to Hera (`sk_repl_set_active_vm(NULL)`) *before* teardown — necessary, not
|
||
optional, see the dangling-pointer finding below; then `capsule_vm_kill(entry.name)`; clear
|
||
the tracked attached-VM state. `capsule_vm_kill()`'s own idempotency covers the case where
|
||
the VM was already gone by some other path — no extra guard needed.
|
||
2. **Hot-unplug (`UNCLEAN`, closed):** identical steps minus the flush — skipped outright, not
|
||
attempted-and-ignored-on-failure, since the device is confirmed gone by the time this signal
|
||
fires. Same active-VM reset-before-kill step applies.
|
||
3. **`W10`'s edge to `DETACH` in the §E graph is specifically about the `EJECT` path's flush
|
||
step** — the hot-unplug/`UNCLEAN` path never touches `WRITE(10)` at all (nothing is written
|
||
during an unclean removal), so `UNCLEAN` itself is *not* blocked on `WRITE(10)`, only
|
||
`EJECT` is. Worth keeping distinct now that both share one node.
|
||
|
||
**Separate, already-existing bug found while tracing this, reported per standing instruction
|
||
(not fixed):** `capsule_vm_kill()` never touches `g_repl_active_vm`. Today, plain `KILL` on a
|
||
VM the console is currently `USE`'d onto leaves `g_repl_active_vm` dangling — the REPL would
|
||
fault on the next command dispatched through it. This is real and reachable *today*,
|
||
independent of `DETACH`/`EJECT`; decision 1 above only fixes it for the new `EJECT`/hot-unplug
|
||
call sites, not the pre-existing plain `KILL` word.
|
||
|
||
**Not yet scoped (deferred within this node):** the exact new tracked-state variable's
|
||
name/location (logically `WIREBIND`'s to own, but `WIREBIND` itself isn't built yet); whether
|
||
`EJECT` needs a name argument or always targets "whatever's currently attached" (leaning toward
|
||
the latter, given the single-USB-device constraint confirmed in `F.8` means there's never more
|
||
than one candidate — not committed here); the console message text distinguishing a graceful
|
||
eject from an unclean one in the transcript.
|
||
|
||
### F.11 — `FIRSTTOUCH` (identity → system-device block-range allocation)
|
||
|
||
Traced `g.total_user_lbn` (`block_subsystem.c:634,650,655,677,688,753`) and `blk_meta_t`'s
|
||
existing chain fields before assuming the M3 wording was still accurate. **Finding: it isn't,
|
||
fully.** "Claim a new range at `g.total_user_lbn`" predates `BMAPFMT`'s decision (§F.4) to drop
|
||
the centralized block-map in favor of distributed per-block ownership. `total_user_lbn` is a
|
||
global high-water mark that only grows when an entirely new *device* attaches (RAM, Artemis's
|
||
disk, a USB drive) — it says nothing about claiming space *within* an already-attached,
|
||
fixed-size device. `FIRSTTOUCH`'s real job is scanning Artemis's own system-resident device's
|
||
existing `blk_meta_t` records for unowned devblocks, not extending any counter.
|
||
|
||
Also confirmed live and unrelated to `BMAPFMT`'s repurposing: `blk_meta_t`'s "Link/chain
|
||
support" fields (`prev_block`/`next_block`/`parent_block`/`chain_length`,
|
||
`block_subsystem.h:232-236`) are real, general-purpose, block-number-granularity linkage — not
|
||
device-chain-specific — and completely untouched by `F.4`'s field redesign.
|
||
|
||
**Decisions made 2026-08-27:**
|
||
|
||
1. **A claim is a scattered chain, not a required contiguous run.** Reuses the existing
|
||
`prev_block`/`next_block`/`chain_length` fields directly — immune to fragmentation, and
|
||
this mechanism already exists, unused, waiting for exactly this. The claim's identity to
|
||
the caller is its chain-head devblock number; `owner_fp` is written to *every* devblock in
|
||
the chain (not just the head), so ownership is directly readable from any member block
|
||
without first walking the chain — consistent with `BMAPFMT`'s whole point of making
|
||
ownership locally readable per-block.
|
||
2. **Discovery: full linear scan, every time, no cached index.** Matches `BMAPFMT`'s own
|
||
explicit "no centralized table, nothing extra to keep in sync" philosophy exactly. One scan
|
||
pass serves both questions this node needs answered — "does this identity already own a
|
||
claim" (`owner_fp` match) and "which devblocks are free" (`owner_fp` all-zero) — collected
|
||
together in the same walk, not two separate scans.
|
||
3. **Failure mode: fail outright, no partial claim.** If the device doesn't have enough free
|
||
devblocks (scattered or not) to satisfy the request, return an error and let the caller
|
||
decide — matches this codebase's existing return-code convention throughout (`capsule_birth_baby()`
|
||
and friends), no new partial-allocation semantics invented.
|
||
4. **Allocation unit: whole devblocks**, already decided in `BMAPFMT` (§F.4, decision 4) —
|
||
`FIRSTTOUCH` doesn't revisit this, just inherits it.
|
||
|
||
**Not yet scoped (deferred within this node):** the actual function signature/call site (this
|
||
pass decided the algorithm, not its C interface); who calls `FIRSTTOUCH` and when — most
|
||
likely `MIGSM` (the migration state machine, M3, still 🟡 partial) deciding "this identity
|
||
needs N more devblocks," but that call site doesn't exist yet either; whether a first-touch
|
||
scan should skip/short-circuit once the caller's own already-attached thumbdrive still has
|
||
room (matching the pool-scope clarification in §F.6: the thumb is the default pool, this
|
||
node is specifically the *overflow* case) — implied yes by that clarification, not explicitly
|
||
re-confirmed here.
|
||
|
||
### F.12 — `BMAPWRITE` (closed — already built, blocked only on `WRITE(10)`)
|
||
|
||
Traced `blk_set_meta()`/`cache_writeback()`/`blk_flush()` fully before assuming any new write
|
||
path was needed. **Finding: this node is already done.** `blk_set_meta()`
|
||
(`block_subsystem.c:1198-1210`) marks the same cache-buffer entry's `meta_dirty` flag that
|
||
ordinary block-data writes use; `cache_writeback()` (`block_subsystem.c:274-285`) — called from
|
||
both `blk_flush()`'s targeted and flush-all paths, the exact function `blk_vm_flush_all()` and
|
||
`SAVE-BUFFERS` already exercise — packs the in-memory `blk_meta_t[]` array back into the
|
||
devblock's metadata region (`meta_to_slice()`) and writes the *whole* 4 KiB devblock (data +
|
||
metadata together, since they share one physical buffer) via `write_devblock_4k()`, real and
|
||
unstubbed all the way down to `dev->write()`.
|
||
|
||
**Why this closes rather than just narrows:** the M3 item as originally written assumed a
|
||
*separate* block-map structure that would need its own bespoke write mechanism. `BMAPFMT`'s
|
||
decision (§F.4) to repurpose the already-existing, already-wired `blk_meta_t` accessors instead
|
||
of building a new table means "writing the map" and "writing a block" were never actually two
|
||
different operations — the infrastructure that already existed for ordinary block persistence
|
||
already covers metadata identically, with zero new code. The only reason this hasn't been
|
||
observed working end-to-end is the same hard blocker already modeled in the graph: `WRITE(10)`
|
||
doesn't exist yet, so `dev->write()` never actually reaches physical media for a USB-attached
|
||
drive today. Nothing else stands between this and working.
|
||
|
||
**Handoff note for `BMAPREAD`'s own future pass:** the read side (`cache_load_devblock()`,
|
||
`block_subsystem.c:308-322`) already unpacks and validates metadata (`meta_from_slice()`,
|
||
magic-checked, resets to a clean default on a bad/missing magic) on every devblock load — very
|
||
likely the same "already built, not a separate item" outcome, but not confirmed here; that
|
||
node may have its own distinct question (e.g. whether a home-blocks drive needs an explicit
|
||
validate-on-insert step beyond ordinary lazy per-devblock read-through-cache), left for its own
|
||
pass rather than assumed away.
|
||
|
||
### F.13 — `BMAPREAD` (closed — already built) — and a real header collision found and fixed
|
||
|
||
Confirmed the handoff note from `F.12`: `cache_load_devblock()` (`block_subsystem.c:308-322`)
|
||
already unpacks and validates every devblock's metadata on load (`meta_from_slice()`,
|
||
magic-checked, resets cleanly on a bad/missing magic) — the read side is exactly as "already
|
||
built, not a separate item" as the write side. Same closure, no new mechanism needed.
|
||
|
||
**But tracing "validate on insertion" turned up a real, previously-unflagged conflict that has
|
||
nothing to do with `blk_meta_t` at all.** `blk_subsys_attach_device()` — the *general* attach
|
||
path every device goes through, USB drives included — always calls `blk_format_or_load_disk()`
|
||
(`block_subsystem.c:581-613`), which reads devblock 0 looking for the block-subsystem's own
|
||
`'STFR'`/v2 volume header. `homeblocks_sig_t`'s magic is `'LAHB'`, not `'STFR'`, so a minted
|
||
home-blocks drive always reads as "unrecognized" here and lands in `BLK_FMT_PROVISIONAL` —
|
||
and `blk_get_buffer()` (`block_subsystem.c:894`) **refuses all writes** to a `PROVISIONAL`
|
||
slot until `blk_subsys_confirm_format()` is called. That function
|
||
(`blk_commit_format()`, `block_subsystem.c:546-571`) writes a fresh STFR header to the *same*
|
||
devblock 0 — which would physically overwrite `homeblocks_sig_t`. Neither outcome (stay
|
||
`PROVISIONAL` forever, or get STFR-formatted and lose the identity header) works. This was
|
||
invisible to `CERTVERIFY`/`WIREBIND`/`MINT` because each only ever traced
|
||
`homeblocks_sig_check()` itself, never the generic attach path running alongside it.
|
||
|
||
**Decision made 2026-08-27:** `homeblocks_sig_t` relocates to **devblock 1** (forth-block 4),
|
||
leaving devblock 0 for the generic STFR/v2 header untouched — no changes to the already-tested
|
||
block-subsystem code. `homeblocks_sig_check()` already takes its starting forth-block as a
|
||
plain parameter (`sig_start_fblock`, not hardcoded), so this is a call-site change, not a
|
||
format-logic change. `MINT` (§F.8) must also run the ordinary `blk_subsys_confirm_format()`
|
||
path at mint time (writing a ordinary fresh STFR/v2 header at devblock 0) so the drive is
|
||
immediately writable through the normal block-buffer path, in addition to writing
|
||
`homeblocks_sig_t`/cert/identity-source starting at devblock 1. `F.8`'s own text has been
|
||
corrected in place to point here rather than restate this.
|
||
|
||
**Not yet scoped (deferred within this node):** whether `MINT`'s STFR-format-then-sig-write
|
||
sequence needs a specific ordering/atomicity guarantee (e.g. what state a drive is left in if
|
||
power is lost between the two writes) — real question, not addressed here; the exact
|
||
`sig_start_fblock` value call sites (`CERTVERIFY`, `WIREBIND`, `RUNCAP`, `MINT`) all need
|
||
updating from their currently-recorded devblock-0-relative assumption to devblock-1, a
|
||
mechanical follow-up once any of this is actually coded, not re-litigated here.
|
||
|
||
### F.14 — `STALL` (the last M2 item) — full BOT-spec recovery designed
|
||
|
||
Traced the actual completion-code handling before designing anything. **Real finding: every
|
||
transfer completion, control or bulk, shares one gate** — `if (code != XHCI_COMPLETION_CODE_SUCCESS)
|
||
{ console_println("xhci: control transfer failed"); break; }` (`xhci.c:1267-1270`). A STALL
|
||
isn't distinguished from any other failure today, and there is no recovery of any kind — no
|
||
xHCI Reset Endpoint command, no USB `CLEAR_FEATURE(ENDPOINT_HALT)`. There is a real safety net
|
||
though: `xhci_bot_wait_for_idle()` (`xhci.c:831-839`) has a bounded iteration count and returns
|
||
`BOT_STATUS_TIMEOUT` rather than hanging forever, since the generic bail-out never resets
|
||
`bot_cmd_kind`. So today's failure mode is "clean timeout, endpoint left permanently wedged for
|
||
everything after it" — not a crash, but not recoverable either.
|
||
|
||
Also confirmed by inspection: none of the pieces real recovery needs exist yet —
|
||
`XHCI_COMPLETION_CODE_STALL_ERROR` isn't defined (only `_SUCCESS`), nor are the `RESET_ENDPOINT`
|
||
(xHCI spec value 14) or `SET_TR_DEQUEUE_POINTER` (value 16) command TRB types. Per direct
|
||
instruction, full recovery is designed now rather than deferred to Milestone 8.
|
||
|
||
**Decisions made 2026-08-27 — the recovery sequence, mirroring the existing bounded-retry
|
||
precedent `bot_tur_retries`/`XHCI_BOT_TUR_MAX_RETRIES` already establishes for TUR:**
|
||
|
||
1. **Distinguish the completion code.** Add `XHCI_COMPLETION_CODE_STALL_ERROR` (6, per xHCI
|
||
spec) alongside the existing `_SUCCESS` (1). The shared completion-code gate branches: a
|
||
plain non-success/non-stall code keeps today's behavior (log and bail); a stall specifically
|
||
enters the new recovery path below instead of falling straight to the generic message.
|
||
2. **xHCI-level reset (two new command TRB types + two new command functions, mirroring the
|
||
existing `xhci_cmd_disable_slot()`/`xhci_cmd_configure_endpoint()` shape exactly):**
|
||
- `xhci_cmd_reset_endpoint(dev, slot_id, ep_id)` — new `XHCI_TRB_TYPE_RESET_ENDPOINT_CMD`
|
||
(14), transitions the stalled endpoint from Halted back to Stopped in the xHC's internal
|
||
context. Which endpoint is "the stalled one" is already known — it's whichever of
|
||
`bulk_in_ep_addr`/`bulk_out_ep_addr` the in-flight transfer purpose
|
||
(`BOT_DATA_IN`/`BOT_DATA_OUT`/`CBW_SENT`/`CSW_RECEIVE`) was using, both already tracked
|
||
fields.
|
||
- `xhci_cmd_set_tr_dequeue_pointer(dev, slot_id, ep_id, new_dequeue, dcs)` — new
|
||
`XHCI_TRB_TYPE_SET_TR_DEQUEUE_POINTER_CMD` (16), repositions the ring's dequeue pointer
|
||
past the failed TRB so the next enqueued transfer resumes cleanly.
|
||
3. **USB-level clear (reuses the existing control-transfer infrastructure already built for
|
||
`GET_DESCRIPTOR`/`SET_CONFIGURATION` — no new transfer-stage machinery, just a new request
|
||
payload):** send `CLEAR_FEATURE(ENDPOINT_HALT)` (standard request, `wValue=0`, `wIndex=`
|
||
the stalled endpoint address) to clear the device's own halt condition and reset its data
|
||
toggle.
|
||
4. **Escalation on a second stall (or if step 3 itself stalls):** issue Bulk-Only Mass Storage
|
||
Reset (BOT class request, `bmRequestType=0x21`, `bRequest=0xFF`, no data stage — again the
|
||
existing control-transfer machinery, a different request payload) followed by
|
||
`CLEAR_FEATURE(ENDPOINT_HALT)` on *both* bulk endpoints (BOT spec 5.3.4's full procedure),
|
||
then retry the original command from scratch — the same "fresh target" restart shape the
|
||
existing TUR-chain retry already uses.
|
||
5. **Bounded, not infinite:** a new `bot_stall_recoveries` counter capped at a new
|
||
`XHCI_BOT_STALL_MAX_RECOVERIES`, exact value TBD but same shape as `XHCI_BOT_TUR_MAX_RETRIES`.
|
||
Exhausting it sets `bot_last_status = BOT_STATUS_FAILED` and `bot_cmd_kind = BOT_CMD_NONE`
|
||
explicitly — a clean terminal failure signaled to `xhci_bot_wait_for_idle()`'s caller,
|
||
rather than relying purely on the outer timeout the way today's un-recovered stall does.
|
||
|
||
**Not yet scoped (deferred within this node):** the exact `XHCI_BOT_STALL_MAX_RECOVERIES` value;
|
||
whether a `WRITE(10)`-specific residual-data concern exists on a stalled Data-Out stage (a
|
||
partially-written SCSI command's recovery semantics may differ subtly from a stalled Data-In
|
||
read — not analyzed here, `WRITE(10)` itself still doesn't exist); real-hardware validation is
|
||
explicitly out of reach until Milestone 8, same caveat `WRITE(10)`'s own CSW-residue question
|
||
carries (§F.1) — this design is believed spec-correct but has only QEMU (which rarely if ever
|
||
issues real stalls) to test against for now.
|
||
|
||
### D.5 — Scope expansion (2026-08-27): identity is common to every VM, not just users
|
||
|
||
Surfaced while scoping `ACLKEY`, stated directly: *"the whole object is to deliver a
|
||
functional Tripod plus identities common to all VM's... this means now that we have to
|
||
create certificates for Hermes, Artemis and Console along with Zuse and CaptBob's
|
||
'thumbdrives.'"* This reframes `VMIdentity` (F.2 above) from a user-VM-specific mechanism into
|
||
the **general per-VM identity primitive** — every node in the pentagon (Hera, Hermes,
|
||
Artemis, a user's own VM, the Console) is expected to eventually carry one, not only VMs born
|
||
from an attached thumbdrive. Doesn't change F.2's design, just its intended reach — worth
|
||
remembering when `VMIdentity` actually gets built, so it isn't accidentally scoped narrower
|
||
than this.
|
||
|
||
### F.3 — `HOTPLUG` (closed)
|
||
|
||
Re-checked directly against the live code before asking anything, per Captain Bob's own
|
||
standing note not to "grep-sample" — read `sk_repl_idle()` (`starkernel/repl.c:82-190`) and
|
||
the `bot_msc_attach_pending`/`bot_msc_attached`/`bot_msc_detach_pending` doc comments in
|
||
`xhci_driver.h:244-265` in full.
|
||
|
||
**Finding: closed.** The original Milestone 2 ask was "decide and implement where the hotplug
|
||
event surfaces... likely a callback registered by whatever owns the home-blocks logic, not
|
||
xHCI code calling into `block_subsystem.c` directly." What's built is a flag-set-by-xHCI,
|
||
flag-consumed-by-`sk_repl_idle()` pattern — not literally a registered callback, but the
|
||
`bot_msc_detach_pending` field's own doc comment states the goal directly: *"keeps xhci.c
|
||
decoupled from block_subsystem.c"* — and that decoupling is real and working, confirmed live
|
||
on all three arches (attach: `blkio_usb_open_msc()` → `homeblocks_sig_check()` →
|
||
`blk_subsys_attach_device()`; detach: `blk_subsys_detach_device()`, symmetric shape).
|
||
|
||
**Confirmed 2026-08-27:** close this node as done. No separate design work remains on
|
||
`HOTPLUG` itself — the answer for `WIREBIND` (the node depending on it) is not "build a new
|
||
hotplug-detection mechanism," it's "reuse this one."
|
||
|
||
**Handoff note for `WIREBIND`'s own future scoping pass:** the existing attach point already
|
||
calls `homeblocks_sig_check()`, which already distinguishes a recognized home-blocks drive
|
||
(`HOMEBLOCKS_SIG_OK`) from blank/foreign/corrupt media (`HOMEBLOCKS_SIG_BLANK`/
|
||
`BAD_VERSION`/`BAD_CRC`/`READ_ERROR`). That result is the natural branch point for `WIREBIND`:
|
||
a recognized drive continues into cert-verify + `capsule_birth_baby()`; a blank/foreign one
|
||
stays plain read-only block storage, matching the reasoning already documented for the
|
||
`WRITE(10)` refuse-on-blank-media logic. Not built yet — `WIREBIND` still needs its own Q&A
|
||
pass — but it doesn't start from zero.
|
||
|
||
**Two new standing items that came with this, both explicitly deferred (not part of the
|
||
current iterative pass, recorded so they aren't lost):**
|
||
- **A full codebase audit pass**, using this same iterative Q&A workflow, to scavenge for more
|
||
"same gap hiding in two places" opportunities the way `ACLKEY` turned out to be one gap
|
||
wearing two names (Milestone 5's "key/lock shape" and D.4's "bumps and holes"), and the way
|
||
`WRITE(10)` turned out to gate four separate-looking fronts at once. Not scoped, not
|
||
started — a future pass of its own.
|
||
- **v2.0.0 release target**: once Tripod is functional end to end, every VM has its identity
|
||
mechanism, and the codebase/documentation are clean per the above audit, tag a v2.0.0
|
||
release. Stated as the destination this whole planning arc is walking toward, not an
|
||
immediate next step.
|
||
|
||
### F.16 — The last four standalone nodes: `SSDSCOPE`, `ROUNDTRIP`, `POLYBLOCK`, `PROMPTBUG`
|
||
|
||
Closes out every node in §E's graph except `WRITE(10)` itself. Three of the four close by
|
||
cross-referencing decisions already made this session, not new tracing; `POLYBLOCK` needed the
|
||
user's own clarification first, since it had never been elaborated beyond one sentence.
|
||
|
||
**`SSDSCOPE` — closed.** "Does the system-resident store (Phase 8's block-fence) ever hold
|
||
anything about *regular* users, or is it purely Zuse/system bookkeeping?" This was already
|
||
answered, just not cross-referenced: D.3 states directly — *"the system has no requirement to
|
||
maintain any single identity itself — no user directory, no central registry of who exists.
|
||
Each thumbdrive is a fully self-contained, portable identity record."* This session's own later
|
||
scoping confirms it structurally rather than just by assertion: `CERTVERIFY`/`RUNCAP`/`MINT`
|
||
(§F.6-F.8) all put a user's cert, keypair, and personality source *on their own thumbdrive*,
|
||
never on Artemis's system-resident fence. `FIRSTTOUCH` (§F.11) does let a user's claim extend
|
||
onto system-resident devblocks, but that's anonymous per-block ownership (`blk_meta_t.owner_fp`,
|
||
a pubkey fingerprint) written directly on the claimed blocks themselves — not a lookup-by-name
|
||
directory entry anywhere. The system-resident fence holds exactly one identity record type:
|
||
Zuse's own (`zuse_cert_devblock_t`).
|
||
|
||
**`ROUNDTRIP` — closed.** "Does a user's session state round-trip across multiple attaches?"
|
||
Implied yes by D.3's detach description, now confirmed structurally rather than left implicit:
|
||
since a user's pool *is* their own thumbdrive by default (§F.6, decision 3) and `EJECT`
|
||
(§F.10) flushes working state back to wherever those blocks physically live before killing the
|
||
VM, the data simply never leaves the drive between attaches. Reattaching the same drive and
|
||
re-birthing via `WIREBIND`+`RUNCAP` naturally resumes from wherever the blocks were left — no
|
||
separate "session resume" mechanism needs building; the drive's own persistence *is* the
|
||
round-trip mechanism, a consequence of decisions already made rather than a new one.
|
||
|
||
**`POLYBLOCK` — closed, reframed as an existing principle, not a missing feature.** Clarified
|
||
directly by the user: the block abstraction at this level should have no idea what backs it —
|
||
"a re-usable, polymorphic definition of a storage device." Traced against `blk_get_buffer()`
|
||
(`block_subsystem.c:866-900`) before concluding anything: it already returns a uniform `uint8_t*`
|
||
regardless of whether the backing LBN resolves to RAM, a raw in-memory device
|
||
(`blk_subsys_add_raw_device()`), or a real disk-cache-backed device — callers (every FORTH
|
||
block word, `LOAD`, etc.) never know or care which. The one backend-specific behavior found
|
||
(`BLK_FMT_PROVISIONAL` write-refusal, disk-backed slots only) doesn't violate this — it's
|
||
encapsulated *within* the uniform interface (an ordinary failed `blk_get_buffer()` call), not
|
||
leaked as caller-visible backend-awareness. **This principle is already true of the live code**,
|
||
confirmed rather than assumed.
|
||
|
||
**`PROMPTBUG` — closed, and found to be less than originally described.** Traced for the
|
||
literal string before closing: no runtime code anywhere prints `"zuse)ok> "` today —
|
||
`SK_PROMPT_TEXT` (`repl.c:50`) is unconditionally `"ok> "`; there is no `zuse_session`-gated
|
||
prompt-text branch in the live REPL at all yet (only `emergency_console` gating exists,
|
||
`repl.c:412,459`). The typo lives in a **doc comment** describing not-yet-implemented intended
|
||
behavior (`repl.c:372`), not in running code. Closed as: whenever a Zuse-specific prompt is
|
||
actually implemented, use `"(Zuse)ok> "` (both parens, generalizing to any attached identity —
|
||
`"(CaptBob)ok> "`), matching the already-corrected form recorded in `D.4`. The comment itself
|
||
is a trivial one-line fix whenever code in this file is next touched — not applied here,
|
||
per standing instruction against unrequested code changes.
|
||
|
||
**Every node in the §E graph is now scoped or closed except `WRITE(10)` itself** — the one
|
||
remaining hard blocker gating real execution of nearly everything captured across this entire
|
||
`§F` pass.
|
||
|
||
### D.6 — Console-driven interactive mint onboarding (vision capture, 2026-08-27)
|
||
|
||
Surfaced live while scoping `MSGMIGRATE`'s message-target question (§F.15) — capture only, per
|
||
this arc's own "capture first, plan second" discipline; not designed in detail here.
|
||
|
||
**Stated directly:** the Console VM (real hardware ownership: serial + framebuffer + PS2
|
||
keyboard) is where blank-media minting actually happens interactively, not a bare programmatic
|
||
`MINT` call. Zuse's own thumbdrive becoming physically present is itself what starts
|
||
authentication (no separate manual step). For **blank** media specifically, the system pulls
|
||
up an interactive "user StarshipOS (LithosAnanke+StarForth) Mint" onboarding form on the
|
||
Console:
|
||
|
||
```
|
||
Full Name:
|
||
Address 1:
|
||
Address 2:
|
||
City:
|
||
State/Province:
|
||
Country:
|
||
Metadata: (hexdump of some encrypted metadata OR QR code later)
|
||
```
|
||
|
||
**Why this matters beyond `MSGMIGRATE` itself:** this directly informs two already-open
|
||
questions elsewhere rather than sitting alone —
|
||
- **`RUNCAP`'s deferred "default personality content" question (§F.6)**: this onboarding data
|
||
is very likely *part of* what a freshly minted identity's personality/init source encodes,
|
||
not a separate concern. Not confirmed as a final answer — flagged as the likely connection.
|
||
- **`MINT`'s own scope (§F.8)**: minting a new identity now has a real interactive-collection
|
||
step in front of the cert/keypair/header-writing mechanics already scoped there. The Console
|
||
becomes an active participant in `MINT`, not just Zuse triggering it standalone.
|
||
|
||
**Not designed here, explicitly deferred (per the user's own "later" on the metadata field):**
|
||
the "hexdump of some encrypted metadata OR QR code" field's actual mechanism, encoding, and
|
||
purpose; the exact validation/editing UX for the form itself (can a field be corrected before
|
||
submit? what happens on a blank/skipped field?); how collected form data actually reaches
|
||
`MINT`'s execution (addressed at the mechanism level only, in `MSGMIGRATE` below — the message
|
||
carries the values, the *encoding* of the metadata field itself is separate and unaddressed).
|
||
|
||
### F.15 — `MSGMIGRATE` (the last real design question)
|
||
|
||
Traced the actual Hermes mechanism before designing anything, rather than treating "message"
|
||
as an abstract placeholder. **Real finding: `MSG-DELIVER` (`capsules/hermes/init.4th:200-203`)
|
||
does `MSG-TO@ IDX>NAME VM-EXEC`** — a message's out-of-line payload (`PADDR`/`PLEN`) is
|
||
**arbitrary FORTH source text, executed on the destination VM via the ordinary interpreter**,
|
||
not a structured/typed RPC call. Migrating an interaction to Hermes needs no new dispatch
|
||
machinery at all — only a decision about what FORTH text goes where.
|
||
|
||
Also worth being explicit about, since it reframes why this migration is even worth doing:
|
||
every "VM" in this system is a Forth VM instance living inside **one kernel address space**,
|
||
not a separate OS process. There is no correctness reason `WIREBIND`/`BINDSTEP`/`CERTVERIFY`
|
||
*must* become messages — Hera's REPL can already call their C functions directly, today, once
|
||
built. The stated reason to migrate anyway is architectural discipline: routing through Hermes
|
||
gives uniform heat-tracking/`STADIUM-CELL` participation in Compudynamics and an audit trail
|
||
via `MSG-SEQ`, matching the standing "nothing is done until it's messaging" completion
|
||
criterion — not solving an isolation problem that doesn't exist here.
|
||
|
||
**Decisions made 2026-08-27:**
|
||
|
||
1. **Real target: the Console VM**, not a self-addressed message to Hera. There is a real
|
||
Console VM today (serial + framebuffer + PS2 keyboard) — this isn't waiting on D.2b's
|
||
pentagon to become real, it already is. Corrects this pass's own first framing (a
|
||
self-addressed-to-Hera fallback was floated and explicitly rejected in favor of this).
|
||
2. **The flow is two hops, not one:**
|
||
- **Hop 1 (Hera → Console):** Hera's existing hardwired attach detection
|
||
(`homeblocks_sig_check()`, unchanged — this is Milestone 2 hardware-driver work, not
|
||
something that itself becomes a message; nothing exists to receive a message before
|
||
detection happens) results in a message to the Console VM reporting the outcome —
|
||
recognized identity, or blank/foreign media.
|
||
- **Console-side behavior (not itself a message):** for a recognized identity, the Console
|
||
proceeds toward the existing cert-verify/bind flow; for blank media, the Console runs the
|
||
interactive mint-onboarding form (§D.6) using its own owned hardware (framebuffer/PS2).
|
||
- **Hop 2 (Console → Hera):** once the Console has what it needs — either confirmation to
|
||
proceed with a recognized identity, or the completed onboarding fields for a new one — it
|
||
sends a message *back* to Hera to actually execute the privileged operation
|
||
(`MINT`/`WIREBIND`/`BINDSTEP`), since Hera owns the VM registry these operations mutate.
|
||
3. **Payload shape: arguments encoded as literals directly in the payload text**, not a
|
||
dedicated no-argument word reading global state. E.g. the Console's hop-2 message to Hera
|
||
for a mint would look like a human-typed command line with the collected fields pushed as
|
||
string literals ahead of the word call (`S" Robert James" S" 123 Main St" ... MINT`) — more
|
||
flexible than a fixed no-argument word, at the cost of the Console needing to build that
|
||
text safely (not addressed here — see deferred list).
|
||
|
||
**Not yet scoped (deferred within this node):** the exact literal-encoding/escaping mechanism
|
||
for building a multi-field FORTH command string safely on the Console side (a real concern —
|
||
untrusted-ish human-typed onboarding text landing in FORTH source text merits care, not
|
||
assumed away); the precise message `TYPE` values for each of the three migrated interactions
|
||
(this pass decided the mechanism and direction, not the constant catalog); whether
|
||
`CERTVERIFY`'s own re-verify-live behavior (`BINDSTEP`, §F.9) changes shape once it's Console-
|
||
mediated rather than a direct Hera-side check; how this reconciles with `PENTAGON`'s still-open
|
||
"which of the 10 edges are real today" question (`D.4`) — this node answers it for exactly the
|
||
Hera↔Console edge, not the other nine.
|
||
|
||
|
||
|
||
### F.17 — `WRITE(10)` implemented and verified (2026-08-28) — the graph's last hard blocker, closed
|
||
|
||
Implemented exactly as scoped in `F.1`, a direct mirror of the existing `READ(10)` machinery
|
||
with the data direction flipped: new `XHCI_XFER_BOT_DATA_OUT`/`XHCI_NEXT_ACTION_BOT_DATA_OUT`
|
||
states, `xhci_bot_send_write10()`/`xhci_bot_write_block()`/`xhci_bot_write_data_out()`
|
||
(`xhci.c`), new `SCSI_CMD_WRITE10` opcode and `BOT_CMD_WRITE10`/`BOT_TUR_CHAIN_WRITE10` enum
|
||
values (`xhci_driver.h`, kept as fully separate fields from the `bot_read10_*` ones rather than
|
||
renamed/reused, so the already-tested `READ10` path is never touched). `usb_blk_write()`
|
||
(`blkio_usb.c`) is real now, no longer the `BLKIO_ENOSUP` stub — `read_only` in `blkio_info()`
|
||
flips to `0`, per `F.1` decision 1, now that it's proven.
|
||
|
||
**Verified live, all three architectures, with a genuine cold-reboot round-trip** — not just a
|
||
same-session read, the real proof this class of change needs: `BLK-CONFIRM-FORMAT`'s BAM/reloc
|
||
zero-page writes and an explicit block-content write both completed via clean `WRITE10` cycles
|
||
(CSW PASS every time), then a full kernel rebuild + fresh boot read the written byte back
|
||
correctly — `amd64=65`, `aarch64=170`, `riscv64=201`, each at LBN 32734 on a disposable
|
||
`disk/usbwrite-test.img` attached via QEMU `usb-storage`, matching `F.1` decision 2's validation
|
||
plan exactly.
|
||
|
||
**One real methodology finding, not project-specific:** the disposable test drive must be
|
||
hotplugged via QMP *after* boot reaches `ok>`, never attached at QEMU launch — attaching before
|
||
`xhci_bringup()`'s controller reset means the port already reads as connected when the driver
|
||
starts, so no fresh Port Status Change event ever fires and hotplug detection looks broken even
|
||
though it isn't (already documented in `project_xhci_milestone_2d_polling`, re-confirmed here
|
||
the hard way on the first attempt).
|
||
|
||
**A real, serious, pre-existing bug found and reported (not fixed) during validation:**
|
||
`EMPTY-BUFFERS` (`empty_all_buffers()`, `block_words.c:148-158`) does not implement standard
|
||
Forth-79 semantics — it force-writes zero to *every* block on *every* attached device, rather
|
||
than discarding cache-slot assignments without writing. Calling it during testing (reasonably
|
||
assuming standard semantics) mass-zeroed blocks across the whole system, including
|
||
`disk/artemis.img` — ~3.3 million bytes, essentially its entire content region. Restored from
|
||
git, confirmed byte-identical to `HEAD`. Avoided for the rest of validation (detach/reattach via
|
||
QMP used instead to force a fresh device read, which is non-destructive).
|
||
|
||
**Consequence for the rest of the graph:** `W10` was the single hub gating `BMAPWRITE`
|
||
(already independently confirmed working, §F.12), `MINT`, and `DETACH`/`EJECT`'s flush step —
|
||
all three are now genuinely unblocked, not just scoped. Nothing about their own design changes;
|
||
they can now actually be built and tested rather than only designed. Real hardware (Milestone
|
||
8) remains untested — this verification is QEMU-only, per `F.1`'s own acknowledged scope.
|
||
|
||
### D.7 — Birth-by-message-only, universal to Zuse and user VMs (vision, 2026-08-28)
|
||
|
||
Stated directly, prompted by planning the freshly-minted-`zuse.img`-thumbdrive-to-`(Zuse)ok>`
|
||
flow: **"Hera gives birth only by request once messaging and storage are functional and
|
||
should display no coupling outside of messages. This will include 'user' VMs too."** A
|
||
sharper, universal restatement of the standing "nothing is done until it's messaging"
|
||
completion criterion (D.1) — not a new principle, but a concrete commitment that this
|
||
specific, next flow should go straight to the message-only shape rather than pass through
|
||
a hardwired interim first, since messaging (`Hermes`) and storage (`Artemis`) are both now
|
||
genuinely live.
|
||
|
||
**Concretely, for the mint-`zuse.img`-thumbdrive-to-`(Zuse)ok>` flow:**
|
||
1. Hera's own hardware polling (`sk_repl_idle()`/`xhci_poll_events()`) is unavoidable —
|
||
she's the only VM whose C code has xHCI MMIO access. This stays.
|
||
2. But Hera must not *decide or act* on what she detects. Per direct instruction: **detection
|
||
ownership moves to a running Artemis VM instance** — a real change from what `HOTPLUG`
|
||
(closed) and `WIREBIND` (§F.5, still just a breadcrumb) already built/scoped, both of which
|
||
currently have Hera deciding and acting directly. Hera's role narrows to relaying the raw
|
||
hardware event; Artemis owns the identity-verification decision and the birth *request*.
|
||
3. Grounded, not assumed: block LBN space is one flat, global address space
|
||
(`block_subsystem.c`'s `g.total_user_lbn`), not per-VM — any VM's dictionary can already
|
||
read any LBN once told which one, no new C-level read plumbing needed for the
|
||
identity-check content itself. The gap is coordination (when, which LBN), not capability.
|
||
|
||
**Real, load-bearing prerequisite surfaced by tracing, not assumed:** Artemis is not
|
||
persistently alive today. Every boot this session confirms she's birthed for her own 4.6
|
||
migration self-test, then explicitly killed (`KILL: Artemis dead`) before the REPL ever
|
||
reaches `ok>`. For any message to reach a running Artemis instance, she has to actually stay
|
||
born — a real, new requirement this direction depends on, not something `HOTPLUG`/`WIREBIND`
|
||
ever needed before.
|
||
|
||
**What this reshapes, not yet re-scoped:**
|
||
- `WIREBIND` (§F.5) was deliberately left a breadcrumb, not fully designed, specifically
|
||
because "wire hotplug → `capsule_birth_baby()` directly" is exactly the hardwired coupling
|
||
this principle now rules out — this direction is the real answer that breadcrumb was
|
||
waiting for, not a contradiction of it.
|
||
- `MSGMIGRATE` (§F.15) scoped a two-hop Hera↔Console flow (Hera reports attach, Console
|
||
requests the privileged operation). Artemis is a **new participant**, not a replacement —
|
||
identity verification/storage (Artemis's role) and human-facing onboarding UX (Console's
|
||
role, §D.6) are both real and distinct; the full flow likely needs both, reconciled together
|
||
rather than re-litigated as either/or.
|
||
- Applies universally, per direct instruction — the same message-only shape governs ordinary
|
||
user-VM birth too, not a Zuse-specific carve-out.
|
||
|
||
**Not yet scoped:** whether Artemis becoming persistent changes her own self-test's shape or
|
||
timing; the exact message types/payloads for Hera→Artemis (raw attach event) and
|
||
Artemis→Hera (birth request); how/whether Console fits into this specific flow versus the
|
||
already-scoped `MSGMIGRATE` two-hop; whether `CERTVERIFY` (§F.7) itself should now run as
|
||
Artemis-mediated logic rather than a bare function call. Capture only, per this arc's own
|
||
established discipline — design happens in its own pass.
|