Captain Bob, 2026-11-12: "Wire L2, keep L7 on". The table held the 128
cards of the 7-bit L1-L7 space; nothing read L7, and the rolling window
recorded whatever the card said, so the 128 were 32 behaviours.
- compudynamics.c: cd_tuning_word() sizes the table to 64 (L1-L6).
- ssm_jacquard.c: card_bits() makes a card's 7-bit mask, L7 set, where a
card is read, seeded and started (C4: 17 -> card 8);
ssm_l8_force_config() still takes the 7-bit index, its L7 bit implied.
- physics_execution_hooks.c, kernel vm_core.c: the rolling window records
only while L2 is on (or before there is a config).
- ssm_jacquard.h says what is wired, bit by bit (it said L7 was);
v3/CLAUDE.md and the L8 reference corrected in place; FABRIC-3 XLVIII.21.
Tests first, seen to fail against HEAD (logs/20261112-210510/kernel-tests):
test_ssm_jacquard 4 of 7, test_physics_l2 1 of 4; all pass now. v4 reads
no rolling window: acceptance is unchanged; the protocol runs next.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RzZgrf7V7Zk2iczBXDRbm
Captain Bob, 2026-11-12: "correct deficiencies". heartbeat_capture_tick_snapshot
wrote cache_hits_delta and bucket_hits_delta as 0 ("TODO: wire ..."), though
physics_hotwords_cache.c counts both. Now each is read from the VM's hot-words
cache as a delta since the previous tick, the way word_executions_delta is
(and after the cache's stats are reset, the count since then); a VM with no
cache has 0. word_executions_delta was measured already: doe_log.c's "TODO: 0"
for it was stale, and its three column notes are corrected.
kernel/tests/test_heartbeat_export.c, 4 checks: a VM in memory with a cache
whose counters the test sets, captured tick by tick. Against the code as it
was: 3 failures (the hits 0 where the cache counted 5 and 3, then 7 and 7).
Now: 0. Built for amd64, aarch64, riscv64 and the hosted v3. Shared v3 code,
under the ruling. v3 is not part of acceptance; no DoE has run since 2026-09-16.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RzZgrf7V7Zk2iczBXDRbm
sk_v4_genesis never wiped its seed or its read-back; sk_v4_drive_arrived
never wiped the proxy's seed after her login; sk_v4_real_zuse_arrived wiped
the seed only on a match. (The logged-in Zuse's own key stays in g_zuse_seed,
by design.)
Option 1: the three use one buffer the identity module owns, g_seeds (seed,
then its read-back), and sk_v4_drive_arrived -- which every drive goes through,
and which alone calls the other two -- runs the drive's handling (now
handle_drive()) and then wipes it, whichever way it went.
sk_v4_identity_seeds() lets the host tests read it.
kernel/tests/test_sk_v4_identity.c, 10 checks, through the real drive-arrival
path: a fake xHCI controller marks a slot attach-pending, the identity code's
idle beat handles it, the real gpt.c reads the stick's table from the RAM disk;
the block layer, signatures, key mint, marker and console faked. Genesis; a
genesis not read back; the proxy logging in; a real Zuse named, then her stick
logging in. Against the code before the wipe: 5 failures (every case's seeds
left). Now: 0. The harness gains fake_disk_bind. Built for amd64, aarch64,
riscv64. #176 done with 2bac2ac8 (the mint).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RzZgrf7V7Zk2iczBXDRbm
sk_v4_mint_zuse wiped seed and rseed only when the real Zuse was minted; its
refusals and every failure after YES left them in stack memory.
Option 1 (Captain Bob, 2026-11-12: "1 is great"): the seeds are in a 64-byte
buffer the caller owns. sk_v4_mint_zuse_with(seeds) runs the mint (now a
private mint()) and then wipes the buffer, whichever way it returned -- one
place, every exit; sk_v4_mint_zuse() gives it a buffer of its own.
kernel/tests/test_sk_v4_mint.c, 17 checks: the real gpt.c on the RAM disk, the
identity code, key mint, signature checks, marker and keyboard faked; each of
the mint's eight exits run with the buffer filled with 0xEE first -- no stick,
under 64 MiB, not YES, the key mint failing, the read-back not hers, no
marker, the marker write failing, and minted. Against the mint as it was: 7
failures (every exit but success). Now: 0. Both runs in logs/20261112-181719/. Built for
amd64, aarch64, riscv64. The identity code's two (sk_v4_genesis,
sk_v4_real_zuse_arrived) follow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RzZgrf7V7Zk2iczBXDRbm
sk_v4_grow.c's dev_rw4k computes 1 KiB block numbers as (uint32_t)devblock * 4,
which wraps past 4 TiB; Artemis is capped at 256 GiB (STICK_ARTEMIS_CAP_GIB,
kernel/Makefile), so it is latent -- but nothing said so where the cast is. Now
a compile-time check there (the C99 negative-array pattern the tree uses): a
cap past 4 TiB stops the build, and a cap not defined at all is an #error, not
a skipped check. gpt.c's casts are bounded by the disk's own size, itself a
32-bit count of 1 KiB blocks, checked before each: each now says so.
kernel/tests' check_cap, seen to fail first (a 4096 GiB cap built); now 3
checks, 0 failures -- 256 GiB builds, 4096 GiB and none refused. Built for
amd64, aarch64, riscv64. The plan's increments 2 and 3 marked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RzZgrf7V7Zk2iczBXDRbm
sk_v4_idle allocated a controller's slot array again whenever max_slots + 1
was more than it held -- the old array dropped, neither copied nor freed --
while the identity code holds pointers into it (g_offered, g_spare,
g_pending_real, g_real_drive, g_zuse_dev). Its two slot loops then ran to
max_slots whatever the array held.
sk_v4_slots_get() (kernel/src/v4/sk_v4_slots.c): allocated once, zeroed, at the
size first asked for; never replaced; the slots that may be used are those it
has. sk_v4_idle uses it, and its loops stop at the array's end.
kernel/tests/test_sk_v4_slots.c, kmalloc_aligned faked, seen to fail first
against the allocation as it was (4 failures: replaced when more were asked
for, 17 said usable where 9 existed, a held pointer left behind, an allocation
for 0); now 6 checks, 0 failures. Built for amd64, aarch64, riscv64; latent
today (max_slots does not grow), so the boots show no change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RzZgrf7V7Zk2iczBXDRbm
sk_v4_run's three stops -- "not started", "did not let go of POST's
blocks", "stopped on a fault" -- ended in for (;;) { }: a core spinning at
100%, the thing #151 removed from the prompt. Each now calls sk_v4_stop()
(kernel/src/v4/sk_v4_stop.c): interrupts off, then arch_halt() for ever. Each
still says why first. sk_v4_stop.c added to the loader's and the kernel's v4
sources.
kernel/tests/test_sk_v4_stop.c, with arch_halt and arch_disable_interrupts
faked (the halt jumps back out after three calls; an alarm fails a spin):
against the stop as it was -- "spun for 2 s without halting"; now -- 2
checks, 0 failures: halts in a loop, interrupts off before the first halt.
Built for amd64, aarch64 and riscv64. #177's "seen on the three
architectures": no boot reaches these stops, so it is shown on the host only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RzZgrf7V7Zk2iczBXDRbm
After the configuration descriptor's first 9 bytes the event loop asked for
min(wTotalLength, 128) whatever it was. A wTotalLength of 9 or less was then
asked for again as "short" -- for ever, that slot never leaving enumeration
(#170); 0 also issued a zero-length data stage. A set longer than the 128-byte
buffer was cut without a word, its later interfaces never seen (#171).
The decision is usb_config_request_length() (moved out of the event loop,
then fixed): 9 or less is 0 -- "xhci: a configuration of N bytes has no
interface -- not used", and nothing more is asked; longer than the buffer is
cut and said -- "xhci: config of N bytes cut to 128 -- rest unseen" (at most
53 characters). The buffer stays 128 bytes; its size is #171's to rule.
kernel/tests/test_usb_config.c, seen to fail first against the decision as it
was (10 failures: wTotalLength 1 to 9 asked for again, the cut unsaid), now
20 checks, 0 failures. Both runs in logs/20261112-180140/kernel-tests/. Built for amd64,
aarch64 and riscv64; the boots come with the protocol. #171's other box, a
composite device on the SER5, is Captain Bob's.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RzZgrf7V7Zk2iczBXDRbm
xhci.c's XHCI_XFER_CONFIG_DESC_FULL case walked the descriptor set inline, in
the event loop, where nothing could test it. The walk is now
usb_config_parse() (kernel/src/usb/usb_config.c), returning what it found;
the case acts on it as before -- the same messages, the same decisions: the
first Interface descriptor only, a BOT interface's bulk endpoints (the last of
each direction), a boot keyboard's first interrupt IN endpoint, bLength 0
ending the walk, nothing read past len. One difference, in debug output only:
the bulk endpoints are logged IN then OUT, not in descriptor order.
kernel/tests/test_usb_config.c, 7 checks, pins the walk as moved: a BOT stick
as QEMU presents it, a boot keyboard with its HID descriptor, a mouse before a
drive (the first interface only), a drive with bulk IN only, bLength 0, an
endpoint cut by len, an interface cut before its protocol byte. All pass.
Built for amd64, aarch64, riscv64; the boots come with the protocol (a data
stick enumerates through this path). The fixes for #170 and #171 follow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RzZgrf7V7Zk2iczBXDRbm
gpt_read() stopped at max used entries (16 for the identity code, 4 in the
mint): a disk whose StarshipOS partitions came after the first max read as
having none, and the entries after max were never range-checked.
Now every entry is checked and every used one counted; a good table with more
used entries than max is GPT_MANY (the first max in parts), and the identity
code says "GPT: a USB drive has more than 16 partitions -- not used". Other
callers treat anything but GPT_OK as before.
Tested first, seen to fail: qemu_session.py gpt_many -- a data stick with its
two partitions as entries 17 and 18 after 16 others, and the same with an entry
19 past the usable range: both "not this system's stick" before (silently cut,
entry 19 never checked; logs/20261112-162236). After: "more than 16
partitions" and "a damaged partition table" (logs/20261112-162921).
Regressions: gpt_bad_entries, gpt_foreign PASS. gpt_zuse FAILS, on a stale
expectation unrelated to this change -- an identity-only GPT drive with no
system stick has been held silently as a possible real Zuse since #92 Task 4;
its log is kept here. The gpt_many images are made before the boot: a first
version made them after, failed in sgdisk and left its QEMU running, stopped
by hand (logs/20261112-162003).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RzZgrf7V7Zk2iczBXDRbm
The header said "M6: Infrastructure present (validation deferred)" and "M7:
Not started (VM integration pending)", and "(LithosAnanke branch)" -- there
is no such branch since the split. .claude/CLAUDE.md had warned since
2026-08 not to propagate it instead of it being corrected. Now: M0-M6 and M7
complete, M7.1 in progress (as CLAUDE.md's Architecture section gives them),
v4 the system booted by default; the correction dated in place. The note in
CLAUDE.md's Gotchas goes, as #144 asks. Comments only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RzZgrf7V7Zk2iczBXDRbm
failed() printed "MINT-ZUSE: failed -- nothing changed" on every failure
after YES, though by then gpt_write_one() had rewritten the stick's partition
table and the identity partition's first blocks had been zeroed.
gpt_write_one() now tells the two apart: GPT_EIO when its first write failed
(nothing written), GPT_PART when a later write or flush failed (the disk is
changed). MINT-ZUSE keeps "failed -- nothing changed" for GPT_EIO only, and
says "failed -- the stick was erased; Zuse not minted" for every failure
after the table is written. The marker is still written last.
Tested first, seen to fail: qemu_session.py mint_part_fail (writes fail only
at the identity partition's first sector, LBA 2048) -- "nothing changed",
logs/20261112-122132. After: mint_part_fail PASS; mint_fail (every write
fails) still "nothing changed", PASS; replace (the success path) PASS. Their
logs here. FABRIC-3 XLVIII.20.
Captain Bob, 2026-11-12: "start #168".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RzZgrf7V7Zk2iczBXDRbm
The four lines on the screen and serial while the proxy is recognised and her stick is not here; R runs sk_v4_mint_zuse(), every other key is dropped. Her stick pulled: the screen again. The system stick pulled while hers is in: hers let go first (the block chain's tail), remembered for the system stick's return. Builds on three ISAs; fallback running.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GE3SDvQn5EoxdGufk84zz2
A GPT stick with an identity partition and no Artemis, the system stick in and its marker naming a real Zuse: checked against the real key; a match attached and logged in (a running session moves to her); no match: 'not this system's Zuse stick -- not used'. Arriving first, it is remembered and checked when the marker is read. Builds on three ISAs; mint_zuse running.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GE3SDvQn5EoxdGufk84zz2
The header scan (Captain Bob, 2026-11-10): 788 source files -- 351 with the
SL-1.0 header, 12 with the old name (docs/working/experiments, kept by
ruling), 31 under someone else's licence (tools/kconfig, Linux's, GPL-2.0:
kept), and 394 with none. Ruled: the SPDX form; capsules (.4th, .f18) and
proofs (.thy) left for a ruling of their own (mkcapsule's 64x16 block format,
content-addressed capsules).
253 files now begin
SPDX-License-Identifier: LicenseRef-Starship-1.0
Copyright (c) 2023-2026 Robert A. James
as a /* */ block (C, headers, assembly, linker scripts) or # lines (scripts,
Makefiles, Kconfig; below a shebang or a coding line); tools/mkcapsule.c,
which had the copyright line, gets the SPDX line only.
Not touched: docs/working/ (archive and old campaign material, as the
ruling kept the experiments); v4/system/post_cases.c (generated by
v4/tools/mkpost.py, "do not edit" -- the generator has the header).
Comments only: the kernel builds on amd64, aarch64, riscv64; make -C v4
test-32: 39 programs, 0 failures; every changed script parses.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GE3SDvQn5EoxdGufk84zz2
Asks for a stick, shows its size, writes nothing until YES; then gpt_write_one, the partition's first devblocks zeroed, a fresh Zuse minted, read back, and only then the marker's real key. Any failure before the marker: 'failed -- nothing changed'. The gate remembers the last stick it turned away as a spare, offered first by the next mint. Builds on three ISAs; not yet called.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GE3SDvQn5EoxdGufk84zz2
sk_v4_identity_offer_wait() beats until a stick that is not this system's arrives (offered), a key is pressed, or the system stick leaves; sk_v4_identity_recheck_offered() runs the arrival check again once the mint has written it. Builds on three ISAs; not yet called.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GE3SDvQn5EoxdGufk84zz2
The old MBR zeroed first, the backup header and entries, the primary, the protective MBR last: a write torn anywhere leaves no table. Builds clean on amd64, aarch64, riscv64; checked on the host against blank 600 MiB and 32 MiB images and a used Zuse stick (sfdisk --verify clean, gpt_read reads it back). Not yet called.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GE3SDvQn5EoxdGufk84zz2
347 headers said "Licensed under the StarForth License, Version 1.0" -- a
shortening (Captain Bob, 2026-11-08: "License file SL I think I shortened
accidently"); LICENSE is, and has been since the first commit, the
Starship License 1.0 (SL-1.0, SPDX LicenseRef-Starship-1.0). 349 files,
566 occurrences, now "Starship License, Version 1.0 (SL-1.0)".
Left as they are, pending a ruling: docs/patent/ (the patent figures),
docs/formal/ (the citable volumes) and the generated reports under
docs/working/experiments/ (32 files). Comments and license text only:
the amd64 kernel builds, every changed script parses.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GE3SDvQn5EoxdGufk84zz2
Not yet accepted (plan 2026-10-29-stick-5, Task 2, step 3).
- gpt.c: the writer, gpt_grow_last() -- the backup entry array and header to
the disk's end, the last partition to the last usable sector (whole MiB,
capped), both headers' CRCs, the backup before the primary, the
protective MBR's size; gpt_alternate_lba().
- sk_v4_grow.c: the volume grown to its partition -- its fence (the Artemis
signature, Zuse's genesis marker) moved to the new top, the header last;
the partition grown no further than the BAM tracks (the block subsystem
places the fence by the device's size, so a volume must fill it).
- sk_v4_identity.c: a stick of 8 GB or more whose GPT ends short of the disk
is grown before its partitions are opened; "Artemis: grown to fill the
stick" / "under 8 GB -- not grown" / "past 256 GiB ... left unused".
- block_subsystem: blk_volume_compute_totals() (compute_totals_from_B,
public) and blk_subsys_set_format_bam_for(); v3's behaviour unchanged.
- mkartemis --bam-for, mkdiskimage.sh --bam-for, kernel/Makefile
STICK_GROW_MIN_BYTES / STICK_ARTEMIS_CAP_GIB: the stick's and data stick's
Artemis BAM sized for 256 GiB (6144 devblocks). STICK.md 5a: 256 GiB (R1).
After: stick_grow PASS (16 GiB: 12129370 user blocks, 8 s POST to prompt;
second boot not grown, recognised; sfdisk --verify: no errors), stick_small
PASS, check_images.sh PASS.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GE3SDvQn5EoxdGufk84zz2
Work in progress on StarForth-v4.0.0; not yet accepted (plan
2026-10-29-stick-5, Task 1).
- zuse_genesis_marker.h: the marker's version 2 -- the proxy's key and the
real Zuse's -- beside version 1.
- capsule_zuse_boot.c: capsule_zuse_boot_check_key() (the identity-record
half of the check) and capsule_zuse_boot_genesis_mint() (the mint half of
v3's genesis) split out; v3's two callers unchanged in behaviour.
- sk_v4_marker.c: the v4 boot reads version 1 as a proxy with no real Zuse,
and writes version 2.
- sk_v4_identity.c: no marker and a blank identity partition -> genesis:
mint, read back, then the marker; the proxy logs in. A marker with a real
key: no session for the proxy (the fixed screen is Task 5).
- qemu_session.py: boot()/shutdown(), so a mode can boot twice; modes
genesis and stick_genesis; stick_boot now expects genesis and the prompt.
Logs: genesis on amd64 before the change (both boots: "no genesis on this
disk", FAIL) and after (minted, then recognised: PASS). The DoE CSV of
032532 is from a run whose QEMU never started (no display; its empty log
directory was removed).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GE3SDvQn5EoxdGufk84zz2
An idle StarshipOS kept a CPU core 100% busy: sk_v4_readline(), with
nothing typed, and sk_v4_identity_wait(), headless until Zuse, waited in
arch_relax(). Both now halt until the next interrupt (hlt; wfi on aarch64
and riscv64): the heartbeat's timer, ISR-driven on all three, wakes them at
100 Hz, so a key is still seen within a tick.
Idle at the prompt, QEMU's CPU: 100% -> 1% of a core on amd64, 1% on
aarch64 and riscv64. Acceptance passes: amd64 logs/20261108-005539, aarch64
-005753, riscv64 -010136 (POST 538/538, one dict_hash, 7 ok), the stick
loaded high, test-32 (39 programs, 0 failures). FABRIC-3 XLVIII.5.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GE3SDvQn5EoxdGufk84zz2
Ruled by Captain Bob, 2026-11-07: "New root CA on the CI box. Valid
StarshipOS rather than snake oil intermediate."
- ci/pki/make-ca.sh makes the StarshipOS Root CA (its key stays on the CI
box) and the StarshipOS Capsule Signing intermediate it issues, and
stores the signing key as the secret CAPSULE_SIGN_KEY.
- The kernel finds the signing certificate by one name,
CAPSULE_SIGNING_CERT_NAME = pki:starshipos-intermediate.der. The
certificate itself lands once make-ca.sh has run; until then the
snakeoil intermediate is in the tree under its old name and the
capsules check as "no root key" (a warning, as "missing" was).
- mkcapsule refuses a signing key that is not that certificate's.
- SIGN_KEY has no default; acceptance, build, nightly and release set it
from the secret.
FABRIC-3.md XLVI.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GE3SDvQn5EoxdGufk84zz2
- hosted (v4/tools/hosted.c): a line longer than the buffer is read to its
end and refused as too long. Before, fgets handed it over in 1025-byte
pieces and the last piece ran as a line of its own.
- bare metal (kernel/src/v4/sk_v4.c): the line editor takes 1024
characters, as the boot does, and a line typed past that is refused as
too long instead of being cut at 255 and run. V4_BOOT_LINE_TOO_LONG is
now reported at the prompt and while a line waits.
- engine (v4/src/exec.c): a store the node itself refuses (to "any port",
"which port", How or Have) left the opcode running on: !+ advanced A,
against node.h's FAULTS, and !p advanced P past the handler, into the
next fault kind's entry. A refused store now does nothing more; a store
that raises an error (D-18) still counts as made, but P stays the
handler's.
- test_port.c: both cases; they fail on the old exec.c (A 1017, P 901).
Engine-level tests (all v4/tests/test_*.c but test_host_*) pass at 32 and
64 bits; test_port also under ASan/UBSan. Not yet run: make -C v4 test,
test-32, hosted-check, and the three QEMU acceptance boots.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GE3SDvQn5EoxdGufk84zz2
KernelArgs gains screen_boot; cmdline.c parses screen=boot (with or without --; any other value:
off). With it, the v4 boot keeps the console mirrored to the framebuffer after POST, with the TTF
font, and says 'Screen: the console stays on until login (screen=boot)'; login and logout work as
without it, and nothing can be typed without Zuse. Without it, unchanged. For the SER5, which has
no known serial port (ruled 2026-10-28). BRINGUP.md: the smoke test's stick and starforth.cfg.
Tests: screen_boot (new) failed on amd64 and aarch64 before, passes on all three ISAs after;
screen_dark_until_login, screen_dark_after_logout, zuse pass. Acceptance: amd64, aarch64, riscv64 to
[zuse@Hera] ok>, POST 538/538, dict_hash 0xfec8c47cfc6f437f; v4 suite and test-32 0 failures.
Plan: docs/v4.0.0/plans/2026-10-27-stick-4-one-disk.md, Task 5.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sk_v4_identity.c: a USB drive is this system's stick if its GPT has both StarshipOS partitions and
it is the disk the loader booted from (BootDisk's DiskGUID), or, when the boot was not from a
stick, the first such disk while none is attached. Then: its Artemis partition must carry a good
artemis_sig ('Artemis: the stick's signature is bad -- not used' otherwise), is attached first --
the first disk slot -- ('Artemis: the stick's partition attached'), Zuse's key is read from its
genesis marker, and its identity partition is checked and attached as before. Any other drive,
raw or GPT: 'USB: a drive that is not this system's stick -- not used'. On a pull the identity
partition is detached, then Artemis. sk_v4_identity_boot / sk_v4_run take the loader's BootDisk.
kernel_main.c: no virtio Artemis on the v4 path. kernel/Makefile: ARTDISK= attaches no virtio disk
(every QEMU target); ARTDISK stays for system=v3.
STICK.md 4 amended as ruled (R2): on v4 only this system's stick is used; users' drives minted by
this system's Zuse come with the users design after INSTALL. BRINGUP.md: Secure Boot off on every
PC (ruled 2026-10-28), RDRAND in QEMU, the SER5 smoke-test stick, A4 not on v4.
Tests: new stick_artemis (x3, no virtio disk), stick_zuse, two_sticks (replaces datastick),
artemis_unsigned, raw_ident; changed stick_boot, gpt_zuse, gpt_hotplug, midpull (replug) -- all
failed before, pass after (two test defects found and fixed: the signature's offset, the 63-char
log cut). Regression 20/20. Acceptance: amd64, aarch64, riscv64 to [zuse@Hera] ok>, POST 538/538,
dict_hash 0xfec8c47cfc6f437f; v4 suite and test-32 0 failures.
Plan: docs/v4.0.0/plans/2026-10-27-stick-4-one-disk.md, Task 4.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
uefi.h: the UEFI device path and Block I/O protocols, a typed LocateDevicePath, and BootDisk
{gpt, is_stick, disk_guid[16]} appended to BootInfo after args (boot_info_offsets.h unchanged).
The loader, before ExitBootServices, cuts its image's device path at the partition's HardDrive
node, locates the whole disk's Block I/O (whole path consumed, not a logical partition, 512-byte
blocks), reads it as 1 KiB blocks through a loader-side blkio adapter, and runs the kernel's own
gpt_read: is it a StarshipOS stick (identity and Artemis partitions), and its DiskGUID
(gpt_disk_guid, new). No HardDrive node (an ISO/CD boot), no GPT, or any failure: not a stick.
Monolithic builds only (the non-monolithic loader does not link gpt.c, and does not build today:
kernel/src/vm/vm_core.c -- reported, not fixed).
The kernel prints 'Boot disk: a StarshipOS stick' or '... not a StarshipOS stick', and on a stick
system=v3 gives 'System: v3 is not on this stick -- StarForth v4' and boots v4.
Tests: stick_v3 (new), stick_boot and zuse x3 failed on the new lines before, pass after; v3_keys
still boots v3 off a stick. Acceptance: amd64, aarch64, riscv64 to [zuse@Hera] ok>, POST 538/538,
dict_hash 0xfec8c47cfc6f437f; v4 suite and test-32 0 failures.
Plan: docs/v4.0.0/plans/2026-10-27-stick-4-one-disk.md, Task 3.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sk_v4_block_chain calls rng_init() after pci_init, with v3's two lines ("entropy: ready" / "entropy:
not available (continuing without)"); genesis on a fresh stick (increment 5) needs it. amd64 QEMU
runs -cpu qemu64,+rdrand (qemu, qemu-stick, qemu-esp) and the amd64 qemu target drops virtio-rng,
so QEMU takes the PC's RDRAND path; aarch64 and riscv64 keep virtio-rng, their only backend.
qemu_session.py: mode entropy (rdrand on amd64, both targets; virtio-rng on aarch64/riscv64; then
entropy: ready). system=v3 sessions get Zuse's raw identity image: v3 never reads a GPT, so the
Zuse stick (Task 1) left v3_keys headless -- found here; CLAUDE.md says so for v3 boots.
Tests: entropy FAIL x4 before, PASS x4 after; v3_keys PASS (rng: backend = rdrand). Acceptance:
amd64, aarch64, riscv64 to [zuse@Hera] ok>, POST 538/538, dict_hash 0xfec8c47cfc6f437f; v4 suite
and test-32 0 failures.
Plan: docs/v4.0.0/plans/2026-10-27-stick-4-one-disk.md, Task 2.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The increment 2 final review's minors 1-4 (Captain Bob, 2026-10-27: "fix 1-4"):
1. A protective MBR with no "EFI PART" header is GPT_BAD, not GPT_NONE: the
MBR says it is a GPT disk, and a GPT disk is never attached whole.
2. A disk whose block 0 cannot be read is GPT_EIO: "GPT: a USB drive could
not be read -- not used", nothing attached (it was read raw, attached
whole, and a raw Zuse drive behind it logged her in).
3. The entry array's LBA is checked against the disk's size directly.
4. gpt_bad_entries: the entry array changed, the header valid -- refused.
Harness modes gpt_no_header, gpt_read_error (blkdebug EIO on sector 0),
gpt_bad_entries. The plan carries the amendment.
Red (amd64): gpt_no_header 070335, gpt_read_error 070717 FAIL;
gpt_bad_entries 070048 PASS (existing check, now pinned). Green: every GPT
mode on amd64 071113-072933; gpt_zuse, gpt_read_error, gpt_no_header on
aarch64 073222-074047 and riscv64 074526-075535; the identity gate's and
increment 1's sessions 080036-082345, emergency 083130 (082635 was run
without EMERGENCY_CONSOLE_ENABLED=1 -- a harness invocation error).
Accepted: suite EXIT 0; 093121, 093352, 093750, POST 538/538, dict_hash
0xfec8c47cfc6f437f; test-32, 0 failures.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stick, increment 2 (plan 2026-10-26-stick-2-gpt-reader; docs/v4.0.0/
STICK.md 3 and 4: GPT for boot media and installed disks). kernel/src/gpt.c:
a CRC-32; gpt_read(), which checks the protective MBR, the primary header and
the partition-entry array (both CRCs, the revision, the sizes, the geometry);
and a partition as a blkio device, refusing blocks past its end. The two
StarshipOS partition types: identity 23BEBFE1-4817-4CF3-A537-9069BF159788,
Artemis BCD7B586-1C8E-4EEF-86A3-273A8D8EE624.
The v4 drive path: a GPT disk is used only through its StarshipOS identity
partition, and that partition -- never the disk -- is given to the block
subsystem, in a raw identity drive's place; a GPT disk with no StarshipOS
partition, or a damaged table ("GPT: a USB drive has a damaged partition
table -- not used"), is not attached (Captain Bob's ruling on the plan); a
disk with no GPT is read raw, as always. Pulling the drive detaches the
partition and ends her session. Reads only.
Red (amd64): gpt_zuse 193158, gpt_hotplug 193804, gpt_foreign 194713,
gpt_bad_crc 194947. Green: 195409, 195627, 195854, 200136; gpt_zuse aarch64
200424, riscv64 200839 (the 64 MiB partition attached, 48724 user blocks,
not the 72 MiB disk); the identity gate's and increment 1's sessions,
201119-203403, all pass.
Accepted (with the KERNEL_ARGS fix that follows): suite EXIT 0; 214300,
214507, 214911, POST 538/538, dict_hash 0xfec8c47cfc6f437f; test-32, 39
modules, 0 failures.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From the fresh review of the stick's increment 1 (With fixes):
- Critical: after logout Zuse's last screen, her prompt included, could be
drawn back on a headless machine. The logout clear (ESC[2J) blanked the
pixels only: Alt+TAB, still read while headless, redrew the terminal
from its saved text; and if she had Alt+TABbed to the graphics view
before the pull, the clear did nothing and her session stayed on screen
(and the console did not draw at her next login). Now vt100_reset()
forgets everything the terminal has shown -- the on-screen text, the
scrollback and their boot snapshots -- makes it visible again and fills
the screen; and Alt+TAB does nothing while the screen is off (v3's never
is, so v3 is unchanged).
- Important: an EMERGENCY_CONSOLE_ENABLED build no longer clears the screen
when Zuse's drive is pulled: its console goes on running.
Red: alttab_after_logout 183308 (4515 lit), alttab_before_pull 183529
(4539 lit; the console not redrawn), emergency_pull 183759 (0 lit).
Green: 184115, 184328, 184552; regression 184805-185956 (screens,
keyboard, hotplug, emergency, v3 typed by keyboard) all pass.
Accepted: 190111, 190312, 190710, POST 538/538, dict_hash
0xfec8c47cfc6f437f; test-32, 39 modules, 0 failures.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stick, increment 1, task 2 (plan 2026-10-26-stick-1-screen-and-keyboard;
docs/v4.0.0/STICK.md 2, 5.3, 5.4). On the v4 path:
- the framebuffer is brought up after POST, cleared and dark: console text
goes to serial only ("splash only, log on serial") until Zuse logs in;
then the screen is the console (TTF, a blinking cursor, as v3's), and
when she logs out it is cleared and dark again (console_fb_mirror(),
console_fb_clear(); the mirror is on by default, so v3 is unchanged);
- the line editor and the node's KEY read every keyboard -- PS/2, USB, the
virtio keyboard -- through the shared bridge (sk_input_getc()); the
headless wait drops keyboard input as well as serial bytes;
- an EMERGENCY_CONSOLE_ENABLED build has the screen from the start.
The framebuffer setup is one helper both paths call (kernel_main.c).
Red (before): screen_dark_until_login 153051 (10835 lit), console/keys
153309, logout 153609, kbd_typeahead 153918, virtio_kbd aarch64 154245
and riscv64 154718; no_framebuffer 155546 passed before and after.
Green: 155926 (0 lit), 160156, 160409 (0 lit after logout), 160630,
virtio_kbd 161414 and 161820, no_framebuffer 161206; usb_kbd with the USB
keyboard the only one (-machine i8042=off) 164548, v3 control 164757;
the identity gate's sessions 162245-163802 all pass; v3 typed by keyboard
164009, 164115. (usb_kbd 160857 and 164313 failed in the harness:
input-send-event's device names a display, not a keyboard.)
Accepted: suite EXIT 0; three clean qemu boots, POST 538/538, dict_hash
0xfec8c47cfc6f437f unchanged (174704, 174911, 175315); test-32, 39
modules, 0 failures.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On riscv64 a system=v3 boot never reached its prompt: Artemis's writes
"timed out" and Zuse was never confirmed (logs/20261026-134850). vblk_io()
bounded its wait by ~1M polling iterations, which on riscv64 under TCG is
about one heartbeat tick of real time -- shorter than a write that allocates
space in a sparse host image. Instrumented, the request was seen completing
just after the give-up ("used idx=409 last=408"), and every later request
then took its predecessor's completion for its own and returned before its
own I/O was done (a stale used index at each later request), reusing the
descriptor chain while the device still owned it: silent read and write
corruption from that point on. FABRIC-3.md's 2026-09-09 "request that never
gets a used-ring completion" is this same early give-up.
Now: the wait is for this request's own completion (the used index
reaching the avail index it was given), bounded by 5 s of real time on the
CPU's counter (timer_now_ns(), which needs no interrupts); a request that
truly never completes takes the disk out of service instead of letting a
later request reuse the chain.
Red (riscv64 system=v3, with temporary instrumentation): 151003, 151100 --
a timeout then dozens of stale completions each. Green: 151238, 151332 --
none; Zuse confirmed; the keyboard line answered. system=v3 with the fix:
riscv64 151438, amd64 151528, aarch64 151610, no timeouts. Accepted (v4):
151722, 151916, 152247, POST 538/538, dict_hash 0xfec8c47cfc6f437f;
test-32, 39 modules, 0 failures.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stick, increment 1, task 1 (plan 2026-10-26-stick-1-screen-and-keyboard).
repl.c's keyboard bridge -- the key tables, Shift/Alt/Caps Lock/Num Lock and
their lights, the keypad, Alt+TAB, and the per-tick USB keyboard servicing --
moves unchanged to kernel/src/hal/kbd_input.c as sk_input_getc() (and
sk_input_drop()), so that the v4 boot's console reads the same keyboards
(docs/v4.0.0/STICK.md 5.4). v3's behaviour is unchanged.
scripts/qemu_session.py: the typed-QEMU-session harness, kept in the repo
from here on (the identity gate's sessions, and v3_keys: system=v3 typed on
QEMU's keyboard with send-key).
Yardstick, system=v3 typed by keyboard: before, amd64 PS/2 134805 PASS,
aarch64 virtio keyboard 135549 PASS; after, 135746 and 135835 PASS.
(riscv64 134850: v3 never reached its prompt -- Artemis virtio_blk writes
time out; v3's block path, before this change; being investigated.)
Accepted: suite EXIT 0; three clean qemu boots to [zuse@Hera] ok>, POST
538/538, dict_hash 0xfec8c47cfc6f437f (145722, 145859, 150231); test-32,
39 modules, 0 failures.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From the fresh whole-branch review of the identity gate (With fixes; no
Critical, four Important):
1. A drive pulled while a line ran: the drives are looked at before every
prompt (sk_v4_identity_check_now), so no prompt is shown once she is
gone and what was typed after the pull does not run as Zuse.
2. Pulled while a line WAITS: the wait is ended at logout, by an empty
line, and not carried into her next session (Captain Bob, 2026-10-26:
"end the waiting line at logout"); re-attaching brings the prompt back.
3. EMERGENCY_CONSOLE_ENABLED: v4 honours it as v3 does -- a bring-up build
has a prompt with no drive (boards/BRINGUP.md A2).
4. Keys typed at a headless machine are read and dropped, not run as
whoever logs in next. (v3 has the same gap, sk_repl_headless_wait;
reported, not changed.)
Each a QEMU session red then green (amd64): midpull 111650 -> 112953,
waitpull 111907 -> 113154, emergency 112524 -> 113609, typeahead
112248 -> 113359; headless 113800 and hotplug 114043 still pass.
Accepted: three clean qemu boots to [zuse@Hera] ok>, POST 538/538,
dict_hash 0xfec8c47cfc6f437f on all three; hotplug on aarch64 and
riscv64; make -C v4 test test-32 sanitize hosted-check EXIT 0;
make -C v4 test-32 0 failures.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The identity gate on the v4 boot (plan 2026-10-26-engine-step4-identity-gate,
task G2; ENGINE.md 2, ruled 2026-10-20). As v3, by the kernel with no v3 VM:
after POST and the block chain, Zuse's public key from the genesis marker,
every xHCI controller brought up, the heartbeat started; then headless until
her drive is attached and checked (capsule_zuse_boot_check, G1). A drive that
arrives is given to the block subsystem by the kernel (Artemis is the
kernel's); her session is kept in the kernel for Hera, never in a node's
memory. The prompt is v3's, [zuse@Hera] ok>; pulling her drive ends the
session and the system goes headless; putting it back brings the prompt.
A blank pair stays headless: genesis writes Artemis's disk, Phase 5 (D2).
kernel/Makefile: ARTDISK and ZUSEDISK default to the minted pair,
disk/zuse-scratch/ (D4); .claude/CLAUDE.md's acceptance says so.
Fixed with it, at Captain Bob's word:
- kernel/Makefile: the ROM-and-map rule is a grouped target (&:). As two
targets of one plain rule its recipe ran twice at once under -j and the
copies met ("cp: cannot create regular file ... File exists"): 3 of 20
parallel builds failed, now 0 of 20.
- The v4 boot drops the logger to --log-level after POST, as v3 does
(sk_log_level_after_post, shared by both paths); INFO lines after POST,
3 before, 0 now.
Accepted: three clean qemu boots as built to [zuse@Hera] ok>, POST 538/538,
dict_hash 0xfec8c47cfc6f437f on all three (unchanged), 0 INFO after POST;
the hot-plug session (attach, line, detach, headless, re-attach) on amd64,
aarch64 and riscv64; headless with no drive; make -C v4 test test-32
sanitize hosted-check EXIT 0; make -C v4 test-32 0 failures.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
capsule_zuse_boot_check() makes the check capsule_zuse_boot_try_attach()
made -- the genesis marker, the drive's identity record (magic, version,
CRC-64, public key) -- and says HERS, GENESIS or NOT_HERS, with no VM;
capsule_zuse_boot_root_pubkey() reads the marker's key. try_attach and
load_root_pubkey call them; their behaviour and messages are unchanged.
The v4 boot will make the same check (identity gate plan, D1).
Shown on v3 (system=v3, amd64): the minted pair, "Zuse: identity
confirmed" and [zuse@Hera] ok> (logs/20261026-083603); a blank pair,
"Zuse: genesis minted onto attached thumbdrive" (logs/20261026-084729).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The host node starts in its ROM (entry 12), and the boot feeds it the
system's source through the console: capsules/v4/sys/map-64.f18, the
fourteen .f18 blobs and end.f18, each found, hash-checked and
signature-checked as any capsule, then EOT. The ROM assembles the system
and starts it at (IDLE); the host reads the image record
(v4_image_from_node) for what only the assembled system knows. A ROM
error is reported with its blob, line and meaning, then PARITY:FAIL.
Deleted: v4/tools/mkimage.c, capsules/v4/nucleus-64.f18, v4/capsule/*.v4,
v4/tools/v4tof18.py. The .f18 files are the source now.
- text.c speaks the rest of the ROM's language (set, here, store, start;
v4_text_value, v4_text_start), so the oracle reads the same source:
test_host_rom_nucleus compares the ROM against text.c on the .f18 files,
word for word, image record included. Host tests load the .f18 files.
- mkmap image writes v4_map_image (the map's description of the system);
hosted and the kernel link the ROM and the map instead of v4_image_64.c.
- Hera (Captain Bob's ruling, option 1): births stream the system the ROM
assembled, as assembled code; hera.4th is unchanged.
test_host_unit's host builds the system with the ROM.
- New parity lines: PARITY:V4_F18 per blob, PARITY:V4_ROM words=2049
rom_hash=0xa6c792b5821010b7 instructions=60725216.
Acceptance: amd64, aarch64, riscv64 boot to ok> with PARITY:V4_POST
tests=538 pass=538 fail=0, POST: PASSED, and PARITY:V4_SYSTEM
word_count=317 dict_hash=0xe5a57f3eb72ff94a on all three -- the same hash
as the prebuilt nucleus gave. make -C v4 test test-32 hosted-check: all
pass. Logs: logs/20261015-141648, -141938, -142350.
Startup cost under QEMU TCG, the ROM's assembly (last V4_F18 line to
V4_ROM): amd64 26.7 s of 132 s to ok>; aarch64 41.0 s of 195 s; riscv64
20.5 s of 101 s. Hosted native: +0.85 s.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Captain Bob, 2026-10-14: "v4 is the default build", "no, qemu to v4
too ... we are effectively trying to retire v3", "the 32 bit build needs
to go into the acceptance test too", "i don't care about v3".
- STARFORTH_V4 defaults on (Kconfig default y) and V4_DEFAULT=1
everywhere: every kernel carries v4 and v3 and boots v4; system=v3 boots
v3; STARFORTH_V4=0 builds v3 alone. Board images write system=v4.
- Acceptance (CLAUDE.md): the three ISAs as built (v4, POST passed,
V4_SYSTEM dict_hash equal) and make -C v4 test-32. v3 is not part of
it.
Acceptance run: amd64, aarch64, riscv64 -- PARITY:V4_POST 538/538,
dict_hash=0xe5a57f3eb72ff94a on all three, no unknown word; test-32: 30
suites, 0 failures. The SER5 image under OVMF boots v4 as built, v3 after
the one-word edit. README, BRINGUP, FABRIC-3 XLIII, the register updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
boards/BRINGUP.md A6, FABRIC-3 SXLIII (ruled: one image, switch at boot).
A STARFORTH_V4 kernel carries v3 too; system= in the boot arguments
chooses, else the build's default (V4_DEFAULT: 1 for plain STARFORTH_V4
builds as before, 0 for board images). make boot_image builds both and
always writes starforth.cfg / cmdline.txt with system=v3 first, and cleans
its ISA's objects first -- that clean was removing build/<arch>/SER5, not
the kernel's objects, until fixed here.
Each ISA: v3 by default (hash unchanged), v4 with system=v4 (538/538).
The SER5 image under OVMF: v3 as built, v4 after a one-word edit on it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
boards/BRINGUP.md A1, FABRIC-3 SXLII. xhci.c keeps a table of controllers
(up to 8) and services each; kernel_main brings up index 0, 1, ... until
there is none. repl.c's and blkio_usb.c's per-slot state is per
controller now (a slot ID is a controller's own), and each USB keyboard
keeps its own last report and lights; an unplugged keyboard releases what
it held.
Test first: keyboard and identity on a second controller behind a bridge,
Zuse on the first -- before, only Zuse; after, all three, on all three
ISAs. Hot-plug and unplug on the second controller. Acceptance unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
boards/BRINGUP.md A3 and S3, FABRIC-3 SXLI. A HID boot keyboard is
configured (interrupt IN endpoint, SET_PROTOCOL boot) and its reports
become the same Linux-keycode events as PS/2 and virtio (usb_kbd.c): the
full layout -- F-keys, navigation block, arrows, Num Lock, keypad. The
console treats Caps and Num Lock as toggles shown on the keyboard's
lights, and the keypad as digits or navigation. While a USB keyboard
runs, its event ring is serviced every tick, not once a second.
Found on the way: with no i8042 (port 0x64 reads 0xFF) the kernel hung at
boot in an endless drain -- a legacy-free PC would stop there. Detected
and skipped now; drains bounded.
QEMU, amd64 with the USB keyboard the only one: 1 2 + . on main keys and
keypad, Caps Lock, Num Lock all right; aarch64/riscv64 configure it too.
Acceptance on three ISAs unchanged; identities hot-plug.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
boards/BRINGUP.md A1, FABRIC-3 SXL. It was matched by QEMU's own IDs on
bus 0 alone, so no real board's was found. Now pci_find_class() reads
every bus/slot/function; pci_bar_size() sizes BAR0; the USB Legacy Support
capability hands the controller over from the BIOS, SMIs off; CSZ=1
contexts are placed 64 bytes apart instead of refused. XHCI_QEMU_ARGS
lets QEMU place the controller as real hardware does.
Test first: an NEC controller behind a PCIe root port -- no attach before,
Zuse minted after, on all three ISAs. Standard acceptance unchanged; five
hot-plugged identities attach. Not rehearsable: firmware handoff, CSZ=1.
Still open: only the first controller found is used.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
boards/BRINGUP.md R2/R3, FABRIC-3 SXXXIX. The entry stub cleans the cache
over the image, builds an identity map (the image's GiB Normal, the rest
Device) and enables MMU and caches at EL2 (or EL1); rpi5_mmu.c makes the
devicetree's RAM Normal and the framebuffer uncached. Mailbox waits give
up after one second; the buffer is cleaned around the exchange; the
framebuffer address goes through dma-ranges, not a Pi 4 mask.
New rpi5-rehearsal target runs the native image on QEMU virt at EL2. It
found three more that would each have stopped the board: the PL011 shift
(mine, c9519173; the host test asserted it), the GIC address not carried
through /soc's ranges (new fdt_reg_address(), checked against the real
Pi 5 DTB), and no timer IRQ at EL2 (HCR_EL2.IMO/FMO/AMO).
Rehearsal: POST 1052 passed, same parity hash as UEFI. Standard acceptance
on three ISAs: POST 1052, hash 0x82f4ae7cb2e2d424.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FABRIC-3 SXXXII.4's deferred LOG-FLUSH, built to the 2026-10-14 rulings
(SXXXVIII): everything a VM logs, at or above the log level, goes into its
own buffer; it goes to Artemis on LOG-FLUSH, when full, and at once on an
ERROR, as one kernel-Hermes LOG_BATCH message she writes on her own turn,
the source being the sender the kernel stamped. Records that cannot go yet
are counted and reported with the next flush. LOG-SHOW (Hera only) reads
the ring back -- it had no reader.
Found by the first test that filled it: ring growth overwrote the oldest
record and put blank slots inside the live range. Fixed test first.
Three ISAs: POST 1052 passed; new parity baseline word_count=526
hash=0x82f4ae7cb2e2d424 (deliberate). CLAUDE.md: Phase 8 PKI closed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Any identity could have arbitrary FORTH run as Zuse: KH-ELEVATE-SEND and
KH-BLK-ATTACH-SEND sent any text to Hera, from any VM (shown live). And the
drain popped a message after running it, so a payload calling a colon word
ran twice. Both fixed test first (FABRIC-3 SXXXVII):
- KH-BLK-ATTACH-SEND is ( dev-addr ok? -- sent? ), Artemis only; the kernel
writes the reply text. BLK-ATTACH-ACK is Hera's only.
- The drain pops before it runs.
- Elevation, ruled not needed: KH-ELEVATE-SEND, message type 8,
zuse-eligibility.4th, ZUSE-ELIGIBLE?/ZUSE-ELIGIBILITY-ADD/
ELEVATE-PUBKEY-UNPACK and the eligibility list removed; its disk area
stays reserved, unused. FABRIC-3.7 closed.
Three ISAs: POST 1052 passed; new parity baseline word_count=523
hash=0xb73b58052d591638 (deliberate).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FABRIC-3.5 SXXXII, closing FABRIC-3 SXXIX bug 2 (SXXXVI). A send marks the
receiver for the switch signal; the VM's own loop on its native stack takes
its messages and yields; Hera's idle beat makes the pending switch instead
of running other VMs' messages on her stack; every VM gets switcher turns
from birth; the drain refuses any call outside the VM's own context (the
test: it held every relayed line before, none now).
Found on the way: amd64's first-entry frame left rsp 8 bytes off the SysV
entry alignment (#GP on the first interpreter run on a VM's own stack), and
a VM yielding from its loop was marked SWITCHED_OUT, which stopped console
relay after one message and would have made KILL refuse it.
Relay of four lines to a WIREBIND identity on all three ISAs, five
hot-plugged identities, and standard acceptance (POST 1052, hash
0x08873e0f44b7cb2a) all pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FABRIC-3.md XXXIV.1, traced and fixed. vm_interpret() copied each text
into the VM's one input buffer and reset the position. LOAD runs its
block by calling it, so the block replaced the line LOAD was on and
nothing after LOAD or THRU on that line was ever interpreted; the same
for -->, a capsule's EXEC and a message kernel-Hermes delivers.
vm_interpret() now keeps what the outer text had not yet read when it is
called from inside another, and puts it back after, as FORTH-79's LOAD
keeps the input stream it interrupts. In the kernel's VM and the hosted
one alike.
POST: LOAD.rest_of_line (1 LOAD 0 0 /) and THRU.rest_of_line
(1 2 THRU 0 0 /) failed without the fix (logs/20261014-061222) and pass
with it. Three-ISA acceptance: logs/20261014-061508 (amd64), -061624
(aarch64), -061837 (riscv64), 1052 tests, none failed, zuse prompt,
hash 0x08873e0f44b7cb2a unchanged. The hosted binary: 100 LOAD 1 + .
gives 8.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Built with EMERGENCY_CONSOLE_ENABLED=1 and booted with no identity logged
in, the prompt printed "ok> ok" without end, 1.4 million times in 200
seconds (logs/20261013-210442). sk_console_readline() gave up at once
whenever no identity was present. The production build answers that by
going silent until a login; the bring-up build, which needs no login,
took it as an empty line and asked again. The give-up is now the
production build's only.
Found rehearsing boards/BRINGUP.md; it predates the console change of
c9519173 (the image built from the commit before that did the same).
The riscv64 bring-up build, no thumbdrive, three lines typed
(logs/20261013-235129): one prompt that waits, and 3, 56 and 81.
Acceptance of the production build, one QEMU at a time: amd64
(-235255), aarch64 (-235411), riscv64 (-235620) to the zuse prompt, no
unknown word, hash 0x08873e0f44b7cb2a on all three.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>