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.
138 KiB
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
- Already done — stale carry-forward, closed 2026-08-26. This item was never marked
complete when the actual work concluded.
FABRIC-2.mdSections 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-RWwas never reachable —acl_recheck()only ever looks up the 11-charACL-RECHECK, never the 14-char-RWvariant). What the original campaign actually measured was the liveACL-TTLmechanism 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 memoryproject_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.mdandM7.1.md— staleBranch: 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 isvirtio_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.mdsays 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. -
Confirmed accurate, not stale (2026-08-26):
VM-FLEET-ATTRACTOR-DESIGN-20260705.md's claim thatdoe-campaign.4this "broken and being superseded." Live-ranSMOKE-CAMPAIGNfrom 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. -
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'spm_last_accuracy_num/pm_last_accuracy_denfraction pair doesn't correspond to anything in the real C struct —include/vm.h'sPipelineGlobalMetricshas a singledouble last_checked_accuracyfield, no num/den pair anywhere. The.thyfile's own comment already scopes the real fix correctly: "a full field-level pass overpipeline_metrics_stateis 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.thycomment, which it now has here — the actual re-model stays unattempted, on purpose.
From FABRIC-2.md §X, Milestone 2 — USB hardware stack
-
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 thebot_msc_attach_pending/bot_msc_detach_pendingdoc comments inxhci_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.cis 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
WIREBINDtoo (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_lbnand 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, commit36d832f. This item is about the identity→range allocation that decides what to relocate blocks into*, still unbuilt.)* Scope clarified 2026-08-27 (FABRIC-3.md§F.6, decision 3): a user's pool is their entire thumbdrive by default (never contested) plus any system-resident device blocks they additionally claim, first-come-first-served — this function only governs that second, system-side extension, not the drive itself. -
SCOPED 2026-08-27 (FABRIC-3.md §F.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 acompudynamics.c-driven decision generically, not aphysics_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
-
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) andblk_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.#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_devblocksare 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 forblockmap_offset/blockmap_devblocksagainst 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_VERSIONmacros, a C99 compile-time size assertion (same disciplinestadium.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. -
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 1KBblkioforth-blocks the 4KB header spans, verifies magic → version → CRC-64 in order, returns one ofHOMEBLOCKS_SIG_OK/_BLANK/_BAD_VERSION/_BAD_CRC/_READ_ERROR. Reusesblock_subsystem.c's existing CRC-64/ISO (compute_crc64, previouslystatic/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.cagainst a fake in-memoryblkio_devand exercises all four outcomes — blank media →BLANK, a correctly-minted header →OK(round-tripsdrive_uuid/minted_time_nscorrectly), 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 exposingcompute_crc64and adding the new source file to every kernel build. -
Implemented (2026-08-26): the "warn" half, live and wired. Wired into
sk_repl_idle()'s USB hotplug attach handler (repl.c), right betweenblkio_usb_open_msc()succeeding andblk_subsys_attach_device()— callshomeblocks_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.chas no SCSIWRITE(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" reasoningEXPIRE's deferral used. Refuse belongs on the write path onceWRITE(10)exists to give it something to gate against.sig_start_fblockis hardcoded to0at 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 fromhomeblocks_sig.c's own location-agnostic check logic.Verified live, not just compiled: hot-attached
disk/usb-thumbdrive-test.img(blank media, noLAHBmagic) 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 byxhci: USB drive not recognized (blank or foreign media) -- read-only general use only, then normal attach completing successfully afterward (no regression — noMSC block-subsystem attach failed). Conservation intact, no panic. Clean zero-warning compile and clean boot on all three architectures. -
Resolved (2026-08-26): reuse
acl_pinneddirectly, 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) areCONSTANTwords, i.e. realDictEntrys.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'sACL-ZUSE-BOOTalready pins both cert constants today — the mechanism is already wired for this.Real gap surfaced along the way, not yet fixed:
ACL-ZUSE-BOOTself-activates and pinsZUSE-CERT-LO/HIunconditionally on every boot — before any legitimate minting step could ever run, permanently locking in the0placeholder 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.imgQEMU 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, ongoingS" name" MINTword 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.imgbleach mechanism.disk/zuse.img(64MB, blank, matching the existing USB-fixture convention exactly — seedisk/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 asHOMEBLOCKS_SIG_BLANK(xhci: USB drive not recognized (blank or foreign media)), correctly simulating a genuine first boot. Deliberately flat/raw, not GPT-partitioned, matchinghomeblocks_sig_check()'s currentsig_start_fblock=0call 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 ownmetadata_devblocksfield and hardcodes no size anywhere, confirmed clean.scripts/bleach_zuse_img.shgained a--size-mboverride so this was never a hidden assumption baked into the tooling either. The earlierproject_usb_thumbdrive_gpt_layout.mdmemory'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
MINTword, and the Zuse recovery path.Correction, supersedes the "reuse
acl_pinned" resolution above (2026-08-26): a pinnedCONSTANTis not actually tamper-proof.ACL-PIN/acl_pinnedonly 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. ACONSTANT'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
VMstruct fields (include/vm.h):zuse_cert_lo/zuse_cert_hi(the cert value) +zuse_cert_installed(one-time fuse bit). Newvm_zuse_cert_install(vm, lo, hi)(src/vm.c) — C-only, no FORTH word wraps it, returns-1on 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>BODYpath structurally rather than by convention. Three new read-only C primitives (src/word_source/starforth_words.c, same shape as the existingHEARTBEAT-TICKS@):ZUSE-CERT-LO@,ZUSE-CERT-HI@,ZUSE-CERT-INSTALLED?.capsules/zuse.4th's oldZUSE-CERT-LO/HICONSTANTwords (andACL-ZUSE-BOOT's two now-pointlessACL-PINcalls on them) deleted outright rather than left as dead/insecure scaffolding —mkcapsule --lintclean (31/31) after the edit.vm_zuse_cert_install()has no caller yet: the real mint flow still doesn't exist (Milestone 6 CA + theMINTword 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: hostedmakebuild clean, zero warnings; clean boot took>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'sS" ACL.4th" EXECcommented 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
MINTitself, found a real conflict with an existing, deliberate decision:include/starkernel/ed25519.his 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 forMINT(§D below) is an interactive, on-deviceS" name" MINTword — 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 reshapeMINTaround 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.halready found, for VM UUIDs, that amd64 has RDRAND and riscv64 has the Zkr extension, but QEMU's aarch64 CPU models (includingmax) 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 avirtio-rngdevice 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 existingvirtio_blk.cexactly — 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-pciadded to all three arches' QEMU invocations (Makefile.starkernel). Wired into boot (kernel_main.c, right after the existingvirtio_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, aarch64861487df65a6d7a26b2c9c34f5ff96ee, riscv64fec51d80e8c169a35aad9d908eab2f39— 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 took>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
MINTword 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) withed25519_keygen(seed, pubkey_out)anded25519_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'sS = (k*a + r) mod Lstep, the one piece of scalar arithmetic verify never needed (verify only ever reduced or compared, never multiplied scalars). Schoolbook 256×256-bit multiply into au128wide accumulator with exactly one final carry-propagation pass — deliberately the same shape asfe25519.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-provenscalar_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'scryptographylibrary (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 took>, Stadium conservation intact, no panics or guest errors. Nothing callsed25519_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
MINTword 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:
- 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. - 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 twouint64_t(16 bytes) — sized against the old placeholderZUSE-CERT-LO/HIFORTH-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. - A genuine blocker, found by asking where the cert would actually live: "mint once,
ever" requires surviving reboots, but
Makefile.starkernel'sqemutarget copied a fresh, pristineOVMF_VARS.fdon every invocation (not just afterclean) — so a UEFI-NVRAM-based cert (the real-hardware-compatible option, and this codebase already has a liveSetVariable/GetVariableprecedent viaSF_VAR_REBOOT_TRIES/SF_VAR_BOOT_ARGS) would never actually persist under this project's own normal test workflow. Digging further: aarch64'sqemurecipe had no persistent NVRAM store at all — a single combined-bios $AAVMF_CODEargument, 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, soclean(which deletes the wholebuild/$(ARCH)/kerneltree,OVMF_VARS.fd/RISCV_VARS.fdincluded) is the bleach step, and a baremake qemunow preserves NVRAM across runs — exactly matching the existing "always passcleanbeforeqemu" 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.fdalongside the existingAAVMF_CODE.fd), with a graceful fallback to the old single--biosmode (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 took>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 theMINTword itself.Cert struct expanded (2026-08-26):
vm_zuse_cert_install()(bothsrc/vm.c's hosted copy and a new kernel-side duplicate insrc/starkernel/vm/vm_core.c-- the kernel build'sVM_EXCLUDElist dropssrc/vm.centirely, same reasonvm_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-sideZUSE-CERT-LO@/HI@replaced withZUSE-PUBKEY@ ( i -- u )(8-byte LE chunki, 0..3, of the public half only -- the seed has no FORTH access at all).ACL-ZUSE-BOOTnow checksZUSE-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/SetVariablelogic right aftervirtio_rng_init()(beforecapsule_birth_mama()); it page-faulted (CR2inside 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 callingSetVariablepost-ExitBootServicessuccessfully (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):
GetVariablereturns fine.SetVariablewith real 64-byte data never returns — that's the exact fault site. The pre-existingSF_VAR_REBOOT_TRIEScall 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.
SetVariablewas called withEFI_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 (mirroringdisk/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 fromkernel_main.c(only two harmless includes remain),init.4th'sACL.4thline 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 asfeedback_no_files_anti_posix.mdso 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 bottomBLK_DISK_SYS_RESERVED(32) blocks of every attached device, invisible to FORTH'sBLOCK/BUFFER. Zuse's cert (and future system metadata) gets a second reservation at the top of the same device, starting atBLK_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_blocksfield inblk_volume_meta_t, appended afterreloc_devblocksand carved from_pad[]— identical graceful-default technique thereloc_start/reloc_devblocksfields 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 disciplinehomeblocks_sig.halready uses — caught a real bug immediately: the hand-summed_pad[]size formula was off by 4 bytes (a compiler-inserted alignment gap beforetracked_blocksthat the manual byte-count missed), found viaoffsetof()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) anddisk/artemis-metafence-test.img(a copy of the pre-existingartemis.img, exercises the graceful-default-on-reload path), both kept as regression fixtures perdisk/README.md's existing convention (mirrorsartemis-reloc-test.imgexactly). 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 writesmeta_fence_blocks=128at 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, untoucheddisk/artemis.imgalso 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 intocompute_totals_from_B()'s existingpayload4kcalculation (total_devblocks - 1 - B - R - F, F =meta_fence_blocks) instead of needing a second, separate subtraction againstuser_blocks—total_blocks,user_blocks, andfree_blocksall 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 bydevblock_from_topcounting down from the device's last physical devblock, refusing (not silently clamping) if the index isn't within the on-diskmeta_fence_blocks. No FORTH word wraps either — C-only, same discipline asvm_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_blocksin 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 viablk_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 usedconsole_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 theMINTword 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" disciplinehomeblocks_sig_talready 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-openMINTword 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, ifvirtio_rngis ready, mint a fresh one (Phase A+B) and write it. Runs right aftervirtio_rng_init(), well beforecapsule_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-invokeACL-ZUSE-BOOTafter Mama birth" workaround is no longer needed;ACL.4th/zuse.4th's self-activatingACL-ZUSE-BOOTsees 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 logsZuse: minted, fuse blown; the on-disk record at the exact expected physical offset independently decodes to magic bytesb'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/writeboth correctly refuse (no space to read or write), so the kernel mints a cert for RAM/this-boot-only use and honestly reportsZuse: 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.imgitself 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" MINTword for an authenticated Zuse session to mint additional regular users (needszuse_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_allowdirectly, or build a separate key-matching primitive —ACL-PINgates 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 bothCERTVERIFY(identity-authentication) and a newRUNCAPmechanism (§F.6, scoped) for per-identity VM content, sincecapsule_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
mkcapsulebuild 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.
-
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 verifyreturnsOK). Both private keyschmod 600. The root key never touches this repo or any kernel code, per the design's own requirement. -
Snakeoil intermediate embedded as a capsule, 2026-08-26. Exported to DER (
capsules/pki/snakeoil-intermediate.der, 418 bytes) and dropped undercapsules/— confirmed the font-capsule precedent needed zero new infrastructure:mkcapsule'sprocess_file()embeds any non-.4thfile verbatim already. Shows up as capsulepki:snakeoil-intermediate.derin the generated capsule table (38 capsules total, up from 37) — retrieve viacapsule_find_by_name()+capsule_get_payload(), nevercapsule_exec_payload()(it's a passive data blob, not executable capsule code). -
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) — walksCertificate → TBSCertificate → SubjectPublicKeyInfo, handles the optional[0] EXPLICIT Versionfield (present on v3 certs), verifies theAlgorithmIdentifierOID is exactly1.3.101.112(RFC 8410 Ed25519) rather than assuming, and extracts the raw 32-byte key from the trailingBIT 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 matchedopenssl 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 newsig[64]field onCapsuleEntryand a parallel emitted array incapsule_generated.c, sinceCapsuleDescitself has no spare bytes — confirmed exactly 64 bytes, every field used), wiringed25519_verify()into the threecapsule_validate()call sites incapsule_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 nook>at all, on any architecture), and the signature-status column oncapsules/BLOCK_MAP.md(confirmed the real, live manifest target —capsules/MANIFEST_AUTO.mdis 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, notsystem()-- 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 linked25519_sign()(src/starkernel/crypto/ed25519.c, already independently verified against OpenSSL in Phase B) directly intomkcapsule. This needed one more small piece:tools/pkcs8_ed25519.c, a narrow DER walker (same shape asx509_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 isopenssl 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 owned25519_keygen(), reproduces the exact pubkey embedded in the cert; a full self-contained sign+verify round-trip (zero openssl involvement) passes.CapsuleDeschad no spare bytes (confirmed exactly 64, every field used), so signatures live in a new parallel array,CapsuleSigEntry capsule_signatures[]({ sig[64], has_sig }, mirroringcapsule_names[]'s existing shape), emitted bymkcapsule --sign-key <path>(new optional CLI flag; omitted or the key file missing → every capsule getshas_sig=0, graceful, not a build failure -- CI has no access to the offline key). NewMakefile.starkernelvariableSIGN_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-testedcapsule_validate()(whose signature and behavior stay untouched). Finds and parses the embeddedpki:snakeoil-intermediate.dercapsule's pubkey once per boot (cached — every later capsule birth reuses it, the cert can't change mid-boot), thened25519_verify()s the requested capsule's payload against its stored signature. Wired into all threecapsule_validate()call sites incapsule_birth.c(capsule_birth_mama,capsule_birth_baby,capsule_run_experiment) vialog_message(LOG_WARN, ...)on anything other thanCAPSULE_SIG_OK— never refuses. (Hit one unrelated build snag wiring this in:vm.handlog.hboth defineLOG_LINE_MAXfor two completely unrelated concepts — vm.h's own is the persistent block-log line size, unconditional#define; log.h's is#ifndef-guarded.log.hhas to be included aftervm.hin any TU that needs both, or the build fails under-Werror; fixed and commented incapsule_birth.cso 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.4thcapsule's stored signature (not its payload/hash, whichcapsule_validate()already checks independently and would have masked the test) directly in the generatedcapsule_generated.c, without regenerating from source, and rebuilt only the changed object: produced exactlycapsule sig: init.4th: INVALID -- signature does not verifyon boot, and the kernel still reachedok>— 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.mdsignature-status column.BLOCK_MAP.mdsignature-status column, done 2026-08-26.--manifestmode's file scan (manifest_file()) is a completely separate code path from build mode'sprocess_file()— confirmed it only ever walks.4thfiles (never the embedded PKI cert or font capsule), and the Makefile invokesmkcapsuletwice per build for two different purposes (once to emitcapsule_generated.c, once for the manifest). Extended--manifestto 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 sharedload_sign_key()used by both) so the manifest can report real per-capsule signing status without touching or requiring a rebuild ofcapsule_generated.c. NewSignedcolumn on the capsule summary table:yes/nowhen--sign-keywas 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, socapsules/BLOCK_MAP.mdreflects this machine's actual signed state by default. Verified: clean compile,capsules/BLOCK_MAP.mdcorrectly showsyesfor all 31 tracked.4thcapsules 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 onMISSINGthe same asINVALIDwould brick boot everywhere but here, not catch anything real. Decided (on request): enforce ONLY onCAPSULE_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_KEYstay WARN-only permanently, not as a transitional state.All three
capsule_birth.ccall sites nowreturn CAPSULE_RUN_ERR_INVALIDonCAPSULE_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 owninit.4th) now genuinely refuses --capsule_birth_mama()returns the error -- on all three architectures identically:capsule sig: init.4th: INVALIDfollowed byInit: Mama birth FAILED. The feared "no ok> at all" blast radius didn't materialize:kernel_main.calready had graceful error handling for a failed Mama birth (log and continue, pre-existing code, not written for this feature) -- the kernel reaches a degradedok>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_CONTRIBbit tomkcapsule.c's flag system, assigned by path match (contrib/prefix), same pattern as howinit.4thalready getsFLAG_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 acontrib/-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.isoviamake -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/dmesgafter insertion — care needed, wrong device = data loss). dd if=build/amd64/kernel/starkernel.iso of=/dev/sdX bs=4M status=progress(or equivalent) — confirmddis the right tool for an El Torito ISO vs. needingisohybridfirst (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-*.socktosocatinto) — 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, commit36d832f), live-verified (redirect + content survive an abrupt kill and reboot) but never wired tostadium_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 genericstadium_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 toDELIVERvs.EXPIREwas 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:
-
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 withmass != 1, and that amass > 1multi-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 viastadium_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 carryidentity = LBN,mass = 1,payloadunused — the actual 1024 bytes of block content stays exactly where it already lives (block cache / disk viablock_subsystem.c), unmoved. Somass != 1is a non-issue; the real gap is just that nothing has ever built the LBN→cell_index residency map (the block-patron analogue ofstadium_word_dispatch()'sresolve_resident_cell()) or the touch-on-access hook (the analogue ofvm_core.c's threestadium_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. -
Resolved (2026-08-25): real block-patron admission + real
MIGRATEdispatch, both live. Newstadium_blocks.h/stadium_blocks.cmirrorstadium_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 offstadium_cell_count()(tombstone-based deletion, since LBN space isn't densely bounded likeword_id), not a dense array. Wired intoblock_word_block()/block_word_buffer()/block_word_update()(block_words.c),#ifdef __STARKERNEL__-guarded.stadium_dispatch()'sMIGRATEcase now callsblk_flush(lbn)for real (confirmedblk_flush(), notblk_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 tozuse)ok>/ok>REPL on all three, conservation (resident_sum + reservoir == Q48_ONE) intact identically across all three;BLOCK/BUFFERtouches 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-mto make quota pressure reachable) ran clean under heavy admission-path load with no corruption. LiveMIGRATEfire confirmed (2026-08-26): interactive flooding alone never triggered it — Hera's reservoir was already sitting exactly at theQ48_ONE / 3floor 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, sostadium_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-EVICTagainst the live Artemis VM. Captured live:Stadium: dispatch cell=63257 behaviour=MIGRATE lbn=100, followed byDBG err after MIGRATE probe=0—blk_flush(100)fired for real, with the correct LBN threaded through from the departing patron'sidentityfield exactly as designed. (Artemis's own reservoir went briefly out of Q48_ONE-balance during the probe — rawSTADIUM-ADMITdoesn'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.) -
Bug found, reported, then fixed on request (2026-08-25/26):
capsules/lib.4th:13-14shadowed the C primitivesUSE/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 printedEXEC: failed: Artemis. Root cause:capsules/lib.4th:13defined: USE ( addr u -- ) EXEC ;— a FORTH word with the same name but a completely different meaning ("load/exec a capsule"), which shadowed the C-registeredUSEin dictionary search order sincelib.4thloads after primitive registration.lib.4th:14did the identical thing toRUN, 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 bareRUN);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 afterlib.4thitself loaded). Both aliases were pureEXECwrappers with zero added behavior, so the fix deleted both definitions fromlib.4thoutright and changed the one real call site plus its matching doc comment (capsules/common/msg.4th:4) to callEXECdirectly — no new names invented, the C primitives untouched,mkcapsule --lintclean (31/31 pass). Verified live: rebuilt and booted amd64 — Hermes still births and herCOMMON-CH-eviction self-test (which depends oncommon:msg.4thhaving loaded) still passes exactly as before; interactively,S" Artemis" USEnow correctly printsUSE: now using Artemisand 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). -
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 calledstadium_grant_quota()(a resource pool for the VM's own future word/block patrons) — neverstadium_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 reachstadium_evict()— not a working example ofCOOLfiring 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: addedsize_t stadium_patron_celltoVMRegistryEntry(capsule_run.h). At birth, right after the existingstadium_grant_quota()call (capsule_birth.c), admit a candidate into the new VM's own quota mirroringstadium_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 tostadium_evict()'s refusal logic isn't something to do casually; unpinned costs nothing since nothing wiresCOOL's dispatch body to actually kill anything, so the worst case of an unrelated natural eviction isstadium_patron_cellgoing stale, which is tolerated the same waystadium_word_forget()already tolerates staleness. Atcapsule_vm_kill()andcapsule_vm_kill_all_nonmama():stadium_evict()the tracked cell if still resident, silently tolerating refusal (already gone).stadium_dispatch()'sCOOLcase needed no new payload body — same as it already is for words, whereCOOLhas no defined extra action beyondstadium_evict()'s own universal reservoir credit; the missing piece was admission and a genuine trigger, not dispatch-body logic. Verified live: a second, newStadium: dispatch cell=... behaviour=COOLnow fires immediately before everyPARITY:KILLline, for both Hermes and Artemis, confirmed on amd64 (distinct from the pre-existingCOMMON-CHword-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). -
**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 fromFABRIC.md's old "open, not resolved" note about which Hermes event maps toDELIVERvs.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 staleONTOLOGY.mdwords note and the earlier glibc misattribution. Confirmed live:capsules/hermes/init.4th'sMSG-ALLOCalready admits every message withSB-DELIVER, andMSG-FREE-NODE(called from bothMSG-ACK-LASTand heat-drivenMSG-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 wayMIGRATEdid:MSG-DELIVER(the FORTH word) already runs the actual delivery (VM-EXECof the payload) before eviction, decoupled from Stadium reap — so by dispatch time delivery is already done, same shape asCOOL, which needs no extra action beyondstadium_evict()'s own universal reservoir credit. Fix:stadium_dispatch()'sDELIVERcase now prints the departing message's real identity (DELIVER msg_idx=N, same shape asMIGRATE'slbn=print) instead of a misleading(stub)label — confirmed live via a forcedMSG-SEND/MSG-DELIVER-ALL/MSG-ACK-LASTsequence 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 printsCOOL 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=0at Hermes's/Artemis's own explicit channel-eviction self-test and again at their VM-patron eviction atPARITY:KILL,COOL identity=1at 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 likeDELIVERturned 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_ttland Stadium'sttlare different things wearing the same name.DictEntry.acl_ttl(vm_core.c:756) is a per-word countdown that batches how oftenACL-RECHECKruns — when it hits 0,acl_recheck()calls the FORTH wordACL-RECHECK(ACL.4th:50), which always renews: STRICT mode setsallow=1, ttl=0(recheck every time); TTL mode computes a fresh heat-based TTL and setsallow=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.ttlis completely inert. Every candidate constructor across the whole codebase (stadium.c,stadium_words.c,stadium_blocks.c, Hermes'sMSG-ALLOC/CH-ALLOC) setscandidate.ttl = 0, and nothing anywhere ever reads, decrements, or reaps on it. The generic TTL-expiry mechanismEXPIREwould 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:
- Is "ACL patron" even the right model, or was
FABRIC.md's original "ACLs → EXPIRE" mapping a category mismatch from the start — conflatingacl_ttl's recheck-amortization counter with Stadium's residencyttl? - 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). - Building this for real means building Stadium's generic ttl-decrement/reap-on-zero
mechanism first — nothing to hook
EXPIREinto today. Is that in scope here, or its own separate item? - What should the reap action actually do, given current ACL policy never revokes — would
EXPIREforce anACL-RECHECK, or something else entirely?
Decision:
- Not at the per-word level.
acl_ttl-hits-zero always renews, never revokes — forcingEXPIRE'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. - 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.
- 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).
- 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.
EXPIREstays 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. - Is "ACL patron" even the right model, or was
-
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 viastadium_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 memoryproject_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
- Idle loop watches for thumbdrive connects (
sk_repl_idle(), already the right hook — confirmed 2026-08-27 during the (superseded)EXPIREresearch: ~1s cadence, already used forxhci_poll_events()/MSC attach). - 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. - 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 anythingmkcapsuledoes today (gap, see D.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). - 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.
- 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-openMINTword from Phase 8's own punch list, now with a concrete attach-flow context to design it against. - 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-PINvs. build a new key-match primitive" question — reuse ACL. Now grounded, not just intuited (mined 2026-08-27 fromFABRIC-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 inFABRIC-2.mdis 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, byFABRIC.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 atSubjectPublicKeyInfo— 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 livecapsules/hermes/init.4th): a message isMSG-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 inFABRIC-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, fillTYPE/FROM/TO/CH, pointPADDR/PLENat 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?
mkcapsuleonly ever builds capsules at build time, baked intocapsule_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 (mirroringkernel_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.mdcross-checked against livesrc/starkernel/repl.cand 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.cis 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.
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 toMINT— a gap that read as "D.4 leftover detail" is actually load-bearing for the ongoing-minting flow, not a side note.MSGMIGRATEhas three separate hardwired prerequisites, all currently unbuilt — the message-bus migration can't even start untilWIREBIND/BINDSTEP/CERTVERIFYexist to migrate from, confirming this is correctly the last thing to tackle in this whole area, not a parallel track.- Only
SSDSCOPE,ROUNDTRIP,POLYBLOCK, andPROMPTBUGare genuinely standalone — everything else in the pile connects to at least one other node. WIREBINDwas missing two real incoming edges (found scoping it, §F.5):CERTVERIFYandRUNCAPboth gate it, not justHOTPLUG. Without a verified identity there's nothing to bind to, and withoutRUNCAPthere's no per-identity VM content to birth — today'scapsule_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.CERTVERIFYand 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.MINTclosed 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 ridebulk_out_ringinstead (newXHCI_XFER_BOT_DATA_OUT/XHCI_NEXT_ACTION_BOT_DATA_OUT, reusing the existingxhci_bulk_out_enqueue_and_ring()againstbot_data_bufrather than the CBW). CBW'sbmCBWFlagsclears the DATA_IN bit (host→device) instead of setting it. - New SCSI opcode —
SCSI_CMD_WRITE10(0x2A) — plusBOT_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'susb_blk_write()stops being theBLKIO_ENOSUPstub:memcpythe caller'ssrcintoxdev->bot_data_buf, then call the newxhci_bot_write_block()— same shape asusb_blk_read().
Decisions made 2026-08-27 (iterative Q&A pass):
read_onlyflag inblkio_info()— flips to 0 immediately oncexhci_bot_write_block()is proven in QEMU. No separate "bring-up mode" gate to design or later retire.- Validation target — a new disposable QEMU-only test image (e.g.
disk/usbwrite-test.img, following the same disposable-clone convention asdisk/artemis-metafence-*.img), attached via-drive+-device usb-storage,drive=.... Neverdisk/artemis.imgitself, 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). - Scope boundary —
WRITE(10)alone is the unit of work.MINT's write-the-identity step andDETACH's flush-back-to-thumb step are separate graph nodes; each gets its own scoping pass onceWRITE(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):
- Where the lock lives. A new dedicated type in its own header —
include/starkernel/vm_identity.h, aVMIdentity { uint8_t owner_pubkey[32]; uint8_t installed; }— embedded by value as a field onstruct VM, exactly the wayVMUuid stadium_vm_id(vm_uuid.h, its own header, embedded field) already does. Explicit standing instruction from Captain Bob: don't growstruct VM/vm.hfeature-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.VMUuidwas already the right precedent;VMIdentityfollows it. Deliberately holds only the public key, not a seed — unlike Zuse's ownzuse_cert_seed/zuse_cert_pubkeypair, a regular VM's lock never needs to sign anything itself, so no private material belongs on it at all. - Zuse override. Zuse keeps an always-allowed override to attach to any VM (consistent
with the existing
emergency_console/zuse_sessionbypass 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. - Binding pin. No pin.
sk_repl_set_active_vm()stays freely retargetable — matches today's Tripod-fleetUSE-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:
BMAPFMTis not a new structure — it's repurposingblk_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.- Consequence flagged, not silently absorbed:
homeblocks_sig_t's reservedblockmap_offset/blockmap_devblocksfields (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. - Replacement for the 40-byte "Security & ownership" block (same budget the old
owner_id/permissions/acl_block/signature[2]occupied; nothing else inblk_meta_tmoves):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 inblk_meta_t(checksum, timestamps,content_type, hash, chain links,app_data[15]) is untouched. - 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 onRUNCAP(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:
capsule_birth_baby()is already generic — it takesdir/descs/names/arenaas plain parameters; it is not wired to the compile-timecapsule_descriptors[]global in any way.mkcapsulesets.desc_capacity = capsule_countalways (mkcapsule.c:955) — despitecapsule.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 —- ...because
RUNCAPdoesn't need to touch the baked-in directory at all. It can construct its own self-contained, heap-allocatedCapsuleDirHeader+ oneCapsuleDesc+ oneCapsuleNameEntry+ a small arena — the exact same shapekernel_main.c:729-750already 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, unmodifiedcapsule_birth_baby(). No directory-merge logic, no changes to the birth path itself.
Decisions made 2026-08-27:
- Mechanism: heap-built single-entry capsule directory, not baked-in-directory extension. Confirmed safe and minimal per the trace above.
- Location of the source content on the drive.
homeblocks_sig_t'sblockmap_offset/blockmap_devblocksfields are already dead weight perBMAPFMT's decision (§F.4) that ownership/ACL/state travel withblk_meta_t, not a centralized on-drive table. Repurpose those two fields (rename toidentity_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, keepshomeblocks_sig_t's byte budget untouched. (Comment update inhomeblocks_sig.hstill owed when this is actually built, same asF.4already flagged.) - 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 atg.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):
- Format: X.509/DER, not a simple custom struct (the
zuse_cert_devblock_tprecedent would have been simpler, but explicitly not chosen). The cert'sSubjectPublicKeyInfoholds the owner's Ed25519 pubkey (same fieldx509_extract_ed25519_pubkey()already reads). - Drive binding: the cert's
serialNumberfield holds the 16-bytedrive_uuid(matchinghomeblocks_sig_t.drive_uuid) rather than adding a new X.509v3 extension —serialNumberis an early, shallowSEQUENCEmember (actually simpler to reach thanSubjectPublicKeyInfo, 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. - Revocation: explicitly deferred. Matches this project's incremental-scope precedent
elsewhere (capsules have a
REVOKEDflag; user-cert revocation is separate, unbuilt surface).CERTVERIFYv1 only checks magic/structure, the embedded algorithm OID (Ed25519, rejecting anything else — same checkx509_extract_ed25519_pubkey()already does for the pubkey field), theserialNumber-vs-drive_uuidmatch, and the signature itself. - 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 atSubjectPublicKeyInfoby design.CERTVERIFYadditionally needs: the rawTBSCertificatebyte range (the exact bytes Zuse's signature covers), the outersignatureValueBIT STRINGcontents, and theserialNumberINTEGERbytes. Verification itself is then one call:ed25519_verify(zuse_pubkey, tbs_bytes, tbs_len, signature), wherezuse_pubkeycomes from this system's ownzuse_cert_devblock_t, not from anything read off the user's drive. - 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, unlikeRUNCAP'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:
- 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 rawhomeblocks_sig_t-at-devblock-0 layout every other node in this arc (CERTVERIFY,WIREBIND,RUNCAP,HOTPLUG's ownsig_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 = devblocks0..Ncovering the sig header + cert + identity-source regions; everything past that is the user's own general block-storage pool directly, consistent withFIRSTTOUCH's pool clarification in §F.6). No GPT-parser node needed in the graph at all — this closes that thread rather than deferring it. - 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.MINTonly 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:
- 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 — matchesSSDSCOPE's "regular users stay thumbdrive-resident" read). Keeps the door open for a user VM to sign something itself later, perD.5's "identity is common to every VM" direction, without redesigning the cert format again. - Storage layout (direct extension of
RUNCAP's already-decidedidentity_src_offset/identity_src_devblocksregion, §F.6 — no further header growth needed): that region's first devblock holds a newuser_identity_seed_trecord (magic/version/seed[32]/pubkey[32]/crc— same shape aszuse_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 sourceRUNCAPreads at birth. drive_uuid: a fresh, independent 16 random bytes fromvirtio_rngat mint time (not derived from the identity seed — two separate random draws, matchinghomeblocks_sig_t's own field being logically "which physical drive," not "whose identity").- Cert construction: builds the exact
CERTVERIFY(§F.7) format —SubjectPublicKeyInfo= the new pubkey,serialNumber=drive_uuid, signed with Zuse's own seed viaed25519_sign()(real signing capability already exists, Phase 8). This is the first confirmed caller that needs DER encoding, not just the decodingx509_ed25519.calready has — flagged inF.7already, restated here sinceMINTis where it's actually needed. - Header assembly:
hdr_crcvia the existinghomeblocks_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
ACLKEYturned out to be one gap wearing two names (Milestone 5's "key/lock shape" and D.4's "bumps and holes"), and the wayWRITE(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.