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
Found that a pinned CONSTANT is not actually tamper-proof: ACL-PIN only
blocks redefinition, not a >BODY-then-store on the word's existing data
field. Moves the Zuse cert value into C-only VM struct fields
(zuse_cert_lo/hi + zuse_cert_installed fuse bit) with a one-time
vm_zuse_cert_install() and read-only ZUSE-CERT-LO@/HI@/INSTALLED? FORTH
accessors, closing the tamper path structurally instead of by convention.
Deletes the now-insecure ZUSE-CERT-LO/HI CONSTANT words from zuse.4th.
vm_zuse_cert_install() has no caller yet -- the real mint flow (Milestone
6 CA, the MINT word) is still open; this is storage + accessors only, not
a stand-in mint. Documented in FABRIC-3.md. Verified: hosted build clean,
mkcapsule --lint clean (31/31), clean boot to ok> on amd64/aarch64/riscv64
with Stadium conservation intact and no panics.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U14ET9CWAtbQMbYqomKgXd
Captain Bob's correction. 64MB (disk/zuse.img, usb-thumbdrive-test.img)
was always just an arbitrary QEMU-test convenience size, never a
real-world constraint -- but the docs and script didn't say so
explicitly, and an earlier memory's "16GB reference size" phrasing
(already hedged as tentative) risked reading as a decided target.
Audited the actual format for hardcoded size assumptions: none found.
homeblocks_sig_t already carries its own metadata_devblocks field,
recording whatever size a real drive's partition actually is.
Changes: scripts/bleach_zuse_img.sh gains a --size-mb override (tested
both the override and the unchanged default); disk/README.md's
zuse.img entry and FABRIC-3.md both now state the point explicitly;
the memory file and its MEMORY.md index line corrected to match (the
GPT layout's design point is the *proportions* -- small metadata
partition, everything else block storage -- not any absolute size).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn
Resolves the acl_pinned punch-list item's open question: credential
data (ZUSE-CERT-LO/HI, ACL-CA-KEY-LO/HI) are CONSTANT words, i.e. real
DictEntrys, and acl_pinned's enforcement (vm_create_word()'s
unconditional shadow-refusal for any pinned name) already covers this
generally -- no new flag needed, zuse.4th's ACL-ZUSE-BOOT already pins
both cert constants today.
That investigation surfaced a real gap: ACL-ZUSE-BOOT pins the cert
constants unconditionally on every boot, before any legitimate mint
could ever run, permanently locking in the 0 placeholder on the very
first boot. This directly shaped Captain Bob's next design pass: a
dedicated zuse.img test thumbdrive, "bleachable" back to pristine
state for repeated first-boot testing; a one-time mint-then-pin flow
(fixing the gap above); a separate ongoing S" name" MINT word for
minting additional regular users; and an explicitly-deferred Zuse
recovery path question.
This commit is the first piece: disk/zuse.img (64MB blank, matching
the existing USB-fixture convention) + scripts/bleach_zuse_img.sh
(idempotent reset). Verified live via QMP hotplug -- reads back as
HOMEBLOCKS_SIG_BLANK, correctly simulating a genuine first boot.
Deliberately flat/raw, not GPT-partitioned, matching
homeblocks_sig_check()'s current sig_start_fblock=0 assumption; both
move to a real GPT-relative offset together once a parser exists. No
kernel code touched -- host-side test tooling only, no 3-arch
acceptance boot needed.
Still open: the mint-then-pin boot fix, the MINT word, Zuse recovery.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn
Wired into sk_repl_idle()'s USB hotplug attach handler, right between
blkio_usb_open_msc() succeeding and blk_subsys_attach_device() -- logs
a distinct message per outcome (recognized / blank-or-foreign /
bad-version / bad-crc / read-error).
The "refuse" half is deliberately not implemented -- there is nothing
real to gate yet. blkio_usb.c has no SCSI WRITE(10) support at all, so
there is no write path today to refuse; attach currently only enables
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 break that legitimate use without protecting anything
real -- same "don't build ahead of a real caller" reasoning EXPIRE's
deferral used. Refuse belongs on the write path once WRITE(10) exists.
sig_start_fblock is hardcoded to 0 at the call site -- correct for
today's unpartitioned raw test/real media (no GPT parser exists yet),
flagged in the comment as the one place that changes once a real
GPT-partition-relative lookup exists, isolated from
homeblocks_sig.c's own location-agnostic check logic.
Verified live: hot-attached disk/usb-thumbdrive-test.img (blank media)
through a running amd64 instance's QMP socket (blockdev-add +
device_add usb-storage) -- captured exactly 4 real TUR+READ10 BOT
cycles (matching the header's 4 forth-block span) followed by the
correct "not recognized" warning, then normal attach completing
successfully afterward (no regression). Conservation intact, no
panic. Clean zero-warning compile and clean boot on all three
architectures.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn
Real, complete verification logic -- not yet wired to any write path.
homeblocks_sig_check(dev, sig_start_fblock, out_sig) reads the 4
consecutive 1KB blkio forth-blocks the 4KB header spans, verifies
magic -> version -> CRC-64 in order, returns HOMEBLOCKS_SIG_OK/_BLANK/
_BAD_VERSION/_BAD_CRC/_READ_ERROR. Reuses block_subsystem.c's existing
CRC-64/ISO (compute_crc64, previously static/file-local, now exposed
via block_subsystem.h) 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: verifies a signature given a location, finding that
location (GPT-partition-relative) stays the caller's job.
Verified against the actual shipped code, not a reimplementation: a
standalone host test links the real homeblocks_sig.c against a fake
in-memory blkio_dev and exercises all four outcomes -- blank media,
a correctly-minted header (round-trips drive_uuid/minted_time_ns), a
flipped CRC, an unrecognized version. All four pass. A full
QEMU-hotplug live test isn't proportionate yet since nothing calls
this function from the live kernel path -- wiring it into the attach
path is the next punch-list item. Clean zero-warning compile and
clean boot on all three architectures confirms no build/link
regression from exposing compute_crc64 and adding the new source
file to every kernel build.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn
Implements the design from the previous commit as-is: homeblocks_sig_t
(4096 bytes, magic+version+drive_uuid+timestamp+cert/blockmap offset
reservations+real hdr_crc), HOMEBLOCKS_SIG_PACK/_GET_MAGIC/_GET_VERSION
macros mirroring CAPSULE_MAGIC_PACK's bit layout, and a C99
compile-time size assertion matching stadium.h's own discipline.
Verified standalone (sizeof == 4096, clean under -std=c99 -Wall
-Wextra -Werror) -- nothing consumes this header yet, so no
functional kernel change and no 3-arch acceptance boot needed for this
step.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn
Mirrors CAPSULE_MAGIC_PACK's bit-packed magic and blk_volume_meta_t's
magic+version+fields+pad-to-4096 structural convention exactly, per
the punch-list item's own direction. Lives at the first 4KiB devblock
of the ~1GB GPT metadata partition -- format doesn't depend on the
still-missing GPT parser.
Deliberately narrow: identifies/authenticates the drive only, does not
invent the block-map or credential/cert formats -- reserves offset/
size pointers to where they'll live instead of embedding them, since
neither format is designed yet (block-map is Milestone 3, cert format
needs Milestone 6's still-ungenerated real CA). hdr_crc is real from
day one, unlike blk_volume_meta_t's unused placeholder, since this
header's whole job is gating the warn-and-refuse security check.
Design only, not yet implemented as a header file -- presented for
confirmation first.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn
The June 2026 campaign's +0.0054%-0.0088% number was cited as current,
but it measured the wrong thing under the wrong name -- the
Rolling-Window-of-Truth mechanism it was named after was dead code,
never reachable, and the measurement itself predates real compiler
optimization. Kept the original entry for historical record, added a
correction pointing to the actual current figure: +0.0603% ACL-TTL
enforcement overhead (FABRIC-2.md SS T), the same closure just applied
to FABRIC-3.md's stale re-measurement punch-list item.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn
Corrects bookkeeping, not measurement. FABRIC-2.md Sections M-T
(2026-08-19/21) already re-ran this and found the original "ACL-RWT"
name was itself wrong -- the Rolling-Window-of-Truth mechanism it
named was dead code removed 2026-07-08, never reachable. What the
campaign actually measured was the live ACL-TTL mechanism. Final
accepted result: +0.0603% ACL-TTL enforcement overhead,
architecture-independent, CV=0.000% (FABRIC-2.md SS T,
project_acl_ttl_overhead_final.md). The checkbox was simply never
marked done when that work concluded -- no new campaign run.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn
- docs/lithosananke/ROADMAP.md + M7.1.md: fixed stale "Branch: lithosananke"
(no such branch post-split), M7.1's "Design Complete" status (shipped
and live, redirected to FABRIC*.md), the M8/success-criteria
self-contradiction (OBSOLETE marking vs. unqualified live criterion),
and the stale AHCI/SATA claim for M9 (real implementation is
virtio_blk.c) -- also corrected BLOCK/BUFFER/UPDATE/FLUSH and block
device abstraction to [x] since both are confirmed live in
src/word_source/block_words.c and block_subsystem.c.
- Top-level ROADMAP.md: marked OBSOLETE (Captain Bob's call -- more than
"stale," the architecture/branch topology/terminology it describes no
longer exist), pointing to docs/lithosananke/ROADMAP.md and
FABRIC*.md for current status.
- docs/03-architecture/word-acl/DESIGN.md: fixed the ACL Phase 7
contradiction -- Phase 7 (LithosAnanke kernel parity) is independently
verified complete per .claude/CLAUDE.md, not "remaining"; removed the
stale lithosananke-branch-parity framing.
- VM-FLEET-ATTRACTOR-DESIGN-20260705.md's doe-campaign.4th "broken" claim:
investigated, ran SMOKE-CAMPAIGN live (completes clean, fleet heat
conserved) -- initially read as contradicting the claim, corrected
directly by Captain Bob: a clean execution trace doesn't disprove the
doc's actual argument (no real controlled-experimental-factor
mechanism). Confirmed accurate, left untouched.
- Isabelle/HOL pipeline-metrics model/C-struct mismatch: confirmed a real
proof-modeling gap (pm_last_accuracy_num/den has no analogue in the
real PipelineGlobalMetrics struct), not stale prose -- tracked here
rather than fixed, matching the .thy file's own scope boundary and
this project's standing caution that each Isabelle gap needs its own
subsystem model.
ACL-RWT DoE overhead re-measurement (the 6th item) intentionally not
started -- a full multi-architecture DoE campaign, not a doc-text fix,
holding for explicit confirmation given the scale.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn
Decided directly on request, no code written. "ACLs -> EXPIRE" isn't
wrong in spirit but was aimed at the wrong unit: acl_ttl-hits-zero
always renews (never revokes), so it can't carry EXPIRE's
residency-ending meaning. The unit that actually has a real 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.
Resolves to: admit the session (not a per-word ACL entry, which would
be redundant with item 4.1's existing word patrons) as the Stadium
patron, once Phase 8 exists. Not in scope now -- there is no session
to admit regardless of any other choice, and building Stadium's
missing generic ttl-decrement/reap mechanism ahead of its only real
consumer would be speculative infrastructure, the same shape of
premature build the no-stubs standard exists to prevent, just
inverted. EXPIRE stays explicitly deferred until Phase 8 lands, with
a concrete trigger for revisiting it -- not abandoned, not left
ambiguous.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn
Same fix as DELIVER, on request. COOL is real and live for both words
(item 4.1) and VMs (this session) -- stadium_evict()'s own universal
reservoir credit is the whole of what "cooling off the floor" means
for both, no extra payload action needed. stadium_dispatch()'s COOL
case now prints the departing patron's identity (word_id for a word,
0 -- the patron-zero convention -- for a VM) instead of "(stub)".
Verified live: both shapes fired correctly on the same boot --
"COOL identity=0" at Hermes's/Artemis's own explicit channel-eviction
self-test and again at their VM-patron eviction at PARITY:KILL,
"COOL identity=1" at a second channel eviction -- conservation intact
throughout. Clean zero-warning compile and clean boot with
conservation intact on all three architectures.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn
Investigated on request. acl_ttl (DictEntry, per-word ACL-RECHECK
amortization) and StadiumPatronHeader.ttl (residency countdown) are
different mechanisms wearing the same name -- ACL-RECHECK always
renews (allow=1, fresh ttl), never revokes, so it doesn't resemble a
reap event at all. Separately, StadiumPatronHeader.ttl is completely
inert across the whole codebase: every candidate constructor sets it
to 0, nothing ever reads or decrements it -- the generic TTL-expiry
mechanism EXPIRE would fire from doesn't exist in Stadium's own engine
yet, a gap one level deeper than "ACL isn't wired to Stadium."
Recorded four open questions that need a real decision before any
code: whether "ACL patron" is even the right model, what unit would
be admitted (redundant per-word vs. a session-scoped patron once
Phase 8 PKI/zuse work lands), whether building Stadium's missing
generic ttl-reap mechanism is in scope here, and what the reap action
should actually do given ACL policy never revokes today.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn
Words have been fully migrated and live via stadium_word_dispatch()
since item 4.1 -- this line just never got updated. Small, independent
doc fix flagged in FABRIC-3.md's punch list, addressed on request.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn
Re-scoped on request. capsules/hermes/init.4th's MSG-ALLOC already
admits every message onto Stadium with SB-DELIVER, and MSG-FREE-NODE
(called from both MSG-ACK-LAST and heat-driven MSG-REAP) already
evicts it -- "behaviour=DELIVER (stub)" has been printing on boot logs
since at least 2026-08-05. The earlier "zero consumer, needs
substantial Hermes lifecycle mapping work" framing was wrong,
carried over unverified from FABRIC.md's old "open, not resolved" note
about which Hermes event maps to DELIVER vs. EXPIRE -- item 4.2
already answered that in code without the prose catching up. Same
documentation-drift class as the stale ONTOLOGY.md words note and the
earlier glibc misattribution.
Checked whether the dispatch body needed a real payload action the way
MIGRATE did: MSG-DELIVER (the FORTH word) already runs the actual
delivery (VM-EXEC of the payload) before eviction, decoupled from
Stadium reap -- so by dispatch time delivery is already done, same
shape as COOL, which needs no extra action beyond stadium_evict()'s
own universal reservoir credit.
Fix: stadium_dispatch()'s DELIVER case now prints the departing
message's real identity (DELIVER msg_idx=N, same shape as MIGRATE's
lbn= print) instead of a misleading (stub) label. COOL is in the
identical situation (real for both words and VMs) but left as-is --
out of scope for this pass, noted in stadium.c's own comment.
Also confirmed EXPIRE (ACL) is genuinely unscoped, not stale docs like
DELIVER turned out to be -- zero Stadium involvement anywhere in
ACL.4th/acl_recheck()/the ACL design doc. Stays open pending real
design decisions.
Verified live via a forced MSG-SEND/MSG-DELIVER-ALL/MSG-ACK-LAST
sequence from Hermes's own REPL context ("USE" now works after the
previous fix): "Stadium: dispatch cell=73653 behaviour=DELIVER
msg_idx=1". Clean zero-warning compile and clean boot with
conservation intact on all three architectures.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn
capsule_vm_kill() had zero Stadium involvement (vm_cleanup()/sf_free()
only), and child-VM birth only ever called stadium_grant_quota() -- a
resource pool for the VM's own future word/block patrons, never
stadium_admit() for the VM itself. stadium_birth_hera() looked like a
precedent but admits Hera into her own quota as a permanently pinned
cell 0, which can never reach stadium_evict() -- not a working example
of COOL firing for a VM.
Adds size_t stadium_patron_cell to VMRegistryEntry. At birth, right
after the existing stadium_grant_quota() call, admits a candidate into
the new VM's own quota mirroring stadium_birth_hera()'s shape
(identity=0, mass=1, behaviour=COOL) but deliberately unpinned --
pinning would need a new "unpin" primitive (none exists) to ever evict
it later, and unpinned costs nothing since nothing wires COOL's
dispatch body to kill anything; the worst case of an unrelated natural
eviction is stale bookkeeping, tolerated the same way
stadium_word_forget() already tolerates staleness elsewhere. At
capsule_vm_kill() and capsule_vm_kill_all_nonmama(): stadium_evict()
the tracked cell if still resident, silently tolerating refusal
(already gone). stadium_dispatch()'s COOL case needed no new payload
body -- same as it already is for words, where COOL has no defined
extra action beyond stadium_evict()'s own universal reservoir credit.
On investigation this turned out not to be entangled with the
still-iterating Tripod/Zuse/messaging vision after all -- birth and
kill already funnel through two single choke points, so the earlier
deferral (previous commit) was overcautious.
Verified live: a second, new "Stadium: dispatch cell=... behaviour=
COOL" now fires immediately before every PARITY:KILL line, for both
Hermes and Artemis, distinct from the pre-existing COMMON-CH
word-eviction self-test's own COOL print. Conservation
(resident_sum + reservoir == Q48_ONE) intact throughout. Clean
zero-warning compile and clean boot on all three architectures.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn
capsules/lib.4th:13-14 defined ": USE ( addr u -- ) EXEC ;" and the
same for RUN, shadowing the C-registered mama_word_use()/RUN primitives
(CLAUDE.md names both, with BIRTH, as untouchable C primitives) with an
unrelated "load/exec a capsule" meaning. This broke the interactive
USE-based VM-redirect (S" Artemis" USE printed "EXEC: failed: Artemis"
instead of redirecting), discovered while chasing FABRIC-3.md's
live-MIGRATE verification.
Traced every real caller before touching anything: RUN's alias was
dead code, never called anywhere as bare RUN. USE's alias had exactly
one real caller -- capsules/hermes/init.4th:397, intentionally
exploiting the shadow to load common:msg.4th right after lib.4th
itself loaded. Both aliases were pure EXEC wrappers with zero added
behavior, so this deletes both definitions outright and switches the
one real call site (plus its matching doc comment in
capsules/common/msg.4th) to call EXEC directly. No new names invented,
the C primitives untouched.
Verified live: Hermes still births and her COMMON-CH-eviction
self-test (depends on common:msg.4th having loaded) still passes;
interactively, S" Artemis" USE now correctly redirects the REPL and
prints "USE: now using Artemis". mkcapsule --lint clean (31/31).
Clean compile and clean boot with Stadium conservation intact
(resident_sum + reservoir == Q48_ONE) on all three architectures.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn
Confirmed the real MIGRATE dispatch fires live: a temporary boot probe
(STADIUM-ADMIT + STADIUM-EVICT against the live Artemis VM, inserted
into the existing 4.6 self-test and reverted immediately after capture)
produced "Stadium: dispatch cell=63257 behaviour=MIGRATE lbn=100" with
blk_flush(100) firing and zero error -- closes the honest gap left open
in the previous commit. Interactive flooding alone couldn't reach this:
Hera's reservoir sits at the Q48_ONE/3 floor from boot self-tests, so
block-touch candidates pull 0 heat and can never out-density an
existing resident, a pre-existing reservoir-floor/eviction interaction
unrelated to this pass.
Also reports (not fixes, per CLAUDE.md) a real dictionary-shadowing bug
found while chasing this: capsules/lib.4th:13 redefines USE as EXEC,
shadowing the C primitive mama_word_use() (REPL VM-redirect) with an
unrelated capsule-loading meaning -- same for RUN at lib.4th:14. Same
bug class as the K-PUSH dictionary-shadowing issue.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn
stadium_admit()'s mass==1 refusal looked like a hard blocker for 1024-byte
blocks, but stadium_word_dispatch()'s real candidate construction proves
Stadium cells carry pure identity/heat/bookkeeping, never the resident's
actual content -- a block patron follows the same shape (identity=LBN,
payload unused), so this was real, scoped work, not a case for stubbing.
New stadium_blocks.h/.c mirror stadium_words.c's admission/cooling shape,
keyed by (quota_slot, lbn) in a fixed-capacity open-addressing hash table
(tombstone deletion) instead of a dense array, since LBN space isn't
densely bounded like word_id. Wired into block_word_block()/buffer()/
update() (block_words.c), __STARKERNEL__-guarded. stadium_dispatch()'s
MIGRATE case now calls blk_flush(lbn) for real instead of printing
"(stub)". Three new Kconfig constants (STADIUM_BLOCK_HEAT_QUANTUM/
STADIUM_BLOCK_COOL_RATE_Q48/STADIUM_BLOCK_TRACK_CAP_MULT) mirror the
word-patron ones, same three-layer wiring.
VM-COOL/DELIVER/EXPIRE stay explicit punch-list items -- VM-COOL
deferred pending the still-iterating Tripod/Zuse/messaging vision,
DELIVER/EXPIRE are their own future subsystem integrations per
FABRIC.md's own "open, not resolved" notes.
Verified clean compile (zero warnings) and clean boot to REPL with
conservation intact (resident_sum + reservoir == Q48_ONE) on all three
architectures (amd64/aarch64/riscv64); BLOCK/BUFFER touches exercised
live from the REPL with no crash; a 22,000-distinct-block flood loop
against an artificially shrunk Stadium ran clean under heavy admission
load. A live MIGRATE console fire was not directly observed this
session (root-caused to a pre-existing reservoir-floor/density-eviction
interaction unrelated to this change, documented in FABRIC-3.md) --
flagged as an honest follow-up, not silently claimed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn
Investigated whether stadium_admit()'s mass==1 refusal blocks block-patron
Stadium admission -- it doesn't: stadium_word_dispatch()'s real candidate
construction confirms Stadium cells carry identity/heat/bookkeeping only,
never the resident's actual content, so a block patron (identity=LBN,
payload unused) fits the same pattern words already use. The real gap is
just that no LBN->cell_index residency map or touch-on-access hook exists
yet -- real, scoped, buildable work, not a blocker.
Deferred VM-COOL pending the Tripod final-shape vision Captain Bob laid
out (thumbdrive minting, one-time Zuse fuse-blow, messaging-only inter-VM
interaction, polymorphic block-boundary behavior) -- recorded in a new
§D so near-term Stadium work doesn't ignore it, without treating a
one-sentence vision as a ready-to-implement spec.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn
FABRIC-2.md closed archival at ~4,400 lines / 51 open items, same reasoning
FABRIC.md itself was closed for at 7,595 lines -- continuing to append made
still-open work hard to find. All 51 open items carried forward into
FABRIC-3.md's new Section A, verified complete via programmatic diff against
the source (49 unique + 2 confirmed pure duplicates from FABRIC-2.md's own
F.3 cross-reference section, not dropped content). .claude/CLAUDE.md's
pointer note updated to name FABRIC-3.md as current.
New Section B: full audit of stadium_dispatch()'s four behaviour stubs
(MIGRATE/DELIVER/EXPIRE/COOL), triggered by investigating "words/VMs/blocks/
messages should all be on the same engine". Found the picture is more
nuanced than "everything's a stub" -- words are already fully live via a
separate bespoke mechanism (stadium_word_dispatch(), wired into vm_core.c's
real word-execution path, item 4.1), contradicting ONTOLOGY.md's stale
"not yet migrated" claim (flagged for a follow-up fix). MIGRATE (blocks)
and VM-COOL are genuinely stub with zero consumers; DELIVER (Hermes) and
EXPIRE (ACL) are substantial, undecided subsystem integrations FABRIC.md
itself already flagged as open, not touched here.
New standing rule, saved as memory feedback_no_stubs_or_todos.md: stub
implementations and TODO placeholders are never acceptable in this
workflow, in any language, ever -- triggered by finding stadium_dispatch()'s
stub handlers during this investigation.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn
Implements the full design from the prior commit in one pass. resolve_lbn()
is the single choke point threaded through the ten public LBN-consuming
entry points (blk_get_buffer, blk_update, blk_flush, blk_is_allocated,
blk_mark_allocated, blk_mark_free, blk_is_valid, blk_get_meta, blk_set_meta,
plus blk_get_empty_buffer covered via delegation) -- an LBN->LBN redirect,
not a new storage allocator, since the LBN space is already unified across
every attached blkio_dev backend. VM window cache staleness across a
relocation reuses the existing blk_vm_check_epoch() mechanism from
Milestone 2h's hot-detach fix for free -- g.epoch bumps on relocation too.
Persistence lands in the same pass: two new uint32_t fields
(reloc_start/reloc_devblocks) appended after hdr_crc in blk_volume_meta_t,
carved from existing padding without moving any earlier field's byte
offset -- an old formatted volume's zeroed padding reads back as
reloc_devblocks=0 ("no reloc capacity"), gracefully, not a format-breaking
change. compute_totals_from_B() generalized to account for the new
reserved region. reloc_flush_to_disk()/reloc_load_from_disk() mirror the
BAM I/O functions' own absolute-devblock-addressing shape; the persisted
copy's owner is first_disk_slot() (already existed, already used for this
exact "which device is canonical" question by blk_get_volume_meta()).
blk_subsys_relocate_block() is a mechanical primitive only -- copies
content (staged through a local buffer, since obtaining the target's
blk_get_buffer() result can evict and invalidate the source's cache
pointer if they share a device), frees the source BAM entry, appends the
exception entry, bumps the epoch, flushes to disk. RELOCATE-BLOCK exposes
it to FORTH, no policy of its own (ACL's job, per this session's direction).
A first live-test attempt gave a false negative against disk/artemis.img
(predates reloc capacity, so relocation only ever existed in memory that
boot) -- traced to the test's own setup before being mistaken for a bug,
then re-verified correctly against a fresh volume (new fixture,
disk/artemis-reloc-test.img): relocated a RAMDRIVE block to the fresh
disk, confirmed live resolution through the redirect, then confirmed both
the redirect and the relocated content survived an abrupt QEMU kill and
full reboot. Also fixed three lingering "glibc" doc-comment
misattributions from Milestone 2h (the actual allocator is this kernel's
own kmalloc) that survived an earlier FABRIC-2.md-only correction. All
three architectures re-verified clean.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn
Complete design, not just the earlier flag: resolve_lbn() as the single
choke point threaded through ten public entry points, LBN->LBN redirect
mechanism (no new storage allocator needed, the LBN space is already
unified across all backend-agnostic blkio_dev devices), VM window
staleness solved for free by reusing the existing epoch mechanism from
Milestone 2h's hot-detach fix, in-memory table shape, and on-disk
persistence via two new fields carved from blk_volume_meta_t's existing
padding (byte-compatible with old formatted volumes) using the same
absolute-devblock I/O pattern the BAM already uses. Ownership of the
persisted copy assigned to first_disk_slot() (already exists, already
used for this exact "which device is canonical" question), with the
multi-primary-device question for a future multi-SSD/cloud world
explicitly punted rather than hand-waved. Noted the known, separate
limitation: USB relocation targets aren't exercisable until WRITE(10)
exists in the xHCI driver -- verification will use two already-writable
devices instead. Policy (when to relocate, whether a target is validly
owned) stays ACL's job, not this primitive's.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn
Milestone 3 direction from this session: ACL decides when to relocate
(capacity pressure or a compudynamics heat/cold signal), migration
expected rare not routine. Flagging only, not designing yet: current
lbn_to_slot() is pure contiguous-range routing, one device owns one
unbroken range -- incompatible with relocating an individual block to a
different device while its LBN stays fixed. Direction sketched (a sparse
LBN-to-actual-device exception table, consulted before the range walk,
zero cost for the common never-relocated case) but the actual structure
is still open. Block-migration implementation now explicitly depends on
this revision landing first.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn
Dropped the hash-modulo-MAX_IDENTITIES approach: there is no way to bound
how many identities might ever exist, so no population size can be
pre-divided into slots regardless of hash function. Resolution: first
time an identity is seen, claim a range at the SSD's existing free-space
frontier (g.total_user_lbn, already used by every device attach) sized
to whatever it needs, write the assignment into the drive's own map;
every later visit just reads it back. No central directory, no quota,
matches Section U's existing requirements exactly.
Identity itself is a certificate signed by this project's own (not yet
created) CA root, extending Milestone 6's PKI design to users instead of
inventing a separate mechanism. Derivation-relevant value is the cert's
embedded pubkey, not its raw bytes -- a renewal shouldn't relocate an
already-allocated range.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn
Checked the math with Captain Bob: a PBAM for ~15GB of user blocks needs
only ~1.92MB (15M blocks / 32768 per 4KB page), fitting easily alongside
identity/credential/PKI material in a 1GB reserved region. No partition
table needed -- same whole-device pattern artemis.img already uses today,
just with a generously fixed ~1GB reserved region instead of tight
bam_start+bam_devblocks packing. Eliminates the GPT-parser work item
entirely (no GPT code exists in src/starkernel/ today). Confirmed no
conflict with Milestone 8's USB boot drive, which is a separate physical
drive (flashed starkernel.iso) with its own independent GPT/ESP need.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn
Makes blk_vm_flush_all() (block_words.c) non-static and declares it in
block_words.h -- it's already the entire implementation behind
SAVE-BUFFERS (block_word_save_buffers() is a one-line wrapper), so
sk_repl_idle() can call the exact same flush path outside word dispatch
without duplicating any logic. Cheap every idle tick regardless of dirty
state: every check inside is a small fixed-size scan, so no separate
pre-check was needed on top of it.
Caught a real bug via a live persistence test before trusting the
feature: the first version gated the flush on sk_repl_get_active_vm()
returning non-NULL, but NULL is that accessor's documented default
(Tripod's own USE-redirect override, "restore default dispatch") --
without an active USE redirect, the flush silently no-op'd for the
entire session. Confirmed live: wrote a byte via BUFFER (no
UPDATE/SAVE-BUFFERS), waited past the idle cadence, killed QEMU abruptly,
rebooted with the same disk image, read back 0 instead of the written
65. Fixed by threading the VM sk_repl_run()'s own loop already resolves
each iteration (g_repl_active_vm ? g_repl_active_vm : vm) down as a
parameter through sk_readline() into sk_repl_idle(), rather than trying
to re-derive it from an accessor with the wrong default. Re-ran the same
test after the fix: read back 65, matching the written byte -- the write
survived an abrupt kill with no explicit flush call anywhere in the
test, proving the idle-tick auto-flush genuinely ran.
All three architectures re-verified clean. FABRIC-2.md Section V item 6
and the corresponding Milestone 3 punch-list item marked done.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn
block_subsystem.c is shared/vendored source -- its calloc()/free() calls
resolve to real glibc in the hosted build, but to this kernel's own
freestanding shims (src/starkernel/vm/host/shim.c, backed by kmalloc()/
kfree() in src/starkernel/memory/kmalloc.c) in the kernel build, which is
where the address-reuse bug was actually diagnosed live. The previous
writeup said "glibc's allocator" -- wrong environment entirely. Verified
kmalloc_aligned() is a plain first-fit walk from heap_head with no
coalescing, confirming the same free-then-immediate-same-size-alloc reuse
behavior originally observed, just attributed to the right allocator.
Caught by the user asking directly whether this project has any glibc
dependency, given .claude/CLAUDE.md's strict ANSI C99/no-GNU-extensions
requirement (confirmed -std=c99 throughout both Makefiles). Documentation
correction only -- no code changed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn