Commit Graph
34 Commits
Author SHA1 Message Date
Robert Allan JamesandClaude Sonnet 5 5fc709a228 Punch list item 2: MINT-SCRATCH-EMIT, verified live on all 3 arches
Adds the hand-transcription mechanism FABRIC-3.md §XXXII.2's punch list
item 2 calls for: MINT-SCRATCH-EMIT prints the last successful
MINT-SCRATCH's drive_uuid + cert devblock as ready-to-paste FORTH
source (HEX-based CREATE ... C, ... byte sequences), so packaging an
unattended identity into a capsule is a mechanical copy out of the
captured boot log rather than a manual hex-to-FORTH translation an
operator could transpose a digit in.

Refuses (no output) if no MINT-SCRATCH has ever succeeded -- printing
4112 zero bytes as if they were a real identity would be a silent,
misleading success, matching this session's own error-handling audit
discipline rather than adding a new silent-failure primitive right
after finishing one.

Verified live on amd64: MINT-SCRATCH-EMIT correctly refuses before any
mint, then after MINT-SCRATCH succeeds, emits UNATTENDED-ID-UUID and
UNATTENDED-ID-CERT as valid FORTH literals. Cross-checked byte-exact:
the cert's own embedded ASN.1 serialNumber field matches the emitted
UUID bytes exactly, confirming x509_build_user_cert()'s drive_uuid
binding round-trips correctly through the scratch-device path. Clean
3-arch qemu boot (amd64/aarch64/riscv64).

Remaining punch-list items (writing/committing a real capsule file for
an actual named identity, the unattended-birth call site, the ACL cap
bit) not started -- authoring a real committed capsule needs a name/
purpose decision that isn't mine to make.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BWpNjdwPtFLuVLaAq44L9K
2026-09-16 05:12:43 -04:00
Robert Allan JamesandClaude Sonnet 5 63864c4b01 Punch list item 1: scratch-device MINT-SCRATCH, verified live on all 3 arches
Implements FABRIC-3.md §XXXII.2's scratch-thumbdrive mint mechanism:
capsule_mint_identity_scratch() (capsule_mint.c/.h) builds a throwaway
RAM-backed blkio_dev via blkio_ram.c's backend and runs
capsule_mint_identity() against it completely unmodified -- same live
Zuse-signing operation, same rng_get_bytes() draw for drive_uuid a real
thumbdrive gets. Reads back only drive_uuid + the cert devblock; the
seed devblock is written into the scratch buffer internally but never
read out (no seed is ever baked into a capsule, per the ratified
no-seed decision).

New FORTH word MINT-SCRATCH (mama_forth_words.c), same stack signature
as MINT, mints into the scratch device instead of any attached drive
and never touches sk_repl_get_attached_blk_dev() or console-pairing
state. Prints the drive_uuid as hex so a live boot log itself proves
each call drew fresh entropy.

Build correction found along the way: blkio_ram.c was excluded from
the kernel build (Makefile.starkernel VM_EXCLUDE) alongside
blkio_factory.c/blkio_file.c. blkio_factory_open() unconditionally
references blkio_file.c's real fopen()/fread() file I/O, which has no
freestanding-kernel equivalent, so the factory function couldn't be
used as-is. blkio_ram.c itself is pure memcpy over a caller buffer --
pulled it alone into the kernel build and wired it directly in
capsule_mint.c, the same way blkio_factory.c's own extern declarations
do internally.

Verified live on amd64: two MINT-SCRATCH calls produced two genuinely
different drive_uuids (b533246d.../ae11b2b2...), confirming fresh
entropy per call rather than stale reuse; VM stayed healthy afterward
(5 6 + . -> 11). Clean 3-arch qemu boot (amd64/aarch64/riscv64),
logs and DoE CSVs committed per standing convention.

Remaining punch-list items (hand-transcription into a .4th block, the
unattended-birth call site, the ACL cap bit) not started.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BWpNjdwPtFLuVLaAq44L9K
2026-09-16 04:42:15 -04:00
Robert Allan JamesandClaude Sonnet 5 42d4bf3dad Stage D batch 7 (final): mama_forth_words.c groups 5+6 -- dictionary lookup/test words + Stadium primitives; Stage D closed (FABRIC-3.md §XXXII.6)
NAME>XT/RUNCAP-TEST/PAIR-TEST and the six STADIUM-* physics primitives
(STADIUM-ADMIT/STADIUM-EVICT/STADIUM-RES-PULL/STADIUM-RES-PUSH/
STADIUM-HEAT@/STADIUM-HEAT!), 12 sites -- closes mama_forth_words.c
and the entire kernel-only error-handling audit.

All 105 sites from the §XXXII.3 triage now accounted for: 28 already
correct, 75 silent sites fixed across repl.c/inference_words.c/
log_words.c/vm_core.c/mama_forth_words.c, 2 special cases resolved by
dropping the error per their own documented contract, 1 resolved via
console_println() per its own recursion constraint. Verified with awk:
zero remaining vm->error=1 sites in mama_forth_words.c lack a
diagnostic within the preceding three lines.

Three-arch clean qemu acceptance passed. This closes Stage D and the
USE/logging/audit thread opened in §XXXII; Stage E (human-vs-unattended
identity model) remains open, not started this pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BWpNjdwPtFLuVLaAq44L9K
2026-09-15 20:25:30 -04:00
Robert Allan JamesandClaude Sonnet 5 90c86c6006 Stage D batch 6: mama_forth_words.c group 4 -- identity/crypto words (FABRIC-3.md §XXXII.6)
MINT + mint_pop_string()/ZUSE-ELIGIBILITY-ADD/ZUSE-ELIGIBLE?/
ELEVATE-PUBKEY-UNPACK, 10 sites. mint_pop_string() gained a field_name
parameter so its diagnostics name which of MINT's four string
arguments failed (phone/email/username/full_name), rather than a
generic message that would leave the operator guessing. Diagnostic
placement matched each function's own sibling convention where one
exists (ZUSE-ELIGIBILITY-ADD), log_message() default otherwise.

Three-arch clean qemu acceptance passed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BWpNjdwPtFLuVLaAq44L9K
2026-09-15 19:53:47 -04:00
Robert Allan JamesandClaude Sonnet 5 8b5300fc4f Stage D batch 5: mama_forth_words.c group 3 -- cross-VM execution/dispatch, both flagged special cases resolved (FABRIC-3.md §XXXII.6)
VM-STEP/VM-EXEC/VM-CALL (5 sites) gained console_println() diagnostics
matching their own existing sibling guards.

SWITCH-MARK-WORK and VM-HEAT (3 sites) resolved per §XXXII.3's own
recommendation: dropped vm->error entirely rather than diagnosing it,
matching each function's own doc comment ("must never error or spam
the console" / "does not print/error"). SWITCH-MARK-WORK is the same
function §XXVIII.3 already fixed once for an off-by-one that fired
silently on every MSG-SEND in the system -- the guard now genuinely
cannot repeat that by contract, not just by the threshold being right.
VM-HEAT's guards now push 0 and return, matching its own
always-returns-a-value stack effect.

Three-arch clean qemu acceptance passed. Zuse's own WIREBIND attach
(every boot) drives MSG-SEND -> SWITCH-MARK-WORK, so this batch's most
safety-critical fix is exercised by standard acceptance, not just
compiled.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BWpNjdwPtFLuVLaAq44L9K
2026-09-15 19:23:12 -04:00
Robert Allan JamesandClaude Sonnet 5 b42c3b195c Stage D batch 4: mama_forth_words.c groups 1+2 -- capsule/lifecycle words + BIRTH/START/KILL (FABRIC-3.md §XXXII.6)
15 of mama_forth_words.c's 48 silent sites fixed, split by functional
grouping per direct instruction: CAPSULE@/CAPSULE-HASH@/CAPSULE-FLAGS@/
CAPSULE-LEN@/CAPSULE-BIRTH/CAPSULE-RUN/EXEC (9), and BIRTH/START/KILL
(6).

Refined the diagnostic-placement rule: match whichever convention that
same function's other already-correct guards use, rather than
defaulting uniformly. BIRTH/START/KILL/EXEC each already had a
console_println() sibling guard ("name too long or empty") -- their
newly-diagnosed guards now match that, same shape as USE's own fix.
The CAPSULE*@ words have no sibling guard to match, so they keep
log_message() (defer_words.c's gold-standard default).

Three-arch clean qemu acceptance passed; BIRTH itself is exercised by
every boot (Hermes/Artemis birth).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BWpNjdwPtFLuVLaAq44L9K
2026-09-15 18:51:54 -04:00
Robert Allan JamesandClaude Sonnet 5 5c5896fbc1 Stage A: fix USE's silent stack-underflow/bad-address guards, and the Hera fault-scoping gap they exposed (FABRIC-3.md §XXXII.1)
mama_word_use()'s two silent vm->error=1 guards (dsp<1 stack underflow,
NULL from vm_ptr()) now print a diagnostic and return, matching the
function's other five guards.

Live verification of that fix alone surfaced a bigger problem: with the
guard no longer silent, the REPL proceeds to interpret the leftover
token as an unrecognized word, which independently sets vm->error, and
sk_repl_step()/sk_repl_run()'s Hera-branch still hard-halted on that.
Investigated kernel_main.c's boot/capsule-load paths directly: they
already catch and clear mama->error entirely separately, before
sk_repl_run() is ever entered -- so the "no fallthrough surface" halt
in these two REPL functions was never protecting a boot-time fault, only
an ordinary interactive REPL-turn one. Both functions now recover
unconditionally on any VM's error, Hera included, matching how a
redirected (WIREBIND/USE'd) identity's session already recovered.
Removed the now-fully-unused sk_fault_handler().

Verified live, amd64: USE rajames at Zuse's own console prints the new
diagnostic, then "VM fault -- session recovered, resuming", console
stays interactive afterward. Three-arch clean qemu acceptance passed
before and after the Hera-fault-scoping change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BWpNjdwPtFLuVLaAq44L9K
2026-09-15 09:06:33 -04:00
Robert Allan JamesandClaude Sonnet 5 05ae7aa886 Fix SWITCH-MARK-WORK off-by-one: was tripping vm->error on every MSG-SEND (FABRIC-3.md §XXVIII.2)
mama_word_switch_mark_work()'s stack-underflow guard checked dsp < 2,
requiring 3+ items, when it only ever needs the 2 IDX>NAME leaves it
(caddr u). Since dsp is index-based (2 items == dsp 1), this rejected
every normal call. MSG-SEND tail-calls SWITCH-MARK-WORK unconditionally,
so this fired on every message sent anywhere in the system -- visible
only where a caller happened to check the target VM's error flag
afterward (mama_word_vm_exec()'s "VM-EXEC: ERROR in Artemis" report).

Verified clean on all 3 architectures: boot reaches Startup: Artemis
live -> zuse@Hera] ok> with no VM-EXEC: ERROR line at all.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BWpNjdwPtFLuVLaAq44L9K
2026-09-14 20:17:01 -04:00
Robert Allan JamesandClaude Sonnet 5 66beae7fd4 Stage 3 follow-on: message-arrival eligibility hook + trampoline-blind switch-storm fix (FABRIC-3.md §XXVIII.2)
Implements the message-arrival eligibility signal FABRIC-3.md §XXVIII.1 left
open (has_work per-slot flag, set via new SWITCH-MARK-WORK primitive from
MSG-SEND) so an idle VM never becomes a switch target purely by waiting out
the readiness threshold.

Also root-causes and fixes a second, independent switch-storm: the tick's
"who is current" check used vm_log_attributed_vm(), which can't see a VM
parked in switch.c's own raw trampoline. Replaced with a dedicated
g_switch_current_vm tracked by the switch mechanism itself, and moved
target-slot eligibility reset to the switch decision point instead of
relying on ISR polling to observe a window that can be only a few
instructions wide.

Verified live on all 3 architectures: clean boot to zuse@Hera] ok>, live
cross-VM message dispatch, and (since a quiet log looks identical to a
livelocked storm once the DoE probe is gone) confirmed genuine REPL
liveness via QMP send-key + screendump on aarch64/riscv64, not log
inspection alone.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BWpNjdwPtFLuVLaAq44L9K
2026-09-14 17:39:20 -04:00
Robert Allan JamesandClaude Sonnet 5 f790d0995e Stage 2: cooperative VM context switch primitive, proven on all 3 arches (FABRIC-3.md §XXVIII)
Third stage of the preemptive context-switching plan. The real
save/restore switch mechanism now exists -- the first time anything has
ever executed on a VM's own native stack (Stage 1 allocated them,
unused).

