Real silicon arrives incrementally (Beelink SER5 in hand now; RasPi 5 + Milk-V
orderable around Mon 2026-08-31), so the hardware release is split per board
in hand, each an even-minor LTS point-in-time cut on the same line:
- v2.2.0 amd64 bare metal — Beelink SER5 (two 16GB sticks: one boots the
thumbdrive image, one mints a real Zuse user)
- v2.4.0 aarch64 bare metal — Raspberry Pi 5
- v2.5.0 all three bare — adds Milk-V (riscv64)
Each closes only when its board's items are proven live (boot to ok>, block-
fence Zuse true, backend entropy non-deterministic), never build-only.
Records the decision in the authoritative Release Versioning Policy
(docs/lithosananke/ROADMAP.md, "Board-by-board hardware rollout, decided
2026-08-29") and re-maps G.4/G.5 in FABRIC-3.md onto the new cadence, noting
v2.0.0 was cut (tag v2.0.0).
First real per-arch RNG backend, added to the v2.0.0 unified entry point in
src/starkernel/rng/rng.c, #if-guarded to amd64: CPUID.01H:ECX[30] RDRAND
detection + inline-asm rdrand draws feeding rdrand_fill() (whole-byte
emission from the low end; a partial final draw is discarded -- throwing
away entropy is always safe).
Probe order honors the release policy: virtio-rng is tried first, so the
QEMU path stays on virtio-rng unchanged; RDRAND is the fallback only real
hardware (which has no virtio-rng device) reaches. QEMU-verified both ways
on amd64: with virtio-rng present -> "rng: backend = virtio-rng" (unchanged);
with virtio-rng absent and RDRAND exposed (-cpu max) -> "rng: backend =
rdrand" + "entropy: ready" + Zuse attach confirmed. rdrand_fill()'s exact
logic host-proven: fills 32-byte/16-byte buffers and yields differing draws
run-to-run (non-deterministic). aarch64/riscv64 builds unaffected (guarded
off). riscv64 Zkr and aarch64 peripheral-RNG backends remain parked for their
real boards.
FABRIC-3.md G.4 amd64 slice marked BUILT + QEMU-verified.
The QEMU-verifiable slice of the real-hardware RNG driver (per FABRIC-3.md
§G.2). New include/starkernel/rng.h + src/starkernel/rng/rng.c provide the
single entropy entry point: rng_init() probes the backend set (v2.0.0:
virtio-rng only) and, on no backend, prints a loud boot-time warning while
rng_get_bytes() returns RNG_ERR_NO_BACKEND - never silently degrading to a
deterministic seed. The backend-selection switch in rng.c is the exact seam
v2.5.0's per-arch drivers (amd64 RDRAND, riscv64 Zkr, aarch64 peripheral) plug
into without touching the call path.
Consumers route through the unified layer instead of virtio-rng directly:
capsule_mint.c (identity seed + drive_uuid) and kernel_main.c phase 8
(rng_init()). virtio_rng.c stays as the sole backend. Built clean on
amd64/aarch64/riscv64. QEMU amd64 boot: POST 1012/0/0 + ok>, "rng: backend =
virtio-rng" + "entropy: ready", Zuse identity confirmed from thumbdrive -
mint/cert behavior unchanged.
FABRIC-3.md §G.2 v2.0.0 slice marked BUILT+VERIFIED.
Root cause of the G.1 follow-up boot-time attach race: on pathological
controller behavior the xhci_poll_events() drain loop had no hard ceiling.
ERDP is written back only when the loop exits, so the controller cannot
reclaim event TRBs mid-drain; if it keeps producing events the head can
chase the software dequeue pointer forever. xhci_poll_events() never returns,
sk_repl_idle() never reaches its bot_msc_attach_pending check, and a fresh
USB BOT device that finished SET_CONFIGURATION is left flagged-but-never-
attached while the guest appears hung.
Fix: bound the drain to a full ring (XHCI_EVT_RING_MAX_DRAIN = 256), so
xhci_poll_events() always terminates and always writes ERDP each call.
Unprocessed events keep their cycle bit and are re-read next poll; nothing
is dropped. On the healthy path one drain processes only the one-or-few
events the controller posts per chained command, so the bound never triggers
except in the pathological case it breaks.
Beyond the G.1 additions: a new macro in include/starkernel/xhci.h and a
bounded loop in src/starkernel/usb/xhci.c. Builds clean on amd64. Verified
across six consecutive fresh QEMU boots (previously intermittently hung).
Full BOT-spec stall recovery per FABRIC-3.md F.14: new STALL_ERROR handling,
Reset Endpoint + Set TR Dequeue Pointer commands, CLEAR_FEATURE(ENDPOINT_HALT),
escalating to Bulk-Only Mass Storage Reset, capped retries
(XHCI_BOT_STALL_MAX_RECOVERIES=2) mirroring bot_tur_retries, clean terminal
failure via xhci_stall_fail().
Purely additive recovery path off the non-success transfer-event branch; the
normal path is unchanged. Builds clean on amd64/aarch64/riscv64. QEMU amd64
boot regression passes: zero stalls, BOT attach (READ CAPACITY10 -> READ10 ->
home-blocks) completes, normal-path xHCI trace identical to baseline. Live
stall injection is not provable under qemu-xhci; deferred to v2.5.0 hardware.
FABRIC-3.md G.1 documented; ROADMAP release-versioning policy folded in.
Code review fixes, all compile clean (hosted gcc + aarch64/riscv64 kernel flags):
- repl.c (H1): reentrancy guards on the MSG-TICK idle pump. sk_repl_idle()
now defers when Hera is mid-interpret (g_mama_interpreting) or when its
own vm_interpret is on the stack (g_idle_pump_active), so a blocking
KEY/EXPECT/QUERY inside a dispatched line can no longer re-enter the
interpreter and clobber the in-flight input buffer.
- virtio_rng.c: clamp device-returned used_len to VRNG_BUF_SIZE before the
caller's data_buf copy, closing a device-controlled OOB read.
- block_subsystem.c: first-write path now keys off created_time==0 instead
of dead magic==0 so fresh blocks get a real created_time stamp; first_free/
last_allocated fixed to absolute Forth LBNs (set in blk_compute_fresh_geometry
from slot->start_lbn, no longer the wrong physical-BAM-index values from
compute_totals_from_B); physical-bounds guard on blk_meta_zone_read/write
prevents unsigned underflow on a corrupt fence >= device size.
- capsule_zuse_boot.c / capsule_wirebind.c: identity seed validated magic ->
version -> CRC-64 (compute_crc64 over offsetof(crc)) before trusting it,
so a corrupt/format-mismatched record is refused, never loaded.
- log.h / starkernel/log.h: unused LOG_LINE_MAX 256 renamed LOG_MSG_LINE_MAX
to lift the include-order collision with vm.h's LOG_LINE_MAX 64; stale
include-order comments dropped (kernel_main.c, shim.c, capsule_birth.c).
- FABRIC-3.md: three stale-doc carry-forward items closed [x] with cbe7b49
notes.
Real KEY/?TERMINAL/QUERY/EXPECT bodies (console WIP):
- repl.h/repl.c: sk_console_getkey()/sk_console_key_available()/
sk_console_readline() public bodies; non-destructive peek buffers the
found byte so a following KEY returns it.
- shim.c: getchar()/fgetc()/fgets()/sf_terminal_ready() routed through the
real console paths instead of stubs; sf_terminal_ready() in platform_io.h
with sf_terminal_ready() implemented for the hosted build (linux/io.c,
POSIX select on fd 0) wired into Makefile.
- io_words.c: ?TERMINAL now returns actual terminal-readiness, not constant 0.
Artifacts: minted disk/artemis.img + rebuilt lfs kernel; BLOCK_MAP.md,
doe csv + qemu log regenerated.
Documents the mismatched-marker/blank-drive root cause, the LOG_INFO-vs-
LOG_WARN filtering trap that made the debug probes look like they weren't
firing, the three-architecture acceptance results, and the answered "why
not just reboot after MINT" design question (both branches already share
install_and_activate(), so there's no duplicate path to reconcile).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KBjfeLPo71sUQ8zC7V7P5m
Hermes and Artemis are now session-less fleet foundation, verified live on all three architectures (commit 52eb1bb) -- the real, load-bearing prerequisite D.7 itself identified is done. The rest of D.7 (message-only birth flow, WIREBIND/MSGMIGRATE reconciliation) remains open, not touched here.
Direct instruction while planning the next goal (mint zuse.img thumbdrive -> (Zuse)ok>): Hera must give birth only by message request, no coupling outside messages, applying universally to user VMs too, not just Zuse -- a sharper commitment to the standing "nothing is done until it's messaging" criterion (D.1) for this specific next flow, since messaging and storage are both genuinely live now.
Traced before capturing anything further: detection ownership moving to a running Artemis VM instance is confirmed a real architecture change from what HOTPLUG/WIREBIND already built/scoped (both currently have Hera deciding and acting directly, not just detecting). Found a real, load-bearing prerequisite this direction depends on: Artemis is not persistently alive today -- every boot log this session confirms she's birthed for her own migration self-test then explicitly killed before the REPL reaches ok>. Capture only, no design commitments yet -- WIREBIND/MSGMIGRATE both need their own reconciliation pass against this.
WRITE(10) moves from "scoped" (§F.1) to "built and verified" (§F.17) -- the graph's original highest-leverage node and single remaining hard blocker is gone. Updates the §E graph (W10 to done, its outgoing edges from "blocks" to "unblocked"), the M2 punch-list checkbox, and the "what the graph makes visible" summary.
Also records a new standalone bug node (EMPTYBUFBUG, matching PROMPTBUG's precedent) for the EMPTY-BUFFERS mass-zero bug found and reported (not fixed) during WRITE(10) validation -- so it isn't lost track of now that the immediate incident (corrupted then restored disk/artemis.img) is resolved.
SSDSCOPE and ROUNDTRIP close by cross-referencing decisions already made this session (D.3's "no central registry" statement, and the pool-ownership/EJECT-flush model from F.6/F.10) rather than new tracing -- both turn out to have zero remaining work of their own. POLYBLOCK needed the user's own clarification first (never elaborated beyond one sentence since 2026-08-25): reframes as an architectural principle (the block layer shouldn't know what backs it), confirmed already true of blk_get_buffer()'s live RAM/raw/disk-cache abstraction. PROMPTBUG traced for the literal string and found to be less than described -- no live code prints "zuse)ok>" today, only a doc comment describing not-yet-implemented behavior; correct intended form recorded, comment itself not touched per standing instruction against unrequested code changes.
Also corrects two stale graph classifications noticed while cleaning up: ACLKEY's own node had never been updated despite being fully scoped back in F.2, and the empty "open" classDef bucket is now removed since every §E node is closed or scoped except WRITE(10) itself.
Traced the actual Hermes mechanism before scoping: MSG-DELIVER already executes arbitrary FORTH source text on the destination VM via VM-EXEC, not a structured RPC -- migrating an interaction needs no new dispatch machinery, only a routing decision. Noted that every "VM" lives in one kernel address space, not a separate process, so messaging WIREBIND/BINDSTEP/CERTVERIFY is an architectural-discipline choice (uniform heat/audit participation in Compudynamics via Hermes), not a correctness requirement.
Decisions: real target is the Console VM (already real hardware: serial+framebuffer+PS2, not waiting on D.2b), a two-hop flow (Hera->Console reports attach outcome, Console->Hera requests the privileged operation), payload arguments encoded as literals directly in the FORTH text.
Captured a substantial new vision detail surfaced live while scoping this (D.6): blank-media minting is meant to be an interactive Console-driven onboarding form (Full Name/Address/City/State/Country/Metadata), not a bare programmatic MINT call -- connects forward into RUNCAP's deferred "default personality content" question and MINT's own scope. Capture only, not designed in detail, per this arc's own capture-first discipline.
Traced the completion-code handling before designing anything: every transfer completion (control or bulk) shares one gate that logs and bails on any non-success code -- STALL isn't distinguished from any other failure, and no recovery exists (no xHCI Reset Endpoint, no CLEAR_FEATURE(ENDPOINT_HALT)). A bounded-timeout safety net in xhci_bot_wait_for_idle() prevents a hang, but the endpoint stays wedged for everything after it. Per direct instruction, designed full recovery now rather than deferring to Milestone 8: new STALL_ERROR completion code, new xHCI Reset Endpoint + Set TR Dequeue Pointer command TRB types (neither exists today), CLEAR_FEATURE(ENDPOINT_HALT) reusing the existing control-transfer plumbing, escalating to a full Bulk-Only Mass Storage Reset on a second stall, bounded via a new retry counter mirroring the existing bot_tur_retries/XHCI_BOT_TUR_MAX_RETRIES precedent exactly. This closes the last open Milestone 2 item.
Confirmed the F.12 handoff: cache_load_devblock() already unpacks/validates metadata on load, same "already built" story as BMAPWRITE. But tracing "validate on insertion" surfaced a real, load-bearing conflict unrelated to blk_meta_t: blk_subsys_attach_device() always runs the generic block-subsystem's own STFR/v2 header check at devblock 0, which would always read a home-blocks drive's 'LAHB' magic as "unrecognized" and leave it permanently write-refused (BLK_FMT_PROVISIONAL) -- or, if force-formatted via blk_subsys_confirm_format(), overwrite homeblocks_sig_t outright, since both want the same devblock 0. This was invisible to CERTVERIFY/WIREBIND/RUNCAP/MINT because none of them traced the generic attach path alongside homeblocks_sig_check(). Fixed: homeblocks_sig_t relocates to devblock 1 (a call-site change only, sig_start_fblock was already a plain parameter); MINT must also run the ordinary format-confirm path at devblock 0 so the drive is writable through the normal block-buffer path.
Traced blk_set_meta()/cache_writeback()/blk_flush() before assuming a new write path was needed. BMAPWRITE is already done: BMAPFMT's decision to repurpose the existing blk_meta_t accessors instead of a new table means the flush path that already exists for ordinary block data already covers metadata identically -- real, unstubbed, all the way to dev->write(). The only blocker is WRITE(10) itself, already modeled in the graph. Handoff note for BMAPREAD's own pass: the read side looks like it closes the same way but wasn't confirmed here.
Also corrects a self-inconsistency from the previous commit: UNCLEAN was marked with a "done" checkmark even though its wiring code isn't written yet, unlike BMAPWRITE which really is working code today. Moved UNCLEAN back to the "scoped, not built" bucket alongside BMAPFMT/CERTVERIFY/etc.
Traced g.total_user_lbn and blk_meta_t's chain fields before scoping: the M3 punch-list wording ("claim at g.total_user_lbn") predates BMAPFMT's distributed-ownership decision and doesn't describe a workable mechanism -- total_user_lbn only grows when a whole new device attaches, not when claiming space within one already attached. Real job is scanning Artemis's already-attached device's own blk_meta_t records for unowned devblocks. Also found blk_meta_t already has real, unused prev_block/next_block/chain_length linkage fields, untouched by BMAPFMT's redesign. Decisions: claims are a scattered chain via those fields (fragmentation-immune, free), discovered via full linear scan every time (no cached index, matches BMAPFMT's own no-centralized-table philosophy), fail outright with no partial-claim fallback if the device can't satisfy a request.
Traced capsule_vm_kill() fully -- it's real and complete, so DETACH needs only a caller plus flush/bookkeeping, not new teardown machinery. Found that D.3's "flush before eject" only makes sense as a deliberate pre-removal step, splitting this into a graceful path (new EJECT word) and the abrupt hot-unplug signal already wired -- per direct instruction, scoped both, closing the previously-separate UNCLEAN (M3) node as "same kill path, no flush attempt." Needs one small new piece of state (which VMUuid is attached via the home-blocks path) since BINDSTEP's live-reverify approach doesn't work once the device is already gone. Reports, without fixing, a separate pre-existing bug found while tracing this: capsule_vm_kill() never resets g_repl_active_vm, so plain KILL on a USE'd VM leaves a dangling pointer today.
Traced the actual retarget path before assuming new plumbing: USE (mama_forth_words.c:430-480) already looks up a VM by name and calls sk_repl_set_active_vm() directly, completely unguarded. BINDSTEP is concretely "add the ACLKEY comparison to this one call site." Found one real gap: usb_blk_dev/xdev are function-static inside sk_repl_idle(), invisible to USE -- needs a small new accessor mirroring sk_repl_get_active_vm()'s own precedent. Decisions: re-verify the attached drive live on every USE call rather than trust a cached pubkey; VMs with no VMIdentity installed yet (Hera/Hermes/Artemis today) stay freely targetable, no regression; installed=1 targets refuse on no-drive-attached or pubkey mismatch, matching USE's existing refusal style. Zuse's override UX stays deferred per direct instruction.
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.
Traced x509_ed25519.h and zuse_cert_devblock.h before scoping: a regular user's cert has a fully separate trust root from the capsule-PKI chain (signed by Zuse's own on-device key, not the offline root CA/snakeoil intermediate), so verification is a single ed25519_verify() call, no chain walk. Corrects D.4's earlier "no new crypto work needed" claim -- x509_extract_ed25519_pubkey() deliberately stops at SubjectPublicKeyInfo, so verifying (not just reading) a cert needs new DER-walking code to capture the TBSCertificate byte range and signature. Decisions: X.509/DER format, drive_uuid bound via the cert's serialNumber field (avoids needing extension parsing), revocation deferred, cert lives in homeblocks_sig_t's already-reserved cert_offset/cert_devblocks.
WIREBIND traced against live capsule_birth_baby()/dispatch_init_forth() and found to have no real mechanism behind it yet -- depends on CERTVERIFY (identity-authentication) and a new RUNCAP mechanism for per-identity VM content, neither shown in the §E graph before now. Followed the thread into RUNCAP: capsule_birth_baby() is already generic, so RUNCAP needs only a heap-built single-entry capsule directory, not new birth machinery. Repurposes homeblocks_sig_t's now-dead blockmap_offset/blockmap_devblocks fields (per BMAPFMT, §F.4) to point at the identity's init source instead. Also captures a user-pool scope clarification: a thumbdrive is a user's pool by default, uncontested; FIRSTTOUCH's claim logic applies only to system-device extension.
Fourth node in the iterative Q&A pass, worked conversationally step by
step: state field justified against blk_bam_entry_t precedent and the
MIGSM/UNCLEAN nodes' own needs; ACL ownership tied to the same VMIdentity
pubkey representation decided for ACLKEY; ACL check ordering grounded in
vm.c's live fast-deny word-execution pattern.
Biggest finding: BLK_META_PER_BLOCK's existing 341x3-into-1KiB packing is
exactly the "3-block cluster + 1KiB metadata" shape raised in discussion --
it's blk_meta_t, with real wired accessors (blk_get_meta/blk_set_meta) but
confirmed zero callers anywhere in the codebase, and stale POSIX-flavored
ownership fields (owner_id/permissions/acl_block) that predate the
anti-POSIX principle and the pubkey-based identity model.
Decided: BMAPFMT is not a new structure, it's repurposing blk_meta_t
(distributed ownership/ACL/state per block, not a separate centralized
table) -- flagged that this makes homeblocks_sig_t's reserved
blockmap_offset/blockmap_devblocks fields unnecessary. New field layout
for the 40-byte security/ownership block: an 8-byte owner pubkey
fingerprint, a fast-deny acl_allow bit, and deliberate reserved slack
per "flexibility until we understand the recipe."
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U14ET9CWAtbQMbYqomKgXd
Third node in the iterative Q&A pass: re-checked sk_repl_idle() and the
bot_msc_attach_pending/detach_pending doc comments directly against the
original Milestone 2 punch-list wording. The flag-set-by-xHCI,
flag-consumed-by-sk_repl_idle() pattern isn't literally a registered
callback but achieves the same documented decoupling goal
("keeps xhci.c decoupled from block_subsystem.c"), confirmed live on all
three arches. Closed as written.
Recorded a handoff note for WIREBIND's own future scoping: reuse the
existing homeblocks_sig_check() result as the branch point (recognized
drive -> cert-verify+birth, blank/foreign -> stays plain block storage)
rather than inventing new hotplug detection. Updated the §E graph's
HOTPLUG node from partial to done.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U14ET9CWAtbQMbYqomKgXd
Second node in the iterative Q&A pass: traced acl_mode/acl_allow/acl_pinned
to DictEntry (per-word, not per-VM) before proposing anything, confirming
"reuse ACL" could only ever mean reuse the pattern, not the data. Decided:
a new VMIdentity type in its own header (mirroring the existing VMUuid
precedent, not another inline VM struct field), Zuse keeps an always-allowed
but explicit-acknowledgment override, and console/VM binding stays freely
retargetable (no one-way pin).
Also captured a scope expansion surfaced during this pass: identity is a
general per-VM primitive needed by Hera/Hermes/Artemis/Console too, not
just user thumbdrives, plus two new standing items (a full codebase
scavenging audit, and v2.0.0 as the eventual release target) in new §D.5.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U14ET9CWAtbQMbYqomKgXd
First pass of the iterative Q&A planning loop over §E's dependency graph:
traced the existing READ(10) implementation in xhci.c to establish that
WRITE(10) is a direct mirror (data direction, new SCSI opcode, new BOT
state) rather than new protocol work, then recorded the three scoping
decisions made (read_only flip timing, QEMU disposable-image validation
target, scope boundary excluding MINT/DETACH) in new §F.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U14ET9CWAtbQMbYqomKgXd
No code changes -- per direct request to represent the accumulated
Milestone 2/3/5 punch-list items plus D's Tripod-vision gaps as a graph
rather than flat lists, since several items turn out to be the exact
same open question asked in different places, and several converge on
the same blocker.
Mermaid dependency graph in FABRIC-3.md §E, cross-referencing:
- WRITE(10) (Milestone 2) as a true hub: independently gates the
block-map write path, the ongoing MINT word, detach/flush-back, and
all real-hardware testing -- landing it once unblocks four
separate-looking fronts.
- Milestone 5's "key/lock data shape" and D.4's "ACL bumps and holes"
gap are literally the same open question, not two separate ones.
- Runtime capsule construction (a D.4 detail) sits on the critical path
to the MINT word, not a side note.
- Message-bus migration has three currently-unbuilt hardwired
prerequisites, confirming it's correctly last in this whole area.
- Only 4 of the ~20 nodes are genuinely standalone; everything else
connects to at least one other open item.
No ordered plan yet -- this is the graph itself, not a sequence.
Documented as the input to the next iterative planning pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U14ET9CWAtbQMbYqomKgXd
No code changes -- continued design capture, per direct request to
link today's vision into the existing FABRIC.md/FABRIC-2.md material
("the existing pile of dirt") before planning.
Mined FABRIC.md's original Stadium theory (§17.1, §20, §24) and found
the EXPIRE-as-TTL correction wasn't just cleaner once "session=VM"
landed -- it was required from the start: the foundational
patron/departure table already says VMs depart via heat decay (COOL),
never TTL (TTL only ever governed messages/ACLs). §20's outer/inner
Stadium nesting already anticipated "attach = admit a VM" as a case,
so no new Stadium theory is needed, only a new admission trigger.
§24's identity-stability rules don't block session-state round-
tripping across separate attaches either way.
Mined FABRIC-2.md and found real grounding for two more decisions: the
acl_pinned one-way-ratchet was already identified as the right shape
for both Zuse's "burn" and console-session ownership, just never built
past word-execution gating -- and the real Hermes message shape
(MSG-CELLS, 9 fields, out-of-line payload, MSG-ALLOC/CH-ALLOC/
MSG-DELIVER) gives message-bus migration a concrete target instead of
an abstract goal. Confirmed empirically that today's xHCI hotplug
attach chain is 100% hardwired, zero messaging anywhere in it -- the
real baseline to migrate from.
Captured the pentagon topology: "just to get a user, 5 VMs are needed"
-- Hera, Hermes, Artemis, the user's own VM, and the Console, fully
interconnected (K5, a pentagon with every diagonal drawn). Flagged the
Console as a full peer node, not the passive relay D.3's flow
description implied -- reconciling that phrasing against the five-node
picture is new open work.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U14ET9CWAtbQMbYqomKgXd
No code changes -- pure design capture, per direct request ("capture
EVERYTHING first then we'll build a plan").
Core correction: a session IS a VM (Zuse is a player in the Stadium
fabric like any other, not a special boolean flag on Hera). This
supersedes an in-progress plan this session to implement EXPIRE as a
Stadium-TTL sweep on the zuse_session boolean -- caught mid-research,
before any code was written. Session end = VM detach, much closer to
the existing COOL/capsule_vm_kill() path than a new TTL mechanism.
Captured the full attach/mint flow: idle-loop thumbdrive watch,
cert-only auth (no password/username, no central user directory),
runtime capsule construction from drive content, ACL-based console/VM
binding (resolves Milestone 5's "reuse ACL-PIN vs. new primitive"
question in favor of reuse), ongoing MINT, and deferred detach/flush-
back. Standing completion criterion: nothing here is done until
hardwired calls are replaced by real Hermes messages.
Sorted the resulting gaps into answered (chain-of-trust mechanism,
ACL-reuse decision, no-central-directory) vs. genuinely deferred
(runtime capsule construction, exact ACL comparison semantics,
message-bus migration scope, polymorphic block-boundary behavior,
SSD-store scope, session state round-tripping, and the underlying
WRITE(10) hard blocker). Also flagged a small unrelated bug: the
prompt should read (Zuse)ok>, not zuse)ok>.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U14ET9CWAtbQMbYqomKgXd
Before flipping, found CAPSULE_SIG_MISSING (no signature at all) is the
normal state everywhere except this machine -- CI and any other
checkout have no access to the offline key, by design. Refusing on
MISSING the same as INVALID would brick boot everywhere but here.
Decided (on request): enforce ONLY on CAPSULE_SIG_INVALID (a signature
that IS present but doesn't verify -- unambiguous tampering/corruption
evidence). MISSING/NO_ROOT_KEY stay WARN-only permanently.
All three capsule_birth.c call sites now return CAPSULE_RUN_ERR_INVALID
on CAPSULE_SIG_INVALID, after logging the same WARN as before.
Verified on all three architectures, both directions, per the original
rollout commitment: positive case (real signed capsules) reboots clean
with zero warnings on amd64/aarch64/riscv64. Negative case (same
one-byte signature corruption used for the WARN-only proof, on Mama's
own init.4th) now genuinely refuses identically on all three:
"capsule sig: init.4th: INVALID" then "Init: Mama birth FAILED". The
feared "no ok> at all" blast radius didn't materialize -- kernel_main.c
already had graceful error handling for a failed Mama birth (log and
continue, pre-existing code); the kernel reaches a degraded ok> rather
than crashing, on all three architectures. Final acceptance pass (real
signed capsules, tampering reverted) clean on all three.
Milestone 6 is now fully closed except magic-number content-type
detection (shared with Milestone 4, separate scope, not started).
Documented in FABRIC-3.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U14ET9CWAtbQMbYqomKgXd
--manifest mode's file scan is a completely separate code path from
build mode (only ever walks .4th files, never the embedded PKI cert or
font capsule) -- extended it to accept the same optional --sign-key
<path> prefix build mode already has, factoring the key-loading code
into a shared load_sign_key(), so the manifest can report real
per-capsule signing status without touching or requiring a rebuild of
capsule_generated.c.
New "Signed" column on the capsule summary table: yes/no when
--sign-key was given, n/a (with an explanatory footnote) when it
wasn't -- never a bare blank that could be misread as "unsigned".
Makefile.starkernel's manifest-generation call site now passes the same
SIGN_KEY_ARGS the real build uses, so capsules/BLOCK_MAP.md reflects
this machine's actual signed state by default.
Verified: clean compile, BLOCK_MAP.md correctly shows "yes" for all 31
tracked capsules on a real signed build; a quick amd64 boot (no kernel
code touched, host tooling only) confirmed no regression.
This closes every open Milestone 6 item except magic-number
content-type detection (shared with Milestone 4, not started) and the
hard-refuse flip (deliberately deferred). Documented in FABRIC-3.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U14ET9CWAtbQMbYqomKgXd
First attempt shelled out to `openssl pkeyutl -sign` (fork/execlp, not
system() -- avoided shell string interpolation of the key path).
Corrected on request: no new external host binary dependency when the
repo's own code can do the job -- same standing preference as the
earlier anti-file correction. Rewritten to link ed25519_sign() (already
verified against OpenSSL in Phase B) directly into mkcapsule.
New tools/pkcs8_ed25519.c: a narrow DER walker (same shape as
x509_ed25519.c, deliberately not shared -- small enough that
duplicating a few TLV-walking lines beat threading a header between the
kernel crypto tree and host tooling) extracting the raw seed from the
intermediate's PKCS#8 private key, plus a minimal self-written base64
decoder (PEM is openssl genpkey's default output; no decoder existed
anywhere in the repo). Verified end-to-end before wiring anything in:
the extracted seed's derived pubkey matches the cert's exactly, and a
full self-contained sign+verify round-trip (zero openssl) passes.
CapsuleDesc had no spare bytes, so signatures live in a new parallel
CapsuleSigEntry array, emitted by a new `mkcapsule --sign-key <path>`
flag (omitted/missing key -> has_sig=0 everywhere, graceful, not a
build failure -- CI has no access to the offline key).
New capsule_sig.c/.h: capsule_verify_signature(), a separate function,
not folded into the already-tested capsule_validate(). Finds and caches
the embedded intermediate cert's pubkey once per boot, then verifies
against it. Wired into all three capsule_validate() call sites in
capsule_birth.c via log_message(LOG_WARN, ...) -- never refuses yet,
per the earlier staged-rollout decision.
Verified independently, both directions, live in the real kernel: a
full clean build (38 signed capsules) boots clean on all three
architectures with zero warnings. Separately, hand-corrupted one byte
of Mama's own init.4th capsule's stored signature (not its payload/hash,
which capsule_validate() already catches and would have masked the
test) and rebuilt just the changed object: produced exactly "capsule
sig: init.4th: INVALID -- signature does not verify" on boot, and the
kernel still reached ok> -- proving warn-only doesn't refuse anything
yet. Reverted before the final, untampered 3-arch acceptance pass.
Still open: flipping WARN to hard-refuse (separate, deliberate step)
and the BLOCK_MAP.md signature-status column. Documented in FABRIC-3.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U14ET9CWAtbQMbYqomKgXd
Root CA + snakeoil intermediate generated entirely offline
(/home/rajames/CLionProjects/lithosananke-ca/, outside this repo,
private keys chmod 600) per this milestone's own requirement: Ed25519,
root self-signed 20-year validity, intermediate real-CA-signed
(CA:TRUE, pathlen:0), chain verified via openssl.
Snakeoil intermediate embedded as a capsule (capsules/pki/
snakeoil-intermediate.der) -- confirmed the font-capsule precedent
needed zero new infrastructure, any non-.4th file under capsules/
embeds verbatim already.
New x509_ed25519.c: a from-scratch, narrow DER walker (not general
ASN.1/X.509, per this milestone's design decision) extracting the raw
Ed25519 pubkey from a cert's SubjectPublicKeyInfo -- handles the
optional v3 version field, verifies the AlgorithmIdentifier OID is
Ed25519 rather than assuming, handles both DER length forms. Verified
against ground truth: the extracted key from the real embedded cert
matches openssl's own reported pubkey byte-for-byte; refusal path
checked against truncated/garbage/empty/wrong-algorithm (real RSA cert)
input. Compiles clean on all three architectures.
Still open: mkcapsule signing step, wiring ed25519_verify() into
capsule_birth.c's three validate call sites (landing warn-only first,
per decision -- a bug here could stop every capsule from birthing,
including Mama's own, on all three arches), and the BLOCK_MAP.md
signature-status column. Documented in FABRIC-3.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U14ET9CWAtbQMbYqomKgXd
Replaces the crashed NVRAM approach entirely. New
include/starkernel/zuse_cert_devblock.h: a standalone on-disk record
(magic + version + 32-byte seed + 32-byte pubkey + a real CRC-64/ISO
from day one, same discipline homeblocks_sig_t established) occupying
devblock_from_top=0 of the fence. Its own header, not inlined at the
boot call site, since the still-open MINT word will be a second
consumer of this exact format.
kernel_main.c's mint-or-load logic now reads the fence, installs an
existing valid cert, or mints fresh via virtio_rng+ed25519_keygen and
writes it. Runs right after virtio_rng_init(), before
capsule_birth_mama() -- unlike the crashed NVRAM attempt, raw block I/O
against Artemis's already-proven device has no boot-timing risk, so the
earlier "re-invoke ACL-ZUSE-BOOT after Mama birth" workaround is gone;
ACL.4th's self-activating ACL-ZUSE-BOOT sees a correct cert on its one
ordinary pass.
Verified independently across every real scenario, never trusting the
kernel's own report: fresh mint decodes correctly on disk with a CRC
confirmed by a from-scratch Python re-implementation of the algorithm;
a reboot without reformatting loads back byte-for-byte identical
seed/pubkey (genuinely "mint once, ever"); a pre-fence volume refuses
cleanly (no crash, no silent data loss, honest "not persistent"
reporting); the real, untouched disk/artemis.img exercises the same
graceful-refusal path identically on all three architectures.
Phase 8's core arc is now functionally complete: real entropy -> real
signing -> real anti-file block-native persistence -> a first-boot mint
that survives reboots. Still open: the ongoing MINT word for minting
additional regular users. Documented in FABRIC-3.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U14ET9CWAtbQMbYqomKgXd
Corrected meta_fence_blocks units 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. This
let the fence fold directly into compute_totals_from_B()'s existing
payload4k formula (total_devblocks - 1 - B - R - F) instead of a
separate user_blocks subtraction: total_blocks/user_blocks/free_blocks
all shrink correctly for free, in both the fresh-format and reload
paths, from one formula change.
New blk_meta_zone_read()/blk_meta_zone_write() -- raw, unpacked 4 KiB
devblock I/O, same shape as the header/BAM/reloc-table regions,
addressed by devblock_from_top counting down from the device's last
physical devblock. Refuses rather than clamps if the index exceeds the
on-disk meta_fence_blocks. C-only, no FORTH word wraps either -- same
discipline as vm_zuse_cert_install(), which will be this zone's first
real tenant.
Verified independently at every step, never trusting the kernel's own
report: capacity math cross-checked against a from-scratch Python
recomputation of the same formula (exact match); accessor correctness
via a temporary probe (written/run/captured/reverted) that wrote a
known pattern and read it back, then independently confirmed via a raw
read of the disk image at the exact expected physical byte offset.
Full 3-arch acceptance boot against the real, untouched disk/artemis.img,
probe code fully reverted -- clean, conservation intact.
Still open: wiring vm_zuse_cert_install() to actually persist through
these accessors, and the MINT word itself. Documented in FABRIC-3.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U14ET9CWAtbQMbYqomKgXd
Corrected substrate: this OS is anti-POSIX, anti-file by design -- the
prior "dedicated system-identity disk" framing was wrong vocabulary,
caught before any code was written (saved as
feedback_no_files_anti_posix.md). The real primitives are
content-addressed capsules and raw LBN blocks, never a filesystem.
Design (agreed on request): a growable metadata fence at the TOP of a
device's block space, mirroring block_subsystem.c's existing bottom BAM
reservation from the opposite end -- the two grow toward each other,
never colliding, same shape as a stack/heap. Starts at
BLK_META_FENCE_INIT (128 blocks), explicitly never RAM-backed. Reuses
Artemis's already-attached, already-proven virtio-blk device -- no new
device. Rejected reusing BAM's own reserved zone directly: those blocks
are fully claimed by BAM bookkeeping, not free space.
Step 1 only: new meta_fence_blocks field in blk_volume_meta_t, appended
after reloc_devblocks and carved from _pad[] -- identical graceful-
default technique reloc_devblocks already established (a pre-existing
volume reads it back as 0, not a format break). Added a compile-time
_Static_assert on the struct's total size, same discipline
homeblocks_sig.h uses -- caught a real bug immediately: the hand-summed
_pad[] formula was off by 4 bytes (a compiler alignment gap the manual
count missed), found via offsetof() rather than re-deriving by hand.
Worked against disposable clones throughout, never the real
disk/artemis.img (ARTDISK is ?=-overridable) -- artemis-metafence-fresh.img
(blank, fresh-format path) and artemis-metafence-test.img (copy of the
pre-existing artemis.img, graceful-default-on-reload path), kept as
regression fixtures matching disk/README.md's existing convention.
Verified independently via direct byte reads of the disk image, not the
kernel's own log output (log_message(LOG_INFO,...) doesn't reach serial
in this build -- unrelated pre-existing gap): fresh format writes 128 at
header offset 184, a reboot without reformatting preserves it, the old
pre-fence image reads back 0. Full 3-arch acceptance boot against the
real, untouched disk/artemis.img also clean.
Allocator (user_blocks math) and zone read/write accessors both still
open -- next steps, documented in FABRIC-3.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U14ET9CWAtbQMbYqomKgXd
Cert storage expanded from the old 16-byte placeholder to a real
32-byte seed + 32-byte pubkey. vm_zuse_cert_install() now has a
kernel-side duplicate in src/starkernel/vm/vm_core.c -- the kernel
build's VM_EXCLUDE list drops src/vm.c entirely (same reason
vm_set_base() already has two independent copies), so the hosted-only
version added earlier this session was never actually linked into the
kernel. FORTH-side ZUSE-CERT-LO@/HI@ replaced with ZUSE-PUBKEY@ (i -- u)
over the public half only; ACL-ZUSE-BOOT now checks
ZUSE-CERT-INSTALLED? before authenticating instead of unconditionally.
Attempted NVRAM-based persistence (GetVariable/SetVariable) for the
first-boot mint flow: page-faulted inside OVMF's variable service
(CR2 in the flash MMIO window). Moving the call site to match the one
proven-safe existing SetVariable call site in this codebase produced
the identical crash -- not a timing issue. Localized with debug
markers (one boot): GetVariable works; SetVariable with real data
never returns. The existing "working" precedent call is actually a
delete-of-nonexistent-variable (size=0, data=NULL), a cheaper path
that never touches flash, so it proved nothing about real writes.
Root cause: this kernel's VMM never maps the region OVMF's variable
service needs for real flash writes -- a genuine gap in UEFI runtime-
services support, not Zuse-specific, and not obviously fixable in a
3-arch-uniform way (flash window location is firmware/arch-specific).
Independently, storing the raw seed in RUNTIME_ACCESS NVRAM would have
been a real security defect regardless of the crash -- readable by any
later-loaded UEFI app or the booted OS.
Reverted to a known-safe state: all NVRAM/mint code removed from
kernel_main.c, init.4th's ACL.4th line back to its documented
commented-out default. Verified clean compile and clean boot on all
three architectures. Cert storage expansion (the part that works)
stays. A dedicated system-identity disk (virtio-blk, already proven
for writes via Artemis) is the recommended next substrate -- not yet
decided or built. Full investigation documented in FABRIC-3.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U14ET9CWAtbQMbYqomKgXd
Scoping Phase C (the MINT word) surfaced a real blocker: "mint one
Zuse, ever" needs the cert to survive reboots, but the qemu target
copied a fresh, pristine OVMF_VARS.fd on every invocation -- a
UEFI-NVRAM-based cert would never persist under this project's own
normal test workflow. Digging further, aarch64 had no persistent NVRAM
store at all (single -bios arg, no split VARS pflash like amd64/riscv64).
Fixed rather than switching storage substrates: amd64/riscv64 now only
copy the VARS template if the destination doesn't already exist, so
`clean` (which deletes the whole build tree) is the bleach step and a
bare `make qemu` preserves NVRAM -- matching the existing "always clean
before qemu" acceptance convention exactly. aarch64 restructured to
split CODE(ro)/VARS(rw) pflash drives matching the other two, with a
graceful fallback to the old -bios mode on hosts without split firmware.
Also resolves two design questions before any cert code: Zuse doesn't
need Milestone 6's CA (that's the capsule-signing chain, a separate
trust domain -- Zuse is a self-sovereign instance-local root of trust),
and flags that this session's own earlier vm_zuse_cert_install() storage
(16 bytes) is too small for a real Ed25519 keypair.
Verified: all three architectures boot clean to ok> with the new pflash
arrangement, Stadium conservation intact, no panics or guest errors.
Infrastructure-only -- no cert code yet. Documented in FABRIC-3.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U14ET9CWAtbQMbYqomKgXd
Extends the previously verify-only ed25519.c with ed25519_keygen() and
ed25519_sign() per RFC 8032 5.1.5/5.1.6, reusing every point-arithmetic
primitive verify already had -- only seed expansion/clamping and
per-message nonce derivation are new. Signing is deterministic; only
keygen ever touches entropy, via a caller-supplied seed (virtio_rng,
Phase A) -- keygen still generates nothing itself.
New scalar_muladd() (scalar25519.c) for signing's S = (k*a + r) mod L,
the one scalar op verify never needed. Schoolbook multiply into a u128
wide accumulator with one final carry pass -- deliberately the same
shape as fe25519.c's existing multiply, which has a documented history
of a real bug from carrying mid-accumulation instead of in one pass.
Verified against an independent implementation, not self-consistency:
a throwaway host harness against Python's cryptography library (OpenSSL-
backed) across 6 trials (5 random seed/message pairs + the empty-message
case) produced byte-for-byte identical pubkeys and signatures every
time. Clean compile on all three architectures and a full 3-arch QEMU
acceptance boot, conservation intact, no panics or guest errors.
Nothing calls the new functions from the live kernel path yet -- that's
Phase C (the MINT word itself), still open, documented in FABRIC-3.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U14ET9CWAtbQMbYqomKgXd
The kernel's ed25519_verify() is deliberately verify-only -- no signing,
no keygen, no entropy source. That conflicts with the on-device MINT
word vision (Zuse signing new user certs live at runtime), so this
reopens that constraint on request rather than reshaping MINT around
verify-only.
vm_uuid.h already found the real gap: amd64 has RDRAND, riscv64 has Zkr,
but QEMU's aarch64 CPU models have neither -- confirmed against QEMU
10.2.1. A deterministic PRNG (fine for VM UUIDs) is not safe for key
generation, so this adds a virtio-rng device instead of a per-arch split:
real host entropy, identical guest-side protocol on all three arches.
New src/starkernel/virtio/virtio_rng.c + include/starkernel/virtio_rng.h,
transport plumbing mirroring the existing virtio_blk.c driver exactly.
Wired into kernel_main.c boot, -device virtio-rng-pci added to all three
QEMU targets.
Verified live (temp probe, written/run/captured/reverted): 16 real bytes
pulled through the full request/notify/poll round trip on all three
arches, three different values confirming real entropy. Final boot
against the reverted, permanent code: clean compile, clean boot to ok>
on amd64/aarch64/riscv64, Stadium conservation intact, no panics or
guest errors.
Ed25519 keygen/signing itself (Phase B) and the MINT word design
(Phase C) remain open, documented in FABRIC-3.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U14ET9CWAtbQMbYqomKgXd