Commit Graph
100 Commits
Author SHA1 Message Date
Robert Allan James c16eaf431f logs: add boot-run audit trail and DoE CSVs from item 4.3.4 work
Three-arch acceptance run (amd64/aarch64/riscv64) plus the interactive
CUBE verification session.
2026-08-07 15:29:10 -04:00
Robert Allan James fa300ef4c6 starkernel: item 4.3.4 -- checkpoint, draw a cube (no new bugs)
Adds VERT/EDGE/CUBE to capsules/fabric.4th (blocks 4913-4915). VERT
( n -- x y z ) reads bits 0/1/2 of a corner index as the X/Y/Z sign
(+-CS from center), so all 8 cube corners come from one word. EDGE
resolves both corners via VERT and calls LINE; CUBE is 12 EDGE calls
(4 bottom, 4 top, 4 vertical).

First item in the 4.3.3.x sequence with no new bug found -- a small signal
that Q.TO-INT, the VARIABLE alignment fix, and the LINE-STUCK? cap were
the real gaps rather than something still lurking in LINE/PROJECT/CART-Y.

Verified live on amd64: a centered, half-size-100 cube renders correctly
-- front/back face squares, back face offset diagonally up-right by
exactly the 45-degree cavalier projection's depth term, all 12 edges
connecting at the right corners.

All three architectures boot clean to ok> with the DoE completing;
dict_hash identical across all three and unchanged from 4.3.3a/4.3.3b.

FABRIC.md item 4.3.4 marked done. This is the checkpoint -- 4.3.x
groundwork stops here for review per this item's own acceptance criterion.
2026-08-07 15:29:04 -04:00
Robert Allan James a3fe0702eb logs: add boot-run audit trail and DoE CSVs from item 4.3.3b work
Three-arch acceptance run (amd64/aarch64/riscv64) plus the interactive
sessions used to test LINE/CIRCLE/ARC/ELLIPSE, the block-span test, and
verify the Q.TO-INT fix.
2026-08-07 15:21:13 -04:00
Robert Allan James cb4326c712 starkernel: item 4.3.3b -- geometry drawing wordset, fixed Q.TO-INT sign bug
Adds LINE (Bresenham in raster space, endpoints projected once each --
valid because the cavalier projection is linear), CIRCLE/ELLIPSE
(36-segment polygon approximation), and ARC (18 segments over a caller
radian range) to capsules/fabric.4th (blocks 4903-4912). TO-RASTER
factored out of CART-PLOT (same behavior) so LINE can reuse the
projection+flip for both endpoints.

Found mid-implementation: colon definitions cannot span block boundaries
in this capsule loader -- verified with a throwaway test capsule, the
continuation lands in a [CAPSULE][DEFER] path that never resolves. LINE's
body is split across LINE-SETUP/LINE-DONE?/LINE-STUCK?/LINE-STEP, each
self-contained within its block, rather than one long definition.

A fourth real bug, serious this time: CIRCLE's first live test rendered
only one quadrant, then hung the VM for several minutes on a follow-up
call. Root cause: q48_to_u64() (include/q48_16.h and
include/starkernel/q48_16.h, backing Q.TO-INT) did an unsigned logical
shift, corrupting any negative Q48.16 value into a huge garbage integer
instead of sign-extending -- inevitable once Q.SIN/Q.COS leave the first
quadrant. That garbage became a bogus LINE target with no bound on
LINE-STEP's Bresenham loop. Fixed q48_to_u64 to shift through a signed
int64_t intermediate (bit-identical for the non-negative case). Also added
LINE-STUCK? (LSTEPS vs FB-WIDTH+FB-HEIGHT, the true worst case for an
on-screen line) as a defense-in-depth cap against any future bad target.

Verified live on amd64 after both fixes: -65536 Q.TO-INT . now prints -1;
LINE/CIRCLE/ARC/ELLIPSE all complete without hanging or erroring, and a
combined screendump shows all four rendering correctly and distinctly.

All three architectures boot clean to ok> with the DoE completing;
dict_hash identical across all three and unchanged from 4.3.3a (expected
-- fabric.4th isn't loaded at boot, and the Q.TO-INT fix doesn't change
dictionary structure).

FABRIC.md item 4.3.3b marked done with full acceptance evidence.
2026-08-07 15:20:58 -04:00
Robert Allan James 01822585f6 logs: add boot-run audit trail and DoE CSVs from item 4.3.3a work
Three-arch acceptance run (amd64/aarch64/riscv64) plus the interactive
Q.SIN/Q.COS verification session.
2026-08-07 13:39:09 -04:00
Robert Allan James 36389e9d4a starkernel: item 4.3.3a -- Q48.16 trigonometry (Q.SIN/Q.COS)
Adds q48_reduce_angle() (range-reduce a signed Q48.16 angle into
[-PI_Q48, PI_Q48] via one integer division plus a bounded fix-up loop) and
q48_sin_approx/q48_cos_approx (Taylor series, terms n=3,5,7,9,11 for sin
and n=2,4,6,8,10 for cos, early exit below 10). Q.SIN/Q.COS registered as
FORTH words in q48_words.c, same pattern as Q.LOG/Q.EXP/Q.SQRT.

Found mid-implementation: this codebase has two independent Q48.16
implementations -- src/word_source/q48_16_words.c (hosted/vendored) and
src/starkernel/math/q48_16.c (kernel-only; the kernel build does not
compile the former at all). The hosted build linked fine after the first
pass; the kernel build failed with undefined references until the same
two functions were added to both .c files and both q48_16.h headers
(include/q48_16.h and include/starkernel/q48_16.h). Not fixed at the root
-- Q.LOG/Q.EXP/Q.SQRT already had this same four-file duplication,
unremarked until now -- just navigated correctly for this item.

Verified live on amd64 via serial injection: sin/cos at 0, +-pi/2, pi, and
3pi (range-reduction across multiple turns) all match expected values
within Taylor-series truncation error (<0.2%).

All three architectures boot clean to ok> with the DoE completing;
dict_hash identical across all three (0x291a660b05fa7b52).

FABRIC.md item 4.3.3a marked done with full acceptance evidence.
2026-08-07 13:39:05 -04:00
Robert Allan James fd1c8ce386 fb/: split into per-ISA subdirectories (amd64/aarch64/riscv64)
Screenshots stay tracked in git, same as before -- just organized so
verification images are easy to find per architecture as Console work
progresses across all three ISAs. scripts/qemu_screenshot.sh (amd64-only)
now writes into fb/amd64/.
2026-08-07 13:23:26 -04:00
Robert Allan James 938f5e87f9 logs: add boot-run audit trail and DoE CSVs from item 4.3.3 work
Three-arch acceptance run (amd64/aarch64/riscv64) plus the interactive
fabric.4th verification sessions used to test CART-PLOT and find the
VARIABLE alignment bug.
2026-08-07 13:21:02 -04:00
Robert Allan James ef9806977a starkernel: item 4.3.3 -- Cartesian coordinate machinery, found and fixed a VARIABLE alignment bug
Adds Module 28 (framebuffer_words.c/.h): PLOT ( x y color -- ), FB-WIDTH,
FB-HEIGHT -- raw hardware-boundary C primitives, kernel-only, no-op on
hosted builds, same pattern as every other module.

Adds capsules/fabric.4th (blocks 4900-4902, mkcapsule --lint clean):
COS45/Z->DELTA/PROJECT/CART-Y/CART-PLOT -- the 45-degree cavalier
orthographic projection and Y-flip, in FORTH per the compose-in-FORTH-first
rule (this is policy, not hardware access).

Found and fixed a second real bug while live-testing CART-PLOT over the
serial socket: defining_word_variable() (defining_words.c) captured
vm->here as a VARIABLE's address with no alignment call first, while
vm_load_cell/vm_store_cell require 8-byte-aligned addresses. This capsule's
VARIABLE ZD landed misaligned (945) purely by chance of what preceded it;
other capsules' variables happened to land aligned by luck, not guarantee.
Real deviation from FORTH-83/ANS, which specifies VARIABLE reserves an
aligned cell. Fixed with vm_align(vm) before capturing addr -- ALIGN
already existed as a word but VARIABLE wasn't calling it.

Verified end-to-end on amd64 via manual serial injection + QEMU screendump:
plotted 4 marker points (origin, +100 X, +100 Y, +50 Z) and confirmed all
landed at hand-calculated raster coordinates, including the diagonal
up-right shift for the Z-axis point -- the projection math is correct, not
just non-crashing. fb/fabric-test-cart-plot.png. 4.3.1's corner diagnostic
still renders correctly in the same shot, confirming no regression.

All three architectures (amd64/aarch64/riscv64) boot clean to ok> with the
DoE completing; dict_hash identical across all three
(0xc7f9adf885e306d2), confirming parity is unaffected.

FABRIC.md item 4.3.3 marked done with full acceptance evidence.
2026-08-07 13:20:52 -04:00
Robert Allan James fbf0625317 FABRIC.md: item 4.3.3 -- settle 45deg cavalier, split off 4.3.3a/4.3.3b
Scoping discussion for 4.3.3 (Cartesian coordinate machinery) surfaced a
larger ask: a full geometry drawing wordset (circles, arcs, ellipses,
radian-based trig), which doesn't exist anywhere in this codebase yet.
Split per the one-task-per-checkbox rule rather than overloading 4.3.3:

- 4.3.3 stays scoped to single-point plumbing: PLOT/FB-WIDTH/FB-HEIGHT (C,
  raw hardware boundary) plus PROJECT/CART-Y/CART-PLOT (FORTH, capsules/
  fabric.4th, blocks 4900+), per compose-in-FORTH-first.
- 4.3.3a (new): Q.SIN/Q.COS in q48_16.c, Taylor series, same pattern as the
  file's existing Q.LOG/Q.EXP/Q.SQRT.
- 4.3.3b (new): LINE/CIRCLE/ARC/ELLIPSE in FORTH, built on 4.3.3 + 4.3.3a.

Also settled: projection angle is true 45 degree cavalier (not 2:1
isometric); "1080p" is a sizing sanity check only, not a hardcoded
constraint (Q48.16's 48 integer bits comfortably cover it).
2026-08-07 11:48:50 -04:00
Robert Allan James ab96ac0970 starkernel: item 4.3.1 -- framebuffer orientation test, found and fixed a real color-swap bug
Adds fb_draw_orientation_test() (framebuffer.c/.h): fills the four raster
corners RED/GREEN/BLUE/YELLOW via fb_fill_rect. Wired into kernel_main.c
calling fb_init() directly -- console_fb_init()/vt100_init() removed from
the boot path, since vt100.c/console.c are superseded by the Console
drawing-fabric redesign (FABRIC.md ss27) and should not be exercised even
incidentally.

The diagnostic caught a real, pre-existing bug on its first run: framebuffer.c's
pack_pixel() had its FB_PIXEL_RGBX32/FB_PIXEL_BGRX32 branches swapped relative
to UEFI GOP's own byte-order naming convention, producing a clean R<->B channel
swap (G unaffected). Spatial placement was already correct -- no flip/rotation.
Fixed by swapping pack_pixel's two return bodies to match framebuffer.h's
already-correct doc comments; kernel_main.c's GOP-format switch needed no change.

Also item 4.3.2 -- QEMU screenshot capability. scripts/qemu_screenshot.sh
already existed (monitor socket + socat + HMP screendump), just unwired and
unused this session. Redirected its PNG output to a new top-level fb/
directory (tracked in git, not logs/, not a gitignored temp dir) and added a
python3+PIL fallback for PPM->PNG conversion since imagemagick isn't
installed here. Left as a standalone script for now, not wired into a
Makefile target.

FABRIC.md items 4.3.1 and 4.3.2 marked done with acceptance evidence.
2026-08-07 11:38:33 -04:00
Robert Allan James f3acfb9b47 FABRIC.md: mark 4.3.x as an open-ended, appendable sublist
Visual cue between 4.3.4 and 4.4 so it's clear at a glance where the
growing 4.3.x Console slice ends and 4.4 begins, without reusing the
document's --- convention (reserved for ## section breaks).
2026-08-07 10:35:17 -04:00
Robert Allan James d4282eb87b FABRIC.md: split 4.3.1 into 4.3.1-4.3.4, one task per checkbox
The prior commit bundled four separate tasks (test pattern, screenshot
capability, coordinate machinery, cube checkpoint) into a single numbered
list inside one checkbox, violating the punch list's own convention of one
task per box. Split into four checkboxes and updated §27 to match.
2026-08-07 10:33:13 -04:00
Robert Allan James f3967fa596 FABRIC.md: item 4.3.1 -- console drawing-fabric groundwork spec (new §27)
Documents the actual hardware-boundary state (framebuffer already wired via
console_fb_init, raw pixel primitives already exist), the missing QEMU
screenshot capability, and the coordinate-system decisions made in
discussion: bottom-left origin, Y-up, Z as depth-into-screen, fixed
orthographic projection as a placeholder (angle still open). Acceptance is
a cube on screen; fonts/scrolling/message-protocol are later 4.3.x items.
2026-08-07 10:31:04 -04:00
Robert Allan James 8bf9480db5 FABRIC.md: item 4.3 -- console discussion held, scope 4.3.1 (framebuffer + coordinate fabric)
CONSOLE.md superseded as a rough prior draft, not edited further. Console
design now lives in FABRIC.md going forward. 4.3.1 is the first slice:
framebuffer sanity check, QEMU screenshot capability, Cartesian/Z coordinate
machinery, and a cube as the first real exercise of the projection math.
2026-08-07 10:27:46 -04:00
Robert Allan JamesandClaude Sonnet 5 7ba7a126c9 logs: add boot-run audit trail and DoE CSVs from item 4.2 work
Serial logs and DoE run CSVs from this session's acceptance-test boots
(the amd64 GOT-indirect-addressing fix and item 4.2's Hermes-native-
on-the-Stadium work) -- committed per the standing convention that
these are audit artifacts, not scratch output.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-07 01:50:45 -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 0a7f144367 amd64: fix GOT-indirect addressing bug in dictionary fast-path lookup
vm_find_word() and dict_find_word_heat_aware() reference the same extern
globals (sf_fc_list/sf_fc_count/sf_fc_cap) but GCC compiled cross-TU
references to them with GOT-indirect addressing (R_X86_64_REX_GOTPCRELX)
under -fPIC. This freestanding, statically-linked UEFI PE image has no
dynamic linker to populate a GOT, so those reads silently returned NULL
instead of the array's real address -- amd64-only, and exquisitely
sensitive to unrelated code-size changes since the choice between direct
and GOT-indirect addressing is a per-call-site GCC heuristic.

Fix: -fno-pic -fno-pie for amd64 only (ARCH_CFLAGS, overriding
COMMON_CFLAGS's -fPIC, which riscv64's -shared loader link still needs).
Also removes -DPLATFORM_TIME_NO_INLINE, a prior one-off workaround for
the identical bug applied to sf_monotonic_ns() specifically, now
redundant. Adds R_X86_64_PC32/R_X86_64_PLT32 handling to
elf_apply_relocations() as a robustness fix for the non-monolithic
split-build path (dead code for the current monolithic boot, where OVMF's
own PE loader relocates the image, not this loader).

Verified: all three architectures boot clean and pass the full item-4.2
Hermes self-test, including MSG-DELIVER-ALL, which previously triggered
the corruption on amd64 only. Write-up in FABRIC.md under item 4.2.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-06 21:18:06 -04:00
Robert Allan JamesandClaude Sonnet 5 56ad128e2e word_source: add LSHIFT/RSHIFT bitwise-shift primitives
No shift primitive existed anywhere in the vendored VM word set. Adds
both as FORTH-83-extension words next to INVERT, guarded against stack
underflow and out-of-range shift counts (u >= 64).

Their absence was masking a real bug: capsules/hermes/init.4th's
CH-MINT-ID (item 4.2) calls LSHIFT to pack a 64-bit channel ID, which
was silently tripping the capsule loader's forward-reference retry
logic and splicing CH-REQUEST's body into CH-MINT-ID's definition.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-06 14:50:05 -04:00
Robert Allan JamesandClaude Sonnet 5 11cd6c8574 FABRIC.md: item 4.2 -- fourth ruling, two more STADIUM-* primitives
Punch list §25 item 4.2 acceptance amended (scoping only, no code).
Found while reading Hermes's actual implementation (capsules/hermes/init.4th):
MSG-COOL-ALL/CH-COOL-ALL mutate each live node's own heat field every
HERMES-TICK, and MSG-TOTAL-HEAT/CH-TOTAL-HEAT sum it -- none of the original
five primitives expose a resident cell's heat at all. Ruled: STADIUM-HEAT@
(read) and STADIUM-HEAT! (write, reconciling the reservoir delta atomically
in C -- same shape as stadium_word_dispatch()'s own cooling code, so
conservation is never left to FORTH to get right). Cooling cadence stays in
Hermes's own HERMES-TICK loop, not a new C-side per-tick sweep -- matches
HERMES.md's language constraint. Primitive surface is now seven, not five;
Done-when updated to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 15:59:05 -04:00
Robert Allan JamesandClaude Sonnet 5 2981ada2a5 starkernel: item 4.1a -- quota granting, Hermes's one-time birth grant
Punch list §25 item 4.1a complete.
New prerequisite item, found while scoping 4.2: no quota-granting mechanism
existed at all. Adds stadium_grant_quota(new_vm_id, from_vm_id) -- a
one-time initial grant at birth, distinct from item 1.3's still-unbuilt
recurring capacity-transfer arbitration. Splits the donor's free list evenly
by cell count, reassigns stadium_owner[] for every moved cell, and grants
the new VM a fresh Q48_ONE reservoir (not a split of the donor's -- per-VM
conservation, same pattern as Hera's own boot grant). Wired into every baby
VM's birth in capsule_birth.c.

Verified via a boot-time self-test in kernel_main.c using a synthetic
identity (not the real UUID pool, not a real capsule birth -- item 0.1's
Hera-alone pruning stays intact). All three architectures booted to ok> with
identical output: grant OK, Hera reservoir=0 (already fully committed to
resident words, correctly unchanged), test-vm reservoir=65536 (fresh
Q48_ONE). dict_hash identical across all three and unchanged from item 4.1's
baseline (0x3d4e1daf289da94f) -- confirms no dictionary word was added.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 15:12:30 -04:00
Robert Allan JamesandClaude Sonnet 5 edfc246579 FABRIC.md: new prerequisite item 4.1a -- quota granting for Hermes's birth
Punch list §25 new item inserted before 4.2 (scoping only, no code).
Found while scoping 4.2: no quota-granting mechanism exists at all --
quota_slot_for_vm() refuses every non-Hera vm_id by design, and both
stadium_admit()'s doc and item 3.2's DONE note defer per-VM free lists to
"when Hera assigns a VM its quota," which nothing builds. Distinguished from
item 1.3's still-open recurring capacity-transfer arbitration: this is
Hermes's one-time initial grant at birth, same shape as Hera's own
whole-pool grant at stadium_boot_init(), not an instance of the open
recurring loop. Ruled: reservoir is not split (Hermes gets her own fresh
Q48_ONE, per-VM conservation per stadium.c's own invariant comment); cell
count is an even split of Hera's current free list, touching no resident
(including pinned cell 0). 4.2 is marked blocked on this item.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 14:49:36 -04:00
Robert Allan JamesandClaude Sonnet 5 4b26be90c3 FABRIC.md: item 4.2 -- third ruling, VMUuid on VM struct (Option A)
Punch list §25 item 4.2 acceptance amended (scoping only, no code).
Found while scoping the C/FORTH primitive layer: all three
stadium_word_dispatch() call sites in vm_core.c hardcode vm_uuid_hera(),
already flagged inline as this item's job, but fixing it needs a running
VM* to know its own identity -- VMUuid exists only on VMRegistryEntry, never
on VM itself. Ruled: add a VMUuid field to VM under __STARKERNEL__ (Option
A), set once at birth from the same VMRegistryEntry.vm_id already assigned,
not threaded through the call chain (Option B). Done-when extended with a
Hermes-word-heat-lands-in-Hermes's-own-reservoir check.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 14:28:24 -04:00
Robert Allan JamesandClaude Sonnet 5 658459b564 FABRIC.md: rule and restate acceptance for item 4.2
Punch list §25 item 4.2 complete (scoping only, no code).
Two rulings taken before implementation starts: the stadium_owner[idx]
fix (§25.7) is explicitly folded into 4.2's scope rather than split out,
since a second VM with a quota is what makes the bug live; the C/FORTH
boundary is a small new kernel-only FORTH primitive layer
(STADIUM-ADMIT/EVICT/RES@/RES-PULL/RES-PUSH), registered like BIRTH/RUN/USE,
the only new C this item may add per HERMES.md's language constraint.
Surfaces two open questions for implementation time rather than resolving
them by assumption: the STADIUM_BEHAVIOUR_DELIVER/EXPIRE mapping for
messages/channels, and whether this dissolves HERMES.md's G8 deferral.
States Done-when acceptance, including the explicit effort-number report
§10 calls for.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 14:11:56 -04:00
Robert Allan JamesandClaude Sonnet 5 3d0b9351bd starkernel: item 4.1 -- hot words onto the Stadium, density-ranked eviction
Punch list §25 item 4.1 complete.
Replaces the round-robin hotwords cache with Stadium density-ranked
admission/eviction on the kernel side, via the §17.7 reservoir mechanism and a
kernel-side word_id -> cell_index map (no DictEntry change, dict_hash
untouched). Adds stadium_birth_hera() to close the cell-0 panic hazard,
STADIUM_WORD_HEAT_QUANTUM/STADIUM_WORD_COOL_RATE_Q48 Kconfig knobs (flagged
untuned), and a stadium_word_forget() FORGET coherence hook to close a
recycled-word_id aliasing gap.

Verified: all five hotwords_cache_* call sites in dictionary_management.c
bypassed under __STARKERNEL__; word dispatch feeds the Stadium at all three
vm_core.c physics_execution_heat_increment() sites; hosted make unaffected;
all three architectures booted to ok> with matching dict_hash
(0x3d4e1daf289da94f) and matching conservation stats (promotions=354
evictions=0, resident_sum=65536 reservoir=0 sum=65536).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 13:37:10 -04:00
Robert Allan JamesandClaude Sonnet 5 bd92c57834 FABRIC.md: unblock and restate acceptance for item 4.1
Records two decisions and restates item 4.1's Done-when criteria now that
§17.7 is DECIDED: word identity maps to a Stadium cell via a kernel-side
word_id -> cell_index table in stadium.c, not a new DictEntry field, keeping
the shared vendored struct and dict_hash untouched; acceptance no longer
leans on the old cache's HotwordsStats (being retired kernel-side, not
extended) but on new Stadium-side counters, the hosted build staying
unaffected, and a three-arch boot with dict_hash matching the pre-4.1
baseline.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 11:29:12 -04:00
Robert Allan JamesandClaude Sonnet 5 c0bbff21c9 FABRIC.md: correct §17.7's stadium_admit() cost claim -- free list already O(1)
3d73b8e described stadium_admit()'s O(N) scans as the typical cost, sourced
from item 3.5's commit note written before item 3.7 landed the per-VM free
lists. Reading stadium.c:312-381 directly: the free-list pop is the primary
path (O(1), no scan), and the fallback scan only triggers once a VM's own
floor is full, scoped to that VM's residents only, never the global array.
Option B is better justified than originally written, not just corrected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 11:18:22 -04:00
Robert Allan JamesandClaude Sonnet 5 3d73b8e12a FABRIC.md: close §17.7 -- reservoir-based O(1) word heat transfer, DECIDED
Resolves the last open question: vm_physics_touch()'s O(n) proportional
fan-out (justified there only by rare fleet touches) isn't viable at word
dispatch frequency. Instead each VM's inner Stadium gets one reservoir
scalar; touch/cool/evict are all O(1) two-party transfers against it,
mirroring Hera's structural role at the fleet level rather than
peer-to-peer redistribution. Admission is the starter grant itself
(Option B): execution_heat stays fully inert in kernel builds, matching
the one-governor rule already committed, at the cost of running
stadium_admit()'s existing O(N) scan on every cold-word dispatch until
item 3.5's free list lands -- accepted debt, not a new gap. Corrects
abb1d92's invariant wording: residents sum to less than Q48_ONE, with
the reservoir holding the remainder.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 10:53:47 -04:00
Robert Allan JamesandClaude Sonnet 5 abb1d92190 FABRIC.md: resolve four of five §17.7 questions -- Stadium heat already conserved
The Stadium cell's heat field (stadium.h:64, item 3.1) was already declared
Q48.16 conserved share of 1.0, citing §19.1, before this section was reopened.
L0 already has a real conservation mechanism (items 3.4/3.5 consume it); it
has just never been fed. execution_heat and Stadium heat are two different
fields with two different jobs -- item 4.1 feeds the second, not converts the
first. Resolves the promotion-threshold, dict_hash-baseline, parity, and
new-loop questions on that basis; states an explicit one-governor-per-build
rule mirroring §17.3. The per-dispatch transfer rule and its cost remain
open and still block item 4.1.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 10:35:05 -04:00
Robert Allan JamesandClaude Sonnet 5 6d3d6e5ea6 FABRIC.md: draft §17.7 -- word-level heat conservation, LEANING, blocks item 4.1
Captain Bob rejected the simple q48_from_u64() scale for converting
execution_heat into the Stadium's heat field, wanting real conservation
for word heat mirroring §19.1's fleet invariant. That conflicts with the
existing absolute promotion threshold (execution_heat > 50) and moves
dict_hash, both load-bearing. Paper only -- item 4.1 does not resume
until this section reads DECIDED.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 20:32:27 -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 ec2c97ef70 FABRIC.md: resolve §17.3 -- words onto the Stadium, hosted/kernel split
Elevates §17.3 from LEANING to DECIDED, settled on paper before item
4.1's code per Captain Bob's request. The core claim (replace
round-robin eviction with Stadium density ranking) already argued for
itself in this section; what was missing was the hosted/kernel split
§25.5's "never two live heat mechanisms at once" implied but never
resolved.

Grounded in code, not policy: dictionary_management.c calls
hotwords_cache_lookup()/evict_*() unconditionally in the word-lookup
path (not gated by ENABLE_HOTWORDS_CACHE at the call sites), and that
file is vendored shared source required to work in both hosted and
kernel builds. The Stadium is kernel-only by construction of
everything built through item 3.7. ENABLE_HOTWORDS_CACHE already
defaults off in both Makefiles today (Kconfig.physics, verified
against both Makefiles directly).

Resolution: kernel builds retire the old cache's effect once item 4.1
lands (Stadium takes over, old call sites bypassed under
__STARKERNEL__ regardless of the Kconfig setting); hosted builds are
unchanged, no Stadium is built for them. Item 4.1 decides the exact
bypass mechanism, not invented here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 18:13:54 -04:00
Robert Allan JamesandClaude Sonnet 5 e55111c2c5 starkernel: item 3.7 -- per-VM free lists (Phase 3 core complete, for real)
Punch list §25 item 3.7 complete. Added to §25.4 after starting item
4.1 surfaced it as an unbuilt prerequisite -- 3.6's earlier "Phase 3
core complete" claim is corrected in this same commit.

StadiumVMQuota table (size STADIUM_MAX_VM_COUNT, linearly searched by
vm_id -- capsule_birth.c's vm_id is monotonic and never reused, so it
cannot index a table directly, and a 4-entry scan costs nothing). New
per-cell stadium_owner byte array records which quota a cell belongs
to, needed so eviction returns a freed cell to the correct VM's list
and so eviction search stays scoped to the evicting VM's own residents
(quota isolation).

Free-list linkage reuses each cell's `link` field as a next-free
pointer while unresident -- link is documented only as generic "index
into the Stadium, not a pointer," so this is a repurposing, not a
header change. Does not answer the separate, still-open question of
which field carries a multi-cell patron's first continuation-cell
index; item 3.5's mass != 1 refusal stands exactly as it was.

Boot-time: every cell chained into one list in ascending index order,
granted whole to vm_id 0 (Hera), the only VM that exists. Ascending
order preserves item 3.6's "Hera is patron zero" invariant once real
birth-wiring lands.

stadium_admit()'s signature changed to take vm_id -- a change to code
shipped in item 3.5, amended there. Pops the calling VM's free-list
head first (O(1)); only falls back to a same-VM-scoped eviction search
if empty.

Caught a real bug before the boot run: the header zero-fill on
eviction (and the initial free-list build) both left contains == 0,
but 0 is Hera's valid index -- the same collision item 3.1's
STADIUM_CONTAINS_NONE fix addressed, recurring at a new site. Fixed by
explicitly setting contains = STADIUM_CONTAINS_NONE at both free-list
sites.

Explicitly out of scope, reported not invented: granting quota to any
VM other than Hera is capacity arbitration (item 1.3 left "how much
moves per transfer" open). stadium_owner is set once at boot and never
rewritten, so quota_slot_for_vm() refuses every vm_id != 0 permanently
until item 4.2 adds the grant path and owner-array writes.

Verified: three-architecture boot (amd64, aarch64, riscv64), all
reaching ok> with identical dict_hash=0x3d4e1daf289da94f matching the
item-3.6 baseline.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 18:09:34 -04:00
Robert Allan JamesandClaude Sonnet 5 72487e7fff starkernel: item 3.6 -- Hera as patron zero, pinned (Phase 3 core complete)
Punch list §25 item 3.6 complete. Phase 3 (§25.4) core is now done:
items 3.1-3.6 all closed.

stadium_evict() now panics via sk_hal_panic() if a resident cell 0
(Hera, patron zero by construction of §6's boot order) is ever
selected for eviction. Placement is deliberate: the check runs before
the pin/contains refusal checks, not after -- if it ran after, a
wrongly-cleared pin would let the ordinary refusal path quietly return
-1 instead of ever reaching the panic, defeating the point of a check
that's supposed to be independent of pin holding.

Per §20.5 #3's explicit wording, not implemented as a filter:
stadium_admit()'s least-dense search is unchanged, still relying on
the general pin skip from item 3.5. Adding a second filter there would
have done exactly what that section warns against ("filtering hides
the bug, asserting reports it").

The panic path is, and will remain, unexercised by the acceptance
mechanism: sk_hal_panic() halts the machine, and triggering it
deliberately is incompatible with the three-arch boot being this
project's sole acceptance test. Correctness rests on the placement
argument, not a test -- same honesty precedent as items 3.4 and 3.5's
other unexercised paths.

Verified: three-architecture boot (amd64, aarch64, riscv64), all
reaching ok> with identical dict_hash=0x3d4e1daf289da94f matching the
item-3.5 baseline.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 17:36:23 -04:00
Robert Allan JamesandClaude Sonnet 5 f8a50561b0 starkernel: item 3.5 -- admission and eviction
Punch list §25 item 3.5 complete.

stadium_admit(candidate) places into an unused cell if one exists (no
comparison needed), otherwise finds the least-dense resident -- skipping
pinned and contains-gated patrons, which are never eviction candidates
-- and evicts it only if the candidate is strictly denser, per §19.3.
stadium_evict(cell_index) dispatches the departing patron's behaviour
before clearing its slot, per §17.2.

Caught a real bug before it ran: the first draft used contains == 0 to
mean "holds nothing," but cell index 0 is a valid index (Hera, item
3.6). Fixed with a proper sentinel, STADIUM_CONTAINS_NONE (UINT32_MAX).

A second-pass review found mass was not accounted for: both functions
handled exactly one cell regardless of the candidate's stated mass,
which leaks cells on eviction of any mass > 1 patron and breaks
capacity conservation. Fixed by refusing any candidate with mass != 1
-- multi-cell patrons need the per-VM free lists item 3.2 already
deferred (§22.3), not built here.

Documented, not fixed: the discriminator bitmap can't distinguish free
from continuation cells, so the free-cell scan reads continuation-cell
payload bytes under the header layout -- latent since nothing creates
continuation cells yet, and the mass != 1 refusal keeps it provably
latent. Superseded by the free list when it exists.

Unexercised at runtime: nothing calls either function yet (no real
patron kind is wired to the Stadium). No self-test added -- filling
~74,000+ cells to reach the eviction-on-full branch was judged
impractical, following item 2.2's own precedent for its unexercised
fleet-full path.

Verified: three-architecture boot (amd64, aarch64, riscv64), all
reaching ok> with identical dict_hash=0x3d4e1daf289da94f matching the
item-3.4 baseline.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 17:28:38 -04:00
Robert Allan JamesandClaude Sonnet 5 0b47c256fc starkernel: item 3.4 -- density ranking
Punch list §25 item 3.4 complete.

stadium_density(cell_index) reads a header's heat and mass and returns
heat / mass -- a division on demand from fields already stored in the
cell, matching §19.3's "read, not computed by a scheduler" literally.
Stays valid Q48.16 without a special fixed-point routine, since heat
is already Q48.16 and mass is a plain integer divisor.

mass == 0 and an out-of-range cell_index both return 0 rather than
dividing by zero -- an empty or never-admitted slot has no footprint
to be dense within.

Deliberately not built here, per the item's own wording: finding the
densest or least-dense resident (§19.3's admission/eviction
comparison) is item 3.5's scope, not this one's. Nothing calls
stadium_density() yet either.

Verified: three-architecture boot (amd64, aarch64, riscv64), all
reaching ok> with identical dict_hash=0x3d4e1daf289da94f matching the
item-3.3 baseline.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 17:16:46 -04:00
Robert Allan JamesandClaude Sonnet 5 378d688898 starkernel: item 3.3 -- behaviour enumeration and dispatch
Punch list §25 item 3.3 complete.

StadiumBehaviour (stadium.h) enumerates exactly the four tags §18.3
already names -- MIGRATE, DELIVER, EXPIRE, COOL -- mapped from §17.1's
patron table: blocks->MIGRATE, messages->DELIVER, ACLs->EXPIRE, words
and VMs both->COOL. Nothing invented; the tag set and mapping were
already in the document.

stadium_dispatch(cell_index, behaviour) dispatches on the tag only,
never asks what kind of patron departed. Handlers are stubs -- the
real actions belong to subsystems not yet migrated onto the Stadium
(Phase 4). Nothing calls stadium_dispatch() yet; item 3.5 is its first
consumer.

The switch is exhaustive with no default case, making §13's "closed
enumeration, fixed at build time" a compiler-enforced property under
this project's -Wall -Werror rather than just prose. Verified live:
temporarily deleted the COOL case, rebuild failed with
error: enumeration value 'STADIUM_BEHAVIOUR_COOL' not handled in
switch [-Werror=switch], restored it, confirmed clean again.

The header's behaviour field stays uint8_t, not the enum type itself,
since C does not guarantee an enum's underlying type and that field's
offset is load-bearing for item 3.1's validated 64-byte layout.

Verified: three-architecture boot (amd64, aarch64, riscv64), all
reaching ok> with identical dict_hash=0x3d4e1daf289da94f matching the
item-3.2 baseline.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 17:06:14 -04:00
Robert Allan JamesandClaude Sonnet 5 eb0fd4fffa starkernel: item 3.2 -- Stadium boot-time allocation
Punch list §25 item 3.2 complete.

stadium_boot_init() (src/starkernel/vm/stadium.c) sizes the global
cell array at boot from a real memory-budget query rather than a
hardcoded count: pmm_get_stats().free_bytes at the point of
allocation, times the new STADIUM_MEMORY_PERCENT Kconfig symbol
(default 1%), rounded down to whole 64-byte cells. Matches §17.6's
position (b) literally. Also allocates the header/continuation
discriminator bitmap item 3.1 declared but did not allocate. Both are
kmalloc'd and explicitly zero-filled (kmalloc does not zero).

Called from kernel_main.c immediately before sk_vm_bootstrap_parity(),
i.e. before any VM exists (§6). Failure is soft -- logs and continues,
does not halt boot -- matching the existing precedent one line below
it (VM bootstrap parity failure does the same).

Added a "Stadium: N cells (M KB)" boot console line at the allocation
site so the acceptance logs are evidence the array was actually
allocated, not just that the kernel still boots -- the same blind spot
item 3.1's uncompiled-header gap exposed.

Verified: three-architecture boot (amd64, aarch64, riscv64), all
reaching ok> with identical dict_hash=0x3d4e1daf289da94f matching the
item-3.1 baseline, and the Stadium boot line confirmed present in all
three serial logs (amd64: 74234 cells/4639 KB, aarch64: 161329
cells/10083 KB, riscv64: 76122 cells/4757 KB).

Not built here, reported per §25.0 rule 3: per-VM free lists (§22.3)
-- granted when Hera assigns quota, not this item's scope.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 16:48:13 -04:00
Robert Allan JamesandClaude Sonnet 5 1b2f0677de starkernel: item 3.1 reopened -- two Kconfig symbols items 1.1/1.4 deferred here
Punch list §25 item 3.1 re-closed after reopening.

Items 1.1 and 1.4's resolutions both explicitly named this item as
where their Kconfig symbols would be implemented, but 3.1's own stated
scope never mentioned them, so the first close missed both:

- STADIUM_CONTAINS_DEPTH_MAX (default 5) -- item 1.1's contains-chain
  depth cap. No consumer yet; reap-gating enforcement is item 3.5.
- STADIUM_CAPACITY_TICK (default 1000) -- item 1.4's capacity
  arbitration cadence in virtual ticks. No consumer yet; capacity
  arbitration itself is not on the punch list.

Both added following STADIUM_MAX_VM_COUNT's exact pattern:
Kconfig.kernel entry, Makefile.starkernel kconfig_int +
VM_FEATURE_FLAG_VARS forwarding, starforth_config.h fallback default.
stadium.h now includes starforth_config.h and carries two more
C99-portable compile-time checks proving both symbols are defined and
sane, same discipline as the byte-count checks. Declaration only --
not inventing the consuming logic to close this out early.

Verified: three-architecture boot (amd64, aarch64, riscv64), all
reaching ok> with identical dict_hash=0x3d4e1daf289da94f, re-run after
the reopening.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 15:15:59 -04:00
Robert Allan JamesandClaude Sonnet 5 6da9373e74 FABRIC.md: note-to-self on the fleet heat truncation leak before 5.1
Punch list §25.7 addition, not a punch list item.

Captain Bob flagged the existing "fleet heat leaks on every multi-VM
touch" entry (§20.2/§25.7) as worth a pinned reminder: it's currently
invisible because Tripod is pruned to Hera alone (item 0.1), so
others_total is always 0 and the leaky division path never executes.
It becomes reachable, and measurable, only once Phase 4 restores
Hermes/Artemis -- so a clean item-5.1 DoE re-run before that point
would be a false negative on VM-CONSERVED?, not evidence the
conservation law holds. Still reported-not-scheduled on purpose.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 14:59:08 -04:00
Robert Allan JamesandClaude Sonnet 5 d55ec3241b starkernel: item 3.1 -- the Stadium cell and header
Punch list §25 item 3.1 complete.

Defines StadiumPatronHeader and StadiumContinuationCell in new
include/starkernel/vm/stadium.h, unioned as StadiumCell per §3's
closed two-valued union. src/starkernel/vm/stadium.c added to
Makefile.starkernel's LOADER_EXTRA_SRCS/KERNEL_EXTRA_SRCS so the
header's compile-time size checks are actually compiled, not merely
included by something that never builds.

Discriminator ruled an external side bitmap (Captain Bob), not a
header field -- amended into §3 and §23.3 before this code was
written. Item 3.1 declares the bitmap's purpose/indexing in a comment
only; allocating it is item 3.2's scope.

Both cell shapes counted for real at exactly 64 bytes with zero
compiler-inserted padding (three C99-portable negative-array-size
assertions -- no _Static_assert, this project targets C99). Header
matches §23.3's original 32+32 split unchanged, since the
discriminator moving outside the cell left nothing to compete for that
space. Continuation cell matches item 1.12's 4+60 figure unchanged for
the same reason.

Verified the size assertion is actually live: broke it to 63,
confirmed the build failed with the expected negative-array-size
error, restored it, confirmed a clean compile.

Verified: three-architecture boot (amd64, aarch64, riscv64), all
reaching ok> with identical dict_hash=0x3d4e1daf289da94f matching the
item-2.2 baseline. Confirmed stadium.o present in both obj/loader/vm
and obj/kernel/vm post-build on amd64, closing the gap the item-2.2 WIP
exposed (an uncompiled header proves nothing).

Left open, not fabricated: §23.4 #2 ("does a typical message fit in
one cell") is unanswerable today -- no message patron struct exists
anywhere in this tree yet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 14:55:30 -04:00
Robert Allan JamesandClaude Sonnet 5 05c9eba360 FABRIC.md: log taxonomy/lexicon as reported-not-scheduled
Punch list §25.7 addition, not a punch list item.

Captain Bob flagged mid-item-3.1 that the physics-flavored vocabulary
(heat, mass, density, patron, Stadium) needs a glossary making explicit
these are named analogies, not physical claims -- and that the growing
set of Kconfig build knobs needs the same terminology discipline so
code, Kconfig help text, and this document don't drift apart. Captured
per §25.7's rule: not fixed, not assigned, becomes an item only if
Captain Bob says so.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 14:38:35 -04:00
Robert Allan JamesandClaude Sonnet 5 b0416794de starkernel: item 2.2 complete -- bound the VM registry
Wires the birth-refusal check into capsule_birth_baby(): calls
vm_registry_live_count() (added in the prior WIP commit) between
capsule validation and vm_registry_alloc(), returning the new
CAPSULE_RUN_ERR_FLEET_FULL and logging via
capsule_parity_log_birth_failed() before any EMBRYO registry slot is
consumed.

Also fixes a gap in that WIP commit: STADIUM_MAX_VM_COUNT was only
ever defined via a Kconfig .config-driven -D flag, with no fallback
default the way every sibling knob in starforth_config.h has -- a
build with no .config present (this one) failed with the macro
undeclared. Added STARFORTH_CONFIG_STADIUM_MAX_VM_COUNT_DEFAULT (4,
matching Kconfig.kernel) following the existing HEARTBEAT_INFERENCE_FREQUENCY
pattern exactly.

All three architectures boot clean to ok> with dict_hash=0x3d4e1daf289da94f,
matching the item-0.10/2.1 baseline. FABRIC.md item 2.2 checked off.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 13:54:22 -04:00
Robert Allan JamesandClaude Sonnet 5 5572e5e429 WIP: item 2.2 -- bound the VM registry (not complete, do not check off)
Partial work toward FABRIC.md punch list item 2.2. Adds the
STADIUM_MAX_VM_COUNT Kconfig symbol (default 4, per item 1.5) wired
through Makefile.starkernel, a new CAPSULE_RUN_ERR_FLEET_FULL result
code, and a vm_registry_live_count() helper in capsule_birth.c that
counts LIVE VMs only (distinct from the existing monotonic
vm_registry_count, which never decrements on death).

NOT YET DONE: nothing calls vm_registry_live_count() yet -- the actual
birth-refusal check is not wired into capsule_birth_baby(). Not built,
not boot-tested. FABRIC.md's item 2.2 checkbox is deliberately left
unchecked; this commit exists only to save in-progress work before a
pause, not to claim the item complete.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 13:30:06 -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 JamesandClaude Sonnet 5 f2eb877691 FABRIC.md: resolve items 1.8, 1.9, 1.10, 1.12
Punch list §25 items 1.8, 1.9, 1.10, 1.12 complete.

1.8 -- block content hash is computed once, at the migration boundary,
as part of the MIGRATE code field; audit retention of the old hash is
an Artemis-layer question, out of scope for the Stadium (§5).

1.9 -- confirmed factually (not decided): vm_dict_resolve_in_bucket()
keeps both old and new definitions resident with newest-wins shadowing,
no GC on redefinition.

1.10 -- closed as no. Identity elision either reintroduces the
per-kind branch §18.3 forbids, or breaks lookups (name, LBN) that
words and blocks already depend on outside the Stadium. Stays a fixed,
always-present 8-byte field for every kind.

1.12 -- linked, not contiguous. Forced by §22.3's already-decided
disjoint per-VM free list, which gives no adjacency guarantee.
4-byte next-index, 60 usable bytes per continuation cell, 18
continuation cells for a 1024-byte block. Completes §23.3's sizing
table and unblocks item 3.1.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 10:42:33 -04:00
Robert Allan JamesandClaude Sonnet 5 4fd9420ecf FABRIC.md: resolve item 1.7 -- VM-Stadium nesting depth is Kconfig-tunable, default 2
Punch list §25 item 1.7 complete.
Bounded rather than permanently prohibited, consistent with item 1.1's
treatment of containment depth. Default 2 matches §21's already-decided
two-level structure; enforced by explicit refusal at VM-birth time if a
birth would exceed the configured depth.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 10:35:36 -04:00
Robert Allan JamesandClaude Sonnet 5 015333e8b8 FABRIC.md: resolve item 1.6 -- a VM's mass is its allocated share
Punch list §25 item 1.6 complete.
Confirms §20.4's proposal as decided: mass = the quota Hera allocated,
not a fixed one-cell footprint. Already implied by §22's elasticity
mechanism and §24.3, and already assumed by items 1.2-1.5 -- this
closes the formal "proposal" flag to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 10:21:15 -04:00
Robert Allan JamesandClaude Sonnet 5 729bfd8bda FABRIC.md: resolve item 1.5 -- outer VM bound is 4, tunable, birth refused at cap
Punch list §25 item 1.5 complete.
Bound defaults to 4 (Tripod's known topology), Kconfig-tunable,
explicitly a placeholder pending a later DoE campaign for an idealized
default. Fixed for the machine's lifetime once set at build. Corrects
the document's own "coldest-VM-reaped is consistent with §19.3" claim,
which doesn't survive §20.2's cold-start birth rule -- birth is
refused at the bound instead, and reaping stays Hera's deliberate act.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 09:32:08 -04:00
Robert Allan JamesandClaude Sonnet 5 69f71035be FABRIC.md: resolve item 1.4 -- capacity-tick ratio is 1000:1
Punch list §25 item 1.4 complete.
Capacity-tick gets its own named constant, defaulted to 1000 virtual
ticks -- grounded in the existing HEARTBEAT_INFERENCE_FREQUENCY
precedent at capsule_vm_physics.c:434-441 rather than an invented
number, well past §12 Q5's order-of-magnitude minimum.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 09:17:08 -04:00
Robert Allan JamesandClaude Sonnet 5 e83b404f66 FABRIC.md: resolve item 1.3 -- capacity-transfer trigger is comparison, not threshold
Punch list §25 item 1.3 complete.
Cadence (capacity-tick, a coarser multiple of the virtual tick) carries
the "sustained density" requirement; the decision itself is a pure
least-dense/densest comparison, no tuned threshold. Transfer amount per
event is reported as unresolved, not invented -- out of this item's scope.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 08:16:04 -04:00
Robert Allan JamesandClaude Sonnet 5 9f8bd2a13d FABRIC.md: resolve item 1.2 -- resting floor is derived, not tuned
Punch list §25 item 1.2 complete.
Floor = max(mass of pinned patrons, one message-sized cell); both terms
derived rather than a tuned constant, closing the zero-pinned-patrons
reachability deadlock the first term alone would leave open.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 08:07:23 -04:00
Robert Allan JamesandClaude Sonnet 5 f81a53cbf6 FABRIC.md: resolve item 1.1 -- containment, not a lock
Punch list §25 item 1.1 complete.
Adds the ninth wire `contains` to §3, resolves §8's open exclusivity
question as containment with a gated reap rule, bounds chain depth at
5 (Kconfig-tunable), and notes the distinction from item 1.7's separate
VM-tree-recursion question.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 08:02:52 -04:00
Robert Allan James daf8f82f3b Add boot log from RISC-V QEMU run (20260804-001614)
Includes system diagnostics and comprehensive test logs for stack and arithmetic words in the LithosAnanke kernel.

Signed-off-by: Robert Allan James <robert.allan.james@gmail.com>
2026-08-04 07:28:30 -04:00
Robert Allan JamesandClaude Sonnet 5 024f8a84b5 starkernel: Phase 0 acceptance -- three-arch boot + reproducibility (item 0.10)
Adds a one-time boot diagnostic in kernel_main.c, right before sk_repl()
is entered: bounded wait for 3 real heartbeat ticks, then prints tick
count, TIME-TRUST, and variance. Needed because printing immediately
after apic_timer_start() (as first tried) measured 1 tick on amd64 and
0 on riscv64 -- not evidence the heartbeat doesn't work, just that
almost no wall time elapses between arming the timer and that point in
boot; report it honestly rather than let it stand as a false negative.

Verified this session (logs/20260804-001727, -001805, -001850,
-001948, -002021):
- All three architectures boot to ok>.
- Tick count non-zero: amd64 4, riscv64 3, aarch64 3.
- riscv64: trust=Q48_ONE exactly, variance=0 -- architecturally
  invariant counter, as designed.
- amd64: dict_hash=0x3d4e1daf289da94f, identical to the pre-item-0.8
  baseline (logs/20260803-231322) -- unchanged output, satisfying the
  GAP-A1 control.
- Two consecutive amd64 boots produced the identical dict hash --
  reproducible, no wall-clock leakage into patron state.

Phase 0 (Substrate) is complete.

Punch list §25 item 0.10 complete.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 00:21:23 -04:00
Robert Allan JamesandClaude Sonnet 5 bfc0cb9bfd starkernel: document the mutex stub's concurrency constraint (item 0.9)
sf_mutex_lock()'s no-op is correct only while nothing in interrupt
context mutates the structure a given lock protects. States that
explicitly, and states why a real spinlock is the wrong fix if that
constraint is ever violated: a single hart would deadlock spinning
against its own interrupt handler. The correct fix in that case is
disabling interrupts around the critical section.

Documentation only, no behaviour change.

Punch list §25 item 0.9 complete.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 00:12:56 -04:00
Robert Allan JamesandClaude Sonnet 5 3699be964d starkernel: converge the tick path and wire the adaptive heartbeat (item 0.8)
Introduces src/starkernel/heartbeat.c as the shared top/bottom-half
implementation of heartbeat_init/tick/service/ticks/trust/state, replacing
the per-architecture duplicates in amd64/riscv64/aarch64 timer.c. Each
arch's timer.c now contributes only heartbeat_read_counter() (rdtsc /
rdtime / CNTPCT_EL0). Per the GAP-A1 ruling the top half stays counter+
latch only; heartbeat_service() (called every REPL idle iteration,
unconditionally per FABRIC.md's fidelity note) does the window/variance/
trust work outside interrupt context. vm_tick()'s call sites are
unchanged -- the engine still runs on the virtual tick.

Per FABRIC.md §26 (ruled 2026-08-03): wires Loop #7's execution-derived
stable/volatile signal into the physical re-arm period. vm_runtime.c's
existing Loop #7 site now calls heartbeat_set_adaptive_period_ns() with
tick_target_ns ratio-rescaled onto a 10ms kernel base (not the hosted
10us HEARTBEAT_TICK_NS -- see §26.3 for the scale mismatch). Each
architecture's re-arm function (apic_timer_rearm() on amd64/aarch64,
riscv64_timer_rearm()) now converts heartbeat_next_period_ns() to its
own raw counter units instead of a fixed constant; amd64 gained a
rearm function it didn't previously need, since periodic-mode auto-reload
never required one before this item.

Verified: all three architectures build with no new warnings and boot
cleanly to ok> with dict_hash=0x3d4e1daf289da94f, unchanged from the
pre-change baseline -- no regression. Verified NOT achieved: live re-arm
period variation under load. A temporary diagnostic (added and reverted)
confirmed Loop #7 never actually fired during a live QEMU session -- a
synthetic word-execution loop drove ~6,500 executions, past the 1000-tick
inference frequency, without tripping vm_tick_inference_engine()'s
pre-existing !vm->rolling_window.is_warm gate. That gate predates this
item and was not investigated -- out of scope. FABRIC.md's Done-when is
amended to record this honestly rather than claim it.

Punch list §25 item 0.8 complete (per amended, weaker acceptance -- see
the item's own annotation).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-04 00:01:48 -04:00
Robert Allan JamesandClaude Sonnet 5 da4cb14702 FABRIC.md §26: rule the physical heartbeat must be adaptive, not fixed-rate
Loop #7 (vm_runtime.c:703-752) already computes an execution-derived adaptive
period and it is orphaned -- nothing in the kernel build ever reads
tick_target_ns. Traces the pthread-based consumer it was written for
(deliberately disabled for kernel builds, no pthread on bare metal) and
confirms via the §18.5 argument that consuming it via the physical re-arm
period does not reopen GAP-A1, since the decision to change the period stays
execution-derived and the virtual tick still owns all patron state.

Rules the scale mismatch (10us hosted base vs 10ms/100Hz kernel base) in
favor of reusing Loop #7's decision logic against the kernel-appropriate
base, and the mechanism (single-writer/single-reader shared state, no
thread/scheduler) against §21.1's existing free-locking finding.

Amends item 0.8 to include this -- no new punch-list item, this is inside
what 0.8 already builds.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-03 23:04:09 -04:00
Robert Allan JamesandClaude Sonnet 5 43aa0a2a36 aarch64: arm the ARM Generic Timer -- CNTP/CNTHP TVAL+CTL, per-tick re-arm
Punch list §25 item 0.7 complete. This is what finally makes items 0.5 and
0.6 provably work end to end.

apic_timer_start(): writes CNTP_TVAL_EL0 (or CNTHP_TVAL_EL2 at EL2 --
aarch64_current_el(), same EL-aware discipline as 0.4-0.6) to
s_timer_period_tsc, then CTL.ENABLE=1/IMASK=0, followed by an ISB. The ISB
is not decorative: confirmed against Linux's own arch_timer_reg_write_cp15()
(arch/arm64/include/asm/arch_timer.h) that only the *control* register write
needs synchronising before the enable/mask state is guaranteed visible to
the interrupt pipeline -- TVAL/CVAL writes do not carry the same
requirement, which is why apic_timer_rearm() omits it.

TVAL is architecturally 32-bit but MSR-to-system-register is always a
64-bit instruction form -- passing a uint32_t operand directly failed to
build (-Wasm-operand-widths). Fixed by truncating to 32 bits ourselves then
zero-extending back to 64 for the operand, which supplies explicit,
provably-correct zeros in the RES0 upper field rather than depending on
unverified hardware behaviour -- Linux's own driver never exercises this
path (it always uses the 64-bit CVAL form instead), so there was no local
source to confirm the alternative against.

apic_timer_rearm() (new): re-writes TVAL only, no ISB needed. TVAL is
relative to "now," not an absolute deadline like riscv64's SBI interface
(item 0.3), so there is no drift-correction bookkeeping -- each write means
"N ticks from this instant." Wired into aarch64_irq_handler() and called
*first*, before heartbeat_tick(), matching riscv64_timer_rearm()'s ordering
discipline exactly: the ARM Generic Timer does not auto-reload, so a return
path that skips this leaves the interrupt condition latched, which the GIC
would redeliver the instant it's EOI'd -- a real storm, the same class of
failure item 0.6's verification investigated (and that time found absent,
because nothing was armed yet).

Verified: builds clean; every generated instruction checked against
disassembly, not just reviewed by eye (both EL branches, correct TVAL/CTL
register names, single shared ISB in apic_timer_start(), no ISB in
apic_timer_rearm()). Boots to ok> with no regression, dict_hash
0x3d4e1daf289da94f unchanged.

Rate measured directly against real wall-clock time via QEMU's own -d int
trap trace (same method as riscv64's item 0.3), two independent windows:
1,090 interrupts over 11.05 s (98.679 Hz) and 4,031 over 40.88 s (98.614 Hz)
-- consistent across both, so this is a real, small, systematic bias
(~1.3-1.4% slow), not measurement noise from polling granularity, which
would have shrunk with the longer window and did not. Attributed to genuine
per-interrupt service latency: TVAL is rewritten mid-ISR, so the trampoline
save/restore, GICC_IAR read, EL branch and GICC_EOIR write all lengthen the
effective period slightly versus the nominal 10 ms, inherent to any
relative-countdown re-arm scheme. Reported as measured, not smoothed over.
The interrupt sustained continuously across both windows with no stall and
no storm, which is the primary evidence re-arm-every-tick is correct;
the small rate bias is overhead, not a defect.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-03 22:14:52 -04:00
Robert Allan JamesandClaude Sonnet 5 cabb0e8bd4 aarch64: minimal GICv2 driver -- distributor, CPU interface, timer PPI
Punch list §25 item 0.6 complete.

Ruling applied (AskUserQuestion, this session): DTB is confirmed unreachable
on this system's aarch64 firmware too (qemu-efi-aarch64 2025.11-3ubuntu7,
same finding as riscv64's item 0.3), so GICD/GICC base addresses and the
timer PPI are named QEMU-virt constants with a recorded caveat, not
DTB-discovered as the item originally asked.

Nothing here was recalled from memory. Base addresses (GICD 0x08000000,
GICC 0x08010000) and the timer PPI (30, non-secure EL1 physical) were read
out of QEMU 10.2.1's own internal devicetree via
`qemu-system-aarch64 -machine virt,dumpdtb=...`, decoded with this tree's own
fdt.c reader rather than a new tool -- correct for this exact QEMU version,
not assumed stable across others. Bonus finding from the same dump: PPI 26
for the EL2 hypervisor timer, which item 0.7 will need for its EL2 path.
Register offsets within each block (GICD_CTLR, GICC_IAR, etc.) are GICv2
architectural constants, not board-specific, and were cross-checked against
Linux's own arm-gic.h driver header rather than recalled either.

Acceptance amended before implementing (§25.0 "when an item is genuinely
wrong"): the original text required observing a delivered-and-acknowledged
timer interrupt, which cannot happen within this item's own scope --
apic_timer_start() (item 0.7) is still the no-op stub, so nothing arms the
timer. This is the same defect the earlier review's C2 fix already applied to
items 0.2 and 0.5; it was missed here. Acceptance is now: GIC initialises
without fault, the IAR/EOIR path is wired into aarch64_irq_handler() and
ready, boots with no regression -- item 0.7's tick-advance is what proves
delivery, exactly as 0.5 already defers to 0.7.

EL-aware (B3, same discipline as items 0.4/0.5): apic_init() selects PPI 30
or 26 from aarch64_current_el(), decided once and cached, not re-derived per
interrupt.

aarch64_irq_handler() now does real work: reads GICC_IAR (the GICv2
acknowledgement step), dispatches to heartbeat_tick() when the INTID matches
the timer PPI, and always completes with GICC_EOIR (INTID 1023 = spurious
handled per the GICv2 spec, not as a special case of "unrecognised"). This
mirrors exactly how riscv64's item 0.2 built full cause-dispatch logic before
its timer was armed in 0.3.

Investigated and resolved a real scare during verification: QEMU's `-d int`
trace showed 1,728 "Taking exception 5 [IRQ]" events by the time boot reached
the prompt, which looked exactly like an interrupt storm (hypothesis: EDK2
firmware leaves CNTP_CTL_EL0 enabled with a stale comparator, and enabling
the GIC path exposes it before item 0.7 reprograms the timer). A direct
one-shot probe inside aarch64_irq_handler() itself -- ground truth for
whether this code path runs at all -- fired zero times across a clean,
bounded boot. The trace events were almost certainly from EDK2 firmware's
own internal timer usage during its own boot phase, before control passes to
this kernel; the earlier conclusion was drawn from the external trace alone
without checking that distinction, and the probe (not the trace) is what
settled it. Probe code fully reverted; not part of the commit.

Verified: builds clean, boots to ok> with no regression, dict_hash
0x3d4e1daf289da94f unchanged from the item 0.1-0.5 baseline, EL banner and
IDT-installed lines still print in order, GIC init line confirms PPI 30
selected. Only aarch64-scoped files touched; amd64/riscv64 not rebuilt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-03 19:48:56 -04:00
Robert Allan JamesandClaude Sonnet 5 8d8f3aaae2 aarch64: split irq_spx into a real save/dispatch/restore/ERET trampoline
Punch list §25 item 0.5 complete.

irq_spx now branches (one instruction, well inside the 128-byte vector slot)
to irq_spx_trampoline, a 672-byte-frame save/restore sequence that calls a
C handler and returns via ERET. The other fifteen vectors are untouched,
still routing to the existing fatal handler.

EL selection (B3) happens once, in aarch64_install_vectors(), not per
interrupt: aarch64_current_el() (item 0.4) picks VBAR_EL1 or VBAR_EL2, and
the same answer is cached in a byte flag (el2_mode_flag) that the trampoline
reads to choose ELR_EL1/SPSR_EL1 vs ELR_EL2/SPSR_EL2 -- the two forms are
genuinely different MRS/MSR encodings, not runtime-selectable operands, so
this is the cheapest correct design: decide once at install time, branch
twice (save, restore) per interrupt afterward. VBAR_EL1 was previously
written unconditionally; this closes that half of item 0.4's known gap.
EL2 is coded from the architecture reference and cannot be boot-tested in
this environment (QEMU's aarch64 virt/EDK2 combination here yields EL1) --
reported as unverified rather than asserted as tested.

FP/SIMD save is not optional (B2, carried from item 0.4's finding that the
build has no -mgeneral-regs-only): the AAPCS64 caller-saved set -- v0-v7,
v16-v31, full 128 bits each -- plus FPSR/FPCR is saved and restored around
the C handler call. v8-v15 are callee-saved by the ABI and deliberately
excluded: the handler, being ordinary compiled C, preserves those itself.

aarch64_irq_handler() (interrupts.c) is deliberately empty. Distinguishing
which interrupt fired needs the GIC's IAR, which does not exist until item
0.6; nothing unmasks or routes any source to this vector yet, so the
function is not reachable during a normal boot. Per the item's own text,
no attempt was made to manufacture an interrupt to exercise this path early
-- 0.6 (GIC) and 0.7 (timer) are what prove it took and returned one.

Verified: every hand-computed frame offset (0, 16, 32 ... 640, frame size
0x2a0=672) checked against the actual disassembly of the built kernel, not
just visually reviewed -- save and restore sequences mirror exactly, and
aarch64_install_vectors' branch on the detected EL, the flag write, and the
trampoline's read of the same flag address all confirmed consistent. Boots
clean on real QEMU output, no regression: dict_hash 0x3d4e1daf289da94f
unchanged from the item 0.1-0.4 baseline, and the item 0.4 EL banner
("AArch64: running at EL1") still prints correctly ahead of "IDT installed.".

Only aarch64-scoped files touched (isr.S, interrupts.c) -- no shared loader
or header changed, so amd64 and riscv64 are provably unaffected; not
rebuilt for this item.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-03 18:47:29 -04:00
Robert Allan JamesandClaude Sonnet 5 f43f3f4482 aarch64: detect exception level at runtime, cached accessor
Punch list §25 item 0.4 complete.

Adds aarch64_current_el() in arch.c: reads CurrentEL[3:2] on first call,
caches the result (CurrentEL cannot change post-ExitBootServices, so every
consumer gets the same answer without repeating the MRS). Called from
arch_interrupts_init() in interrupts.c -- the earliest point with both a
working console (up since M1) and a genuine first consumer (vector
installation is the first EL-dependent operation) -- and the detected level
is printed to the boot log there.

Declared via extern-in-place in interrupts.c rather than added to the shared
arch.h: "exception level" has no amd64/riscv64 equivalent, matching the
convention already used for riscv64_timer_rearm() in item 0.3.

Verified on real QEMU output: "AArch64: running at EL1", correctly positioned
immediately before "IDT installed." in the serial log. Boots clean, dict_hash
0x3d4e1daf289da94f unchanged from the item 0.1-0.3 baseline.

Scope: this item establishes the detection and exposes it; it does not yet
change VBAR/ELR/SPSR or timer-register selection to use it. arch_interrupts_init()
still writes VBAR_EL1 unconditionally, and now says so explicitly in its own
doc comment -- if aarch64_current_el() ever reports 2 on real hardware,
exceptions taken at EL2 vector through VBAR_EL2, which nothing programs yet.
That gap is items 0.5 (vectors/saved-state) and 0.7 (CNTP vs CNTHP) to close,
per FABRIC.md's GAP-B3 finding. The boot-log EL2 case prints a note pointing
at both.

Only aarch64-scoped files touched (arch.c, interrupts.c) -- no shared loader
or header changed, so amd64 and riscv64 are provably unaffected; not rebuilt
for this item.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-03 18:30:25 -04:00
Robert Allan JamesandClaude Sonnet 5 5784d8a1a8 riscv64: finish SBI timer verification and clean up stale timer docs
Punch list §25 item 0.3 complete.

The functional work (FDT reader, `time` CSR switch, SBI TIME extension
arm/re-arm, sie.STIE) was committed separately by Captain Bob as accd79f,
honestly labeled "NOT complete." This finishes it: stale documentation
cleanup, then rigorous verification against the item's literal acceptance
text, which the prior commit had not yet done.

Doc cleanup: every remaining reference to `rdcycle` / "assumed 1 GHz" in
riscv64/timer.c and riscv64/apic.c rewritten to describe the actual `time`
CSR / SBI-armed behavior. Caught and corrected my own arithmetic error in
the process: a draft claimed the ns-overflow bound improved to "~58 years"
under the new counter; direct computation shows the bound is actually
~3.26 days (2^48 ns) and is *frequency-independent* -- s_ns_per_tick and
tick rate scale inversely and cancel. Verified with a script before
writing the final comment, not asserted.

Verification, since "boots to prompt" was not sufficient for this item's
acceptance ("heartbeat_ticks() advances ... within measurement noise"):

- No FORTH word exposes heartbeat_ticks() to the REPL, and adding one would
  be a new primitive outside this item's scope. GDB-over-QEMU-stub reads of
  the static counter failed ("Cannot access memory"), likely a virtual-vs-
  runtime address mismatch; abandoned rather than debugged further, since a
  better instrument was available.
- QEMU's own `-d int` trap tracing (independent of all guest code) shows
  2,797 `cause:5 desc=s_timer` interrupts delivered by the time boot reaches
  the prompt, and the interleaved `desc=supervisor_ecall` entries are exactly
  the SBI set_timer calls from riscv64_timer_rearm(), confirming the full
  interrupt -> handler -> re-arm -> SBI loop.
- Measured rate over a real 10.127 s window: 1,013 further interrupts,
  100.028 Hz observed against 100 Hz configured -- 0.03% deviation. The
  sustained, non-decaying rate is itself proof the one-shot re-arm succeeds
  on every cycle, not just the first.
- Confirmed regression-free on amd64 and aarch64 too: uefi_loader.c, uefi.h,
  boot_info_offsets.h and the new fdt.c/fdt.h are shared across all three
  builds (amd64 also consumes the offset constants via kernel_entry.S), so
  all three were rebuilt and booted. Identical dict_hash
  0x3d4e1daf289da94f on all three, matching the item 0.1/0.2 baseline.

Reported, not fixed, per rule 3:
- DOE_INJECT=1's EXEC-DOE now fails as "UNKNOWN WORD" against the pruned
  Hera-alone capsule -- doe.4th was never loaded by init.4th even before
  item 0.1's prune, so this is a pre-existing gap the prune surfaced, not a
  new defect. Discovered while chasing why a 200-rep injected DoE run
  produced no new CSV rows after 25 minutes; it had failed in the first
  millisecond, not run the whole time.
- repl.c's own comment claims a "Heartbeat: N ticks" diagnostic prints to
  the serial log; no such print exists anywhere in the tree.
- riscv64/arch.c's arch_read_timestamp() still uses rdcycle with a stale
  1 GHz comment, but it has zero callers on this architecture -- dead code,
  left alone.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-03 18:23:37 -04:00
Robert Allan James accd79fc70 riscv64: integrate minimal flattened devicetree reader and switch timer to time CSR
Punch list §25 item 0.3 NOT complete.

- Added `starkernel/fdt.h` and `fdt.c` for minimal read-only devicetree parsing: sufficient for boot-time lookups such as `timebase-frequency`.
- Bootloader now captures the devicetree blob (DTB) from `EFI_DTB_TABLE_GUID` into `BootInfo::dtb`.
- RISC-V timer subsystem now uses the `time` CSR as the primary timestamp source, abandoning the hardcoded `cycle` frequency assumption.
- Timer rate is read from `timebase-frequency` in the DTB when accessible; otherwise, a fallback value is used with a RELATIVE trust level.
- Integrated the SBI TIME extension for one-shot timer deadlines, ensuring re-arming occurs after each tick to avoid missing heartbeats.

Verified: riscv64 builds clean, boots to the ok> prompt with no regression; `riscv64/timer.c` reports accurate frequencies on QEMU's default firmware.
Signed-off-by: Robert Allan James <robert.allan.james@gmail.com>
2026-08-03 13:10:17 -04:00
Robert Allan JamesandClaude Opus 5 f3821ed686 riscv64: real trap entry with save/restore and SRET return
Punch list §25 item 0.2 complete.

Verified: riscv64 builds clean and boots to the ok> prompt with no regression;
dict_hash 0x3d4e1daf289da94f, unchanged from item 0.1's baseline. Disassembly
confirms the 320-byte frame, all 16 integer caller-saved registers, the FS
check, and SRET on exit; riscv64_trap_entry lands at 0x414fa8, 4-byte aligned
as stvec direct mode requires.

Not verified, and the item says so: neither new path was exercised. No timer is
armed until 0.3, so riscv64_interrupt_handler never ran, and no exception
occurred during boot, so the fatal path was not observed -- it is preserved
structurally, same branch to the same unchanged handler. This is why C2
rewrote the acceptance to no-regression rather than to having taken and
returned from a trap.

Register set is the LP64D psABI caller-saved list, not this document's summary:
integer ra/t0-t6/a0-a7 (16), FP ft0-ft11/fa0-fa7 (20) plus fcsr, and sepc +
sstatus. Callee-saved registers are the C handler's responsibility.

The FP half is conditional on sstatus.FS != Off, which the item did not
anticipate. Nothing in boot.S or kernel_entry.S programs FS, so its value is
whatever firmware leaves; touching an f-register with FS == Off raises an
illegal-instruction trap, and doing that inside the trap handler would be
unrecoverable. Omitting the FP save is not an option either -- the built
riscv64 image contains 530 FP instructions (fld, fmul.d, fcvt.lu.d among them),
confirming B2's finding against the binary rather than the build flags alone.
So the save is conditional, and sstatus is restored after the f-registers.

Dispatch: scause bit 63 routes to riscv64_interrupt_handler with scause in a0;
cause 5 (supervisor timer) calls heartbeat_tick(). Other causes are ignored
rather than fatal -- none are enabled to arrive. Everything else still falls
through to riscv64_exception_handler, unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 12:52:34 -04:00
Robert Allan JamesandClaude Opus 5 c3e4fc282c capsules: prune init.4th to Hera alone
Punch list §25 item 0.1 complete.

Verified: all three architectures build clean and boot to the ok> prompt with
Hera alone. Blocks executed are exactly 2057 -> 2049 (-> lib.4th/4050) -> 2050.
Zero occurrences of "Hermes" or "Tripod" in any of the three serial logs, and
the only parity record is MAMA_INIT -- no baby VM is born. mkcapsule --lint
passes 26 files, 0 failing, 0 violations.

Deleted blocks 2051, 2052, 2053, 2054, 2055, 2056, 2058, 2059 -- readiness
handshake, broadcast test, TRIPOD-TEST, HERMES-E2E and the fleet-DoE
scaffolding. Edited 2057 (banner), 2049 (dropped both births with their
CD-INIT calls and the common:msg.4th / process.4th loads, which are wholly
Hermes-dependent; kept lib.4th; VM-TREE and VM-CHILDREN no longer name absent
children) and 2050 (kept the BOOT-BANNER call, dropped the two calls to the
deleted words). capsules/hermes/ and capsules/artemis/ untouched on disk.

New Hera-alone parity baseline, identical across all three ISAs:
  dict_hash=0x3d4e1daf289da94f  capsule_hash=0xb4c4b5559146a3bd
This supersedes the pre-prune baselines in logs/ and is what item 0.10's
double-boot reproducibility check compares against.

Commit contents beyond the item's own edit, noted rather than slipped in:
capsules/BLOCK_MAP.md is regenerated by any build; the three serial logs are
this item's acceptance evidence and CLAUDE.md requires committing them; the
DoE CSVs are auto-extracted by the qemu target on every run. Leaving generated
output from this item's acceptance run for a later item to sweep up would be
worse than including it here.

Reported, not fixed: tools/mkcapsule.c emits two -Wstringop-truncation warnings
on the host build (:404, :562), pre-existing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 12:47:21 -04:00
Robert Allan JamesandClaude Fable 5 39aa54b9a4 FABRIC.md: fix GAP-D1 through D3, close out §25.7.1
D1  The three passages still arguing from the K-denominator justification that
    §2's correction removed: §17.3's opening now cites density's need for a
    volume (§19.2); §17.5's sizing argument re-grounded on mass swamping and
    §23.1's abolition of by-reference payloads; §17.6(d)'s bullet no longer
    cites §2 for a claim §2 explicitly disavows.
D2  §19.6 #1 and #2 struck through as resolved (#1 by §23.1 with the residue
    scheduled as item 1.12; #2 by §24.3), matching §17.4's convention.
D3  §20.3's stale LEANING now points at §21's DECIDED.

§25.7.1's status updated: all fourteen findings closed. A1 ruled and applied,
B1/B2 verified against the tree and fixed into their items, B3/C1-C7/D1-D3
applied. The findings text is preserved as the record of what was found.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 11:34:14 -04:00
Robert Allan JamesandClaude Fable 5 9b61a3355e FABRIC.md §25: fix GAP-C1 through C7 — punch item defects
One batch commit for the seven mechanical item fixes from §25.7.1, each tagged
in place:

C1  Item 0.1 no longer contradicts its own Refs line. Delete set is 2051-2056 +
    2058-2059; blocks 2057, 2049 and 2050 survive edited, with the per-block
    edits now spelled out. The old "remove 2050-2059" wording would have
    deleted the banner.
C2  Items 0.2 and 0.5 accept on no-regression. Both previously required having
    taken and returned an interrupt at a stage where no interrupt source
    exists; the real evidence lands with 0.3's and 0.7's tick-advance
    acceptance, and both items now forbid pulling later work forward to
    manufacture it.
C3  Item 0.3 carries sie.STIE and the every-tick re-arm -- the SBI timer is
    one-shot and a missed re-arm stops the heartbeat forever with no error.
C4  §23.4 #4 is now schedulable as item 1.12 (continuation-cell encoding);
    3.1's blocker line and the Phase 3 gate reference it.
C5  Item 1.11 is formally blocked on 4.3 instead of informally deferred.
C6  Item 0.10's "sane" sharpened to trust near Q48_ONE and variance small
    relative to the new expected_delta; the amd64-control framing is noted as
    valid again under the GAP-A1 ruling.
C7  The commit template no longer hardcodes a model name.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 11:33:04 -04:00
Robert Allan JamesandClaude Fable 5 8ac284f8c1 FABRIC.md §25: GAP-B2 verified and fixed, GAP-B3 fixed — FP state and EL scope
B2, verified against Makefile.starkernel: no architecture restricts FP register
use. amd64 has no -mno-sse (:124) -- works with live interrupts today, save-set
adequacy unverified. aarch64 lacks -mgeneral-regs-only (:146). riscv64 builds
-march=rv64gc -mabi=lp64d (:162) -- hard-float ABI, and kernel code genuinely
uses doubles (hotwords_stats_print). Items 0.2 and 0.5 now require saving the
ABI caller-saved FP set plus control/status registers, with the exact lists
verified against the psABI/AAPCS64 rather than this document, and explicitly
forbid "fixing" it via soft-float, which would break existing code.

B3: item 0.4's EL detection now governs everything EL-dependent -- vector base
register (today's isr.S writes VBAR_EL1 unconditionally, never consulted for
exceptions taken at EL2), saved-state pair, eret target, and timer registers.
Item 0.5's hardcoded ELR_EL1/SPSR_EL1 wording replaced accordingly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 11:31:26 -04:00
Robert Allan JamesandClaude Fable 5 09d0067b65 FABRIC.md §25: GAP-B1 verified and fixed — no DTB access exists
Read BootInfo (uefi.h:624-639): memory map, runtime services, ACPI,
framebuffer, stack, args -- no FDT pointer. The only FDT mentions in the whole
kernel tree are comments in riscv64/timer.c acknowledging one would be needed.
Items 0.3 and 0.6 instructed "read from the device tree" against a kernel that
cannot reach one.

Item 0.3 now carries the prerequisite explicitly: capture the DTB pointer from
the EFI configuration table into a new BootInfo field in the shared loader,
serving both 0.3 and 0.6. Item 0.6 references that field and, if the DTB is
unreachable on aarch64 EDK2, requires stop-and-report rather than falling back
to constants unilaterally.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 11:30:38 -04:00
Robert Allan JamesandClaude Fable 5 11c8678477 FABRIC.md: rule GAP-A1 — the engine tick is virtual
Captain Bob's ruling on §25.7.1 GAP-A1, adopting the recommended resolution.

The engine's tick is a virtual tick: a pure, deterministic function of the
execution stream, which is what exists today and why parity holds today. The
hardware heartbeat is the TIME-TRUST instrument and the idle wake source, and
drives nothing that feeds patron state. When the system idles, the REPL poll
loop pumps virtual ticks so TTLs still expire in real time, in a context where
parity was never claimed.

Applied to: §16.4 (correction appended -- the fire-on-tick-count rule was
necessary but not sufficient, since the hash measures the composition of the
instruction and tick streams, not the engine's schedule alone); §17.1 (the one
clock is the virtual tick, and the mechanisms cannot be split across clocks
because TTL expiry has instruction-stream side effects); §18.4 (L0 advances on
the virtual tick); item 0.8 (disambiguated -- the hardware tick drives
instrumentation only, vm_tick call sites unchanged); item 2.1 (transfer
restates onto the virtual tick, which is what makes its identical-sum
acceptance achievable).

Item 0.10 needed no change: with the engine execution-paced, its double-boot
check is a valid regression guard and amd64 is genuinely a control again.

Phase 0's timer bring-up stands: it makes the instrument real on three ISAs
and is the substrate SMP will need. It does not drive the engine.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 11:26:38 -04:00
Robert Allan JamesandClaude Fable 5 e8f8b098ed FABRIC.md §25.7.1: second review pass findings, awaiting rulings
Full re-read of the document as it stood after the first review's corrections,
looking for what would break a lower-capability model working the punch list.
Nothing in this commit fixes anything -- it records fourteen findings for triage.

The one that gates coding: GAP-A1. §16.4's inference "same tick ordinal → same
hash" is unsound, because the hash covers execution_heat, which is co-written by
the instruction stream and the tick stream, and a hardware timer makes the
interleaving of those two streams wall-clock-dependent. Firing on tick count
fixes the engine's schedule, not the composition. Blast radius: item 0.8 is
ambiguous between two different kernels, 0.10's double-boot check fails by
construction under one of them, and 2.1's corrected acceptance is still
unachievable. Recommended resolution recorded (virtual tick as a pure function
of the execution stream; hardware heartbeat as instrument and wake source only)
but explicitly not decided.

Also: three unverified prerequisites (DTB reachability for 0.3/0.6, FP-register
save vs compile flags for 0.2/0.5, EL-dependence of the vector path vs 0.4's
scope), seven punch-item defects (0.1 self-contradiction on block ranges,
0.2/0.5 unsatisfiable acceptance, 0.3's missing STIE and re-arm, §23.4 #4 not
being a schedulable item, 1.11's informal deferral, 0.10's stale control
framing, the hardcoded model attribution), and three amendment inconsistencies
(stale K-denominator language in §17.3/§17.5/§17.6d, §19.6 #1-#2 unmarked as
resolved, §20.3's stale LEANING).

Triage order is stated in the section: A1 first, B1/B2 are ten-minute reads,
C and D mechanical after that. C1-C3 minimum before any coding model starts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 11:21:15 -04:00
Robert Allan JamesandClaude Opus 5 32f87d9e59 FABRIC.md §25: record why item 1.1 blocks item 3.1
Review item REVIEW-D4.

Item 1.1 (exclusive access, the "sitting in a car" primitive from §8) sits in
Phase 1 alongside questions with no structural effect, and it is not in that
class. A per-patron exclusivity primitive plausibly needs a held flag or holder
index -- a ninth wire in §3's table, in the header item 3.1 builds. Resolving
1.1 after 3.1 means rebuilding the cell header.

§25.4 already blocks Phase 3 on items 1.1-1.7, so the ordering was right; what
was missing was why 1.1 specifically, which is what gets an item quietly
reordered later by someone who does not know what it was holding up.

Item 3.1 now names both of its blockers explicitly: item 1.1, and §23.4 #4, the
continuation-cell encoding surfaced by REVIEW-C3. It also now requires defining
both members of §3's closed two-valued union rather than just the header.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 11:10:43 -04:00
Robert Allan JamesandClaude Opus 5 2d79ab5244 FABRIC.md §25: item 1.3 inherits §18.5's determinism constraint
Review item REVIEW-D3.

Item 1.3 decides what triggers a capacity transfer and said it should read the
density gradient rather than a schedule -- correct about what, silent about
when. Hera's arbitration mutates patron mass, so §18.5 binds it directly:
anything influencing patron state advances on tick count, and wall-clock time
must never be an input to a decision.

Without that stated in the item, a reasonable implementation could pace
arbitration off a wall-clock interval and reintroduce, in a new place, exactly
the defect item 2.1 exists to remove. Whatever 1.3 decides must be expressible
in ticks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 11:10:02 -04:00
Robert Allan JamesandClaude Opus 5 b8fb094dd2 FABRIC.md §25: item 2.2 rests on grounds bounding can actually deliver
Review item REVIEW-D2, following from REVIEW-A1/B1.

Item 2.2 claimed the unbounded registry "makes fleet K an identity that cannot
fail" and accepted on VM-CONSERVED? becoming able to fail. Both were wrong.
Heat is transferred rather than renormalised (§20.2), so conservation is already
a real invariant and already falsifiable -- via the dropped-remainder path at
capsule_vm_physics.c:240-244 and integer truncation at :304-305. Bounding the
population changes neither, so the item promised something it could not deliver.

The bound is still needed and now rests on the two grounds §2 states: finite
state for §13's induction and model checking, and density requiring a capacity
to be dense within, without which §19.3's admission rule has nothing to compare
against. Acceptance is now the bound existing, birth-at-bound behaving as 1.5
specifies, and the three-architecture boot unaffected.

Fixing the truncation leak is a separate and larger piece of work and stays in
§25.7 rather than being folded in here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 11:09:45 -04:00
Robert Allan JamesandClaude Opus 5 941fb778e2 FABRIC.md §25: item 2.1 acceptance now tests what 2.1 changes
Review item REVIEW-D1.

Item 2.1 restates vm_physics_touch on tick count and accepted on the
dictionary-hash double-boot check from item 0.10. That check cannot detect the
work: §18.5 establishes that vm_physics_touch writes node->physics, not
DictEntry.execution_heat, and so never reaches the parity hash. The dict hash
would be identical whether 2.1 succeeded, failed, or was skipped entirely.

Acceptance is now an identical fleet heat sum across two boots of the same
capsule -- fleet heat being the quantity the item actually changes. The
dict-hash check still runs, as a regression guard rather than as evidence.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 11:09:16 -04:00
Robert Allan JamesandClaude Opus 5 d7d9e274c8 FABRIC.md: sweep terminology to Stadium
Review item REVIEW-C4.

The header claimed §1-15 retained "arena" to stay quotable, but the rule was
not applied consistently -- §15 had been swept, §13 had not. A document with two
names for its central object costs every reader something on every section.

Swept all body text. "arena" now survives in four places only, each deliberate:
the src/starkernel/vm/arena.c file path; the two naming notes, which discuss the
word itself; §12's preserved question list, which the section explicitly retains
as the source several later sections quote; and the block quotes of §12 in §21
and §23.

One deviation from the review's stated rule, which said quoted text keeps the
original word. §17.3 quotes §4 and §17.6 quotes §13 -- both self-quotes within
this document, whose sources were just swept. Left unswept they would cite text
that no longer exists, so a reader following the reference finds different
words. Those two were updated to track their sources. §12's quotes were not,
because §12 itself is deliberately preserved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 11:06:13 -04:00
Robert Allan JamesandClaude Opus 5 1f44f47e69 FABRIC.md §23.3: fix header arithmetic, and stop asserting an unsized cell
Review item REVIEW-C3.

The stated fields sum to 28, not the ~32 claimed, and 28 + 32 inline payload is
60, not the 64-byte cell. Now stated as 28 used plus 4 reserved. The reserve is
deliberate: it keeps the header a clean half-cell, leaves room for the
header/continuation discriminator §3 now requires, and gives §8's exclusivity
primitive somewhere to live if item 1.1 resolves to a holder index.

The larger problem was the "17 cells: 1 header + 16 payload" figure for a
1024-byte block. That silently assumed continuation cells are contiguous and
carry nothing but bytes. §22.3 allocates from a per-VM free list with no
adjacency guarantee, so continuation cells would need a next-index -- 4 bytes
off each one's payload, making the same block 18 continuation cells rather than
16. The alternative, guaranteeing contiguous runs, reintroduces the
fragmentation §3 avoids.

Those are different designs with different costs, and the choice sets the mass
of every large patron. Rather than pick one, the row is marked undetermined and
recorded as §23.4 #4, which gates punch item 3.1: the cell structure cannot be
built until it is settled.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 11:00:37 -04:00
Robert Allan JamesandClaude Opus 5 a654e907e5 FABRIC.md §12: heading no longer overstates closure
Review item REVIEW-C2. The heading claimed all six questions closed while the
table below marked Q5 partial and a note explained why. Five closed, one
partial.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 10:59:07 -04:00
Robert Allan JamesandClaude Opus 5 3a9dfae280 FABRIC.md §17.1: heading no longer undercounts patrons
Review item REVIEW-C1. The heading read "Four patrons die four different ways"
above a table listing five. §17's preamble carries the caveat that its counts
predate §20, but a heading is where a reader anchors.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 10:58:43 -04:00
Robert Allan JamesandClaude Opus 5 e44dc7830c FABRIC.md §16.3: correct the DictEntry wire count against the struct
Review item REVIEW-B2, settled by reading include/vm.h:335-351.

The subsection's table and prose disagreed: the table listed the code field as
present, the prose named it as one of two missing wires, and neither accounted
for payload.

Actual mapping against §3's eight wires: identity, heat, TTL and pin are present
in correct form; link is a pointer rather than an index; the code field is a raw
function pointer rather than an enumerated tag; mass and payload are absent.
Four correct, two wrong-form, two absent -- not "six of eight."

The wrong-form pair matters more than the count. §13 names pointer-free index
links as the single biggest difference between a tractable proof effort and a
research project, and §18.3 requires the function pointer to become a closed
tag. Those are exactly the two.

The claim that the dictionary "is" a Stadium entry is therefore overstated and
has been weakened to what the struct supports: the dictionary already has the
concepts, and two of the eight wires need to change form. That still carries §1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 10:58:21 -04:00
Robert Allan JamesandClaude Opus 5 fd820fc9eb FABRIC.md §3: declare the closed two-valued cell union
Review item REVIEW-A3.

§23.1 dissolved the payload threshold by making large patrons heavy -- a
1024-byte block is 17 cells, one header and sixteen of payload. Those sixteen
carry no identity, heat, TTL or code field, so they are a second cell shape,
which §3's opening line ("One structure. No variants") forbade without saying
so. An implementer reading §3 literally would have built the wrong structure.

Declared: a cell is either a patron header or a continuation cell owned by
exactly one patron. The union is closed, two-valued, and fixed at build time.

This introduces no new principle -- it is the same discipline §18.3 applies to
behaviours, and a two-valued union is the smallest instance of a closed
enumeration. It is not a type field: the distinction is structural, tells the
engine only whether a cell begins or continues a patron, and is exhausted by
that. Continuation cells are never ranked, reaped or dispatched; they are floor
space accounted for in their owner's mass.

§13's "one datatype" bullet amended -- the datatype is a two-constructor sum
rather than a bare record, costing one case split.

§23.4 #3 (identity elision) expanded: it is downstream of the
header/continuation encoding, since a scheme reusing the identity field as
discriminator would couple the two decisions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 10:57:42 -04:00
Robert Allan JamesandClaude Opus 5 518334abef FABRIC.md §22.3: free lists are per-VM, not shared
Review item REVIEW-A2.

§22.3 specified that cells are drawn from a shared free list. That undercut the
row that decided §21: its SMP discriminator claims messages are the only
boundary-crossers, so no shared memory and no locks, permanently. A shared free
list is shared mutable state touched by every VM on every admission and reap,
and would need a lock or atomics under SMP. §22.3's defence -- that VMs never
touch each other's cells -- does not cover it, because the free list is nobody's
cell and allocation touches it.

Each VM now holds its own free-list head index into the global array. Hera hands
over cells when she grants quota; a VM allocates and frees only within what it
holds. One index space, one datatype and §13 are all unaffected, and
disjointness becomes total rather than nearly total.

Capacity transfer remains arithmetic plus a list splice, arbitrated at a known
point per §22.5 #2.

The layout choice itself is unchanged -- one global array, one index space
remains correct for §13.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 10:56:34 -04:00
Robert Allan JamesandClaude Opus 5 055e944a73 FABRIC.md: correct §2 and §20.2 — fleet K is a real conservation law
Review item REVIEW-A1, settled by reading (REVIEW-B1).

§20.2 claimed fleet K was "bookkeeping" that could not fail, on the premise
that heat is renormalised after population changes. That premise is false. Read
end to end in capsule_vm_physics.c:

  - vm_physics_transfer (:147-154) subtracts from one and adds to another,
    clamped at zero: "Nothing is created or destroyed."
  - Birth (:156-185) seeds Hera with Q48_ONE; every other VM starts at zero --
    "cold mass added to a closed system." Population growth rescales nothing.
  - Death (:225-247) transfers the dying VM's heat to its root before zeroing.
  - Touch (:250-311) pulls proportionally, clamped so it "can never manufacture
    heat."

There is no renormalisation. VM-CONSERVED? tests a genuine invariant and is
falsifiable today, by two paths: the documented dropped-remainder case at
:240-244, and integer truncation in the proportional fan-out at :304-305, which
loses heat on every multi-VM touch and drifts the sum downward monotonically.

Consequently §2's justification was also wrong -- capacity does not give a
normalised heat share its denominator, and §19.2 says mass never enters K. §2
now claims the bound on the two grounds it can honestly claim: finite state for
§13, and density needing a capacity to be dense within.

This also reframes the Artemis campaign's K-invariance arm. It was not
measuring an identity; it measured a quantity that could drift and did not trip
a 5% epsilon over that run.

The truncation leak is recorded in §25.7 as reported-not-scheduled. It is small
per touch but monotonic, and its rate has never been measured.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 10:56:03 -04:00
Robert Allan JamesandClaude Opus 5 7fe0133d07 FABRIC.md — reconcile §1-24, add §25 punch list
Reconciliation. §1-15 were written before any code was read and had drifted
from §16-24 in twenty places. Each superseded claim is now marked in place
rather than rewritten -- §19.4 and §19.5 quote the original §4 wording
directly, and the retracing trail matters more than a tidy read.

Two real gaps surfaced during the reread, neither previously recorded:

- §3's wire table was missing `mass`. Density is heat / mass, so mass has to
  live in the entry, and §3 is the table an implementer would work from. It is
  now an eighth wire.
- §8 asserts a per-patron exclusivity primitive -- "one sits in a car, the only
  exclusive thing in the room, no global lock" -- that nothing in §16-24
  defines. §21 covers ISR-vs-mainline concurrency, which is a different
  question. Marked OPEN at the source and carried as punch list item 1.1.

Also corrected: §9's admission table is complete for all five patron kinds with
both `?` marks closed; §12's six questions are all resolved; §5's third
category is supplied; §15's five-line summary had two wrong lines; §4, §6, §7,
§17.4 and §17.6 status markers now match reality.

§25 is the punch list, with its operating instructions first: one item at a
time, no jumping ahead, no scope increase, no fabricating anything unverified,
stop when blocked. Every checked item gets its own commit carrying both the
change and this file with that box ticked, so the document is never a claim
about work not in the branch.

Phase 0 is the substrate -- Hera alone plus real timers and IRQ return paths on
all three ISAs. Nothing else can start until it is done.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 10:27:49 -04:00
Robert Allan JamesandClaude Opus 5 7c436351a0 FABRIC.md §23 — Q1 dissolved, Q2 sized
Q1 (payload threshold) does not need answering, it needs removing. §3 wanted
inline-if-small/by-reference-if-large so a cell need not be sized for a block.
§19 gives a better answer: a large patron occupies more cells, chained by
index, which is exactly what mass already means. A block is not by reference,
a block is heavy.

  If the payload is in the Stadium, it counts toward mass. If it is not in the
  Stadium, the patron is not resident -- it is a handle to the warehouse.

That closes the §19.6 loophole without a rule: a 1 MB block cannot occupy one
cell and read as dense. It also preserves the hysteresis in §19.3, which
depends on blocks being genuinely expensive to keep resident. Nothing in §3 is
violated -- cells stay fixed-size, links stay indices, the Stadium stays an
array. By-reference is reserved for things outside the Stadium, which are not
patrons.

Q2's premise is stale: §17.5 removed the screen grid, so the screen is no
longer the worst case. Underneath it sits a question §17.5 left half-settled --
it decided the dirty event is the patron but not what one event covers. Per
cell, an 80x25 redraw is 2,000 patrons and floods the Stadium; per region it is
about 25. Leaning region-based, to be confirmed with the console work. The
worst case then becomes messages, giving the rule: size the cell so a typical
message is exactly one cell.

Proposes 64-byte cells (one cache line), ~32-byte header, ~4096 cells per VM
at 256 KB. Structure decided, constants leaning -- heat at 8 bytes is fixed by
the existing Q48.16 convention, the rest need validating against a real build.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 10:12:44 -04:00
Robert Allan JamesandClaude Opus 5 d74872c222 FABRIC.md §22 — elasticity resolved: elastic, via quota over one cell pool
§7, §12 Q4 and §17.6(c) are one question at three scales. Resolved elastic.

Under §19's density definition elasticity stops being a feature and becomes a
negative feedback loop: a busy VM's heat share rises, density rises, capacity
flows toward it, mass rises, density falls back. Capacity flows down the
density gradient -- diffusion, no threshold, no damping constant. §4's "read,
not decided" applied one level up. §7's own argument also holds: birth sizes
the resting volume rather than a cap, which is far easier to guess right and
self-corrects when wrong.

The larger finding is that hard-versus-elastic was the wrong framing. Elastic
is cheap or expensive entirely according to layout, which §21 did not settle.
Chosen: one global array of cells, one global index space, per-VM quota as a
count rather than a contiguous range. Cells come from a shared free list, so
transfer is arithmetic on two integers -- no fragmentation, no adjacency
requirement, index links keep working.

This makes §13 simpler rather than harder: one array, one datatype, one total
function over one finite index set, with nesting as a partition. §21's
reasoning survives -- K per level and messages as the only boundary-crossers
are both preserved, and SMP-safety holds so long as quota changes are
arbitrated by Hera. What is given up is physical fault containment, recorded
rather than glossed.

Adds a required ordering: capacity must move slower than heat, or two conserved
quantities chase each other and the ratio never settles. That is §12 Q5's
separation of timescales arriving as a concrete instance.

Opens: the resting floor (proposed derivation -- floor a quota at the mass of
its pinned patrons, which is derived rather than tuned), what signal triggers a
transfer, and the exact timescale ratio.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 10:09:43 -04:00
Robert Allan JamesandClaude Opus 5 421c1d7602 FABRIC.md §21 — Q6 resolved: nested
Q6 leaned toward nested for the right conclusion and the wrong reason.

Its premise -- that a single region "reintroduces locking, the one mechanism
this architecture has otherwise never wanted" -- is false. Every mutex in the
kernel build is a no-op (shim.c:415); the kernel compiles STARFORTH_MINIMAL and
the shim stubs dict_lock and tuning_lock out entirely. The architecture has not
avoided locking, it has locking, inert. The cost Q6 weighs is currently zero, so
Q6 cannot be decided on it.

Decided nested on six other grounds, the strongest being SMP-readiness: nested
keeps messages the only boundary-crossers, so no shared memory and no locks
ever, whereas a single region would need real locks and the present no-op stubs
would silently become a correctness hole. The most practical is that the outer
level already exists and works (§20.1) -- single-region means discarding a
working two-level structure.

Also records a step-one finding that lands before any Stadium work: enabling
timer interrupts introduces genuine ISR-vs-mainline concurrency where none
exists today. Making the mutexes real would deadlock a single hart outright,
since an ISR spinning on a lock the mainline holds can never be released. The
top-half/bottom-half split of §18.4 is the answer, stated as a rule:

  Nothing in interrupt context may mutate Stadium structure. Ever.

That constraint should be written at the stub site so the no-op is not later
"fixed" into a spinlock.

Opens: two capacities to size rather than one, and elasticity (§12 Q4 / §7 /
§17.6c) becomes the live fork now that nesting makes capacity transfer real.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 10:06:17 -04:00
Robert Allan JamesandClaude Opus 5 1a5cd3fc74 FABRIC.md §20 — VMs are patrons
§17 named four patrons and omitted the only kind already implemented. §9's
admission table has always included VM, and §6 says Hera becomes the first
entry; neither reconciles with a four-patron taxonomy.

This is a finding rather than a proposal. vm_physics_fleet_heat_sum() already
sums execution_heat_q48 across live VMs against Q48_ONE -- that is a Stadium's
K over VM patrons, and §19.1's definition was derived from it.

Two consequences:

- The outer level is unbounded. The VM physics registry is a kmalloc-backed
  linked list, self-described as "unbounded, not a fixed array", so heat is
  renormalised to 1.0 however many VMs exist. By §2's own test, fleet K is
  currently bookkeeping -- VM-CONSERVED? cannot fail. This also explains why
  the Artemis campaign's K-invariance arm found nothing: the quantity cannot
  vary. Bounding the population is what would make it measurable.

- Nesting is half-built. Outer Stadium holds VM patrons; each VM's inner
  Stadium holds words, blocks, ACLs and messages. That is §12 Q6's nested
  option with the outer level already present. LEANING nested.

Proposes VM mass = the capacity share Hera allocated, making §7 concrete and
giving Hera a lifecycle signal that distinguishes starved from small. Marked
proposal: VMPhysics has no share field today.

Resolves §20.5 #3: Hera is pinned, and any attempt to evict her is a kernel
panic asserted at the eviction site, not filtered out of the candidate set.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 10:02:52 -04:00
Robert Allan JamesandClaude Opus 5 85f59fca2e FABRIC.md §19 — mass, density, and what K actually is
Supplies the concrete definition §4 was missing. §4's claim that ranking is
read rather than decided is empty until the thing being read is a number.

Three quantities, not one:
  Heat    conserved share moved by traffic, sum = 1.0 always (already built)
  Mass    cells a patron occupies -- its footprint (new)
  Density heat / mass -- heat per cell (new, derived)

K is left untouched. vm_physics_conserved() already defines it as a normalised
heat share summing to Q48_ONE, not an occupancy ratio; defining it as
mass/capacity would have contradicted implemented, tested code.

Ranking, admission-when-full, and migration hysteresis all read off density
with no policy and no damping constant.

Two corrections to §4:
  - The self-limiting claim keeps its conclusion but loses its mechanism. A hot
    entry is easier to reach, not harder; the real governor is conservation,
    since heat is zero-sum and capped at 1.0.
  - "Density generates heat" reverses the causality. Traffic confers heat;
    density is heat per cell, derived downstream.

Open: mass depends on payload threshold and header size (§12 Q1, Q2), which are
now prerequisites rather than sizing details; and vm_physics_touch scales heat
transfer by wall-clock time, which §18.5 forbids and which must be restated on
tick count before L0 can use it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 09:57:59 -04:00
Robert Allan JamesandClaude Opus 5 8af19a545b Add FABRIC.md — Stadium design, sections 1-18
Captures the 2026-08-03 design session: collapsing the four independent
heat/TTL/pin implementations (blocks, messages, console cells, ACLs) into
one bounded Stadium of fixed-size entries, driven by an engine below every VM.

Sections 1-15 are the original design argument. Sections 16-18 add:

- 16  Substrate findings. No IRQ return path exists on aarch64 or riscv64;
      riscv64's time base is a hardcoded 1 GHz guess; the dictionary already
      carries six of the seven entry wires; the engine must stay deterministic.
- 17  Patrons. TTL, heat decay and pin are three distinct mechanisms on one
      tick, not a type field. Reap means leaves the floor, not destroyed. The
      framebuffer is a utility, not a patron. Dynamic in capacity, static in
      structure.
- 18  The engine (L0). L0 and L8 bookend the gated loops L1-L7, both ungated.
      Jacquard stays 7-bit/128 states, accounting for L0 by its absence.
      Dispatch enumerates behaviours, never patron kinds.

Determinism traced end to end and confirmed intact: TIME-TRUST is measured
and never fed back, inference inputs are wholly execution-derived, decay is
tick-based, and the parity hash covers only word name and execution_heat.
One pre-existing exception recorded — vm_physics_touch scales fleet heat by
wall-clock elapsed time, outside the parity path.

Draft. Sections marked DECIDED / LEANING / OPEN throughout.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 09:45:05 -04:00
Robert Allan JamesandClaude Opus 5 8ad9ac52aa Add three-arch Artemis stress campaign serial logs (audit artifacts)
Serial logs for the 30-replicate ART-STRESS-CAMPAIGN run, one per
architecture:

  logs/20260802-160052/amd64    30 reps x 50 trials, 1500/1500 pass
  logs/20260802-173226/aarch64  30 reps x 50 trials, 1500/1500 pass
  logs/20260802-181803/riscv64  30 reps x 50 trials, 1500/1500 pass

4500 trials, zero failures. These are the audit trail for the claim that
the block_words.c stale-pointer cache-aliasing fix holds at scale, and for
cross-arch disk read/write/persistence.

Committed as raw blobs, matching the ~6.7GB of existing log objects in
history. Note that .gitattributes already declares logs/**/*.log and the
bare-metal CSVs as LFS-tracked, but git-lfs is not installed in this
environment, so those filters are silent no-ops -- the 744MB log at
logs/20260802-115945/ is likewise a raw blob, not a pointer. Flagged for a
proper fix (install git-lfs, confirm server-side LFS support, decide
whether to migrate existing history) rather than half-applying LFS to only
these three files.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 21:52:35 -04:00
Robert Allan JamesandClaude Opus 5 7597a9ccd4 Add Artemis compudynamics implementation plan; add stress campaign fixture
The 30-replicate Artemis surface-stress campaign ran across all three
architectures: 30 reps x 50 trials x 3 arches = 4500 trials, zero failures.
The block_words.c stale-pointer cache-aliasing fix holds at scale.

Adds ART-STRESS-CAMPAIGN (block 4174) plus ART-STRESS-REP rep-tagging in
the CSV header/summary emitters, so a multi-replicate run is distinguishable
in the serial log. Campaign auto-invoke is left enabled in block 4170 for
now; Makefile.starkernel's QEMU boot deadlines stay at 12h to accommodate
long-running experiments.

Adds docs/working/architecture/ARTEMIS-COMPUDYNAMICS-IMPLEMENTATION-PLAN-20260802.md,
which documents the real gap this campaign exposed: block heat and message
heat do not decay at all. ART-TICK has zero call sites anywhere in the tree,
and HERMES-TICK has zero C call sites -- every caller is Hera poking it by
hand. BLK-HEAT@/MSG-HEAT@ read a number nothing ages, so blocks never reap
by cooling and message TTL never expires on its own.

The plan mirrors word-level physics as the reference model: lazy decay at
each access point against vm->heartbeat.tick_count, plus a bounded
background sweep with a resumable cursor (the existing answer to "sweeping
22,998 blocks per tick is too expensive"). Phase 1 Artemis, Phase 2 Hermes,
Phase 3 K participation deferred behind the Logical BAM.

The plan's preamble also records a wrong turn taken while investigating:
chasing VM-fleet heat instead of block heat, and building synthetic
Hera-driven VM-EXEC calls to force a physics reading -- which TRIPOD.md
prohibits outright. That work was reverted; the record is kept so it isn't
repeated.

Status: plan approved in shape, NOT final and NOT started. Six open
questions need answers and further design discussion is pending.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 21:49:16 -04:00
Robert Allan James 1cb68502fb Add Artemis stress test for detecting cache aliasing bugs. Include statistical hypothesis evaluations, fix validation data, and run reports for validation across architectures.
Signed-off-by: Robert Allan James <robert.allan.james@gmail.com>
2026-08-02 14:49:52 -04:00
Robert Allan JamesandClaude Sonnet 5 148c4aa12c Fix silent disk overwrite of unrecognized Artemis disks
The generic block subsystem (blk_format_or_load_disk) auto-reformatted
any disk lacking its own low-level 'STFR' header at attach time, before
Artemis's Forth-level BLANK/LithosAnanke/Unrecognized classification
ever ran -- so ART-HALT-UNRECOG's "Disk preserved" message was false.

Split detection from commit: an unrecognized/blank disk is now left
PROVISIONAL (geometry computed in memory only, all writes refused)
until explicitly confirmed via the new blk_subsys_confirm_format() /
BLK-CONFIRM-FORMAT primitive. Artemis calls it from ART-FORMAT and
ART-RESUME, never from ART-HALT-UNRECOG.

Verified on amd64/aarch64/riscv64: parity intact (identical dict_hash),
normal recognized-disk resume + persist-read unaffected, and a
regenerated disk/artemis-unrecognized-test.img (the old copy had itself
been silently corrupted by this exact bug) now stays byte-for-byte
identical across a halted boot on amd64 and riscv64.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-02 11:44:23 -04:00
Robert Allan JamesandClaude Sonnet 5 cc6c8c43f3 Fix ABORT to actually unwind to QUIT instead of one level
ABORT is documented and tested in this codebase as standard FORTH-79
behavior -- system_words_test.c:63: "Should clear stacks and return to
QUIT" -- meaning it should unwind all the way back to the outermost
interpreter loop, abandoning whatever's left of the current line/block.
The implementation only unwound one level: every place that checked
vm->abort_requested cleared it the instant it saw it, so it never
survived to propagate past the first nested frame.

This surfaced via Artemis's ART-HALT-UNRECOG (capsules/artemis/init.4th):
on an unrecognized disk it correctly printed "ARTEMIS HALT: unrecognized
disk content" and called ABORT, but WELCOME (the next line in the same
block) ran anyway, and Artemis announced ready to Hermes and joined the
fleet normally -- contradicting .claude/ARTEMIS.md's "Refuse to mount...
do not overwrite it" requirement. Root cause is general, not
Artemis-specific, and present identically in both the hosted and kernel
VM cores.

Fixed at every level execution can nest through, verified by exhaustively
grepping every !vm->error-gated continuation loop and adding the parallel
!vm->abort_requested check:

- execute_colon_word (src/vm.c, src/starkernel/vm/vm_core.c): stop
  clearing the flag on return -- every colon-word call is a recursive
  call to this same function, so leaving it set lets every enclosing
  frame's own check also unwind.
- vm_interpret (src/vm.c, src/starkernel/vm/vm_core.c): stop parsing
  further words in the current input string once the flag is set.
- exec_block_with_retry (src/starkernel/capsule/capsule_loader.c):
  capsule birth's line-by-line block executor -- stop processing further
  lines in the current block, but return 0 (not -1), so
  capsule_exec_payload still loads later blocks in the same capsule
  payload. Returning -1 here would have silently broken word definitions
  in blocks that come after the aborting one for reasons unrelated to
  why it aborted (concretely, Artemis's ART-PING/LOAD-DOE in blocks
  4851/4852, which follow the entry block 4133).
- THRU and --> (src/word_source/block_words.c): stop processing further
  blocks/lines in their own loops.
- DODOES (src/word_source/defining_words.c): the CREATE...DOES> runtime
  has its own hand-rolled execution loop, separate from
  execute_colon_word -- same bug class, same fix. Also guarded the
  post-loop "if (vm->rsp < base_rsp) vm->rsp = base_rsp" clamp so it
  doesn't fire on an abort exit -- ABORT's own reset_vm_state() already
  set rsp; restoring it to base_rsp would have partially undone that.
- Both REPL loops (src/repl.c, src/starkernel/repl.c x2 call sites):
  clear the flag after each line, mirroring the existing vm->error
  pattern, so a mid-line abort doesn't silently freeze subsequent
  interactive input.

Verified directly: ": AB-TEST 1 2 3 ABORT 999 . ;  AB-TEST 42 . CR
777 . CR" -- 999 never prints (stops mid-colon-word), 42 never prints
(stops the rest of the same line), 777 prints fine (next line
unaffected). Artemis: WELCOME/"Artemis ready" no longer fires after the
halt message. No regression: all three architectures still show PASS:
persist-read, PASS: E2E msg flow, and matching dict_hash on the normal
(non-aborted) boot path; hosted test suite 965 passed / 0 failed.

Known follow-up, not fixed here (see memory for details): Artemis still
announces ready to Hermes via a separate call path (CD-INIT, block 4141)
that never went through capsule_exec_payload's block chain in the first
place, and the disk file still picks up incidental writes even on a
correctly-halted boot -- likely generic block-subsystem housekeeping,
not traced yet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-02 10:07:18 -04:00
Robert Allan JamesandClaude Sonnet 5 3d9664d176 Update ARTEMIS.md build status to match reality
The flat-pool storage scope ("Immediate Goal") has been implemented for
some time -- capsules/artemis/init.4th boots live in the Tripod fleet on
every kernel boot -- but the doc still said "do not begin implementation
without explicit instruction from Captain Bob" in two places, and
CLAUDE.md's pointer still said "build-authorization deferred."

.claude/ARTEMIS.md: replaced both stale lines with a dated Build Status
section giving an honest per-criterion accounting rather than a blanket
"done": 4 of 6 acceptance criteria fully confirmed (boot-state detection,
free-map init, fetch/persist, and -- as of the persist-read fix earlier
today -- cross-boot/cross-arch persistence), K-conservation implemented
but not hard-asserted, and the unrecognized-disk halt implemented but
never actually exercised against real unrecognized content. Future
material (zones, USB hot-plug, ACL records, PKI) remains correctly
marked deferred -- unchanged.

.claude/CLAUDE.md: updated the Artemis pointer line so it's consistent
with the above instead of contradicting it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-02 09:15:16 -04:00
Robert Allan JamesandClaude Sonnet 5 1c1097ec40 Fix Artemis persist-read: reformat corrupted disk/artemis.img
Chases down the amd64/aarch64/riscv64 "FAIL: persist-read" that
capsules/artemis/init.4th's ART-READ-TEST self-test has been reporting
in every boot log in this repo's history.

Root cause: not a code bug. disk/artemis.img had been stuck in a
corrupted state (valid LithosAnanke magic header, but data not matching
what ART-READ-TEST expects) since before this repo's own git history
begins -- already broken at the initial commit, carried over from the
pre-split monorepo. The FAIL was accurate: it correctly reported bad
data, not bad code.

Verified via a fresh disk/artemis-debug-roundtrip.img: format ->
self-test PASS -> write-test PASS -> reboot -> resume -> PASS:
persist-read, confirmed 3 times in a row. The write/read/persist code,
free map, block allocator, and C-level block subsystem cache/writeback
logic are all correct.

Fix: blanked disk/artemis.img and let a normal boot format + write-test
it fresh, then verified PASS: persist-read on amd64, aarch64, AND
riscv64 against the same reformatted image -- confirming the arch-neutral
on-disk format works cross-arch too (a boot on one architecture writes
data the other two can correctly read back).

disk/artemis-debug-roundtrip.img is kept as a regression fixture, already
in a known-good passing state -- a future break here is a real
regression, not fixture rot like artemis.img turned out to be.

disk/README.md: documented both images' state, and corrected a stale
claim that these images are managed via scripts/rundisk.sh -- that
script actually targets a separate, currently-unused disks/ (plural)
directory for the hosted VM's --disk-img= flag, not this kernel-QEMU
disk/ (singular) one.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-02 09:11:07 -04:00
Robert Allan JamesandClaude Sonnet 5 1e2dc7fbf0 Close punch-list item #4: amd64 dict_hash non-determinism does not reproduce
Ran the amd64 kernel acceptance leg 5 times back to back as the punch
list's action item asked. dict_hash was byte-identical across every run
for every VM (Artemis, both Hermes instances, Hera/MAMA_INIT), and
matched aarch64/riscv64 exactly each time. Not jitter, and not
stable-but-different either.

The underlying mechanism the hypothesis pointed at is still real and
unchanged (capsule_dict_hash_hook() still folds execution_heat into the
hash; amd64 still runs its timer in RELATIVE mode under this
hypervisor) — but PARITY:MAMA_INIT and the child-VM PARITY:BIRTH lines
all print before the heartbeat starts, and heat only decays on
heartbeat ticks, so there's no window for the timer's non-determinism to
reach execution_heat before any of these hashes get computed. Most
likely the original 2026-07-24 observation was a one-off (loaded host
machine, coincidental timing), not a real gap.

No code change. No amendment to CLAUDE.md's acceptance criteria needed —
"identical dict_hash across all three architectures" holds up under
repeated testing. Closure note added to the punch list; the 5 verification
runs' logs and DoE CSVs are kept as the supporting evidence.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-02 08:05:28 -04:00
Robert Allan JamesandClaude Sonnet 5 d922e152aa Add riscv64-clang Makefile target; closes out 2026-07-24 punch list
Resolves item #3 of docs/working/archive/session-logs/2026-07-24-punch-list.md
("riscv64 hosted build isn't reachable via plain make"), the last open
item from that list. Decided against options (b) chasing GCC's riscv64
nanosleep-visibility failure at its root (undiagnosed, open-ended) and
(c) leaving it manual — instead wired the already-verified clang recipe
(commit 4485c38 / e287334) into the Makefile, mirroring the existing
rpi4-cross pattern.

Makefile: new riscv64-clang target. CFLAGS deliberately does not reuse
$(BASE_CFLAGS) (hardcodes -std=c99); clang needs -std=c11 -pthread here
instead. Registered in `make help` and .PHONY.

docs/lithosananke/hosted-acceptance-test/README.md: riscv64 section now
points at `make riscv64-clang` instead of the long manual invocation.
Updated Background section and commit list to reflect that all three
punch-list items touching this doc (#1 asm fix, #2 doc command, #3 make
target) are now resolved.

Verified: `make riscv64-clang` produces a binary with identical results
to the manual command it replaces (965 passed / 0 failed, "ALL
IMPLEMENTED TESTS PASSED!", "3 Goodbye!" for the piped acceptance script).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-02 07:43:01 -04:00