Port DoE trial-summary persistence (doe_region.c) onto the reshuffled branch

Local master had 9 unpushed commits building block-backed DoE persistence on
Artemis's own disk (FABRIC-3.md SXXXV.3/.4/.12, designed and built 2026-09-17,
never pushed) that this branch never inherited after the Tripod/kernel
reshuffle diverged history. Ports the mechanism cleanly onto the current
Hera/Artemis/Hestia shape:

- doe_region.h/.c: growable, CRC-protected trial-summary ring in Artemis's
  top-of-device metadata fence, immediately past log_region.c's own ceiling
  (devblock_from_top 98-102). Same accessor/CRC discipline as log_region.c.
- log_words.c: DOE-PERSIST-TRIAL and DOE-RESUME-COUNT kernel-only FORTH words.
- multiuser-doe.4th: per-ISA tagging (MU-ISA-TAG!) and campaign resume support
  (MU-EXEC-CAMPAIGN now takes a start-idx).
- per-isa-doe.4th (new): RUN-AMD64-CAMPAIGN/RUN-AARCH64-CAMPAIGN/
  RUN-RISCV64-CAMPAIGN entry points, resuming from DOE-RESUME-COUNT.
- extract_doe_region.py (new): host-side reader, CRC-verified, no filesystem
  assumptions -- reads the ring directly off a raw disk image.

Deliberately not ported: the R/LaTeX report-generation pipeline
(analyse_multiuser_doe.R, multiuser_doe_report.tex, build_multiuser_doe_report.sh)
-- not needed to prove persistence works, and unscoped for this pass.

Verified live on all three architectures: clean boot, word_count 530->532,
zero UNKNOWN WORD. amd64 and riscv64 both live-exercised end-to-end (write via
DOE-PERSIST-TRIAL, count via DOE-RESUME-COUNT, clean QEMU exit, then read back
correctly with extract_doe_region.py against the raw disk image -- no host
serial dependency, matching the real bare-metal requirement this campaign is
rehearsing for).