New sk_vm_switch_to() (switch.S, one per arch) is an ordinary function
call, not an interrupt -- so unlike Stage 0's trap frame, the ABI already
covers every caller-saved register; only the callee-saved set needs
explicit save/restore (amd64: rbx/rbp/r12-r15, no FP at all since SysV
has no callee-saved XMM; aarch64: x19-x28/x29/x30 + d8-d15; riscv64:
s0-s11/ra + fs0-fs11, FS-gated like Stage 0 but read once and reused for
both halves within one call, since FS is genuine global CPU state, not
part of what's switched). A sibling sk_vm_switch_prime() in the same file
builds the synthetic first-entry frame, kept in assembly so the layout
can never drift out of sync with sk_vm_switch_to() itself.

New switch.c/switch.h: sk_vm_context_switch(from, to) handles first-entry
priming vs. resuming a parked context, and updates registry state (new
VM_STATE_SWITCHED_OUT, distinct from VM_STATE_STOPPED -- STOPPED means no
live frame, this means the opposite). sk_vm_switch_entry() is the minimal
permanent trampoline every freshly-entered VM lands in: no production
behavior defined yet, so it just yields straight back to whoever switched
to it, forever.

Closes the confirmed unguarded-KILL UAF found during planning:
capsule_vm_kill(), mama_word_kill(), and capsule_vm_kill_all_nonmama()
all now refuse (or silently leak rather than free, on the cold-restart
path where arch_cold_reset() wipes everything immediately after anyway)
tearing down a switched-out VM. Side effect found, not built on purpose:
the existing MSG-TICK idle-pump already filters on VM_STATE_LIVE, so it
automatically stopped dispatching into a switched-out VM with zero
changes needed there.

Verified via a temporary SWITCH-TEST probe (boot-triggered, since nothing
can type interactively into a foreground-only QEMU session) that
round-tripped a sentinel through 5 real Hera<->Hermes switches on all 3
architectures: 5/5 rounds, 0 failures, clean continuation to ok>. Probe
fully reverted after capture; kernel_main.c shows zero diff.

Also: Makefile.starkernel's LOADER_EXTRA_SRCS/LOADER_ASM needed the new
files added explicitly (this project's "loader" PE binary is the full
running kernel, not a thin bootstrap stage), and aarch64's switch.S
needed the same #ifndef _WIN32 guard around .hidden that isr.S already
carries (aarch64's loader assembles via clang targeting a PE/COFF
target with no .hidden equivalent) -- caught by a build failure, fixed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016UNhH1mhi52i6Qihh7ZV5S
2026-09-13 15:25:39 -04:00
Robert Allan JamesandClaude Sonnet 5 3c2daf50d1 Extend BIRTH/CAPSULE-BIRTH to all VMs symmetrically; flag a real std79 lockdown gap (FABRIC-3.md §XXV)
Scoping the workload-into-factorial design's placement-mode factor
led to a real architectural improvement: rather than EXEC-ing a
workload capsule into an already-running, ACL-locked identity's own
persistent dictionary (filesystem-shaped, doesn't dodge the block-
collision exposure just traced in §XXIV), a workload now runs as a
fresh ephemeral child VM, BIRTH'd per trial and reaped after --
matching the project's own stated principle of automanagement over
imposed policy. CAPSULE-BIRTH already passes vm->stadium_vm_id (who
is birthing this VM) as the new child's parent, not a hardcoded Hera
constant, confirmed by reading the C -- so a workload trial genuinely
inherits the specific identity's own lineage when that identity does
the birthing.

Which surfaced a real premise: only Hera could call BIRTH/
CAPSULE-BIRTH at all (registered only in register_mama_forth_words(),
confirmed directly, not part of the earlier §XX messaging-symmetry
fix which deliberately kept this as one of her remaining privileges).
Extended symmetrically now, agreed explicitly before touching code:

- mama_forth_words.c: BIRTH and CAPSULE-BIRTH added to
  register_child_vm_words(), matching §XX's own pattern.
- acl-std79.4th: ' BIRTH , ' CAPSULE-BIRTH , added to ACL-STD79-LIST
  (new block 4048) -- a deliberate, explicit, named exception to the
  lockdown's own "standard words only" guarantee, not a silent one.
  Symmetric registration alone can't weaken any lockdown on its own:
  ACL-LOCKDOWN-STD79 is allowlist-based, deny-by-default, so a newly
  registered word is auto-denied there unless explicitly added.

Verified: clean build on all 3 architectures, zero new warnings.
Hera's own dict_hash unchanged (expected); Hermes/Artemis show the
same new dict_hash on all 3 architectures. Live-tested against a
real attached std79-locked identity: CAPSULE-BIRTH executes
correctly (returns vm_uuid_none() for a deliberately out-of-range
capsule-id, zero fault, zero ACL denial).

Found, and explicitly stopped short of fixing, a separate pre-
existing gap while verifying the above: MSG-STATUS and MSG-K
(messaging.4th words, not on the std79 allowlist) execute for a
locked identity instead of being denied. ACL-LOCKDOWN-STD79 is
confirmed to actually run; something more specific isn't reaching
messaging.4th's dictionary entries. Root cause not traced -- needs
its own investigation into vm_core.c's dictionary-link mechanics and
whichever capsule actually loads messaging for these identities.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EXieurDfDSsDFdnSyusuWo
2026-09-12 22:31:52 -04:00
Robert Allan JamesandClaude Sonnet 5 7edccd2c35 Hera can't message: root-caused and fixed by symmetry (FABRIC-3.md §XX)
Hera never loaded common:messaging.4th, unlike every other VM in the
fleet. The standing belief was this was deliberate, to avoid moving
her dict_hash off baseline. Checked live instead of assumed: loading
messaging.4th into her dictionary silently dropped every colon-
definition referencing one of 8 STADIUM-* primitives that
register_child_vm_words() gives every other VM but
register_mama_forth_words() never gave her -- a missing-primitive gap,
not a designed privilege boundary. Confirmed mama_word_birth is
genuinely VM-agnostic and SPAWN-EVENT is an unwired placeholder before
proposing the fix.

Fix: register the same 8 STADIUM-* primitives for Hera, load
messaging.4th from init.4th the same way Hermes/Artemis/console/mint
already do, and give her own idle-loop context a direct MSG-TICK call
(not VM-EXEC, which would hit the same reentrancy class the existing
per-other-VM pump loop already guards against) so her own queued
messages actually drain. Her dictionary is now a proper superset of
every child VM's, plus her remaining extra privileges -- not
structurally different from any other VM, just additionally
privileged.

Verified: dict_hash identical across amd64/aarch64/riscv64
(0xc8f4b09e36f4fc4a), Hermes/Artemis dict_hashes unchanged and still
cross-arch identical, all three boot clean to zuse)ok> with zero
UNKNOWN WORD faults, mkcapsule --lint clean.

