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
Captain Bob, 2026-11-12: "Build an appropriate test harness! Love the idea."
The kernel had no tests below the boots, so defects QEMU cannot provoke could
not be tested first. Plan: docs/v4.0.0/plans/2026-11-12-kernel-host-tests.md.
kernel/tests/: one host program per kernel source, built with that source as
it is, the kernel's flags (-std=c99 -Wall -Wextra -Werror), and fakes --
the console captured line by line, a deterministic RNG that can fail, and a RAM
disk whose reads, writes or flushes fail at chosen 1 KiB blocks.
make -C kernel/tests test runs them; SRC_<test>= runs a test against other
code, which is how a test is seen to fail against the code before a fix.
test_gpt.c, 12 checks: a table written and read back; 16, 17 and 18 used
entries against max 16 and an entry out of range after the 16th (#180); the
RNG, the first write, a later write and a flush failing (#168). Against gpt.c
before #168 and #180: 4 failures, each defect caught. Now: 0. Both runs in
logs/.../kernel-tests/. Not yet a protocol stage: to rule (the plan, step 5).
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
kernel/Makefile gave capsules/v4/sys/map-64.f18 and map-32.f18 one rule with
two plain targets -- in GNU make, two rules sharing a recipe. Under -j both
ran at once: two make -C v4 sysmap built and ran the same v4/build/mkmap-<w>
side by side (Build run 255's RASPI job: "mkmap-32: Permission denied").
Now one grouped target (&:, GNU make 4.3+, as here and in the CI image).
Seen to fail first: the two targets with -j8, mkmap removed each time, 12
runs -- 6 failed ("Text file busy", "Permission denied"). After: 0 of 12.
Both sets of runs in logs/20261112-152711/race-185/. The map files are unchanged.
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 3).
- kernel/Makefile: a v4 `qemu` boot attaches a fresh data stick
(DATASTICK=1) and nothing else; ARTDISK/ZUSEDISK keep the minted pair only
for system=v3 (QEMU_V3_BOOT, from KERNEL_ARGS).
- qemu_session.py: the Makefile's defaults for every mode but those that ask
for the pair by name (stick_artemis, two_sticks, artemis_unsigned,
raw_ident, v3_keys) or for no drive; `zuse` now expects the genesis line.
- nightly.sh: accept stick-high runs stick_genesis (the stick as built).
- .claude/CLAUDE.md "The disks", the stick-high line; disk/README.md (the
pair: tests and v3 only); STICK.md 11, increments 3 and 5 done.
Before the change: zuse on amd64 FAIL -- the pair's "identity confirmed",
no genesis line (logs/20261108-130732).
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
Plan 2026-10-29-stick-5, Task 2, steps 1-2:
- qemu_session.py: --stick-gib N (the session's copy of the stick extended,
sparse); modes stick_grow (16 GiB: grown, over 12000000 user blocks, the
prompt, the first boot's time; the second boot of the same copy not grown
again; sfdisk --verify clean) and stick_small (4 GiB: under 8 GB, not grown,
said so).
- kernel/Makefile: STICK_GIB and STICK_FRESH on qemu-stick, for those.
- check_images.sh: the PC stick's and data stick's Artemis BAM sized for the
256 GiB cap (6144 devblocks).
Before the change: stick_grow FAIL (no growth line, 1137649 user blocks,
sfdisk: the backup GPT not at the end); stick_small FAIL (no line);
check_images.sh FAIL on the two BAM checks only.
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
v4.0.0-alpha.2's release run (200) was killed in the StarForth session:
Isabelle's defaults (Java 4 GiB, a thread per CPU). scripts/nightly.sh
proofs now runs one thread, Java 512 MiB, Poly/ML 900 MiB, through a
settings file of a home made for the run (heaps linked to the user's):
47 s, ~1.8 GiB, logs/20261107-195341. Nothing was published from alpha.2;
its tag stays. LITHOS_VERSION 4.0.0-alpha.3. FABRIC-3.md XLVII.6.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GE3SDvQn5EoxdGufk84zz2
v4.0.0-alpha.1's release run (196) was killed in the proofs: release.yml's
full `make init` fetched Isabelle into the workspace and pointed isabelle
at it, without the image's prebuilt HOL-Library, so the proofs built it
from scratch. Nothing was published. Ruled: the tag stays; release.yml runs
`init INIT_PROOFS=0`; LITHOS_VERSION 4.0.0-alpha.2, the first published
alpha. FABRIC-3.md XLVII.5, CHANGELOG.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GE3SDvQn5EoxdGufk84zz2
Captain Bob, 2026-11-07: "Kernel is 4.0.0. This is our first alpha
release." LITHOS_VERSION 2.1.0 -> 4.0.0-alpha.1; the engine (VERSION)
stays 3.2.0. The 4.x line is a working line (even major); v3.0.0 is no
longer the next number, and an LTS line takes the next odd major when
FABRIC-3.5.md XXX.5's criteria are met. Recorded in kernel/Makefile's
versioning policy.
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
The SER5 stopped at the loader's "Loading kernel from ESP...": built -fno-pic -fno-pie with
ImageBase 0, the image carried ~9,000 32-bit absolute addresses, and the firmware loaded it above
2 GiB, where the sign-extended ones (R_X86_64_32S) point into unmapped memory -- a page fault in
debug_checkpoint() before [CKPT 001]. Reproduced on QEMU's pc machine at 3 GiB (#PF, CR2 =
FFFFFFFFBD316542, the right address sign-extended); q35 always loads it below 2 GiB, so no QEMU
boot ever showed it.
amd64 now builds -fpie with kernel/include/starkernel/pie_hidden.h force-included (#pragma GCC
visibility push(hidden)), so globals are RIP-relative and none is reached through a GOT -- the
2026-08-06 bug that put -fno-pic there. The link refuses any object carrying a 32-bit absolute or
GOT relocation (4,531 on the old objects, none now). test_runner.c's visibility("default") on
global_test_stats is kept for the -fPIC ISAs only. make -f kernel/Makefile ARCH=amd64
qemu-stick-high boots the PC stick on pc with 3 GiB and is part of acceptance (.claude/CLAUDE.md).
Captain Bob, 2026-10-29; FABRIC-3.md XLIV.
Verified: qemu-stick-high with Zuse's pair, ok> and 7 ok, RIP sampled inside .text at base
0xBD297000 (logs/20261029-070253); acceptance amd64 -070508, aarch64 -070715, riscv64 -071138,
PARITY:V4_POST tests=538 pass=538 fail=0, POST: PASSED, dict_hash=0xfec8c47cfc6f437f on all;
make -C v4 test-32, 39 suites, 0 failures. Not yet booted on the SER5.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
mkdiskimage.sh --starship-from IDENT ART lays a minted pair out as a StarshipOS stick: an identity
partition holding IDENT's bytes and an Artemis partition holding ART's, each exactly its file's
size (Artemis's signature and fence are placed from the volume's size). kernel/Makefile: a
zusestick target, made fresh for every boot, is ZUSEDISK's default (ZUSE_PAIR names the pair);
STICK_PAIR puts the pair in the PC stick. qemu_session.py's Zuse drive is the pair as a stick.
check_images.sh checks both forms byte for byte against the pair (60 PASS).
On today's kernel all 23 sessions pass on the Zuse stick, and acceptance: amd64, aarch64,
riscv64 to [zuse@Hera] ok>, POST 538/538, dict_hash 0xfec8c47cfc6f437f; v4 suite and test-32
0 failures. The plan gains Task 5, screen=boot (ruled 2026-10-28).
Plan: docs/v4.0.0/plans/2026-10-27-stick-4-one-disk.md, Task 1 (R1 as recommended).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
qemu-stick boots the amd64 stick image (build/boards/pc/lithos-pc.img,
copied per run) as a USB drive on q35, exactly as hardware would.
DATASTICK=1 adds the data stick to any qemu run as a second USB drive.
qemu_session.py gains --target and the stick_boot and datastick modes.
Checks: stick_boot (the stick's own identity partition attaches, headless
on a blank identity); datastick on amd64/aarch64/riscv64 (both drives
found, the data stick's 500 MiB identity attaches); the seven GPT modes,
the identity-gate and increment 1 sessions, emergency; the suite; three
acceptance boots to [zuse@Hera] ok> with POST 538/538 and
dict_hash=0xfec8c47cfc6f437f; test-32 0 failures.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stick's increment 3, Task 1 (docs/v4.0.0/plans/2026-10-27-stick-3-images.md;
STICK.md 4). make boot_image TARGET=PC now writes the 2 GiB stick: ESP 64 MiB
(BOOTX64.EFI, startup.nsh, starforth.cfg), StarshipOS identity 500 MiB (zeros),
StarshipOS Artemis to the end. make -f kernel/Makefile ARCH=<arch> datastick
writes the 1.5 GiB data stick, identity and Artemis, fresh every time. Sizes in
kernel/Makefile; every partition on whole MiB.
tools/mkartemis formats and signs an Artemis partition at build with the
kernel's own code -- v3's block_subsystem.c (blk_subsys_confirm_format) and
artemis_sig.c (artemis_sig_genesis_stamp) -- through a blkio device over a byte
range of the image; --check reads one back the same way (read-only). disk_uuid
from getrandom(2): each image its own. mkdiskimage.sh gains --starship,
--mkartemis, --esp-mib; Milk-V and the Pi images are unchanged. The kernel is
not changed.
Red: scripts/check_images.sh FAIL (no datastick, the PC image one partition).
Green: 23/23. Mutation: --check on a range shifted by one block fails.
Accepted: suite EXIT 0; 133146, 133417, 133815, POST 538/538, dict_hash
0xfec8c47cfc6f437f; test-32, 0 failures.
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>
kernel/Makefile wrote the boot arguments with printf '$(KERNEL_ARGS)\n', so
arguments starting with '-' were taken for an option -- "printf: Illegal
option --", the build stops; the Makefile's own documented example,
KERNEL_ARGS="--doe --log-level=info", hit it -- and a '%' in them would be a
conversion. Now printf '%s\n' '$(KERNEL_ARGS)', as the board images already
write theirs, at all four places (the amd64, aarch64 and riscv64 qemu
recipes, and qemu-esp).
Red: the stick's increment 2 sessions with KERNEL_ARGS=--log-level=info
failed in make. Green: amd64 203627, aarch64 203833, riscv64 204237 --
starforth.cfg holds "--log-level=info" and the boot shows INFO lines after
POST. Accepted: 214300, 214507, 214911; 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 boot left the source's last new line in the console input, and the
first KEY after the boot read it. What the ROM has not read when it
starts the system is taken back (boot.c, rom_harness.h). hosted-check:
KEY after the boot reads the key typed.
- A comment or macro left open at the end of a blob ran on into the next,
and the ROM accepted source text.c refuses. The loader ends each blob
with FS (V4_ROM_BLOB_END) after its new line; the ROM ends a blob there
as text.c ends a file: an open comment or macro is an error, and the word
being assembled ends. test_host_rom_asm: seven blob pairs against text.c.
- store to an address outside RAM faulted the ROM with no message. It is
now refused, as text.c refuses it: ERR-RANGE, "address out of range".
- The kernel build did not regenerate capsules/v4/sys/map-<w>.f18 before
packing the capsule directory; it now does.
ROM: 2084 words, rom_hash 0x4c458c6e48b35456. dict_hash unchanged,
0xe5a57f3eb72ff94a. make -C v4 test test-32 hosted-check pass. QEMU:
amd64, aarch64, riscv64 to ok>, POST 538/538, POST: PASSED.
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: "change to PC, not SER5. it's not supposed to be
specific, but PC generic." boards/ser5 -> boards/pc, TARGET=PC,
lithos-pc.img; the image was never specific to one machine (the generic
UEFI/ACPI path, FABRIC-3 III). The Beelink SER5 stays named where the text
means the unit on the bench (its Secure Boot, BIOS, serial header) and in
the records. Built: lithos-pc.img, system=v4, with its SBOM.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>