Commit Graph
142 Commits
Author SHA1 Message Date
Robert Allan JamesandClaude Sonnet 5 6fc0ee33a9 WIREBIND: real thumbdrive-attach call site, no manual steps
Assembles pieces already built and individually verified this session
-- CERTVERIFY (vm_identity_from_cert(), Phase A/B), RUNCAP, the
console-VM + user-VM pair (§F.22) -- into one automatic sequence,
replacing the RUNCAP-TEST/PAIR-TEST diagnostic words that exercised
each piece by hand.

New capsule_wirebind_try_attach() (capsule_wirebind.h/.c), called from
sk_repl_idle() alongside capsule_zuse_boot_try_attach() on every
HOMEBLOCKS_SIG_OK attach: sig->cert_offset==0 means this is Zuse's own
genesis-mode drive (no cert region) -- that's already
capsule_zuse_boot_try_attach()'s job, skip. Otherwise, with Zuse already
authenticated this boot (nothing to verify a regular cert against
otherwise), reads the cert devblock(s) and calls vm_identity_from_cert()
against mama_vm's own zuse_cert_pubkey and the drive's own drive_uuid.
On success: reads the drive's own user_identity_seed_t for its
username, births a console VM + RUNCAP-born user VM pair (idempotent --
no-ops if that username is already live this session), installs the
verified VMIdentity onto the user VM, and registers the "<username>~user"
pairing sk_repl_dispatch_line() (repl.c, §F.22) looks for. Deliberately
does NOT auto-USE the new console -- that stays an explicit,
ACL-gated step (BINDSTEP, §F.9), not something a bare attach should
trigger silently.

Verified end-to-end live in QEMU, including a genuine negative case:
attached disk/user1.img (signed by a different, earlier-session Zuse
instance) and got a correct "cert verification FAILED -- drive
refused" -- proof the check is real, not a rubber stamp. Minted a
fresh identity with this boot's own Zuse, reattached, and got
"WIREBIND: SamS attached and ready" printed with zero manual commands,
followed by a working USE + async WELCOME relay end to end (queued,
no UNKNOWN WORD, delivered and executed in the paired user VM on the
next idle tick). Clean 3-architecture regression: Hermes/Artemis both
birth live, no unexpected ACL denials or UNKNOWN WORD.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZGkimpfyh63EZyRkNbkPD
2026-08-28 16:57:17 -04:00
Robert Allan JamesandClaude Sonnet 5 b0f12710bb Console-VM + user-VM pair: real async message-passing relay
Console sessions now route through the same general VM-to-VM messaging
system (Phase C) any VM can already use for its own reasons -- not a
synchronous shortcut. Per direct instruction: real async MSG-SEND/
MSG-DELIVER (Option B), not a VM-EXEC-based synchronous relay, because
messaging is a general capability, not a console-specific mechanism.

New CONSOLE-CMD-EVENT message type (common:messaging.4th). New
sk_repl_dispatch_line() (repl.c), called from both sk_repl_step and
sk_repl_run in place of a direct vm_interpret(): if the active VM's own
name has a live "<name>~user" counterpart registered, the raw input
line is wrapped as an S"-embedded CONSOLE-CMD-EVENT MSG-SEND and
interpreted on the console VM instead of being run directly -- the
console's own next MSG-TICK (Hera's idle pump) delivers it into the
paired user VM via VM-EXEC, same mechanism every other message already
uses. Falls back to direct interpretation if there's no pairing, or if
the line contains a `"` (known v1 limitation, warned about explicitly
rather than silently mishandled).