riscv64 finding (not a defect in this port, no fix applied): DOE-PERSIST-TRIAL
failed 3/3 on a first attempt against a fresh disk image with
"virtio_blk: vblk_io timed out". Root cause: disk/artemis.img is a sparse file
(180KB allocated of a 1GB apparent size); the new DOE region (devblock_from_top
98-102) is genuinely virgin territory on a fresh image, and riscv64's slower
TCG emulation missed the existing virtio_blk spin-bound budget (already
documented as fragile, FABRIC-3.md 2026-09-09) on that first cold touch.
Confirmed by isolation: a plain BLOCK/UPDATE at an already-warm LBN succeeded
instantly, the pre-existing LOG-ERROR" (same meta-zone mechanism, an
already-warmed offset range) also succeeded, and a reboot reusing the
now-partially-warmed disk let DOE-PERSIST-TRIAL succeed immediately and
verify correctly on read-back. Pre-existing environmental fragility, not a
logic bug in the ported address computation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-09-23 11:01:26 -04:00
co-authored by Claude Sonnet 5
parent 6302dcb50e
commit 2d773ca923
44 changed files with 201851 additions and 26 deletions
+92
View File
@@ -19,6 +19,9 @@
#include "starkernel/log_attrib.h" /* vm_log_attributed_vm() */
#include "starkernel/capsule_birth.h" /* capsule_vm_registry_get, VMRegistryEntry */
#include "starkernel/console.h" /* console_println() -- (LOG-APPEND-RAW) diagnostics */
#include "starkernel/doe_region.h" /* doe_region_append() -- DOE-PERSIST-TRIAL */
#include "starkernel/capsule_vm_physics.h" /* vm_physics_fleet_heat_sum(), vm_physics_conserved() */
#include "starkernel/capsule_vm_switch_signal.h" /* sk_vm_switch_signal_switch_count() */
#include <string.h> /* strlen */
#endif
@@ -296,6 +299,92 @@ static void log_word_append_raw(VM *vm)
}
#endif
/* ── DOE-PERSIST-TRIAL ( run cfg rep nw mode fail isa-addr isa-u -- ) ── */
/*
* Kernel-only. Writes one trial-summary record to Artemis's on-disk DoE
* ring (doe_region.c), FABRIC-3.md §XXXV.3/.4. Called from
* MU-EMIT-TRIAL-MARKER (multiuser-doe.4th / per-isa-doe.4th) right
* alongside the existing console print, so a campaign's data survives
* past the QEMU serial log without needing a live host tail.
*
* fleet_k_q48/fleet_conserved/switch_count_cumulative are NOT caller-
* supplied -- read directly here via vm_physics_fleet_heat_sum(),
* vm_physics_conserved(), sk_vm_switch_signal_switch_count(), same
* discipline (LOG-APPEND-RAW) uses for its own source attribution: a
* trial-end snapshot no FORTH caller could spoof or get stale by passing
* the wrong value.
*/
#ifdef __STARKERNEL__
static void log_word_doe_persist_trial(VM *vm)
{
if (vm->dsp < 7) {
console_println("DOE-PERSIST-TRIAL: stack underflow");
vm->error = 1;
return;
}
cell_t isa_u = vm_pop(vm);
cell_t isa_addr = vm_pop(vm);
cell_t fail = vm_pop(vm);
cell_t mode = vm_pop(vm);
cell_t nw = vm_pop(vm);
cell_t rep = vm_pop(vm);
cell_t cfg = vm_pop(vm);
cell_t run = vm_pop(vm);
if (isa_u < 0 || isa_addr < 0 || (isa_addr + isa_u) > (cell_t)VM_MEMORY_SIZE) {
console_println("DOE-PERSIST-TRIAL: isa string address out of range");
vm->error = 1;
return;
}
const char *isa = (const char *)&vm->memory[isa_addr];
uint64_t fleet_k_q48 = vm_physics_fleet_heat_sum();
unsigned fleet_conserved = vm_physics_conserved() ? 1u : 0u;
uint64_t switch_count = sk_vm_switch_signal_switch_count();
if (doe_region_append((uint32_t)run, (uint32_t)cfg, (uint32_t)rep, (uint32_t)nw,
(uint32_t)mode, (uint32_t)fail,
fleet_k_q48, fleet_conserved, switch_count,
isa, (uint32_t)isa_u) != 0) {
vm->error = 1;
}
}
#endif
/* ── DOE-RESUME-COUNT ( isa-addr isa-u -- n ) ── */
/*
* Kernel-only. §XXXV.12 campaign crash-resume: counts CRC-valid, live
* records in Artemis's DoE ring matching the given ISA tag. Called by
* each RUN-*-CAMPAIGN entry word (per-isa-doe.4th) before MU-EXEC-
* CAMPAIGN, so a re-boot after a crash resumes at the first trial that
* doesn't already have a good persisted record, instead of restarting
* the whole campaign from trial 0.
*/
#ifdef __STARKERNEL__
static void log_word_doe_resume_count(VM *vm)
{
if (vm->dsp < 1) {
console_println("DOE-RESUME-COUNT: stack underflow");
vm->error = 1;
return;
}
cell_t isa_u = vm_pop(vm);
cell_t isa_addr = vm_pop(vm);
if (isa_u < 0 || isa_addr < 0 || (isa_addr + isa_u) > (cell_t)VM_MEMORY_SIZE) {
console_println("DOE-RESUME-COUNT: isa string address out of range");
vm->error = 1;
vm_push(vm, 0);
return;
}
const char *isa = (const char *)&vm->memory[isa_addr];
uint32_t n = doe_region_count_for_isa(isa, (uint32_t)isa_u);
vm_push(vm, (cell_t)n);
}
#endif
/* ── Registration ────────────────────────────────────────────────────── */
/**
@@ -308,6 +397,7 @@ static void log_word_append_raw(VM *vm)
* String literals: LOG-ERROR" LOG-WARN" LOG-INFO" LOG-TEST" LOG-DEBUG"
* Stack string: LOG-ERROR-STR LOG-WARN-STR LOG-INFO-STR
* LOG-TEST-STR LOG-DEBUG-STR
* Kernel-only: (LOG-APPEND-RAW) DOE-PERSIST-TRIAL DOE-RESUME-COUNT
*/
void register_log_words(VM *vm)
{
@@ -344,5 +434,7 @@ void register_log_words(VM *vm)
#ifdef __STARKERNEL__
register_word(vm, "(LOG-APPEND-RAW)", log_word_append_raw);
register_word(vm, "DOE-PERSIST-TRIAL", log_word_doe_persist_trial);
register_word(vm, "DOE-RESUME-COUNT", log_word_doe_resume_count);
#endif
}