Commit Graph
16 Commits
Author SHA1 Message Date
Robert Allan JamesandClaude Sonnet 5 2b1ba031a5 Drain at the outermost checkpoint -- FABRIC-3.6.md task 3.4
sk_hermes_drain_checkpoint() interprets one queued payload per checkpoint
(ruled: one message per checkpoint), reusing sk_vm_at_outermost_interpret()
and placed before the switch-signal block in vm_core.c's existing
cooperative checkpoint (sk_vm_context_switch() doesn't return until
switched back to, so drain must come first or it silently never runs on
a switching checkpoint).

Amends FABRIC-3.5.md SXLIII.5, caught by advisor() before writing the
naive version: "recursive drain is prevented for free" via
g_vm_interpret_depth is true but only for same-message re-drain -- it
doesn't cover the separate same-VM reentrancy hazard FABRIC-3.md SXX
already named for Hera specifically (VMCallState saves rsp/exit_colon/
ecw_nesting only, never input_buffer/input_length/input_pos). Draining
calls vm_interpret() on the same vm whose own vm_interpret() call is
still paused mid-word at the checkpoint; without saving and restoring
the cursor by hand, the enclosing REPL line or LOAD block would be
silently truncated. sk_hermes_drain_checkpoint() snapshots and restores
input_buffer/input_length/input_pos/mode/error/abort_requested around
the call. Not a divergence from the ruling -- cursor preservation is the
implementer's own obligation inside the ruled mechanism.

Gated behind a system-wide pending-total counter so the common
no-message-in-flight case costs one integer read per word dispatch, not
a stadium_max_vm_count()-sized queue scan (also flagged by advisor() as
a real hot-path cost, not deferred).

Verified live on all three architectures: a self-test publishes a real
payload to Hermes, proves the depth gate via VM-EXEC-ing an existing
harmless colon word into Hermes (genuine nested vm_interpret(), depth 2,
must not drain), then drains directly from genuinely-outermost context
and confirms exactly one clean drain. dict_hash unmoved and identical
across architectures.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 01:27:11 -04:00
Robert Allan JamesandClaude Sonnet 5 66c2f3e539 Stage D batch 3: fix silent error sites in vm_core.c, incl. the two highest-value primitives; two real NULL-deref bugs found and fixed (FABRIC-3.md §XXXII.6)
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
All 13 silent vm->error=1 sites in vm_core.c now log a diagnostic
first: vm_enter_compile_mode, vm_compile_word, vm_compile_literal,
vm_compile_call, vm_exit_compile_mode, execute_colon_word (2 sites
each/combined), and the four fundamental memory primitives
vm_load_u8/vm_store_u8/vm_load_cell/vm_store_cell.

Found and fixed two real NULL-pointer-dereference risks while adding
the diagnostics: vm_compile_call() and vm_exit_compile_mode() each had
a combined `if (!vm || <cond>) { vm->error = 1; ... }` guard that
dereferenced vm->error even on the !vm branch of its own condition.
Split both, and applied the same defensive split to the four memory
primitives since vm_ptr()/vm_addr_ok() both tolerate vm==NULL
internally.