New capsule_console_birth() (capsule_console.h/.c): a bare VM whose
only content is loading common:messaging.4th -- the console side of a
pairing, parallel in shape to RUNCAP's user-VM birth but with fixed
embedded content instead of a devblock read (no identity, no thumbdrive
involved). New PAIR-TEST diagnostic word (mama_forth_words.c, matches
RUNCAP-TEST's own precedent): births both halves of a pairing and
registers the "<name>~user" mapping. Not the real pairing call site --
that's the eventual attach/onboarding flow -- this exists to exercise
the relay live before that flow exists.

Found and fixed a real, serious bug live: console_set_vm_name() stored
the caller's raw pointer instead of copying it. mama_word_use() (USE)
passes a VMRegistryEntry field living on its own stack frame -- once
USE returns, that pointer dangles, corrupting every console tag after
the first USE (observed directly as garbled "[[]" / binary-looking
prefixes instead of "[CaptBob]"). Fixed at the source: console_set_
vm_name() now copies into internal storage. That surfaced a second,
related bug across every console_get_vm_name()-based save/restore call
site in mama_forth_words.c (BIRTH, VM-STEP, VM-EXEC, CONNECT-HERMES,
CONNECT-ARTEMIS): saving just a pointer into the single internal buffer
meant an intervening console_set_vm_name() call silently corrupted the
saved value before the restore ever ran. New console_save_vm_name()
copies into caller-owned storage; every save/restore site updated.

Verified end-to-end, live in QEMU: typed WELCOME at a paired console
VM -- it did not execute directly (no UNKNOWN WORD), printed ok
immediately (queued, async), and on the next idle tick
"[CaptBob~user] Minted identity -- default personality" appeared on
its own -- genuine delivery and execution in the paired user VM through
the real MSG-SEND/MSG-DELIVER pipeline. Console tags confirmed clean
(no garbling) across all three architectures' full regression boot.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZGkimpfyh63EZyRkNbkPD
2026-08-28 16:39:07 -04:00
Robert Allan JamesandClaude Sonnet 5 cc9521d2cc Retire emergency CLI: Zuse goes thumbdrive-resident, ACL.4th activated
Three tightly-coupled changes, verified together per Captain Bob's own
"getting rid of the emergency cli" direction:

1. Zuse's identity is thumbdrive-resident, never system-resident. New
   zuse_genesis_marker_t (magic/version/zuse_pubkey[32]/crc) replaces
   zuse_cert_devblock_t's slot in the top-of-device fence -- the system
   now remembers only that a root identity exists and its pubkey, never
   a seed. zuse_cert_devblock_t is kept in the repo, marked superseded,
   no longer written by any code path.

   capsule_mint_identity() grows a genesis mode (issuer_vm=NULL): no
   cert is built or written (Zuse isn't verified against a separate
   signer -- she's recognized by pubkey match against the marker) and
   two new optional out-params (out_pubkey/out_seed) let the caller
   install the cert immediately after a genesis mint.

   New capsule_zuse_boot_try_attach() (capsule_zuse_boot.c), called
   from sk_repl_idle() on every fresh USB attach (the only point in the
   boot lifecycle a thumbdrive can actually be detected -- attach
   polling doesn't exist yet at kernel_main.c's old one-shot mint point,
   which is why that whole block is gone): no marker + blank drive ->
   genesis-mint; marker present + matching drive -> read its own
   user_identity_seed_t, install the cert. Either way, re-runs
   ACL-ZUSE-BOOT (zuse.4th) so zuse_session activates exactly like it
   always has for a same-boot cert install -- ACL-PIN only blocks
   redefinition, not re-execution, so no new C-side auth logic needed.

2. ACL.4th activated (capsules/init.4th) -- inactive all session until
   now. Found and fixed a real bug this immediately surfaced: zuse.4th's
   ACL-ZUSE-BOOT tried `['] ACL-ZUSE-BOOT ACL-PIN` from inside its own
   still-compiling definition -- the word isn't findable yet at that
   point, so the whole definition silently failed to compile every
   previous boot this session (dormant, since ACL.4th never loaded).
   Fixed: pin after the definition closes, not from within it -- it
   only needs to happen once anyway, and pinning doesn't block the
   re-invocation genesis/attach needs.

3. The unauthenticated emergency-CLI ACL bypass is retired
   (repl.c): `emergency_console = is_hera ? (zuse_session ? 0 : 1) : 0`
   deleted from both sk_repl_step and sk_repl_run. Every word run from
   Hera's own bare prompt now goes through ordinary ACL enforcement;
   emergency_console is driven only by the genuine C-level fault
   handler again.

Added ZUSE-SESSION? (starforth_words.c), a read-only diagnostic
matching ZUSE-PUBKEY@'s own precedent, to verify the whole chain
directly rather than by inference.

Verified end-to-end live in QEMU: fresh boot, no thumbdrive ->
ZUSE-SESSION? reads 0. Attach a genuinely blank drive via QMP -> genesis
mint fires automatically (no typing) -> ZUSE-SESSION? reads -1 (true).
Hermes/Artemis both birth clean on all three architectures with ACL
now actually enforced for the first time all session -- no denials, no
UNKNOWN WORD beyond the deliberate POST self-test cases.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZGkimpfyh63EZyRkNbkPD
2026-08-28 16:10:40 -04:00
Robert Allan JamesandClaude Sonnet 5 7fc3e93358 Apollo test rig: commit live-minted Zuse + Captain Bob thumbdrives
disk/zuse.img and disk/user1.img now hold real Ed25519 keypairs, a
Zuse-signed cert, and real profile data (Konrad Suse/Zuse and Captain
Bob/CaptBob respectively), minted this session via MINT and verified
byte-exact against the raw devblocks. Keeping them as tracked fixtures
for the Apollo test phase.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZGkimpfyh63EZyRkNbkPD
2026-08-28 15:34:43 -04:00
Robert Allan JamesandClaude Sonnet 5 6fd87923a5 MINT: parameterize with full name, username, email, phone
Extends user_identity_seed_t (version 2) with fixed-size full_name/
username/email/phone fields -- plenty of unused pad space (4016 bytes)
was already there. Deliberately NOT encoded into the DER cert's Subject
field: that would mean building a real X.509 RDNSequence (OIDs for
commonName/emailAddress, PrintableString/UTF8String tagging), well past
this project's own stated "deliberately not a general ASN.1/X.509
[builder]" scope. This human-readable profile data isn't
security-relevant the way pubkey/serial are (the only two fields
CERTVERIFY/BINDSTEP actually check) -- it travels alongside the keypair
in the plain identity record instead.

capsule_mint_identity() takes full_name/username (required, validated
non-empty and within their fixed field widths) and email/phone (NULL or
empty = null, matching the schema's own nullable convention). The MINT
FORTH word's stack signature grows to 4 string pairs
( fname-c fname-u uname-c uname-u email-c email-u phone-c phone-u -- ok? ).

Verified live in QEMU: minted two real identities with real profile
data -- Zuse (full_name "Konrad Suse", username "Zuse",
zuse@pantheon.org) onto disk/zuse.img, and a regular user (full_name
"Captain Bob", username "CaptBob", capt.bob@pantheon.org) onto
disk/user1.img -- then read the raw devblock bytes back off both images
directly and confirmed every field byte-exact at its correct struct
offset. Clean 3-architecture regression boot confirms no side effects.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZGkimpfyh63EZyRkNbkPD
2026-08-28 15:33:02 -04:00
Robert Allan JamesandClaude Sonnet 5 f6e2737f1e Phase E: MINT -- real keypair, Zuse-signed DER cert, working default identity
capsule_mint_identity() (new capsule_mint.h/.c): mints a fresh identity
onto a blank/unminted thumbdrive -- real Ed25519 keypair from virtio_rng,
a fresh drive_uuid (independent random draw, not derived from the
identity seed, per FABRIC-3.md §F.8 decision 3), a Zuse-signed DER cert
in the CERTVERIFY format, and a small working default personality (a
real WELCOME word, not a stub -- FABRIC-3.md §F.6/§F.8's own "default
personality content" question stays open, but whatever mints today must
actually do something when RUNCAP births it). Refuses to overwrite a
drive that already reads as a recognized home-blocks drive, mirroring
WRITE(10)'s own refuse-on-non-blank posture (decided now, not just
"reasonable by analogy" as §F.8 left it).

x509_build_user_cert() (x509_ed25519.h/.c): the encode-side counterpart
to the existing decode functions (x509_extract_ed25519_pubkey(),
x509_verify_signature(), x509_extract_serial()) -- a minimal DER TLV
writer producing exactly the fields those functions read. Host-tested
round-trip against the real decoder before trusting it in the kernel,
including a high-bit-serial case that exercises the DER integer-padding
rule; all assertions pass (pubkey/serial round-trip, signature verifies
against the real issuer, correctly rejects the wrong key and a
corrupted signature).

New user_identity_seed_t (user_identity_seed.h): the on-disk record for
a minted identity's own keypair, same magic+version+fields+pad-to-4096+
real-CRC convention as zuse_cert_devblock_t and homeblocks_sig_t. Fixed
devblock layout: sig(1), cert(2), seed record(3), default personality(4).

New MINT word (mama_forth_words.c) and a small accessor
(sk_repl_get_attached_blk_dev(), repl.h/.c) exposing the currently
attached USB device regardless of home-blocks recognition -- MINT's own
target is a blank drive, which by definition never sets Phase D's
sk_repl_get_homeblocks_dev().

Verified end-to-end live in QEMU: MINT on a genuinely blank test drive,
then (after a detach/reattach so the sig cache picks up the fresh
header -- a known workflow gap, not fixed here, flagged for whoever
builds the real Console onboarding flow) RUNCAP birthed a VM from that
drive's own newly-minted content, and VM-EXECing its WELCOME word
printed the default personality banner. The full mint-to-birth Tripod
identity flow works end to end for the first time. Clean 3-architecture
regression boot confirms no side effects on normal boot.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZGkimpfyh63EZyRkNbkPD
2026-08-28 14:44:59 -04:00
Robert Allan JamesandClaude Sonnet 5 e1e839258d Phase D: RUNCAP -- runtime capsule construction from thumbdrive content
capsule_runcap_birth() (new capsule_runcap.h/.c): builds a heap-only,
single-entry CapsuleDirHeader + CapsuleDesc + CapsuleNameEntry + arena
from a home-blocks drive's identity_src region (skipping the first
devblock, reserved for MINT's user_identity_seed_t record) and hands it
to the existing, unmodified capsule_birth_baby() -- no new birth
mechanism, matching FABRIC-3.md §F.6's own trace.

Found and closed a real gap in that trace along the way:
capsule_birth_baby()'s signature check calls capsule_get_signatures(),
which unconditionally returns the compile-time-baked global array --
meaningless for a heap-built directory, where index 0 would compare
RUNCAP's own content against whatever real capsule happens to occupy
that slot in the baked array (guaranteed-wrong, not a security check).
Added an explicit skip_pki_sig flag (0 for all 4 existing call sites,
1 for RUNCAP): that content's trust comes from CERTVERIFY, a separate
root, not the capsule-PKI chain.

Also found live: capsule_birth_baby() never sets the registry entry's
own .name (every existing caller does this itself afterward via
capsule_vm_registry_set_name() -- RUNCAP now does too), and
capsule_exec_payload() requires a "Block NNNN" header per chunk of
content or it's silently skipped, never executed -- not a bug, but
necessary context for whoever authors MINT's default personality
content next.

Added a small accessor pair (repl.h/.c) exposing the currently attached
home-blocks device/sig -- the same gap F.9's own BINDSTEP scoping had
already flagged, needed by both.

Verified end-to-end live in QEMU: synthetic identity-source content
written directly to a thumbdrive image's raw devblocks (no capsule
build, no mkcapsule) was read, compiled, and executed by a genuinely
new VM via a diagnostic RUNCAP-TEST word -- confirmed via VM-EXEC
invoking a word defined only in that source. Clean 3-architecture
regression boot (no RUNCAP drive attached) confirms no side effects.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZGkimpfyh63EZyRkNbkPD
2026-08-28 14:29:42 -04:00
Robert Allan JamesandClaude Sonnet 5 21bca315ff Phase C: distributed messaging capsule + idle-loop pump
Extract the messaging vocabulary (arenas, MSG-*/CH-*/MBR-* words) out of
capsules/hermes/init.4th into a new shared capsules/common/messaging.4th
that Hermes and Artemis each load at birth, giving every VM its own
private MSG-ARENA/CH-ARENA instead of only Hermes having one. Hermes
stays the owner of the one real, canonical COMMON-CH; Artemis subscribes
into it via VM-EXEC at her own birth, and Hermes proactively subscribes
Hera (idx 0) since Hera always exists first.

Hera does NOT get her own copy: register_child_vm_words()'s own doc
comment explains why the STADIUM-* primitives common:messaging.4th
depends on are deliberately never registered in her dictionary (keeps
her dict_hash off item 4.1's baseline). Confirmed live by loading it
into her dictionary anyway first -- every colon-definition referencing
an unregistered Stadium primitive was silently dropped (MSG-HEAT@/!,
CH-HEAT@/!, MSG-COOL-ALL, MSG-TICK all missing after boot). Reverted
that path; she orchestrates via BIRTH/VM-EXEC/VM-CALL instead.

Added capsule_vm_registry_get_by_index() (capsule_birth.c/.h) for
registry enumeration by birth-order position, and a pump in repl.c's
existing idle hook that walks every live VM once per idle beat and
VM-EXECs MSG-TICK into each one except Hera's own entry.

Verified clean (no UNKNOWN WORD / VM-EXEC errors after birth) on all
three architectures.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZGkimpfyh63EZyRkNbkPD
2026-08-28 13:02:23 -04:00
Robert Allan JamesandClaude Sonnet 5 5e9802845a Implement SCSI WRITE(10), closing the graph's highest-leverage blocker
Direct mirror of the existing READ(10) implementation (FABRIC-3.md §F.1),
data direction flipped: new XHCI_XFER_BOT_DATA_OUT/XHCI_NEXT_ACTION_BOT_DATA_OUT
states, xhci_bot_send_write10()/xhci_bot_write_block()/xhci_bot_write_data_out()
in xhci.c, new SCSI_CMD_WRITE10 opcode and BOT_CMD_WRITE10/BOT_TUR_CHAIN_WRITE10
enum values. usb_blk_write() in blkio_usb.c is real now, no longer the
BLKIO_ENOSUP stub. read_only flips to 0 in blkio_info() now that it's proven.

Verified live end-to-end on all three architectures with a genuine cold-reboot
round-trip (not just a same-session read): BLK-CONFIRM-FORMAT's BAM/reloc
writes and an explicit block content write both completed via clean WRITE10
cycles (CSW PASS), and the written byte read back correctly after a full
kernel rebuild + fresh boot -- amd64=65, aarch64=170, riscv64=201, each at
LBN 32734 on a disposable usbwrite-test.img attached via QEMU usb-storage.

Added Makefile.starkernel's QEMU_EXTRA (empty by default, no behavior change)
to attach the disposable test image for this validation; the drive must be
hotplugged via QMP after boot reaches ok>, not attached at QEMU launch --
attaching before xhci_bringup()'s controller reset means no fresh Port
Status Change event fires (see project_xhci_milestone_2d_polling memory).

Found and reported, not fixed, during testing: EMPTY-BUFFERS
(empty_all_buffers(), block_words.c) does not implement standard Forth-79
semantics -- it force-writes zero to every block on every attached device
instead of discarding cache assignments. This corrupted disk/artemis.img
during an earlier test run; restored from git, confirmed byte-identical.
Avoided in the final validation runs (detach/reattach used instead to force
a fresh read).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZGkimpfyh63EZyRkNbkPD
2026-08-28 07:19:31 -04:00
Robert Allan JamesandClaude Sonnet 5 6e9c1d3bc2 Phase 8 C (4/n): metadata fence allocator integration + zone I/O
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
2026-08-26 19:40:43 -04:00
Robert Allan JamesandClaude Sonnet 5 2035ebeac0 Phase 8 C (3/n): top-of-device metadata fence, step 1 (field round-trip)
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
2026-08-26 19:18:21 -04:00
Robert Allan JamesandClaude Sonnet 5 e5cbc71f46 Phase 8 C (2/n): expand cert storage; NVRAM persistence crashed, reverted
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
2026-08-26 15:55:27 -04:00
Robert Allan JamesandClaude Sonnet 5 f223a31cec Phase 8 C (1/n): QEMU harness now preserves NVRAM across runs
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
2026-08-26 15:18:15 -04:00
Robert Allan JamesandClaude Sonnet 5 53e6c5709f Phase 8 B: real Ed25519 keygen/signing, verified against OpenSSL
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
2026-08-26 14:46:27 -04:00
Robert Allan JamesandClaude Sonnet 5 309e792f07 Phase 8 A: virtio-rng entropy source for real Ed25519 signing
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
2026-08-26 14:32:20 -04:00
Robert Allan JamesandClaude Sonnet 5 6f5605d479 Phase 8: zuse cert storage moved out of the dictionary (fuse-blow install)
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
2026-08-26 13:58:22 -04:00
Robert Allan JamesandClaude Sonnet 5 4dd1321ea4 Regenerate capsule manifest and artemis test image
Routine build/test artifacts left over from Phase 8 work session.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrMTq3cJkrr8REFRkMRhLZ
2026-08-26 08:11:42 -04:00
Robert Allan JamesandClaude Sonnet 5 8b2fb448c7 Clarify: home-blocks thumbdrives are not bound to any particular size
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
2026-08-26 07:56:07 -04:00
Robert Allan JamesandClaude Sonnet 5 35f84d4a45 Add disk/zuse.img + bleach_zuse_img.sh: first-boot mint testing (Phase 8)
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
2026-08-26 07:51:44 -04:00
Robert Allan JamesandClaude Sonnet 5 28c1b12c7f Wire homeblocks_sig_check() into USB attach: the warn half (Phase 8)
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
2026-08-26 07:14:51 -04:00
Robert Allan JamesandClaude Sonnet 5 2c45744995 Implement homeblocks_sig_check(): the drive signature check (Phase 8)
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
2026-08-26 07:05:37 -04:00
Robert Allan JamesandClaude Sonnet 5 329677b077 Fix COOL's misleading (stub) label too
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
2026-08-26 03:13:50 -04:00
Robert Allan JamesandClaude Sonnet 5 ad9b9d0c7b Fix DELIVER's misleading (stub) label -- it was never actually a gap
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
2026-08-26 03:03:39 -04:00
Robert Allan JamesandClaude Sonnet 5 3e0966095c Stadium: real VM-patron admission + real COOL dispatch at KILL (FABRIC-3.md §B)
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
2026-08-26 02:53:56 -04:00
Robert Allan JamesandClaude Sonnet 5 af1eb0ca2d Fix USE/RUN dictionary shadowing: remove dead lib.4th aliases
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
2026-08-26 01:01:26 -04:00
Robert Allan JamesandClaude Sonnet 5 c7c9332321 Stadium: real block-patron admission + MIGRATE dispatch (FABRIC-3.md §B)
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
2026-08-25 23:28:59 -04:00
Robert Allan JamesandClaude Sonnet 5 36d832ff47 Single-block relocation: RELOCATE-BLOCK, resolve_lbn(), persisted exception table
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
2026-08-25 19:14:01 -04:00
Robert Allan JamesandClaude Sonnet 5 af267a52a6 Artemis Milestone 2h: hot-detach -- 2h complete
blk_subsys_detach_device() (block_subsystem.c) walks the device chain,
refuses removal of anything but the current tail (a mid-chain removal
would corrupt every later slot's start_lbn -- this architecture's own doc
already argues USB stays last specifically to avoid that), unlinks,
shrinks total_user_lbn, closes and frees the slot. Discards rather than
flushes dirty state -- the device is physically gone by the time this
runs (PORTSC disconnect only). Trigger wiring mirrors the attach path:
bot_msc_attached (set only once attach actually succeeds) gates a new
bot_msc_detach_pending flag set at PORTSC disconnect (not Disable Slot
completion, which is conditionally skipped and would miss concurrent
connect/disconnect pairs), consumed in sk_repl_idle().

Advisor flagged the real hazard ahead of time: block_words.c's VM block
window (blk_vm_lbn[]/blk_vm_cbuf[]) can go stale across a detach then a
same-LBN re-attach, and suggested a pointer-identity re-check in
blk_vm_load() as a minimal fix. That fix was implemented, then directly
falsified by its own designed-for-this test: attach a blank device, read
a block (populating the cache), detach, re-attach a device with distinct
content at the identical LBN, read again -- served stale content from
the first device. Root cause, confirmed live: glibc's allocator hands
free(slot) straight back to the very next same-size calloc(), so the
"fresh" and stale pointers were bitwise identical despite being two
different devices. Fixed properly with a monotonic blk_subsys_epoch()
counter (bumped on every attach/detach) checked by a new
blk_vm_check_epoch() helper at the one choke point (blk_vm_find(), plus
blk_vm_flush_all() which reads the same arrays directly) that covers
every path touching the window cache -- unfooled by address reuse.

Verified live with a new disk/usb-thumbdrive-test2.img fixture (distinct
content from the existing blank test image): attach A, read (cache hit
populated), detach, re-attach B at the same LBN, read again -- correctly
ran a fresh device read and returned B's real content, not A's stale
cached zeros. The failing pointer-comparison attempt's own capture log
kept as evidence, not deleted. All three architectures re-verified clean.
FABRIC-2.md Section X 2h marked complete -- enumeration through
hot-detach all live and verified; only WRITE(10) (2g's own still-open
item) remains unimplemented in the driver, not blocking anything here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn
2026-08-25 14:10:05 -04:00
Robert Allan JamesandClaude Sonnet 5 c54ea24aaf Artemis Milestone 2g: Data-In stage read and CSW receive/validation
Completes the CBW -> Data-In -> CSW chain for READ(10) started last
commit. xhci_bot_read_data_in() and xhci_bot_receive_csw(), each a
single Normal TRB on the bulk IN Transfer Ring via a new
xhci_bulk_in_enqueue_and_ring() helper (mirrors the OUT-side helper
from CBW send). All three stages now chain automatically via the
existing deferred next_action pattern: CBW completion defers into
Data-In, Data-In completion defers into CSW receive, CSW completion is
where signature/tag/status validation happens.

Data-In reads into a new fixed 512-byte bot_data_buf -- single-block
scope for this increment, matches QEMU's usb-storage reported block
size; xhci_bot_send_read10() now refuses rather than overflow/truncate
if a request exceeds it. CSW validation (BOT spec section 5.2) checks
dCSWSignature and dCSWTag (a new bot_last_tag field, latched from the
CBW) before trusting bCSWStatus at all, so a garbled/misaligned CSW
read can't be misread as a clean pass. usb_bot_csw_t follows the same
struct-with-explicit-length-not-sizeof discipline as usb_bot_cbw_t.

Verified live via a temporary probe (written, run once, log captured,
reverted per this project's own probe convention), all three
architectures, byte-identical: the full CBW -> Data-In -> CSW exchange
completes cleanly, well-formed CSW with correct signature and echoed
tag, no wedge, clean disconnect immediately after. The SCSI command
itself reports CSW status FAILED against the current test fixture --
expected at this stage (no TEST UNIT READY / UNIT ATTENTION handling
implemented yet, consistent with a fresh-attach unit-attention
condition, not a transport-layer defect) and not root-caused further
here; the BOT mechanism itself is confirmed correct end to end.
Probe-free re-verification afterward on all three architectures.

FABRIC-2.md Section X Milestone 2g's CSW checklist item marked done;
"get one real READ(10) working end to end" stays explicitly open,
distinguishing "the mechanism works" from "the SCSI command succeeds."

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R4VMX6VSKCten8nGgaMkq4
2026-08-25 09:33:17 -04:00
Robert Allan JamesandClaude Sonnet 5 a88c004ecb Artemis Milestone 2g: CBW construction and send for SCSI READ(10)
First real use of the bulk Transfer Rings Configure Endpoint wired up.
xhci_bot_send_read10() builds a 31-byte Command Block Wrapper (USB Mass
Storage Class Bulk-Only Transport spec section 5.1) and submits it as a
single Normal TRB on the bulk OUT ring via a new
xhci_bulk_out_enqueue_and_ring() helper -- a CBW is always exactly one
TRB, so unlike the EP0 helper this one rings its own doorbell rather
than leaving that to a caller assembling a group.

usb_bot_cbw_t is a real struct (every field up to the CDB array is
naturally aligned, and this driver's targets are all little-endian
already assumed everywhere else), but its DMA length is the explicit
USB_BOT_CBW_LENGTH (31) constant, never sizeof(*cbw), since the
compiler may pad the struct to 32 bytes. The SCSI READ(10) CDB itself
is written byte-by-byte since its LBA/Transfer Length fields are
big-endian on the wire, unlike everything else in this driver -- the
one place two byte orders are both live in the same function.

Completion is correlated via the existing pending_transfer_slot_id/
transfer_purpose gate (new XHCI_XFER_CBW_SENT purpose) -- no
ring-specific dispatch needed, since this driver's single-outstanding-
transfer scope already implies which ring produced an event.

This covers construction and send only (one third of a full READ(10):
CBW -> Data-In stage -> CSW) -- reading the Data-In stage and CSW
receive/validation are separate, explicitly not-yet-implemented items.

Verified live via a temporary probe (written, run once, log captured,
reverted per this project's own probe convention) -- all three
architectures, byte-identical: CBW submitted -> CBW send completed,
then a clean disconnect even with the Data-In stage never drained
(confirms no wedge on a dangling BOT transaction). Probe-free
re-verification afterward on all three architectures.

FABRIC-2.md Section X Milestone 2g's CBW checklist item marked done.
Also records a monitoring gotcha hit three times this session: `ls -t`
over the logs/ tree can return a stale leftover log from an earlier
run in the same session -- fixed going forward by reading the log path
off the actual running QEMU process's own command line instead, and a
memory note added so it doesn't recur next session.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R4VMX6VSKCten8nGgaMkq4
2026-08-25 09:07:33 -04:00
Robert Allan JamesandClaude Sonnet 5 92ce1f85dd Artemis Milestone 2g: Configure Endpoint command
Adds the xHCI Configure Endpoint command for the two bulk endpoints
identified by the previous increment, and fixes control-transfer
sequencing to match the spec: xHCI 1.2 section 4.3.5 requires Configure
Endpoint before SET_CONFIGURATION is sent to the device, the reverse of
the order this driver used through 2f (which happened to work against
QEMU's lenient qemu-xhci emulation but wasn't spec-correct).

New XHCI_TRB_TYPE_CONFIGURE_ENDPOINT_CMD, EP Context type constants for
Bulk IN/OUT, and an XHCI_EP_ADDR_TO_DCI() macro (DCI = 2*EndpointNumber
+ Direction) in xhci.h. xhci_cmd_configure_endpoint() builds the Input
Context (Slot + one EP Context per DCI up to the highest bulk endpoint
in use) and submits the command via the existing next_action deferral
mechanism, correlated on completion via a new
XHCI_CONN_AWAIT_CONFIGURE_ENDPOINT connect_state, then chains into the
existing SET_CONFIGURATION path.

Two allocations had to grow beyond what Address Device sized them for:
the Input Context (previously room for one EP Context only) and, less
obviously, the Device Context that DCBAA[slot_id] itself points at --
the controller only touches DCIs named in a command's own Add/Drop
flags, so growing that buffer required copying its existing Slot+EP0
content forward rather than zeroing it, to avoid handing the controller
a blank EP0 out from under an endpoint this command isn't touching.
Bulk Transfer Rings (bulk_in_ring/bulk_out_ring) are allocated and
wired into the new EP Contexts but not yet exercised by an actual
transfer -- CBW/CSW submission is next.

Verified live via QMP hotplug, all three architectures, byte-identical:
bulk endpoint identification -> configure endpoint command submitted ->
configure endpoint succeeded -> the existing set configuration ->
device configured chain, then a clean disconnect/disable-slot teardown
afterward with the larger Device Context installed.

FABRIC-2.md Section X Milestone 2g's endpoint identify+configure
checklist item marked fully done.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R4VMX6VSKCten8nGgaMkq4
2026-08-25 08:40:35 -04:00
Robert Allan JamesandClaude Sonnet 5 96d55fcd87 Artemis Milestone 2g (partial): bulk endpoint discovery + 2e disconnect teardown
Picked up from a crashed session: xhci_driver.h/xhci.h already had the
bulk_in/out_ep_addr/max_packet fields and Endpoint-descriptor offset
macros scaffolded, but the actual walk that populates them was never
written. Added it: after 2f confirms a Mass Storage/SCSI/BOT interface,
a nested walk continues through the Endpoint descriptors that follow it
(bDescriptorType==5, stopping at the next Interface descriptor or end
of stream), keeping only Bulk-type endpoints and splitting IN/OUT by
bEndpointAddress bit 7. Also reset the four new fields in
xhci_bringup(), which the scaffolding had missed.

Also completed 2e's disconnect teardown, which was fully implemented
this session (not scaffolded): a Disable Slot command is now submitted
on a real disconnect, with the port's tracked slot ID captured and
cleared from port_slot_id[] immediately (before the command completes)
so a fresh connect on the same port isn't confused for one already in
progress, and DCBAA[slot_id] cleared only on a successful completion.

Verified live via QMP hotplug (deliberate device_add/device_del against
freshly launched, individually-tracked instances -- not whatever
happened to be attached at boot), all three architectures,
byte-identical: bulk IN endpoint=0x81, bulk OUT endpoint=0x02, then a
clean disconnect -> disable slot succeeded, no wedge. Caught and fixed
a documentation near-miss in the same pass: an initial draft cited the
probe-free three-arch acceptance boots as this feature's verification
evidence, but a stale leftover log directory from a pre-crash orphaned
QEMU process had been picked up by an `ls -dt | head -1` glob during
monitoring and mistaken for this session's own result -- the real
acceptance logs never had a device attached at all. Re-verified against
real PIDs and real log paths before writing FABRIC-2.md's final
writeup.

FABRIC-2.md Section X Milestone 2 updated: 2e's disconnect-teardown
checklist item marked done, 2g's endpoint-identification item marked
partially done (identification only -- Configure Endpoint / EP Context
wiring is still open).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R4VMX6VSKCten8nGgaMkq4
2026-08-25 08:17:50 -04:00
Robert Allan JamesandClaude Sonnet 5 b4bbd043d0 Artemis Milestone 2f: SET_CONFIGURATION -- 2f complete
Chains off a confirmed Mass Storage/SCSI/BOT interface match via the
existing next_action deferral mechanism: device descriptor -> config
descriptor -> SET_CONFIGURATION is now a single automatic sequence.
bConfigurationValue is read directly out of the already-fetched
config_descriptor buffer, no extra transfer needed.

First write control transfer this driver has issued (every prior one was
a read), so it needed its own submission helper,
xhci_ep0_control_write_nodata() -- SET_CONFIGURATION has no Data Stage
(wLength=0), and per USB 2.0 spec 8.5.3 a no-data control transfer's
Status Stage is always IN, the reverse of an OUT-data request's status
stage. XHCI_SETUP_TRT_NO_DATA already existed in xhci.h, unused until now.

Verified live via QMP hotplug, all three architectures, worked first try,
byte-identical: "set configuration submitted" -> "device configured",
guest stays running throughout (checked via QMP query-status). Disconnect
confirmed clean on every arch afterward, no wedge. FABRIC-2.md Section X
Milestone 2f updated -- 2f is now fully complete, 2g (Bulk-Only Transport)
can start.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QPfdtaXs9ay1nbwuMnrscu
2026-08-25 07:40:16 -04:00
Robert Allan JamesandClaude Sonnet 5 b9c540a78b Artemis Milestone 2f: Configuration descriptor read + Mass Storage/BOT class confirmation
Chains off the device descriptor request via a new deferred-action mechanism
on xhci_dev_t (next_action/next_action_slot_id/next_action_length): a short
9-byte Configuration descriptor read learns wTotalLength, then a full read
retrieves Config+Interface+Endpoint descriptors, walked for the Interface
descriptor to confirm bInterfaceClass/SubClass/Protocol == Mass Storage/
SCSI/Bulk-Only Transport.

The deferral exists because ringing the next doorbell synchronously inside
xhci_poll_events()'s event-processing loop -- before the current event's
ERDP write -- hung the guest outright (confirmed live via checkpoint
logging, amd64). Fixed by moving the actual control-transfer submission to
a small dispatch at the end of xhci_poll_events(), after ERDP is updated.

A debug hack that shipped mid-session (forcing a repeated 9-byte read
instead of chaining into the real 44-byte length, to isolate whether the
hang was doorbell-ordering or length-specific) has been reverted: restored
the real length and re-verified live. The doorbell-ordering fix was the
whole story -- the 44-byte read completes cleanly.

Verified live via QMP hotplug, all three architectures, byte-identical
results: wTotalLength=0x2c, bInterfaceClass=0x08, bInterfaceSubClass=0x06,
bInterfaceProtocol=0x50 -- confirmed Mass Storage/SCSI/BOT. Disconnect
confirmed clean on every arch, no wedge. FABRIC-2.md Section X Milestone 2f
updated with the full writeup.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QPfdtaXs9ay1nbwuMnrscu
2026-08-25 07:27:07 -04:00
Robert Allan JamesandClaude Sonnet 5 bb84eba7e3 Artemis Milestone 2e: virtual USB thumb-drive hotplug test + block-layout decisions
Verified live on amd64: booted with the xHCI controller present but no USB
device attached (no Port Status Change at ok>), then hotplug-attached a
virtual USB thumb drive via QMP (usb-storage on xhci0.0, backed by
disk/usb-thumbdrive-test.img) and got an immediate port status change
event -- the real connect trigger Milestone 2e's PORTSC handling will
consume next.

Confirmed blk_subsys_attach_device() (src/block_subsystem.c) is already
the correct integration point for USB -- it already appends a new device
to the end of the existing LBN chain, matching the intended design.
Documented the remaining gaps: no blkio_usb.c backend yet, no hot-detach
path in the device chain yet.

Decided the on-drive layout for USB thumb drives: GPT-partitioned (unlike
artemis.img's whole-device StarForth header), ~1GB metadata partition +
remainder for blocks, 16GB reference drive size, sizing tentative. No GPT
parser exists in kernel code yet -- new prerequisite work for Milestone
2h/3, not blocking current 2e work.

disk/usb-thumbdrive-test.img added as a tracked test fixture, per this
repo's standing convention that virtual disk/thumb-drive images used for
testing are committed, not left in scratchpad.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HZ8kNoTuP63pbQtro4qvrm
2026-08-22 09:49:15 -04:00
Robert Allan JamesandClaude Sonnet 5 9c8ad8f6ff Artemis Milestone 2e (in progress): xHCI Command Ring write path proven live
Enable Slot command TRB submitted via a new xhci_submit_command()/
xhci_cmd_enable_slot(), ring doorbell 0, confirmed by a real Command
Completion Event on all three architectures -- the first time this driver
has written a TRB rather than only reading the Event Ring (2d). Added the
Command Ring's previously-missing Link TRB (xHCI 1.2 spec sec 4.9.2) for
wraparound correctness.

Port Register connect/disconnect handling, slot-ID/context bookkeeping,
Address Device, and the callback surface into Section U's code are still
open -- this is the discriminating first step, not full 2e.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HZ8kNoTuP63pbQtro4qvrm
2026-08-22 09:37:55 -04:00
Robert Allan JamesandClaude Sonnet 5 2b16daba16 Artemis Milestone 2d: xHCI Event Ring servicing, polled not interrupt-driven
Implements Event Ring TRB parsing and ERDP dequeue-pointer update
(xhci_poll_events(), src/starkernel/usb/xhci.c), called from
sk_repl_idle()'s existing ~1s idle cadence rather than a per-arch
interrupt handler.

A first attempt wired real interrupt delivery (PCI->IOAPIC GSI routing,
a dedicated isr_stub34/vector 0x22, GIC/PLIC routing mirroring
virtio_input.c). Checked live via QMP query-pci before trusting it: the
amd64 PIRQ swizzle formula predicted GSI 16 for the xHCI controller at
PCI slot 4; the real QEMU-assigned IRQ was 10, and embedded ICH9
functions contradicted the same formula too. Reverted all of it back to
the exact committed baseline rather than chasing chipset PIRQ routing
further, and reframed around Section U item 6's own design intent
("interrupt-driven, coarse cadence, cheap early-exit... quick check
blocks... done") via sk_repl_idle() instead -- USB insertion is a
human-timescale event, not a hot path.

Added -device qemu-xhci to all three QEMU launch targets (required for
any of this to be testable). Verified end to end via genuine post-boot
hotplug (QMP device_add/device_del usb-storage): all three architectures
detect a live attach within seconds. A false-alarm heartbeat "freeze"
found mid-verification traced to querying the wrong counter
(vm->heartbeat.tick_count, which only advances during word execution,
not the kernel's real ISR-driven heartbeat_ticks()) -- confirmed via a
temporary diagnostic word, captured and reverted.

Full writeup, including the discarded interrupt-routing attempt and the
false-alarm investigation, in FABRIC-2.md's Milestone 2c/2d entries.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HZ8kNoTuP63pbQtro4qvrm
2026-08-22 09:25:25 -04:00
Robert Allan JamesandClaude Sonnet 5 c2f1d94c97 Artemis Milestone 2c: xHCI controller bring-up wired, DoE CSV export off by default
xhci_bringup() (HC reset, DCBAA, Command/Event rings, RUN/STOP) was
uncommitted and referenced an XHCI_WAIT_FOR macro that was never defined,
breaking the build. Wired all four wait sites to the existing
xhci_wait_bit() helper instead, matching each register/bit/polarity
needed (halt-before-reset waits for HCH set; HCRST, CNR, and post-RUN
HCH waits all wait for their bit to clear).

Also flipped g_doe_log_enabled's default from 1 to 0 -- the per-tick
[HADES][DOE] CSV export was flooding every boot log and slowing
interactive verification for no reason during ordinary acceptance runs;
HB-ON still re-enables it at the REPL for anyone running an actual DoE
campaign.

Three-arch acceptance: amd64/aarch64/riscv64 all boot clean to ok>,
zero DoE rows in any log. aarch64 and riscv64 both exited cleanly via
BYE with no exception, confirming the earlier SMC->HVC PSCI fix still
holds. Logs and DoE CSV artifacts from this run included.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HZ8kNoTuP63pbQtro4qvrm
2026-08-22 08:33:35 -04:00
Robert Allan JamesandClaude Sonnet 5 5970c54912 Artemis Milestone 2b complete: xHCI PCI discovery and BAR0 mapping
New src/starkernel/usb/ subsystem directory (added to both
LOADER_SRCS_BASE and KERNEL_SRCS_BASE wildcards in Makefile.starkernel,
matching the existing virtio/*.c pattern). xhci_find_and_map() locates
the controller via the already-generic pci_find_first(), enables it,
maps BAR0 via the already-generic pci_map_bar(), and fills in all four
register-region pointers (cap/op/runtime/doorbell) plus max_slots/
max_ports/max_intrs from HCSPARAMS1 -- ready for controller bring-up
(2c) to consume directly.

No pci.c extension needed, per 2a's finding that PCI discovery here is
ID-based lookup (already generic), not class-code scanning. Verified:
clean standalone syntax check, full amd64 kernel build with zero
warnings, live boot still reaches POST 1012/0/0 unaffected (nothing
calls xhci_find_and_map() yet, so this is purely additive).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 07:58:27 -04:00
Robert Allan JamesandClaude Sonnet 5 3d91015659 Artemis Milestone 1 complete: QMP socket added to all three QEMU
launch targets, three-arch verified

Makefile.starkernel: added -qmp unix:$QMP_SOCK,server=on,wait=off to
the amd64/aarch64/riscv64 qemu targets, matching the existing serial
chardev socket pattern exactly (same discoverability, same cleanup on
exit). Verified on all three architectures: QMP greeting arrives on
connect, qmp_capabilities handshake succeeds, device_add/device_del
round-trip correctly.

Real finding surfaced during device_add testing (recorded in
FABRIC-2.md's punch list for Milestone 2): the q35 machine's pcie.0
root bus doesn't support runtime PCI hotplug without a bridge --
Milestone 2's qemu-xhci USB controller needs to be present in the
static launch command, with USB devices hot-attached to its bus at
runtime, not the controller itself hot-added.

Also noted: g_doe_log_enabled's default-on per-tick heartbeat CSV
export was briefly mistaken for a hang during aarch64 verification --
it isn't one, just a large volume of routine diagnostic output before
reaching ok>. Not changing the source default; adopting HB-OFF
immediately after boot as the working pattern for the rest of this
punch list's dev work.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 07:48:52 -04:00
Robert Allan JamesandClaude Sonnet 5 f2e3bb1a90 ACL-TTL overhead: full 36-cell campaign complete (6 seeds), FABRIC-2.md
Section T -- +0.0603%, final accepted figure

Extended Section S's 3-seed/9-pair campaign to 6 seeds/18 pairs (36
cells) per Captain Bob's request for a fuller campaign before moving
on. All 36 cells: 480/480 rows, 0 errors, 17,280/17,280 rows total.

Every one of 18 disabled cells reads exactly 261063 ticks -- CV=0.000%
across all 3 architectures and 6 seeds, zero exceptions. Every enabled
cell's tick count is fully determined by seed alone, identical across
all 3 architectures, zero exceptions. Pooled overhead across all 18
pairs: +0.0603% (mean +0.0603%, stdev 0.0008%, range +0.0598%-
+0.0617%) -- statistically indistinguishable from Section S's 9-pair
figure, now confirmed over double the data with 3 entirely new seeds.

This closes the ACL-TTL overhead measurement line of investigation
(Sections P, Q, R, S, T). +0.0603% is the final accepted figure.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 23:46:02 -04:00
Robert Allan JamesandClaude Sonnet 5 392561e7de ACL-TTL tick campaign extension checkpoint: 34/36 cells
riscv64/99999/disabled, aarch64/11111/disabled added. All 480/480
rows, 0 errors. 2 cells remain.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 23:35:50 -04:00
Robert Allan JamesandClaude Sonnet 5 a52fcf0367 ACL-TTL tick campaign extension checkpoint: 32/36 cells
riscv64/11111/enabled, aarch64/24680/enabled added. All 480/480 rows,
0 errors. 4 cells remain.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 23:24:13 -04:00
Robert Allan JamesandClaude Sonnet 5 db05f29e49 ACL-TTL tick campaign extension checkpoint: 30/36 cells (5/6)
aarch64/99999/enabled, amd64/99999/disabled added. All 480/480 rows,
0 errors. 6 cells remain.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 23:12:27 -04:00
Robert Allan JamesandClaude Sonnet 5 ef74e45a3b ACL-TTL tick campaign extension checkpoint: 28/36 cells
aarch64/99999/disabled, riscv64/99999/enabled added. All 480/480 rows,
0 errors. 8 cells remain.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 23:00:02 -04:00
Robert Allan JamesandClaude Sonnet 5 2be3c8c68e ACL-TTL tick campaign extension checkpoint: 26/36 cells
amd64/99999/enabled, aarch64/24680/disabled added (cell 26 needed a
retry after an unexplained external SIGTERM killed the qemu process
mid-boot -- matches a previously-noted, still-unexplained SIGTERM
recurrence from a process named "claude", first seen 2026-08-18;
1-line stub log from the killed attempt kept as audit trail). All
successful cells: 480/480 rows, 0 errors.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 22:48:14 -04:00
Robert Allan JamesandClaude Sonnet 5 a5cd1b76c8 ACL-TTL tick campaign extension checkpoint: 24/36 cells (2/3 done)
amd64/24680/disabled, amd64/24680/enabled, aarch64/11111/enabled
added. All 480/480 rows, 0 errors. Cross-arch consistency continues
holding: seed 24680 gives 261219 on both riscv64 and amd64; seed 11111
gives 261222 on both amd64 and aarch64.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 22:35:17 -04:00
Robert Allan JamesandClaude Sonnet 5 f8693d50be ACL-TTL tick campaign extension checkpoint: 21/36 cells (3 new seeds)
Extended from 3 to 6 seeds (added 24680/11111/99999) per Captain Bob's
request for a fuller campaign before moving on. amd64/11111/enabled,
riscv64/24680/enabled, riscv64/24680/disabled added. All 480/480 rows,
0 errors. Disabled-arm determinism (261063) holding across new seeds.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 22:18:19 -04:00
Robert Allan JamesandClaude Sonnet 5 e9d5353b6e ACL-TTL overhead: full 18-cell campaign complete, FABRIC-2.md Section S
-- +0.0604% mean, architecture-independent, fully deterministic

All 18 cells (9 arch/seed pairs x disabled/enabled, zuse-authenticated
throughout) complete: 8,640/8,640 rows, 0 errors. Every disabled cell
reads exactly 261063 ticks -- CV=0.000% across all 3 architectures and
3 seeds. Every enabled cell's tick count depends only on seed, identical
across all 3 architectures for a given seed. Pooled overhead: +0.0604%
(mean +0.0604%, stdev 0.0010%, range +0.0598%-+0.0617%).

This is now the accepted ACL-TTL overhead figure for this workload,
superseding Section P's invalidated wall-clock numbers (ACL never
actually armed) and refining Section R's single-pair pilot (+0.0448%,
n=1) to a tight, fully-reproducible, architecture-independent result
across 9 independent pairs.

One tooling bug fixed mid-campaign (cells 1-3): tick-extraction regex
missed the "[Hera] " console-tagger line prefix; underlying VM runs
were unaffected, affected cells' values recovered by hand from their
serial logs before the fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 15:06:36 -04:00
Robert Allan JamesandClaude Sonnet 5 0f0f28e784 ACL-TTL tick campaign checkpoint: 17/18 cells
aarch64/13579/disabled, riscv64/13579/disabled added. All 480/480
rows, 0 errors. 1 cell remains.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 15:00:32 -04:00