Unblocks rewriting the turn-attractor (FABRIC-3.md §XIX) to coordinate
via real MSG-SEND/MSG-TICK instead of blocking VM-EXEC.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EXieurDfDSsDFdnSyusuWo
2026-09-12 16:05:11 -04:00
Robert Allan JamesandClaude Sonnet 5 5a9425b91b Add VM-HEAT primitive: groundwork for a compudynamic turn-attractor (FABRIC-3.md §XIX)
Prompted by reading the std79-doe K report: checked whether the campaign's
"real cross-VM dispatch load" was actually concurrent or strictly
serialized. It's serialized at two levels -- VM-EXEC's vm_interpret(target,
...) is a direct synchronous C call (Hera fully blocked until it returns),
and even the background physics tick (vm_tick(), vm_runtime.c) is driven
by each VM's own execution loop, so idle identities accrue zero ticks
between their own turns. K's perfect conservation (FABRIC-3.md §XVIII)
verifies sequential per-VM accounting correctness, not concurrent-access
safety, since there was never concurrent access to test.

Agreed direction: fix this without a scheduler, by reusing the same
least-dense-candidate judgment stadium_admit() already trusts for
eviction, applied to "whose turn is next" instead of "who gets evicted" --
a fleet-level turn-attractor giving the next turn to whichever live
identity currently has the lowest execution_heat_q48, no fixed round-robin,
no priorities, no preemption. Lives beside Stadium in
capsule_vm_physics.c (already the fleet-level consumer of Stadium
primitives, e.g. the K mechanism itself), not inside stadium.c ("the
floor" -- residency/eviction, a different concern from turn order) and
not a new subsystem.

This pass lands only the primitive the mechanism needs: VM-HEAT
( c-addr u -- heat-q48 ), pushing a named VM's current
execution_heat_q48 via vm_physics_heat_of() -- previously C-internal
only (doe_log_heat_by_name(), doe_log.c), never exposed to FORTH. Silent
0 on an unknown/dead name (no print/error), since a turn-attractor
scanning many candidates every turn shouldn't have to filter console
noise for names that simply aren't live. Registered everywhere
VM-EXEC/VM-CALL already are. Builds clean on all three architectures;
live-tested on amd64: Hera -> 65452, Hermes -> 43, unknown name -> 0, no
faults.

The turn-attractor loop itself (std79-doe.fth's trial ordering) is not
yet built -- next step, not done here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EXieurDfDSsDFdnSyusuWo
2026-09-12 14:53:06 -04:00
Robert Allan JamesandClaude Sonnet 5 662ef44e59 Fix EXEC/LOAD block-persistence gap and WIREBIND/USE interpreter-race panic (FABRIC-3.md §XII)
Found live while building a cross-ISA FORTH-79 dictionary exerciser:

- capsule_exec_init() zeroed a capsule's block content immediately after
  running it, so LOAD (a genuine FORTH-79 standard word, ACL-allowed even
  for locked identities) could never actually read back what EXEC had
  just written. Removed the clear from capsule_exec_init(); block content
  now persists like any other Standard BLOCK/BUFFER/UPDATE write.
  kernel_main.c's own explicit post-birth clear of Mama's init.4th range
  is untouched.

- USE could redirect the console to a WIREBIND identity's VM before that
  VM's vm_enable_interpreter() step of its own birth sequence had run,
  causing the next typed line to hit vm_assert_interpreter_enabled() and
  panic the entire machine -- not the per-session-recoverable ACL-fault
  path a redirected VM otherwise gets. USE now checks interpreter_enabled
  first and refuses with a retry message instead.

Also includes the amd64/aarch64/riscv64 acceptance boot logs and DoE CSVs
from this session.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EXieurDfDSsDFdnSyusuWo
2026-09-10 16:07:38 -04:00
Robert Allan JamesandClaude Sonnet 5 f4ded3e1a8 MINT: add a FORTH-79/83-standard-words-only lockdown personality
Captain Bob, 2026-09-07: "starting with that 00 user we created, we're
going to give access only to FORTH 79 and 83 standard words. everything
else is locked down."

New capsules/acl-std79.4th (blocks 4023-4047): walks a VM's own
dictionary (>LINK/LINK> traversal, same as ACL-INIT-PRIMITIVES/WORDS
already use) and permanently denies+pins every word not on an explicit
FORTH-79/83 allowlist, extracted from the real registered word set
(stack_words.c through control_words.c), not recited from memory.
Deliberately excludes, beyond plain non-standard words: BYE (100% ACL
bypass to the emergency console -- "needs more discussion, exclude for
now"), COLD/WARM/REBOOT/SAVE-SYSTEM (system lifecycle), the block/screen
editor L/S/SHOW/EDIT/UPDATE/SAVE-BUFFERS (lets a session rewrite
persistent block/capsule content, defeating the lockdown even though
nominally standard), BLK-ACL-*/BLK-OWNER@ (StarForth-specific), and
FORGET/FENCE (flagged as an unrestricted superpower word, 2026-09-03
audit). Keeps WORDS/VLIST/SEE (introspection only -- ACL is enforced
per-target-word at execution time regardless of how an XT was
obtained) and the parenthesized control-flow runtime primitives
((BRANCH) etc. -- IF/DO/LOOP compile calls to these; denying them
breaks ordinary control flow, not security).

MintPersonality enum (capsule_mint.h) lets capsule_mint_identity()
select which personality-source template gets written to a new
identity's devblock -- MINT_PERSONALITY_DEFAULT (unchanged) or
MINT_PERSONALITY_STD79_LOCKDOWN (EXECs acl-std79.4th then
ACL-LOCKDOWN-STD79 as the VM's own last bootstrap step). The actual
restriction logic stays entirely in FORTH per .claude/CLAUDE.md's
Word-Level ACL System rules ("ACL policy belongs in ACL.4th, never in
C") -- capsule_mint.c only picks which few-line bootstrap stub to
write. MINT's own stack signature gains a trailing restrict? flag;
capsule_zuse_boot.c's genesis mint (Zuse herself) explicitly passes
MINT_PERSONALITY_DEFAULT -- the superuser is never restricted.

Two real bugs found and fixed live during testing, both the same class
of self-referential fault: ACL-LOCKDOWN-STD79's own walk loop calls
ACL-STD79-ALLOWED?/ACL-STD79-LIST/ACL-ALLOW!/ACL-PIN on every single
iteration to do its job -- none of those are FORTH-79/83 standard
words, so the walk was denying its own load-bearing infrastructure
partway through and then faulting the next time it tried to call it
("VM fault -- emergency console disabled; halting", reproduced twice
live). Fixed by explicitly protecting all four in the allowlist
(block 4047) -- they must stay allowed for the walk to finish, not
because they belong on a "standard words" list.

Verified live end-to-end: minted a throwaway test identity with the
restrict? flag, confirmed her WIREBIND birth completes cleanly (no
faults, no shadow conflicts) on a single real attach, then USE'd into
her VM and confirmed standard arithmetic and user-defined words work
(1 2 + . -> 3; : X 5 5 * . ; X -> 25) while KILL is entirely unknown to
her dictionary and VM-EXEC is denied. One real, non-fatal side effect
found and left as-is (not asked to fix): the fleet's inter-VM messaging
pump (MSG-ARENA) is also denied by the lockdown, logging a harmless
per-idle-tick warning -- a fully locked-down VM doesn't participate in
message routing.

Not yet applied to the real identity 00 -- this commit is the
mechanism, verified against a disposable test identity only.

Three-arch clean qemu acceptance (single Zuse device, standard
regression case) passed on amd64, aarch64, and riscv64.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Ec88YKxxhZGG1RNnune78
2026-09-07 15:41:56 -04:00
Robert Allan JamesandClaude Sonnet 5 2c1b3cd695 Four bugs found live verifying the 8 identity thumbdrives (FABRIC-3.md §IX)
All found by actually running the identity workflow §VII/§VIII made
possible, not by code review:

1. Zuse/WIREBIND cross-contamination on detach: capsule_zuse_boot_logout()
   and capsule_wirebind_unclean_detach() both had no device parameter, so
   an unrelated device detaching (while the real owner's own stayed
   attached) incorrectly tore down the wrong session. Both now compare
   the departing device against their own tracked one, mirroring
   capsule_wirebind.c's pre-existing g_wirebind_attached_dev precedent.

2. Dictionary-entry memory leak: vm_create_word()'s sf_malloc()'d
   DictEntry (plus a second per-entry allocation for transition_metrics)
   was never freed by vm_cleanup(), in both the hosted and kernel
   implementations. Caused a real kernel PANIC after 8-9 repeated VM
   birth/kill cycles in one boot. Fixed by walking vm->latest in both.

3. sf_malloc/sf_free (alloc_kernel.c) was a 4MB bump arena with a
   deliberate no-op free, sized on "VM born once, never killed" -- fix #2
   alone didn't stop the panic because free() itself discarded the
   pointer regardless. Given a real free list (first-fit reuse).

4. Headless-console gate didn't re-engage after a mid-boot logout: the
   original fix (sk_console_mark_login(), one-way sticky) only gated the
   first login of the boot. Replaced with a live check
   (sk_console_identity_present()) re-evaluated continuously, including
   inside sk_console_readline()'s own blocking idle loop -- the console
   is normally sitting blocked there when a hot-unplug logout happens, so
   checking only at the top of the REPL loop wasn't enough.

Also: MINT now verifies its own write (verify_mint(), capsule_mint.c) by
reading back through the same check a real attach performs, rather than
trusting blkio_write()'s BLK_OK alone -- logged via log_message(), not
console_println(), per direct instruction.

Verified live, amd64: the full 8-identity repeated attach/detach cycle
that previously panicked at the same point every time now completes
clean, and a full serial-log sweep found zero bare unauthenticated
prompts anywhere in the run. Three-arch clean-qemu acceptance passed.

Still open, not fixed here: a 3+-simultaneous-device USB enumeration
failure found in a separate live test, not yet root-caused.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018EjXFo7mPXjUMjfJeuUUz4
2026-09-06 01:49:13 -04:00
Robert Allan JamesandClaude Sonnet 5 9e81de3f43 xHCI/BOT driver: genuine multi-device support (FABRIC-3.md §VII)
Per-slot registry (xhci_msc_slot_t/dev->msc_slots, sized off the
controller's own reported max_slots) replaces the single-device scalar
fields the driver carried since Milestones 2e-2h. Boot-time port scan no
longer stops at the first connected device; a connect/disconnect that
arrives while the Command Ring is busy is now queued and drained instead
of dropped. blkio_usb.c and repl.c's own single-device state (device
descriptor buffers, blkio_dev_t, attach bookkeeping) became per-slot
registries the same way.

Live multi-device testing (not just compiling) surfaced a second, more
severe bug outside the original plan: transfer_purpose and next_action
were also single scalars shared across the whole controller. Two devices
enumerating concurrently could have one's completion silently overwrite
the other's still-outstanding one, permanently stalling it with no error.
Fixed by moving both per-slot and, critically, reading the Transfer Event
TRB's own real Slot ID field instead of trusting external bookkeeping.

Verified live, all three architectures, mandatory clean-qemu acceptance:
existing single-device path unchanged, and two devices attached
simultaneously (amd64) both progress independently through enumeration
without corrupting or stalling each other.

Also in this pass (implemented and verified in earlier turns this
session, committed together per direct instruction):
- Headless-until-login console policy: no prompt/banner until a real
  identity logs in via an attached thumbdrive (WIREBIND or Zuse, neither
  special), reusing EMERGENCY_CONSOLE_ENABLED as the debug/recovery
  escape hatch (now default-off).
- KILL/g_repl_active_vm dangling-pointer fix: killing the VM the console
  is currently USE'd onto now detaches back to Hera first, matching the
  existing EJECT/UNCLEAN precedent.

FABRIC-3.md §VII/§VIII carry full closure notes for all three.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018EjXFo7mPXjUMjfJeuUUz4
2026-09-05 22:14:14 -04:00
Robert Allan JamesandClaude Sonnet 5 b031b802e3 Rename FABRIC series: FABRIC.md->0, FABRIC-2.md->1, FABRIC-3.md->2, FABRIC-4.md unchanged
FABRIC.md -> FABRIC-0.md
FABRIC-2.md -> FABRIC-1.md
FABRIC-3.md -> FABRIC-2.md (the current/living document)
FABRIC-4.md unchanged (new #3 to follow separately)

Every cross-reference repo-wide updated to match, including doc-comment
citations inside kernel source (.c/.h) files -- done via an ordered
placeholder substitution (FABRIC-3.md->placeholder2, FABRIC-2.md->
placeholder1, FABRIC.md->placeholder0, then placeholders resolved to
final names) in a single pass per file to avoid double-shifting
already-renamed references.

One line in capsules/font.4th grew past the 64-char block-format limit
as a side effect of the longer filename; shortened it and reverified
with mkcapsule --lint (34/34 pass) before rebuilding.

Verified 3-arch boot to ok> (amd64/aarch64/riscv64, each in the
foreground) after the fix; logs and DoE CSVs from this session's
verification runs included per this repo's own audit-artifact
convention.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 11:22:51 -04:00
Robert Allan JamesandClaude Sonnet 5 60d9c2520e FABRIC-3.md §I.3/§I.8: WIREBIND EJECT/detach + EXPIRE re-scoped as logout
Closes §I.3 (Milestone 5 remainder): WIREBIND now tracks which VM is
attached via the home-blocks USB path, and a new EJECT word plus the
existing hot-unplug signal both flush/reset-console/kill through it
(FABRIC-3.md §F.10).

Closes §I.8 (EXPIRE/ACL), re-scoped: the original "admit the zuse
session as a Stadium patron and reap on TTL" plan was invalidated a
second time -- Zuse authenticates directly onto Hera, who is patron
zero and permanently pinned, so there is no patron for a reap sweep to
ever find. Built instead as a detach-triggered logout
(capsule_zuse_boot_logout()), the same trigger EJECT/hot-unplug use for
regular WIREBIND users, so neither identity is a special case. Required
a companion fix: install_and_activate() used to skip re-running
ACL-ZUSE-BOOT whenever the cert was already installed, which made a
logout permanent for the rest of the boot; the outer re-attach gate now
checks zuse_session (clears on logout) instead of zuse_cert_installed
(a deliberate permanent one-way ratchet, left untouched).

Verified 3-arch boot to ok> (amd64/aarch64/riscv64, each in the
foreground) after both steps; logs and DoE CSVs from this session's
verification runs included per this repo's own audit-artifact
convention.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 09:21:01 -04:00
Robert Allan JamesandClaude Sonnet 5 2b9fa02354 §H.12 steps 21-22: ELEVATE-REQUEST + ELEVATE-GRANT + SEND-ELEVATE-REQUEST
Phase 7 complete, closing out §H.12's punch list. MSG-DELIVER turned out
to VM-EXEC payload text directly rather than dispatching by type, so the
"handler" is ELEVATE-GRANT, a word the delivered text calls. New Hera-only
C primitives (ZUSE-ELIGIBLE?, NAME>XT, ELEVATE-PUBKEY-UNPACK) stay plain
and unconditional; capsules/zuse-eligibility.4th composes the actual
eligibility check + ACL-ALLOW!/ACL-TTL! grant in FORTH.
SEND-ELEVATE-REQUEST (common:messaging.4th) builds the payload text and
sends it via the item-20-gated CH-REQUEST.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QgooKd5hJNtTYqB6CyK5f9
2026-09-03 13:15:40 -04:00
Robert Allan JamesandClaude Sonnet 5 7d53344875 §H.12 step 19: ZUSE-ELIGIBILITY-ADD word, no gating (corrected mid-step)
Plain FORTH word wrapping zuse_eligibility_add() unconditionally. Two
wrong first attempts (C-level zuse_session check, then a FORTH wrapper
checking it) both corrected: zuse_session isn't a special axis needing
its own gate anywhere -- Zuse's authority is the absence of any ACL
restricting her, not a flag any word checks.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QgooKd5hJNtTYqB6CyK5f9
2026-09-03 11:38:07 -04:00
Robert Allan JamesandClaude Opus 5 ed86a759e1 §H.12 steps 7-9: thread real parent VMUuid through the birth call chain
Session.parent now comes from the actual birthing VM's own
stadium_vm_id, not a hardcoded vm_uuid_hera(). Added a VMUuid parent
parameter to capsule_birth_baby() and, one level up, to
capsule_console_birth()/capsule_runcap_birth() (neither had a VM* in
their own signature, but every caller did). Updated all 6 real call
sites: BIRTH, CAPSULE-BIRTH, CONNECT-ARTEMIS, CONNECT-HERMES,
RUNCAP-TEST, PAIR-TEST (mama_forth_words.c) and the console+user birth
pair in capsule_wirebind_try_attach() (capsule_wirebind.c). Two
functions had their vm parameter marked __attribute__((unused)), now
genuinely used -- attribute removed.

Steps 8 (Session.name from capsule name) and 9 (identity defaults to
installed=0) were already satisfied by step 5's existing
session_register() call and its identity-zeroing -- confirmed by
inspection, no further code needed.

Verified 3-arch boot to ok> (amd64/aarch64/riscv64).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 06:38:32 -04:00
Robert Allan JamesandClaude Sonnet 5 849b83b727 Zuse default-attach: xHCI initial-port-scan fix, ZUSEDISK wiring, mismatched-marker resync
xhci_bringup() now scans for already-connected ports at bring-up
(xhci_scan_ports_for_already_connected()), not just later hotplug events,
so a USB device present on the QEMU command line at launch is detected.
Makefile.starkernel attaches disk/zuse.img on the xhci0 bus by default in
all three arch qemu targets (ZUSEDISK=, empties for a bare boot).

capsule_mint_identity() gained a drive_known_blank param to skip a fully
redundant second homeblocks_sig_check() when the caller already confirmed
HOMEBLOCKS_SIG_BLANK itself.

Root-caused what looked like a hang after the drive attached: Artemis's
fence still carried a genesis marker from before a mid-session reformat,
while the reformatted disk/zuse.img read back BLANK -- a mismatched pair
capsule_zuse_boot_try_attach() correctly declined to act on, leaving the
boot idling at a plain ok> with nothing left to log (indistinguishable
from a hang under slow TCG). Fixed by zeroing both disk/artemis.img and
disk/zuse.img at their original sizes, giving a matched blank pair.
Verified full three-architecture acceptance: amd64 fresh genesis-mint,
aarch64/riscv64 clean reload against the same now-minted images.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KBjfeLPo71sUQ8zC7V7P5m
2026-08-28 21:42:14 -04:00
Robert Allan JamesandClaude Sonnet 5 09d78c99d0 BINDSTEP + fence-persistence fix: identity arc closed end to end
Two items, closed together per direct instruction.

1. Fence-persistence root cause, found and fixed: meta_fence_blocks
   (the field gating whether blk_meta_zone_write() can succeed at all)
   was carved out of what used to be unused padding in blk_volume_meta_t
   -- the code's own comment already documented this. disk/artemis.img
   was formatted before that field existed, so its on-disk bytes there
   have always read back as 0, and the existing-volume load path
   (blk_format_or_load_disk()) never recomputes it -- only a fresh
   format does. Every "fence write FAILED" message this entire session,
   old block-fence flow and new zuse_genesis_marker_t alike, traces to
   this one thing. Patching the field in place without redoing the rest
   of the geometry would risk corrupting whatever's already allocated
   near the top of the volume, so the only safe fix is a genuine
   reformat -- done, with explicit confirmation, since it discards
   disk/artemis.img's accumulated persistent test state (regenerated
   fresh at next boot regardless, not real data). Verified: fence write
   now succeeds with no failure suffix, and the full mint-once ->
   reboot -> reattach -> re-authenticate cycle works for the first time
   this session ("Zuse: identity confirmed from attached thumbdrive",
   ZUSE-SESSION? goes 0 -> -1 without re-minting).

2. BINDSTEP (FABRIC-3.md §F.9): capsule_wirebind_verify_cert() extracted
   as a shared function so WIREBIND (the original attach) and BINDSTEP
   (every USE of an identity-locked VM) check the exact same thing the
   exact same way. mama_word_use() now re-verifies live, not cached,
   whenever the target VM has VMIdentity.installed=1 -- reads whatever
   drive is CURRENTLY attached, re-verifies its cert, compares owner
   pubkey against the target's own installed identity, refuses on any
   mismatch or no drive attached. A target with installed=0 (Hera,
   Hermes, Artemis, any console VM) stays freely targetable, unchanged.

Two related bugs found and fixed live while testing BINDSTEP, not
assumed away: USE was Mama-only, so a console-paired session (§F.22)
had no way back to Hera at all -- any attempt to call USE from inside
a console VM hit "UNKNOWN WORD: USE", a genuine dead end. Per direct
instruction, USE isn't console-specific -- it should work VM-to-VM
universally, same as VM-EXEC already does -- so it's now registered in
register_child_vm_words() too. That alone wasn't enough: the console
relay (sk_repl_dispatch_line()) would have captured a bare USE call and
sent it to the paired user VM as a message instead of running it.
Fixed with a small suffix-match guard (sk_repl_line_calls_use()) --
real FORTH syntax always puts USE last, so a trailing-token check
reliably recognizes it without needing a full tokenizer, and it always
runs directly, never relayed.

Verified live end to end: USE on an unlocked VM works unconditionally;
USE escaping a console back to Hera now works; USE on an identity-
locked VM succeeds while its own drive is attached and is refused
once detached ("USE: FinT~user refused -- no matching identity
currently attached"). Clean 3-architecture regression, including
confirming disk/artemis.img's reformatted geometry loads correctly as
an already-recognized volume ("Artemis: LithosAnanke disk -- resuming")
on aarch64 and riscv64 too, not just the amd64 boot it was reformatted
under.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZGkimpfyh63EZyRkNbkPD
2026-08-28 18:28:53 -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 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 db6640682b mama_forth_words: KILL must not physics-retire Hera before refusing her
S" Hera" KILL called vm_physics_retire(hera_id) before capsule_vm_kill()'s
own Hera guard ever ran. Hera's self-referential parent_vm_id makes
vm_physics_find_root_id() return her own id immediately, so
vm_physics_retire() treated her as an unreachable root and zeroed the
fleet's entire execution_heat_q48 sum -- silently, with only the
harmless-looking "cannot kill Hera" message as output. Guard the retire
call the same way capsule_vm_kill() already guards the actual kill.

Three-arch acceptance boot, all clean to ok>:
logs/20260813-083429/amd64, logs/20260813-083551/aarch64,
logs/20260813-083738/riscv64.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 08:38:33 -04:00
Robert Allan JamesandClaude Sonnet 5 5a28458b21 starkernel: item 4.2 -- Hermes native on the Stadium (complete)
Migrates Hermes's message/channel lifecycle onto the Stadium's unified
heat/capacity economy: MSG-ALLOC/FREE-NODE and CH-ALLOC/FREE-NODE now
route entirely through stadium_admit()/stadium_evict(), replacing the
old local free-list + independent heat-field mechanism. Eight
kernel-only STADIUM-* FORTH primitives (ADMIT, EVICT, RES@, RES-PULL,
RES-PUSH, HEAT@, HEAT!, WORD-HEAT), VM.stadium_vm_id threaded through
all three vm_core.c dispatch sites (replacing item 4.1's hardcoded
vm_uuid_hera()), and the stadium_owner[idx] fix so evict-credit lands
in the VM that actually admitted a patron, not whoever owned cell 0.

This session's own contribution, on top of that pre-existing
implementation: found and fixed two bugs blocking the item's own K≡1.0
conservation self-check (HERMES-K was reading 0, not 65536):

- Q.SLOT admission-heat fix (capsules/hermes/init.4th): MSG-SEND/
  CH-ACCEPT admitted with Q.1 (the entire fleet-wide "1.0" unit) per
  item, a leftover from before the Stadium migration when each
  message/channel had its own unconstrained heat field. Instantly
  drained the shared, finite reservoir.

- Reservoir floor for word-execution admission (stadium_words.c):
  stadium_word_dispatch() (item 4.1) pulls STADIUM_WORD_HEAT_QUANTUM on
  every word dispatch, not just first admission -- exhausts a VM's
  entire reservoir in ~32 dispatches, starving any application-level
  economy sharing that VM's reservoir before it gets a chance to pull
  anything. word_dispatch_pull() now clamps word-execution's own pulls
  to leave a Q48_ONE/3 floor (same fair-share figure COMMON-CH's own
  floor already uses); application-level pulls are unaffected.

- STADIUM-WORD-HEAT primitive + stadium_words_resident_heat(): the
  floor deliberately leaves word-execution residents holding real
  heat, invisible to HERMES-K's original formula (MSG+CH+reservoir,
  no term for word patrons). Adding this term closes K to exactly
  65536 on all three architectures.

Also rules on two open scope questions in FABRIC.md: MBR-ALLOC/
MBR-FREE-NODE stay off the Stadium (membership records have no heat
field, never did -- the acceptance bullet's inclusion of them was a
completeness gesture predating a check of the actual layout), and
records the effort number (12 implementation files, +759/-120 lines).

Verified: all three architectures boot clean, full self-test passes,
Stadium conservation closes exactly (resident_sum + reservoir =
Q48_ONE) at both the C/Stadium level and the FORTH-level HERMES-K
check.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-07 01:49:23 -04:00
Robert Allan JamesandClaude Sonnet 5 9b305a5be7 starkernel: item 3.8 -- VM identifiers as UUID/GUID
Punch list §25 item 3.8 complete. Added after starting item 4.1
surfaced the need to thread a vm_id into stadium_admit()'s new quota
parameter; Captain Bob ruled UUID/GUID rather than keeping the
narrower uint32_t.

New VMUuid type (vm_uuid.h/vm_uuid.c): two uint64_t halves, RFC-4122-
shaped for logging. Not real randomness -- checked directly against
QEMU 10.2.1's actual CPU feature set: amd64 RDRAND and riscv64 Zkr are
both real, available features here; aarch64 has no RNG property on any
CPU model including "max" (verified exhaustively via QMP
query-cpu-model-expansion). Captain Bob ruled a uniform fallback
across all three ISAs rather than a per-architecture split.

Fallback is a deterministic PRNG (splitmix64) seeded from the Mama
capsule's content hash, pre-filling a 16-entry FIFO pool at boot and
refilling with another batch of the same stream when exhausted --
exactly the shape requested. Same capsule booted twice produces the
same id sequence, preserving the dict_hash reproducibility this
session has relied on throughout.

Hera keeps a fixed, reserved all-zero id, not drawn from the pool --
capsule_birth.c uses vm_id == 0 as a load-bearing sentinel in three
places (KILL protection x2, fleet heat-fanout parent-chain
terminator), found by reading before writing any code.

Two real sentinel-collision bugs caught before shipping, same class as
STADIUM_CONTAINS_NONE: vm_uuid_none() (all-ones, not all-zero) for
"not yet assigned"/"no VM" placeholders; confirmed item 3.7's quota
table already used an in_use boolean rather than a vm_id sentinel, so
no second collision was actually possible there -- the dead,
never-referenced STADIUM_QUOTA_SLOT_EMPTY macro was removed.

Blast radius larger than first scoped, flagged mid-work rather than
silently absorbed: capsule_vm_physics.c/.h (the fleet heat-transfer
layer item 2.1 modified earlier this session) has its own vm_id-keyed
node table and walks parent_vm_id chains through the same identity
space, so it needed the same change, plus its callers in
mama_forth_words.c and sk_vm_bootstrap.c.

One live FORTH word contract changed, by explicit ruling: CAPSULE-BIRTH
was ( capsule-id -- vm-id ), a single cell -- can't hold 128 bits.
Captain Bob picked pushing two cells ("there is doubles support in the
FORTH std word set anyway"): ( capsule-id -- vm-id-hi vm-id-lo ).
MAMA-VM-ID changed the same way: ( -- 0 0 ).

Verified: full (not standalone-file) kernel rebuild to catch cross-file
breakage given the size of this change -- it surfaced the
capsule_vm_physics.c blast radius a narrower check would have missed.
Three-architecture boot (amd64, aarch64, riscv64), all reaching ok>
with identical dict_hash=0x3d4e1daf289da94f matching the item-3.7
baseline.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 19:50:34 -04:00
Robert Allan JamesandClaude Sonnet 5 542d7dbf0d starkernel: restate VM fleet heat transfer on the virtual tick
Punch list §25 item 2.1 complete.
vm_physics_touch() no longer takes a wall-clock timestamp -- it reads
fleet_heartbeat_tick_count internally, which is execution-paced
(vm_runtime.c:143), not wall-clock. VMPhysics.last_active_ns ->
last_active_tick, VMFleetTouchSample.elapsed_us -> elapsed_ticks, and a
new explicit `touched` flag replaces the old `> 0` sentinel (tick 0 is
a legitimate value a first touch can land on, unlike wall-clock ns).

Verified: three-architecture boot (amd64 x2, aarch64, riscv64), all
reaching ok> with identical dict_hash=0x3d4e1daf289da94f matching the
item-0.10 baseline. No new compiler warnings in the touched files.

Honestly flagged, not fixed: with Tripod pruned to Hera alone (item
0.1), vm_physics_touch()'s fan-out has no other live VM to pull heat
from, so the fleet-heat-sum acceptance criterion is trivially satisfied
rather than genuinely stress-tested -- a real check needs Phase 4's
multi-VM fleet. fleet_transfer_slope_q48's seed (65536/3) was
calibrated for elapsed microseconds and has not been re-fit for elapsed
ticks; left as-is rather than guessed, deferred to item 5.1's DoE work.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 13:15:50 -04:00
Robert Allan James a5ed8c3d87 Initial commit — LithosAnanke kernel 2026-08-01 07:49:56 -04:00