Live-verified, amd64: 999999999 @ . recovers correctly at Zuse's own
console (Stage A's mechanism holds), but the new vm_load_cell
diagnostic itself didn't print -- traced to memory_words.c's own
redundant, still-silent vm_addr_ok() pre-check in memory_word_fetch()
(and the same shape in memory_word_store()), which intercepts before
ever reaching vm_load_cell(). memory_words.c is vendored, out of this
initiative's scope, spun off to FABRIC-4.md -- recorded as a concrete
cross-reference for that future work rather than left to be
rediscovered.

Three-arch clean qemu acceptance passed, full POST suite included.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BWpNjdwPtFLuVLaAq44L9K
2026-09-15 17:29:37 -04:00
Robert Allan JamesandClaude Sonnet 5 d9da82b065 Stage 4 increments 2+3: WIREBIND VMs as switch-signal participants + mark-and-defer tombstone reap (FABRIC-3.md §XXVIII Stage 4)
Increment 2: WIREBIND user VMs (the ones that actually run FORTH work;
console VMs are pure REPL proxies and never participate) register as
Stage 3 switch-signal participants at attach, unregister at teardown.
Slot table bumped 8 -> 16, matching messaging.4th's own VM-MAX -- a real,
already-agreed ceiling, not an invented number. Added
sk_vm_switch_signal_unregister() (compaction-based; Tripod VMs never
needed removal, WIREBIND VMs cycle constantly and would otherwise
exhaust the bounded table).

Increment 3: implements the plan's own ratified option (A) for the
async-detach UAF risk -- mark-and-defer via a new pending_reap flag on
VMRegistryEntry, deliberately not a new VMState (capsule_vm_kill()
already treats VM_STATE_DEAD as idempotent success, which would silently
swallow a reap attempt; SWITCHED_OUT still accurately describes a
tombstoned VM until the moment it's actually freed). unclean_detach()
sets it when capsule_vm_kill() refuses a SWITCHED_OUT target; the Stage 3
checkpoint (vm_core.c) checks it before ever attempting to resume a
pending switch target, and calls the new capsule_vm_force_reap() instead
-- the one caller allowed to bypass capsule_vm_kill()'s own refusal,
because it runs at the exact safe cooperative point the switcher itself
controls. A new idle-tick sweep cleans up the WIREBIND live-table entry
once the reap has actually happened.

Verified clean on all 3 architectures (baseline regression -- no
WIREBIND attach happens in a plain boot). The reap mechanism's own
correctness under a genuinely parked context is verified separately,
next, via a temporary deterministic probe.

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

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

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

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BWpNjdwPtFLuVLaAq44L9K
2026-09-14 17:39:20 -04:00
Robert Allan JamesandClaude Sonnet 5 862d7d9c48 Stage 3 follow-on: fix stack-ownership corruption + DoE switch columns (FABRIC-3.md §XXVIII.1)
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
DoE CSV gained 6 switch-signal columns (switch_count_cumulative,
switch_current_slot, switch_*_readiness, switch_ticks_since), and verifying
them with a boot-time HB-ON probe surfaced a real livelock: the preemption
checkpoint could fire inside a VM-EXEC-nested execute_colon_word() call and
switch away from a stack it didn't own, parking a borrowed region of the
caller's stack under the wrong VM's saved-context pointer. The trampoline
bounce was the visible (safe) half of this; the corruption was the quiet
half, live in every prior "clean" Stage 3 boot without ever showing up in
the log.

Fixed by gating the checkpoint on being at the outermost vm_interpret()
call (g_vm_interpret_depth / sk_vm_at_outermost_interpret(), vm_core.c),
per Bob's decision. Also fixed two related bugs found in the same pass:
g_switch_back_to was a single global stale after first entry, now per-VM
state (native_switch_back_to); note_switch_performed() fired on resume
instead of switch-out, now called before the switch.

Verified on all 3 architectures: steady log growth (no freeze), zero
leaked QEMU processes, DoE columns internally consistent, Hermes/Artemis
confirmed genuinely executing (not just trampoline-bouncing). Temporary
HB-ON boot probe reverted after capture.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016UNhH1mhi52i6Qihh7ZV5S
2026-09-13 23:58:59 -04:00
Robert Allan JamesandClaude Sonnet 5 986d042aa7 Stage 3: timer-driven preemptive switching, live on all 3 arches (FABRIC-3.md §XXVIII)
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
Fourth stage of the preemptive context-switching plan, and the biggest.
LithosAnanke now genuinely, continuously preempts between Hera, Hermes,
and Artemis -- timer-driven, running live for the entire remainder of
every boot once the Tripod fleet registers, not a bounded probe.

A real design fork was resolved before writing code: the naive approach
(the timer ISR calling Stage 2's sk_vm_context_switch() directly) is
broken -- Stage 0's trap frame lives on whatever stack was active at
interrupt time, and jumping to a different stack via Stage 2's own
independent swap mid-handler would abandon that trap frame unresumed,
guaranteed corruption on the first tick. Chose the safer of two named
options: the ISR only ever sets a flag and returns completely normally
through its own full epilogue; the actual switch happens moments later,
via Stage 2's already-proven mechanism, at a safe cooperative checkpoint
on the mainline (execute_colon_word()'s per-word dispatch loop, checked
on literally every word, not throttled to the existing 256-word
heartbeat-tuning cadence) -- confirmed with the user that word-level
granularity is fine-grained enough given the eventual Zynq FPGA target
where a word is a mnemonic.

New capsule_vm_switch_signal.c/.h: a purpose-built run-readiness signal,
deliberately separate from capsule_vm_physics.c's execution-heat engine
(that one's own header documents itself as never touched from interrupt
context, by design). Slot table sized with headroom (8) rather than
hardcoded to today's 3 participants, so extending participation later is
another register() call, not a redesign -- per direct request to leave
room for swapping the participant set. Simple linear accumulate-then-
threshold for this first cut; a fancier law can replace it later without
touching the mechanism around it. heartbeat_tick() gains its one
deliberate, documented amendment to this file's own top-half/bottom-half
discipline -- the first time this codebase reaches into VM-scheduling
state from real ISR context.

Registration happens only after all three VMs are fully born, right
before the REPL starts -- no critical-section protection yet against
being switched away mid-birth-setup.

Known, flagged rough edge (not reconciled this pass): MSG-TICK's own
idle-pump and this new mechanism can still independently move control
between the same VMs; not observed to interact badly in verification,
but not fully unified either.

Verified interactively at the console on all 3 architectures with
continuous background preemption running throughout -- amd64 computed
`1 1 + .` -> 2, aarch64 computed `1 1 + dup DUP * . CR` -> 4, both
correct, REPL fully responsive, zero fault indicators over sustained
runtime.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016UNhH1mhi52i6Qihh7ZV5S
2026-09-13 19:32:36 -04:00
Robert Allan JamesandClaude Sonnet 5 57ac3fc304 Stage 1: per-VM native stacks, allocated but not yet executed on (FABRIC-3.md §XXVIII)
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
Second stage of the preemptive context-switching plan. Every VM (Hera,
every capsule_birth_baby()-born VM including WIREBIND identities) now
gets its own dedicated 2 MiB native C stack at birth -- but nothing runs
on it yet, that's Stage 2. Pure allocation-machinery proof.

Design correction made before writing code: the plan called for cloning
sk_vm_arena_alloc()'s guard-page pattern, but that pattern turns out to
be Mama-only -- host_services.c's kernel_alloc() gives every baby VM a
plain kmalloc() block for its dictionary arena, not a real guarded PMM
allocation. Stacks get the real treatment instead (new
sk_vm_native_stack_alloc()/_free() in arena.c): independent
pmm_alloc_contiguous() + guard pages for every VM without exception, no
singleton, no kmalloc fallback -- a stack overflow is exactly the
failure mode guard pages exist for, and a corrupted stack could corrupt
whatever saved context Stage 2 trusts.

2 MiB size matches this project's own established kernel-stack
convention (g_kernel_stack/g_rpi5_native_stack), not a guess -- that one
shared 2 MiB stack today already carries all VMs' combined nested
VM-EXEC recursion.

Three new VM struct fields, freed in vm_cleanup() alongside the existing
call_stack free. Allocation failure is non-fatal to birth.

All 3 architectures re-verified clean boot to ok>, no native-stack
allocation failures for any Tripod-fleet VM.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016UNhH1mhi52i6Qihh7ZV5S
2026-09-13 14:30:46 -04:00
Robert Allan JamesandClaude Sonnet 5 2a30212bd3 Real per-VM log persistence: source attribution + ACL pin (FABRIC-3.md §XXVII)
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
Wires the previously-unused vm_log_attributed_vm() into LOG-APPEND's kernel
primitive so persisted log records carry a trustworthy source (the real
attributed VM's registry name, or "HADES" pseudo-source) instead of a
caller-supplied, trivially forgeable string. Drops src-addr/src-u from
LOG-APPEND's stack signature accordingly. Pins LOG-APPEND via bare ACL-PIN
in Artemis's own init.4th, matching BIRTH/CAPSULE-BIRTH's precedent for a
privileged word that can't reach the shared, host-portable ACL.4th.

Also fixes two console-banner nitpicks: a mis-rendering em dash (U+2014)
in the boot banner, and drops "Emergency" from the CLI banner text.

Doc corrections to artemis_sig.h/zuse_eligibility_list.h reconciling the
three fixed devblock ranges now in play. LOG-FLUSH (the intended normal
entry point) and level-aware log eviction remain open, flagged not fixed.

Re-verified clean boot to ok> on all 3 architectures after every change.
riscv64 showed one new, unrelated virtio_blk write-timeout anomaly during
Artemis's early physics self-test (self-recovered, boot unaffected,
sector doesn't map to the log region) -- flagged, not investigated.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016UNhH1mhi52i6Qihh7ZV5S
2026-09-13 08:56:33 -04:00
Robert Allan JamesandClaude Sonnet 5 2c1b3cd695 Four bugs found live verifying the 8 identity thumbdrives (FABRIC-3.md §IX)
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
All found by actually running the identity workflow §VII/§VIII made
possible, not by code review:

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

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

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

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

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

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

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

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

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

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

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

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 11:22:51 -04:00
Robert Allan JamesandClaude Sonnet 5 e5cbc71f46 Phase 8 C (2/n): expand cert storage; NVRAM persistence crashed, reverted
Cert storage expanded from the old 16-byte placeholder to a real
32-byte seed + 32-byte pubkey. vm_zuse_cert_install() now has a
kernel-side duplicate in src/starkernel/vm/vm_core.c -- the kernel
build's VM_EXCLUDE list drops src/vm.c entirely (same reason
vm_set_base() already has two independent copies), so the hosted-only
version added earlier this session was never actually linked into the
kernel. FORTH-side ZUSE-CERT-LO@/HI@ replaced with ZUSE-PUBKEY@ (i -- u)
over the public half only; ACL-ZUSE-BOOT now checks
ZUSE-CERT-INSTALLED? before authenticating instead of unconditionally.

Attempted NVRAM-based persistence (GetVariable/SetVariable) for the
first-boot mint flow: page-faulted inside OVMF's variable service
(CR2 in the flash MMIO window). Moving the call site to match the one
proven-safe existing SetVariable call site in this codebase produced
the identical crash -- not a timing issue. Localized with debug
markers (one boot): GetVariable works; SetVariable with real data
never returns. The existing "working" precedent call is actually a
delete-of-nonexistent-variable (size=0, data=NULL), a cheaper path
that never touches flash, so it proved nothing about real writes.
Root cause: this kernel's VMM never maps the region OVMF's variable
service needs for real flash writes -- a genuine gap in UEFI runtime-
services support, not Zuse-specific, and not obviously fixable in a
3-arch-uniform way (flash window location is firmware/arch-specific).

Independently, storing the raw seed in RUNTIME_ACCESS NVRAM would have
been a real security defect regardless of the crash -- readable by any
later-loaded UEFI app or the booted OS.

Reverted to a known-safe state: all NVRAM/mint code removed from
kernel_main.c, init.4th's ACL.4th line back to its documented
commented-out default. Verified clean compile and clean boot on all
three architectures. Cert storage expansion (the part that works)
stays. A dedicated system-identity disk (virtio-blk, already proven
for writes via Artemis) is the recommended next substrate -- not yet
decided or built. Full investigation documented in FABRIC-3.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U14ET9CWAtbQMbYqomKgXd
2026-08-26 15:55:27 -04:00
Robert Allan JamesandClaude Sonnet 5 39838b604f Quiet the POST ECW dispatch trace; three-arch clean-boot verification
vm_core.c: demote the per-word "ECW: w=... func=... 'NAME'" dispatch trace
from LOG_INFO to LOG_DEBUG. POST forces the logger to LOG_TEST for the
duration of the self-test run, and LOG_TEST includes LOG_INFO, so every
single word execution during POST was echoing this trace -- hundreds of
lines burying the actual module summaries and pass/fail tally. Still
available via --log-level=debug.

Combined with HEARTBEAT_DOE_LOG=0 (command-line Kconfig override, no
default change -- experiments/bare_metal/'s own DoE tooling still gets
HEARTBEAT_DOE_LOG=1 by default), all three architectures now boot clean:
UEFI -> POST summary -> Mama birth -> Hermes self-test -> heartbeat ->
ok>, with no [HADES][DOE] rows and no ECW flood. Verified by three-arch
QEMU boot; logs attached.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-12 13:08:49 -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 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 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 James a5ed8c3d87 Initial commit — LithosAnanke kernel 2026-08-01 07:49:56 -04:00