Traced two hidden dependencies before scoping: (1) no GPT parser/writer exists anywhere in kernel code, and per direct instruction GPT is dropped entirely rather than deferred -- the raw homeblocks_sig_t-at-devblock-0 layout every other node (CERTVERIFY/WIREBIND/RUNCAP/HOTPLUG) already treated as interim becomes the permanent format. (2) xhci_dev_t's BOT/MSC state is singular, not per-slot, raising a concern about the vision's "mint a second thumb while Zuse is active" implying two simultaneous USB devices -- resolved: Zuse's identity is system-resident (loaded from Artemis's own block-fence at boot), never thumbdrive-based, so MINT only ever needs one attached target drive. Decisions: minted identities get a real keypair (virtio_rng + ed25519_keygen), stored via a new user_identity_seed_t record occupying RUNCAP's identity_src region's first devblock; drive_uuid is a separate random draw; cert construction reuses CERTVERIFY's exact format, signed with Zuse's own seed -- first confirmed need for DER encoding, not just decoding.
1931 lines
138 KiB
Markdown
1931 lines
138 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.
|
||
|
||
- [ ] Implement CBW/data/CSW for SCSI WRITE(10) — this is where the earlier "read/write,
|
||
unquestionable" requirement actually gets satisfied. Still the single biggest functional
|
||
gap in the xHCI driver — blocks writing to a real USB thumb drive at all (`blkio_usb.c` is
|
||
read-only today specifically because of this).
|
||
|
||
- [ ] Implement basic error/stall recovery (CSW failure status, endpoint stall clear) — at
|
||
minimum enough to not wedge the controller on a single bad transfer.
|
||
|
||
### 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.**
|
||
|
||
- [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.
|
||
|
||
- [ ] Implement writing the block-map to a drive.
|
||
|
||
- [ ] Implement reading/validating the block-map from a drive on insertion.
|
||
|
||
- [ ] 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.
|
||
|
||
- [ ] Decide and implement unclean-removal handling (Section U's explicitly flagged open
|
||
question — never answered) — at minimum, detect a mid-flush disconnect via Milestone 2e's
|
||
disconnect signal and decide what state that leaves affected blocks in.
|
||
|
||
### 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.
|
||
|
||
- [ ] Implement detach behavior on console disconnect or VM teardown.
|
||
|
||
### 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.
|
||
- **`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*.
|
||
- **Polymorphic block-boundary behavior for user VMs** — still just the one original sentence
|
||
from 2026-08-25, never elaborated.
|
||
- **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.
|
||
- **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. **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.
|
||
|
||
**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/>(Milestone 2 — biggest single blocker)"]
|
||
HOTPLUG["✅ Hotplug event surfacing<br/>CLOSED 2026-08-27, M2 (§F.3)"]
|
||
STALL["❌ USB error/stall recovery (M2)"]
|
||
|
||
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)"]
|
||
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)"]
|
||
BMAPREAD["❌ Read/validate block-map on insert (M3)"]
|
||
MIGSM["🟡 Migration state machine (M3)<br/>partially answered: ACL decides *when*"]
|
||
UNCLEAN["❌ Unclean-removal handling (M3)"]
|
||
|
||
ACLKEY["❓ ACL 'bumps and holes' comparison<br/>semantics — SAME GAP in M5 and D.4"]
|
||
WIREBIND["❌ Wire hotplug → capsule_birth_baby() (M5)"]
|
||
BINDSTEP["❌ Extend sk_repl_set_active_vm()<br/>with key-match (M5)"]
|
||
DETACH["❌ Detach behavior (M5)"]
|
||
|
||
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)"]
|
||
|
||
SSDSCOPE["❓ SSD identity-store scope<br/>for regular users (D.4)"]
|
||
ROUNDTRIP["❓ Session state round-trip<br/>across attaches (D.4)"]
|
||
POLYBLOCK["❓ Polymorphic block-boundary<br/>behavior (D.1, since 2026-08-25)"]
|
||
PROMPTBUG["🐛 (Zuse)ok> prompt typo<br/>trivial, independent"]
|
||
|
||
W10 -->|blocks| BMAPWRITE
|
||
W10 -->|blocks| MINT
|
||
W10 -->|blocks| DETACH
|
||
W10 -.->|blocks real-hardware testing of everything below| 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
|
||
|
||
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
|
||
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 done
|
||
class W10,STALL,FIRSTTOUCH,BMAPWRITE,BMAPREAD,UNCLEAN,WIREBIND,BINDSTEP,DETACH,MINT,BMAPFMT,CERTVERIFY,RUNCAP blocked
|
||
class ACLKEY,MSGMIGRATE,SSDSCOPE,ROUNDTRIP,POLYBLOCK open
|
||
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.
|
||
|
||
**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.
|
||
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.
|
||
|
||
### 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.
|