FABRIC-3.md: record this session's work
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
2d773ca923
commit
1a490816f9
+387
-6
@@ -5473,7 +5473,90 @@ device it was minted on.
|
||||
- The **old 30 MiB `disk/artemis.img`** is left alone / superseded, not deleted here -- disposal
|
||||
of the old file is a separate, later call once the new one is confirmed live and correct.
|
||||
|
||||
### XXXV.7 -- Genuinely open, not decided here
|
||||
### XXXV.7 -- §XXXV.6 EXECUTED live: fresh `artemis.img` formatted, all 9 identities re-minted,
|
||||
verified end to end (2026-09-17)
|
||||
|
||||
Before executing, confirmed the root cause that made re-mint (not just re-registration)
|
||||
mandatory, not merely likely: `capsule_zuse_boot.c`'s `genesis_marker_read()` reads Zuse's
|
||||
`zuse_pubkey` root-CA marker from **Artemis's own disk fence** (`devblock_from_top=0`), not from
|
||||
her thumbdrive -- her thumbdrive only carries her seed/cert, checked *against* that marker.
|
||||
With the marker gone (fresh `artemis.img`), `capsule_zuse_boot_try_attach()`'s own logic
|
||||
silently no-ops for a non-blank, already-minted drive (the `!have_marker` branch only proceeds
|
||||
if the attaching drive is *also* blank) -- her old thumbdrive genuinely could not have
|
||||
re-authenticated against the new disk, confirming the user's original instinct
|
||||
("reminting will invalidate the existing identities too") was correct, not merely cautious.
|
||||
Also confirmed `capsule_mint_identity()` refuses to write over an already-recognized drive
|
||||
(`MINT_ERR_ALREADY_MINTED`), so bleaching all 9 thumbdrive images to blank first was mandatory,
|
||||
not optional, before any of them could be re-minted.
|
||||
|
||||
**Executed, in order:**
|
||||
1. Bleached all 9 `disk/thumbdrives/*.img` (zuse, bob, 00-06) to all-zero, 64 MiB each,
|
||||
verified zero nonzero bytes and size intact. Committed (`e56974e`).
|
||||
2. Booted amd64 (`make -f Makefile.starkernel ARCH=amd64 qemu QEMU_DISPLAY=none`, orchestrated
|
||||
headless via a bash+socat+QMP driver script, not `-display gtk` this time since this session
|
||||
has no visual terminal) with Zuse's now-blank drive attached at launch (the Makefile's own
|
||||
default `ZUSEDISK`). Confirmed live in the serial log: `Artemis: blank disk -- formatting`
|
||||
(fresh 1 GiB image formatted), `Zuse: genesis minted onto attached thumbdrive` (root CA
|
||||
written fresh to the new fence), prompt reached `zuse@Hera ok>` (session authenticated).
|
||||
3. With Zuse authenticated, minted the other 8 identities one at a time against the **same
|
||||
still-running instance** -- hot-attached each blanked thumbdrive over QMP
|
||||
(`human-monitor-command` wrapping `drive_add`/`device_add` against `xhci0.0`, mirroring the
|
||||
2026-09-06 session's HMP-not-QMP-JSON approach but reached through the QMP socket the current
|
||||
`Makefile.starkernel` actually wires up, since no separate HMP monitor socket exists in the
|
||||
current target), minted via the live console (`MINT-NUM`, a thin `MINT` wrapper defined
|
||||
at the console for the 6 numeric identities exactly as in the original 2026-09-06 procedure;
|
||||
full `full_name`/`username`/`email` `MINT` call for `bob`), then hot-detached
|
||||
(`device_del`/`drive_del`) before the next. All 9 `MINT: identity minted` confirmations
|
||||
present in the log (the 9th being the "00" identity, validated first as a single-device
|
||||
dry run before looping the remaining 7 -- caught and fixed a false-negative bug in this
|
||||
session's own verification helper, a classic `grep -c ... || echo 0` double-output bug, before
|
||||
trusting the result on the other 7).
|
||||
4. Clean `BYE` shutdown, confirmed zero leaked `qemu-system-x86_64`/`socat` processes afterward.
|
||||
All 9 thumbdrive images confirmed nonzero in their first 6 devblocks (same verification
|
||||
convention as the original mint); `disk/artemis.img` confirmed no longer blank
|
||||
(27,618 nonzero bytes of real fence/format data). Full log preserved:
|
||||
`logs/20260917-114652/amd64/qemu-amd64-20260917-114652.log`.
|
||||
|
||||
Committed and pushed (image/log commit follows this one). `disk/README.md` updated with the
|
||||
same account.
|
||||
|
||||
### XXXV.8 -- `capsules/per-isa-doe.4th` built: N=3 ratified over the §XXXV.5 recommendation
|
||||
(2026-09-17)
|
||||
|
||||
Captain Bob picked **N=3 reps/cell (18 trials/ISA, ~24.8h/ISA, ~74.5h/~3.1 days total)** over
|
||||
§XXXV.5's own N=2 safety-margin recommendation -- more replication depth, accepting less margin
|
||||
against the unmeasured aarch64/riscv64 per-trial-cost risk §XXXIV.4 already flagged. Scope for
|
||||
this pass, also confirmed directly: build and commit the driver capsule only, no live multi-hour
|
||||
campaign launch yet.
|
||||
|
||||
Built `capsules/per-isa-doe.4th` (blocks 5117-5119 -- the *only* 3 blocks left in the entire
|
||||
[2048, 5120) user block namespace; `mkcapsule --lint` enforces this ceiling and caught the first
|
||||
draft's block numbers landing one past it). Deliberately a thin wrapper, not new campaign
|
||||
mechanics: `multiuser-doe.4th`'s own `MU-EXEC-CAMPAIGN` already implements the full ratified
|
||||
6-cell factorial (`MU-N-CONC`=3 x `MU-N-MODES`=2) generically over `(seed n-reps -- )`, so this
|
||||
capsule adds only what the per-ISA pivot needed on top of it -- one fixed, documented seed per
|
||||
ISA (`AMD64-SEED`/`AARCH64-SEED`/`RISCV64-SEED`, arbitrary but fixed, not tuned),
|
||||
`PER-ISA-NREPS`=18, an ISA tag (`PER-ISA-TAG!`) threaded into a redefined
|
||||
`MU-EMIT-TRIAL-MARKER` so records from all 3 ISAs' sequential campaigns can be told apart once
|
||||
they land on the same shared `disk/artemis.img` (§XXXV.2 item 2's own requirement), and three
|
||||
single-word no-argument entry points (`RUN-AMD64-CAMPAIGN`/`RUN-AARCH64-CAMPAIGN`/
|
||||
`RUN-RISCV64-CAMPAIGN`) so a boot only ever needs one command, no operator-supplied seed/reps
|
||||
arguments to get wrong live.
|
||||
|
||||
**Verified live, load-only (no campaign trial run):** booted amd64, `S" multiuser-doe.4th" EXEC`
|
||||
then `S" per-isa-doe.4th" EXEC`, confirmed zero `UNKNOWN WORD`/redefinition-warning/error lines
|
||||
anywhere in the log after either load, confirmed all 3 entry words resolve (`' RUN-AMD64-CAMPAIGN
|
||||
DROP` etc., no error), confirmed the constants compiled correctly (`AMD64-SEED . AARCH64-SEED .
|
||||
RISCV64-SEED . PER-ISA-NREPS .` printed exactly `20260917 20260918 20260919 18`), clean `BYE`
|
||||
shutdown, zero leaked qemu processes. `disk/artemis.img`'s incidental `mounted_time` header churn
|
||||
from this boot was reverted before committing, per `disk/README.md`'s own documented convention.
|
||||
Log preserved: `logs/20260917-122832/amd64/qemu-amd64-20260917-122832.log`.
|
||||
|
||||
**Not done in this pass, by explicit scope:** no live campaign trial has actually been run on any
|
||||
ISA yet -- `RUN-AMD64-CAMPAIGN`/etc. are verified to *load and resolve*, not to have completed
|
||||
even one real trial. Launching the first live ~24.8h run is a separate, later step.
|
||||
|
||||
### XXXV.9 -- Genuinely open, not decided here
|
||||
|
||||
- **Exact v2 decimation factor**, if/when v2 is built at all (§XXXV.4).
|
||||
- Whether `THRU`'s own line-swallowing defect (§XXXIV.1, still un-root-caused at the C level) gets
|
||||
@@ -5482,9 +5565,307 @@ device it was minted on.
|
||||
applies either way.
|
||||
- Disposal of the two stale partial amd64 runs' data (§XXXIV.5's own still-open item) is unrelated
|
||||
to this pivot and remains undecided.
|
||||
- Whether the old 30 MiB `disk/artemis.img` gets deleted/archived once the new 1 GiB image is
|
||||
confirmed live (§XXXV.6).
|
||||
- **Nothing built yet** -- this section (including §XXXV.6's ratified drive-resize decision) is
|
||||
design/decision only, no capsule/C code, no new disk image, and no re-mint actually performed
|
||||
or committed for the drive resize, the persistence region, or the N=2 campaign shape.
|
||||
- Whether the old 30 MiB `disk/artemis.img` gets deleted/archived now that the new 1 GiB image is
|
||||
confirmed live and re-minted (§XXXV.7).
|
||||
- The block-backed DoE persistence writer itself (§XXXV.3/§XXXV.4) is still design-only -- no
|
||||
capsule/C code written for it. `per-isa-doe.4th` (§XXXV.8) only tags trial markers for it,
|
||||
does not persist them anywhere the serial log doesn't already reach.
|
||||
- When/whether to actually launch the first live per-ISA campaign run (amd64 first, ~24.8h) --
|
||||
not started, explicit scope decision this session.
|
||||
- The `ZUSE-ELIGIBILITY-ADD` elevation-eligibility list (narrower than baseline identity auth --
|
||||
only gates the `ELEVATE` flow in `capsules/zuse-eligibility.4th`, confirmed by reading its only
|
||||
call site) was **not** re-populated for any of the 8 re-minted identities during this pass --
|
||||
their baseline identity/auth works (confirmed live), but elevation eligibility, if any of them
|
||||
had it before, was not restored. Left open; re-add via `ZUSE-ELIGIBILITY-ADD` later if needed.
|
||||
|
||||
### XXXV.10 -- §XXXV.3/.4's persistence writer BUILT and live-verified end to end
|
||||
(2026-09-17)
|
||||
|
||||
**Root-cause correction first:** two live campaigns (the original single-ISA 180-trial run and
|
||||
this section's own per-ISA amd64 leg) were launched between §XXXV.8 and this entry on the
|
||||
mistaken assumption that the campaign was ready, without re-checking that §XXXV.9 explicitly
|
||||
flagged the persistence writer as design-only. Both runs depended entirely on a live serial-log
|
||||
tail and were killed mid-flight (one by a colliding peer session, one deliberately, by request)
|
||||
with no durable record beyond the raw serial logs already committed. Flagged directly by Captain
|
||||
Bob ("Just plain dropped the ball and ignored all previous instructions") -- correct. Saved as
|
||||
`feedback_verify_design_implemented_before_launching_campaign.md`: grep the actual source for a
|
||||
named primitive before treating a design section as launch-ready, don't infer readiness from a
|
||||
nearby "ratified"/"executed" header.
|
||||
|
||||
**Built, mirroring `log_region.c`/`log_region.h`'s exact pattern per §XXXV.3:**
|
||||
- `include/starkernel/doe_region.h` / `src/starkernel/doe_region.c` -- new, separate growable
|
||||
ring (own control header, own `devblock_from_top` base at 98, immediately past
|
||||
`log_region.c`'s own ceiling at 97 -- confirmed by direct arithmetic, not the `log_region.h`
|
||||
doc comment's slightly-off "97+" framing, which actually meant "97 is log_region's last used
|
||||
devblock," so 98 is the first genuinely free one). 64-byte slots (`doe_trial_slot_t`: two
|
||||
`uint64_t` aggregates -- `fleet_k_q48`, `switch_count_cumulative` -- six `uint32_t` trial
|
||||
fields matching `MU-EMIT-TRIAL-MARKER` exactly -- `run_id`/`cfg`/`rep`/`nw`/`mode`/
|
||||
`fail_count` -- plus `fleet_conserved` and an 8-byte ISA tag), 64 slots/devblock, growable
|
||||
1->4 devblocks (256 slots ceiling, well over one full 3-ISA campaign's 54 trials). Plain FIFO
|
||||
eviction at the ceiling (no priority-level concept for a trial summary, unlike
|
||||
`log_region.c`'s `LOG_REGION_PROTECTED_MAX_LEVEL`).
|
||||
- `DOE-PERSIST-TRIAL ( run cfg rep nw mode fail isa-addr isa-u -- )`, registered in
|
||||
`src/word_source/log_words.c` (same file as `(LOG-APPEND-RAW)`, same kernel-only pattern).
|
||||
`fleet_k_q48`/`fleet_conserved`/`switch_count_cumulative` are read directly inside the C word
|
||||
(`vm_physics_fleet_heat_sum()`, `vm_physics_conserved()`, `sk_vm_switch_signal_switch_count()`)
|
||||
-- not caller-supplied, same non-spoofable-attribution discipline `(LOG-APPEND-RAW)` already
|
||||
established for its own source field.
|
||||
- `Makefile.starkernel`: `doe_region.c` added to both `LOADER_SRCS_BASE` and `KERNEL_SRCS_BASE`
|
||||
alongside `log_region.c`.
|
||||
- Wired into both `multiuser-doe.4th`'s own `MU-EMIT-TRIAL-MARKER` (Block 5051, empty ISA tag --
|
||||
the base campaign driver has no ISA concept of its own) and `per-isa-doe.4th`'s redefinition
|
||||
(Block 5118, real `PER-ISA-TAG-BUF`/`PER-ISA-TAG-LEN` passed through) -- both fit within the
|
||||
existing block allocation with no new blocks needed (`per-isa-doe.4th` had zero spare blocks
|
||||
per §XXXV.8's own note; fit entirely inside the 4 lines of headroom Block 5118 already had).
|
||||
`mkcapsule --lint` PASS on both files after editing.
|
||||
|
||||
**Verified live, full round trip, not just "it compiles":**
|
||||
1. Clean build, zero new warnings, all 3 ISAs (amd64/aarch64/riscv64) -- confirmed `doe_region.o`
|
||||
present in both loader and kernel object trees.
|
||||
2. Booted amd64, loaded `multiuser-doe.4th` then `per-isa-doe.4th`, called
|
||||
`111 22 3 4 5 0 S" testisa" DOE-PERSIST-TRIAL` directly at the console -- `ok`, no error;
|
||||
`VM-ERROR? .` printed `0`. Clean `BYE`, zero leaked `qemu-system-x86_64` processes.
|
||||
3. **With QEMU fully exited**, read `disk/artemis.img` directly off disk with an independent
|
||||
host-side script (no live process, no serial socket involved) -- confirmed the control header
|
||||
at `devblock_from_top=98` (magic `'DOED'`, `devblocks=1 head_slot=0 tail_slot=1
|
||||
record_count=1`) and the first slot at `devblock_from_top=99` contain exactly
|
||||
`run_id=111 cfg=22 rep=3 nw=4 mode=5 fail=0 conserved=1 isa="testisa"` -- the literal values
|
||||
passed at the console, byte-for-byte, persisted with no host in the loop. This is the actual
|
||||
property §XXXV.1 identified as missing and required.
|
||||
4. Reverted the synthetic test write from `disk/artemis.img` (`git checkout --`) before
|
||||
committing, per this repo's own established convention (§XXXV.8's `mounted_time` precedent)
|
||||
-- the region's first real record should be an actual campaign trial, not test data.
|
||||
|
||||
**Still open, unchanged from §XXXV.9:** the host-side extraction/reader tool (reading the ring
|
||||
back into a CSV, mirroring `scripts/extract_doe.sh`'s role for serial logs) is not built -- v1
|
||||
scope per §XXXV.3 was the writer only, verified here by a one-off Python script reading the raw
|
||||
bytes directly, not a maintained tool. Launching the first live multi-hour campaign is still a
|
||||
separate, later, explicit decision -- this entry closes the persistence-writer gap, nothing more.
|
||||
|
||||
### XXXV.11 -- The reader tool from §XXXV.10's own "still open" note: built, plus a full
|
||||
R/LaTeX report pipeline, all live-verified (2026-09-17)
|
||||
|
||||
Requested directly: "mount the virtual drive on a loop device... spool the csv's into a file
|
||||
for the proper deeply detailed R/LaTeX report with svg charts... build that workflow script for
|
||||
automated production of the PDF artifact while execution of the experiment is underway." Closes
|
||||
the one item §XXXV.10 itself flagged as still missing.
|
||||
|
||||
**Built:**
|
||||
- `scripts/extract_doe_region.py` -- standalone reader, independent reimplementation of
|
||||
`doe_region.c`'s own ring-walk logic in Python (same devblock_from_top fence math, same
|
||||
head/tail/record_count ring-order walk) reading `disk/artemis.img` directly. Two modes:
|
||||
`--img PATH` (default, no privilege needed -- opens the image as a plain file) and `--loop`
|
||||
(attaches via `losetup --read-only`, needs sudo, detaches on exit -- the literal loop-device
|
||||
step requested; noted in the script's own docstring that the ring is raw binary at a fixed
|
||||
offset, not a filesystem, so there is nothing to `mount` in the POSIX sense once attached, it
|
||||
reads exactly like a regular file). Read-only in both modes; a torn read mid-write from a
|
||||
live campaign is rejected the same way `doe_region_ctrl_read()` itself rejects a bad
|
||||
magic/version -- "not yet initialized," not a crash.
|
||||
- `experiments/bare_metal/analysis/analyse_multiuser_doe.R` -- new analysis script, house style
|
||||
(`ggplot2`/`svglite`, same `theme_light_report()` shape as `analyse_multirep.R`), but
|
||||
deliberately NOT that script's fixed-manifest pattern: auto-discovers whatever CSV or CSV
|
||||
directory it's given and **degrades gracefully to a placeholder table/chart when 0 trials
|
||||
exist yet**, rather than erroring -- the explicit "while execution of the experiment is
|
||||
underway" requirement. Also writes an auto-generated LaTeX fragment
|
||||
(`report/sections/multiuser_summary_autogen.tex`) with the actual current trial count, fail
|
||||
rate, and per-cell breakdown, computed fresh every run -- the report's own numbers stay
|
||||
truthful across re-runs instead of going stale.
|
||||
- `experiments/bare_metal/analysis/report/multiuser_doe_report.tex` -- new self-contained
|
||||
report (same preamble/colour-palette convention as `bare_metal_doe_report.tex`), every
|
||||
data-dependent figure wrapped in `\IfFileExists` so the report builds correctly whether 0
|
||||
trials or many have been extracted.
|
||||
- `scripts/build_multiuser_doe_report.sh` -- orchestrates all of the above: extract -> R
|
||||
analysis -> `svg_to_pdf.py` (existing, reused unmodified) -> `pdflatex` (twice, for
|
||||
cross-references) -> `multiuser_doe_report.pdf`. `--csv PATH` skips disk extraction entirely
|
||||
(smoke-testing against an arbitrary CSV); `--loop` passes through to the extractor.
|
||||
|
||||
**Real bug found and fixed while verifying, not left in:** the R script's original
|
||||
`SCRIPT_DIR` detection (`sys.frames()[[1]]$ofile`, the same pattern `analyse_multirep.R` already
|
||||
uses) silently fell back to `getwd()` when invoked from the orchestration script's own working
|
||||
directory -- wrote `charts/`, `tables/`, and `report/sections/` at the repo root instead of
|
||||
under `experiments/bare_metal/analysis/`, confirmed live (all three stray directories actually
|
||||
appeared there on the first orchestrated run). Fixed with the robust `commandArgs()`
|
||||
`"--file="` idiom, re-verified correct placement, stray directories removed. Not touched in
|
||||
`analyse_multirep.R` itself -- out of scope, not verified broken there, flag only.
|
||||
|
||||
**Verified live, full pipeline, both branches:**
|
||||
1. **No-data branch:** ran the full orchestration script against the current (post-§XXXV.10
|
||||
test-write-reverted) `disk/artemis.img` -- 0 trials, placeholder chart/table/summary
|
||||
generated, real 2-page PDF produced, correctly states "No trial data has been persisted to
|
||||
the DoE region yet."
|
||||
2. **Populated branch:** booted amd64, wrote 3 synthetic trial records via `DOE-PERSIST-TRIAL`
|
||||
at the console (one deliberately with `fail=1`), clean `BYE`. `extract_doe_region.py`
|
||||
round-tripped all 3 records correctly, in ring order, byte-exact. Full orchestration run
|
||||
(`--csv` override, to isolate the R/LaTeX stage from the disk-extraction stage) produced a
|
||||
real 4-page PDF with correct auto-generated summary text ("3 completed trial(s)... Overall
|
||||
fail rate... 33.3%...") and real embedded SVG-derived charts.
|
||||
3. Reverted the synthetic test write from `disk/artemis.img` afterward (same convention as
|
||||
§XXXV.10) and regenerated the final on-disk report/CSV against the real, empty state before
|
||||
committing -- what's in the repo now honestly reflects "no campaign has run yet," not
|
||||
leftover demo numbers.
|
||||
4. Reverted an unrelated side effect caught before committing: `svg_to_pdf.py` (existing,
|
||||
unmodified) re-converts every SVG in the shared `charts/` directory on each run, which
|
||||
touched ~85 pre-existing, unrelated committed figure PDFs with meaningless
|
||||
binary/metadata-only diffs (almost certainly `rsvg-convert` version drift, not content
|
||||
changes) -- reverted before staging, confirmed only the new `multiuser_*` files remain
|
||||
changed.
|
||||
|
||||
**Not done, explicitly out of scope for this pass:** the actual multi-day campaign launch is
|
||||
still a separate, deliberate decision -- this closes only the "how do we get a report out of
|
||||
it" side. `--loop` mode was implemented per the literal request but not live-tested (the
|
||||
default `--img` direct-file mode was what got exercised above; both modes share the same
|
||||
`read_ring()` code path once handed a device/file path, so this is a thin, low-friction gap, not
|
||||
an unverified core mechanism).
|
||||
|
||||
### XXXV.12 -- End-to-end resilience design: per-slot CRC + campaign crash-resume
|
||||
(design ratified 2026-09-17, build in progress)
|
||||
|
||||
Prompted directly: "we will start designing the end to end" -- scoped to the whole disk-to-PDF
|
||||
pipeline as one coherent system, not the pieces built ad hoc across §XXXV.10/.11. Two real gaps
|
||||
found by walking the pipeline stage by stage:
|
||||
|
||||
1. **Slot-level corruption is currently undetectable.** §XXXV.10's control header has its own
|
||||
CRC (`doe_region_ctrl_read()` rejects a bad one as "not yet initialized," same discipline
|
||||
`log_region.c` already uses). Individual `doe_trial_slot_t` records have no such check --
|
||||
and since `DOE_SLOTS_PER_DEVBLOCK`=64 slots share one 4 KiB devblock via a read-modify-write
|
||||
(`doe_region_write_slot()`), a crash mid-write to *any* slot in a devblock can corrupt its
|
||||
*siblings* too, silently, with nothing to catch it.
|
||||
2. **The campaign itself has no crash-resume.** `RUN-AMD64-CAMPAIGN` (§XXXV.8) always starts
|
||||
`MU-EXEC-CAMPAIGN`'s trial loop at index 0. A crash at trial 11/18 leaves 10 good persisted
|
||||
records but restarts the whole 18-trial run from scratch on reboot, re-running trials whose
|
||||
data already exists.
|
||||
|
||||
**Ratified design, block-namespace-checked before committing to it (both fit in existing
|
||||
headroom, no new blocks needed -- the user range was flagged in §XXXV.8 as fully exhausted):**
|
||||
|
||||
- **Per-slot CRC**: `doe_trial_slot_t` already carries 12 unused padding bytes (§XXXV.10) --
|
||||
an 8-byte CRC fits with 4 bytes of padding to spare, no struct growth. Computed/verified the
|
||||
same way `doe_region_ctrl_t`'s own header CRC already is (CRC over every field except itself).
|
||||
- **`DOE-RESUME-COUNT ( isa-addr isa-u -- n )`**: new kernel-only primitive, same file/pattern
|
||||
as `DOE-PERSIST-TRIAL` (`log_words.c`). Walks the ring counting CRC-valid records matching the
|
||||
given ISA tag. A corrupted slot does not count as "done" -- that trial safely re-runs rather
|
||||
than being silently skipped, so corruption degrades to "redo a trial," never "lose one
|
||||
silently."
|
||||
- **`MU-EXEC-CAMPAIGN` signature changes** from `( seed n-reps -- )` to
|
||||
`( seed n-reps start-idx -- )` (`multiuser-doe.4th` Block 5055) -- resuming needs no persisted
|
||||
"which trials ran" state at all, because the trial matrix is fully deterministic from
|
||||
`(seed, n-reps)` (Fisher-Yates over a fixed `SEED`, §XXXIII's own design): stash `start-idx`
|
||||
on the return stack, seed `MU-RUN-ID` with it, start the `DO` loop from `start-idx` instead of
|
||||
`0`. Verified by hand-counting: fits in exactly the 13 lines Block 5055 already uses, no new
|
||||
block. This is a real interface change (3rd arg now required, was optional-by-absence before)
|
||||
-- the only callers are the 3 `per-isa-doe.4th` entry words, confirmed by grep, so nothing
|
||||
else breaks.
|
||||
- **Each `RUN-*-CAMPAIGN` entry word** (`per-isa-doe.4th` Block 5119) calls
|
||||
`DOE-RESUME-COUNT` for its own ISA tag immediately before `MU-EXEC-CAMPAIGN`, passing the
|
||||
result as `start-idx`. Fresh campaign -> 0 -> unchanged behaviour. A resumed campaign picks up
|
||||
exactly where the ring says it left off. Verified by hand-counting: grows from 9 to 12 lines,
|
||||
still within Block 5119's 16-line ceiling.
|
||||
- **Extractor gets real slot-level CRC verification** (`scripts/extract_doe_region.py`) --
|
||||
pulling the actual CRC64 table/polynomial from `src/block_subsystem.c`'s `compute_crc64()`
|
||||
this time, not the incorrect guessed implementation removed in §XXXV.11, so host-side
|
||||
verification genuinely matches the kernel side bit for bit rather than only appearing to.
|
||||
|
||||
**Explicit scope note on testing:** the real amd64 per-ISA campaign (§XXXV.8 launch, tracked
|
||||
separately) is live on the only QEMU instance this project's own hard rule allows at a time.
|
||||
This work is being built and compile/lint-verified only while that campaign runs; live
|
||||
boot-verification (the same discipline every other closed item in this series has had) is
|
||||
deferred until that campaign finishes or is deliberately interrupted -- flagged here rather than
|
||||
silently skipped or claimed as tested when it wasn't.
|
||||
|
||||
### XXXV.13 -- Two real bugs found live, both fixed, before this design shipped further
|
||||
(2026-09-17)
|
||||
|
||||
Captain Bob quit the then-running campaign directly ("I think you should quit the running
|
||||
experiment and rerun it from scratch") once §XXXV.12's build unblocked live testing. First live
|
||||
verification pass found the ISA-tagging mechanism was **completely non-functional**, in two
|
||||
independent ways -- confirmed by checking the actual printed marker, not just "it resolves."
|
||||
|
||||
**Bug 1 -- redefinition never takes effect (found via the log, not a test).** The live campaign's
|
||||
own trial markers printed `MU-TRIAL run=... ` with no `isa=` field at all. Root cause: this
|
||||
project's FORTH compiles a colon definition's word calls to a direct, early-bound reference at
|
||||
*compile time*. `MU-RUN-TRIAL` (in `multiuser-doe.4th`) was compiled -- and its call to
|
||||
`MU-EMIT-TRIAL-MARKER` bound -- when `multiuser-doe.4th` loaded, before `per-isa-doe.4th` ever
|
||||
loaded and declared its own `MU-EMIT-TRIAL-MARKER`. That later declaration creates a second,
|
||||
independent dictionary entry; it does not, and structurally cannot, retarget `MU-RUN-TRIAL`'s
|
||||
already-compiled call site. `per-isa-doe.4th`'s whole "redefine the marker" strategy (§XXXV.8)
|
||||
never worked, from the moment it was written -- every persisted record from that campaign has an
|
||||
empty isa tag, not the intended per-ISA one, defeating §XXXV.2 item 2's own requirement.
|
||||
|
||||
**Fix:** moved ISA-tag storage into `multiuser-doe.4th` itself -- `MU-ISA-TAG-BUF`/
|
||||
`MU-ISA-TAG-LEN`/`MU-ISA-TAG!` (Block 5049, 4 lines of existing headroom) and a single, owning
|
||||
`MU-EMIT-TRIAL-MARKER` (Block 5051) that reads the shared variable at *run time*, not a
|
||||
redefinition anyone has to hope gets picked up. `per-isa-doe.4th` (Block 5119) now just calls
|
||||
`MU-ISA-TAG!` before `MU-EXEC-CAMPAIGN` -- no redefinition anywhere. Net effect: `per-isa-doe.4th`
|
||||
shrank from 3 blocks to 2 (Block 5118 fully freed, confirmed via `mkcapsule --manifest`), the
|
||||
opposite of the block-pressure this whole design worried about.
|
||||
|
||||
**Bug 2 -- found immediately after, live-testing the fix itself.** The first corrected build
|
||||
still printed `isa=` followed by blank space, not `amd64`. Root cause: `MU-ISA-TAG!`'s body
|
||||
(`DUP 16 MIN MU-ISA-TAG-LEN ! MU-ISA-TAG-BUF MU-ISA-TAG-LEN @ CMOVE`) was copied verbatim from
|
||||
the original `PER-ISA-TAG!` word it replaced -- and that word had the exact same latent bug:
|
||||
after the `DUP`/`MIN`/`!` sequence, the stack holds `(c-addr u)`, but the following
|
||||
`MU-ISA-TAG-BUF MU-ISA-TAG-LEN @ CMOVE` pops `(u BUF len)` as `CMOVE`'s `(src dest count)` --
|
||||
passing the ISA-string *length* as the copy *source address*, not the real string address. This
|
||||
bug was already present in `PER-ISA-TAG!` since §XXXV.8 and had never been caught, because
|
||||
§XXXV.8's own verification only checked that the three entry words *resolved*
|
||||
(`' RUN-AMD64-CAMPAIGN DROP`, no error), never that the tag they set was actually correct data --
|
||||
`CMOVE` reading from a bogus small "address" didn't fault, it just silently copied garbage/blank
|
||||
bytes. Fixed: `16 MIN DUP MU-ISA-TAG-LEN ! MU-ISA-TAG-BUF SWAP CMOVE` (the `SWAP` puts `c-addr`
|
||||
and `MU-ISA-TAG-BUF` in the correct `CMOVE` order). The old, buggy `PER-ISA-TAG!` word no longer
|
||||
exists anywhere in the codebase -- removed as part of Bug 1's fix, not patched in place.
|
||||
|
||||
**Both fixes verified live, together, in one boot:** `S" amd64" MU-ISA-TAG!` then
|
||||
`MU-ISA-TAG-BUF MU-ISA-TAG-LEN @ TYPE` -> `amd64` (was blank). Manually set all six
|
||||
`MU-RUN-ID`/`MU-CFG`/`MU-REP`/`MU-NW`/`MU-MODE`/`MU-FAIL-COUNT` variables then called
|
||||
`MU-EMIT-TRIAL-MARKER` directly -> `MU-TRIAL isa=amd64 run=7 cfg=3 rep=2 nw=8 mode=1 fail=0`,
|
||||
every field matching exactly. `VM-ERROR? .` -> `0`. `S" amd64" DOE-RESUME-COUNT .` -> `1`
|
||||
(counts the record just written); `S" riscv64" DOE-RESUME-COUNT .` -> `0` in the same boot,
|
||||
same ring (correctly isolates by ISA, doesn't cross-count). Clean `BYE`, zero leaked processes.
|
||||
Synthetic test write reverted from `disk/artemis.img` before committing (same convention as
|
||||
every prior entry in this series).
|
||||
|
||||
**Lesson, applies beyond this one case:** "resolves without error" and "produces correct data"
|
||||
are different claims -- §XXXV.8 verified the former for the ISA-tagging mechanism and reported it
|
||||
as done; both real bugs here would have been caught immediately by checking one actual printed
|
||||
value, which is what finally surfaced them. Worth remembering the next time "verified live"
|
||||
gets written down for something that only checked word resolution.
|
||||
|
||||
### XXXV.14 -- Flagged, not built: L8 Jacquard has no fault-triggered fallback to SSM_MODE_C0
|
||||
|
||||
Raised directly by Captain Bob mid-campaign, discussing what this DoE might reveal: the
|
||||
intended design is that a faulting VM should drop to `SSM_MODE_C0` ("Minimal, stable/predictable
|
||||
workloads," `include/ssm_jacquard.h`) and continue as a plain, non-adaptive VM rather than fault
|
||||
outright -- "the faulting VM simply becomes a plain old uncompudynamic VM." Confirmed by reading
|
||||
the actual source, not assumed: `ssm_l8_update()` (`src/ssm_jacquard.c:88`) computes its target
|
||||
mode purely from four workload-metric loop-gate bits (L2/L3/L5/L6, fed from `vm_time.c`) -- no
|
||||
reference to `vm->error` or any fault condition anywhere in the file. `SSM_MODE_C0` is real and
|
||||
is every VM's boot-time starting mode (`vm_bootstrap.c`), but nothing routes a VM back to it in
|
||||
response to a fault; the mode selector is an adaptive performance tuner, not a fault safety net,
|
||||
and only the first job is wired up. Bob's own diagnosis: "an oversight when building the
|
||||
stadium... forgot to carry that over from the hosted StarForth" -- a real regression from the
|
||||
standalone StarForth repo's design, not a new gap invented here (this repo has no visibility
|
||||
into that repo to confirm exactly how it was wired there).
|
||||
|
||||
One relevant building block already exists on this side: `ssm_l8_force_config()`
|
||||
(`src/word_source/inference_words.c`) -- a manual FORTH-callable primitive that force-sets a
|
||||
VM's L8 mode. Currently only ever invoked by hand; nothing calls it automatically on a fault.
|
||||
That is the natural mechanism a real fix would hook, not something to build from scratch.
|
||||
|
||||
**Not built.** Flagged per Bob's own explicit instruction ("we will continue in this direction
|
||||
with the workflow") -- find and log these live, keep the campaign running, don't stop to fix
|
||||
each one as found. Real feature, real gap, deliberately deferred.
|
||||
|
||||
### XXXV.15 -- Extractor bug: SLOT_FMT never updated after §XXXV.12's alignment fix
|
||||
|
||||
Found checking the real, live campaign's first two trials against the console log. Both slots
|
||||
read back as "CRC mismatch" in `extract_doe_region.py` even though the kernel-side write path
|
||||
was correct and the console log showed clean trials. Root cause: §XXXV.12's struct-alignment fix
|
||||
added an explicit `_align_pad` `uint32_t` field to `doe_trial_slot_t` (`doe_region.h`), but
|
||||
`extract_doe_region.py`'s `SLOT_FMT`/`SLOT_CRC_SPAN` were never updated to match -- every field
|
||||
after `isa` was being read from the wrong byte offset, so `slot_crc` itself was read from the
|
||||
wrong bytes entirely. Fixed (`SLOT_FMT = "<QQIIIIIII8sIQ"`, `assert SLOT_SIZE == DOE_SLOT_SIZE`
|
||||
tightened from `<=` to catch exactly this kind of drift next time). Re-extracted the real
|
||||
campaign's first two trials correctly: `run=0 cfg=0 rep=14 nw=2 mode=uniform fail=0`,
|
||||
`run=1 cfg=5 rep=8 nw=8 mode=heterogeneous fail=0`, both `fleet_conserved=1`,
|
||||
`fleet_k_q48=65536` (exactly Q48.16 1.0) -- clean so far.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user