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>
21 KiB
The stick, increment 4: Artemis and Zuse from one disk — Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:executing-plans (Captain Bob prefers native execution). Steps use checkbox (
- [ ]) syntax for tracking.
Goal: the v4 boot takes Artemis and Zuse from one disk -- the stick it booted from, or in QEMU's
clean qemu boots the one USB disk with both StarshipOS partitions. Artemis's partition is attached
first (where Zuse's genesis marker is read), then the identity partition is checked as an identity
drive. Virtio Artemis leaves the v4 path. Entropy is on the v4 path. system=v3 is refused on a
stick.
Architecture:
- The UEFI loader reads its boot disk's GPT with the kernel's own
gpt.c, through UEFI's Block I/O, and passes the result to the kernel inBootInfo: the disk's GUID, and whether it is a StarshipOS stick. - The kernel chooses the system with that. In
sk_v4_identity.cit chooses the USB disk the same way, attaches that disk's Artemis partition and then its identity partition, and uses no other drive. - Before any of that, the acceptance boots and the harness move from the raw scratch pair to the same pair laid out as a stick (Task 1). So every step stays testable, and acceptance never goes headless.
Tech Stack: kernel C99 (-Werror), UEFI (EFI_DEVICE_PATH_PROTOCOL, EFI_BLOCK_IO_PROTOCOL,
LocateDevicePath), scripts/mkdiskimage.sh, QEMU q35/virt + OVMF/EDK2, scripts/qemu_session.py.
Spec: docs/v4.0.0/STICK.md §4 ("Which disk", the Artemis format confirmation, raw identity
drives), §5 items 1, 2 and 5, §9 (entropy, parity), §11 increment 4.
To rule (Captain Bob) — recommendations
R1. Where the acceptance boots get Zuse once virtio Artemis is off v4.
- The problem: today Zuse's genesis marker (her public key) is read from the virtio Artemis disk
(
disk/zuse-scratch/artemis.img), and her identity from the raw scratch thumbdrive. This increment takes Artemis off the v4 path (§5.1, §11), so a boot with the raw pair has no marker and stays headless. Genesis on a fresh data stick, which retires the pair, is increment 5. - Recommended: a "Zuse stick" made from the committed pair. It is a GPT disk whose identity
partition is
zuse-thumb-ident.img's bytes and whose Artemis partition isartemis.img's bytes, each partition exactly its file's size (64 MiB and 1 GiB). A partition must be exactly its file's size, because Artemis's signature and fence are placed from the volume's size. It is built fresh for each boot, and it becomesZUSEDISK's default. - Why it works: the formats inside the partitions are unchanged (§4), so this is the same Zuse,
the same bytes and the same
dict_hash; only the container changes. - Its two forms:
- the data stick's form, every ISA:
make ... zusestick; - the PC stick's form, amd64:
boot_imagewithSTICK_PAIR=disk/zuse-scratch, for the recognition boot from USB.
- the data stick's form, every ISA:
- Increment 5 retires it with the pair, as increment 3's ruling already says of the pair.
ARTDISKstays on theqemucommand line forsystem=v3boots until then; the v4 path no longer reads it.
R2. Raw identity drives on v4.
- The contradiction: §4 says both "Zuse and Artemis are taken from one disk ... a second stick, or a blank drive ... is not used" and "raw identity drives stay accepted".
- What changes now: once Artemis is only on a stick, a raw drive can be Zuse's only if it is a copy of the stick's own identity partition. And attaching it to the block subsystem before the stick would put it in the first disk slot, where the fence must be.
- Recommended: on v4, a USB drive that is not this system's stick is not used ("USB: a drive that
is not this system's stick -- not used"), raw or GPT. On
system=v3, raw drives are read exactly as today. §4's bullet is amended to say so.
My calls (no ruling needed unless you disagree):
- The boot disk is matched by the GPT header's disk GUID, which the loader reads from the disk
it booted from. When the boot disk is not a stick (the
clean qemuboots start from an ISO, or from riscv64's ESP image), the first USB disk that has both StarshipOS partitions is used, as §4 says for QEMU.- Two sticks written from one image share a disk GUID. If both are plugged in, the first one found is used.
- amd64 QEMU gets RDRAND (
-cpu qemu64,+rdrand; QEMU 10.2's TCG accepts it underenforce), and the amd64qemutarget drops virtio-rng, so the PC's RDRAND path is the one QEMU runs (§9). aarch64 and riscv64 keep virtio-rng (§5.2: their only backend).system=v3boots on amd64 QEMU then take entropy from RDRAND too; v3's code is unchanged. - An Artemis partition whose
artemis_sig_tdoes not check out is not used, and so neither is its disk. Nothing is formatted or confirmed by the kernel in this increment: the stick's Artemis is formatted at build (§4).
Global Constraints
- As increment 3's plan:
- branch
StarForth-v4.0.0; strict C99;-Werrorfor kernel code; - v3's behaviour unchanged; QEMU/bare-metal parity; no fix outside the plan;
- full acceptance on every task: three
clean qemuboots to[zuse@Hera] ok>, POST 538/538,dict_hash0xfec8c47cfc6f437fequal on all three,make -C v4 test-320 failures, logs and DoE CSVs committed.
- branch
- Every new console or log message is at most 63 characters (
LOG_LINE_MAX64 cuts longerlog_messagelines; that defect awaits your ruling and is not fixed here). - Partition types:
- StarshipOS identity
23BEBFE1-4817-4CF3-A537-9069BF159788 - StarshipOS Artemis
BCD7B586-1C8E-4EEF-86A3-273A8D8EE624 - EFI System
C12A7328-F81F-11D2-BA4B-00A0C93EC93B
- StarshipOS identity
- The block chain on v4: fast RAM and the ramdrive, then the stick's Artemis partition (the first
disk slot), then its identity partition. Nothing else is given to the block subsystem on v4. On a
pull, the identity partition is detached first and Artemis second (
blk_subsys_detach_deviceremoves only the chain's tail). - No genesis, no format confirmation, no disk writes by the kernel in this increment: a blank stick stays headless, as today ("Zuse: no genesis on this disk -- the system stays headless").
- Before each architecture:
git checkout -- disk/zuse-scratch, as today; the Zuse stick is built from those files.
Review Focus
- A non-stick drive that enumerates before the stick -- for example a raw drive on port 1 -- must
not take the first disk slot. Pinned by
raw_ident(andtwo_sticks): it is "not used", and the firstblk: diskline is Artemis's. - Pull and replug of the stick detaches identity and then Artemis, then attaches Artemis first
again and recognises Zuse. Pinned by
midpull,hotplugandwaitpullon the Zuse stick, and bymidpullchecking two "Artemis: the stick's partition attached" lines. - The loader's probe on firmware that offers no Block I/O for the boot disk (an ISO boot,
riscv64's virtio ESP) must give "not a stick", never a fault. Pinned by every
clean qemuboot showing "Boot disk: not a StarshipOS stick" on all three ISAs. system=v3on a clean-qemu boot still boots v3 (only a stick refuses it). Pinned byv3_keys.- The Zuse stick is the pair's bytes exactly, or Zuse would not be recognised, or recognised from
the wrong place. Pinned by
check_images.shcomparing each partition with its file (cmp), andmkartemis --checkon the Artemis partition.
Task 1: Zuse's pair as a stick, and the sessions moved onto it
Files:
- Modify:
scripts/mkdiskimage.sh(--starship-from IDENT_IMG ART_IMG) - Modify:
kernel/Makefile:ZUSE_PAIR ?= disk/zuse-scratch;- a
zusesticktarget that writes$(BUILD_DIR)/zusestick.imgand is rebuilt every time; ZUSEDISKdefaults to that image, andqemudepends onzusestickwhile it is the default;STICK_PAIRforboot_image/qemu-stick.
- Modify:
scripts/check_images.sh(the Zuse stick, both forms) - Modify:
scripts/qemu_session.py(the session's Zuse drive is a Zuse stick built from its copy of the pair; the raw identity image kept asidentfor the GPT-mode payloads) - Modify:
.claude/CLAUDE.md("The disks" paragraph:ZUSEDISKis now the pair as a stick),disk/README.md(one paragraph)
Interfaces (produces):
-
mkdiskimage.sh --out IMG --table gpt --starship-from IDENT ART [--esp-mib E SRC:DEST...]:- an ESP of E MiB when given;
- an identity partition of exactly IDENT's size, holding IDENT's bytes;
- an Artemis partition of exactly ART's size, holding ART's bytes;
- the image sized
1 + E + |IDENT| + |ART| + 1MiB; - each size must be a whole MiB, or a message and exit 2;
- copies are sparse (
dd conv=notrunc,sparse); no mkartemis.
-
make -f kernel/Makefile ARCH=<arch> zusestick→$(BUILD_DIR)/zusestick.img, from$(ZUSE_PAIR)/zuse-thumb-ident.imgand$(ZUSE_PAIR)/artemis.img. -
make boot_image TARGET=PC STICK_PAIR=disk/zuse-scratch→ the PC stick with the pair in its partitions.qemu-stick STICK_PAIR=...passes it through. -
Step 1: Failing tests.
check_images.shgains:zusestick: two partitions, identity then Artemis; identity 131072 sectors at LBA 2048, Artemis 2097152 sectors right after it;sfdisk --verifyclean; each partitioncmp-equal to its file;mkartemis --checkpasses on Artemis.- The PC stick with
STICK_PAIR: three partitions, its ESP as the stick's; identity and Artemiscmp-equal to the pair.
- The harness's Zuse drive becomes the Zuse stick.
- Expected:
check_images.shFAILs (nozusesticktarget, no--starship-from).
-
Step 2: Run it: FAIL as expected.
-
Step 3: Implement
--starship-from, the targets, and the harness change. -
Step 4:
check_images.shPASSes.- With today's kernel, every session passes on the Zuse stick: Zuse is recognised through the
GPT identity partition, and her marker still comes from the virtio Artemis disk. Run on amd64:
zuse,headless,hotplug,midpull,waitpull,typeahead,screen_dark_until_login,screen_dark_after_logout,kbd_typeahead,emergency(withEMERGENCY_CONSOLE_ENABLED=1), the seven GPT modes,stick_boot. - Then
zuseanddatastickon aarch64 and riscv64.
- With today's kernel, every session passes on the Zuse stick: Zuse is recognised through the
GPT identity partition, and her marker still comes from the virtio Artemis disk. Run on amd64:
-
Step 5: Suite; full acceptance (now on the Zuse stick); commit
test(build): Zuse's minted pair as a stick -- the acceptance boots and the sessions use itwith the logs.
Task 2: entropy on the v4 path
Files:
-
Modify:
kernel/src/kernel_main.c(sk_v4_block_chain:rng_init()afterpci_init, with v3's two lines "entropy: ready" / "entropy: not available (continuing without)") -
Modify:
kernel/Makefile:- amd64
qemuandqemu-esp:-cpu qemu64,+rdrand, and virtio-rng removed; qemu-stick:-cpu qemu64,+rdrand.
- amd64
-
Modify:
scripts/qemu_session.py(modeentropy) -
Step 1: Failing test. Mode
entropy(all three ISAs, plus amd64--target qemu-stick):- after
System: StarForth v4, the log showsrng: backend = rdrandon amd64 (both targets) andrng: backend = virtio-rngon aarch64 and riscv64, thenentropy: ready; - on the clean-qemu targets:
[zuse@Hera] ok>. - Today: FAIL (no
rng:line on the v4 path).
- after
-
Step 2: Run it on all four: FAIL.
-
Step 3: Implement.
-
Step 4:
entropypasses on all four.v3_keys(amd64,system=v3) still passes, now withrng: backend = rdrand. -
Step 5: Suite; full acceptance; commit
feat(kernel): entropy on the v4 path; QEMU's amd64 CPU has RDRAND, as a PC'swith the logs.
Task 3: the boot disk from the loader; system=v3 refused on a stick
Files:
- Modify:
kernel/include/starkernel/uefi.h:EFI_DEVICE_PATH_PROTOCOL, its GUID;EFI_BLOCK_IO_PROTOCOLwithEFI_BLOCK_IO_MEDIA, its GUID;- a typed
LocateDevicePath; BootDisk, appended toBootInfoafterargs(soboot_info_offsets.his unchanged).
- Modify:
kernel/src/boot/uefi_loader.c(the probe, beforeExitBootServices) - Modify:
kernel/include/starkernel/gpt.h,kernel/src/gpt.c(gpt_disk_guid) - Modify:
kernel/src/kernel_main.c(the "Boot disk:" line; the refusal) - Modify:
kernel/Makefile(the non-monolithic loader also buildsgpt.c) - Modify:
scripts/qemu_session.py(modestick_v3; the "Boot disk" checks)
Interfaces:
-
BootDisk:typedef struct { uint8_t gpt; uint8_t is_stick; uint8_t disk_guid[16]; } BootDisk;gpt: the boot disk's GPT readGPT_OK;is_stick: it has both a StarshipOS identity and a StarshipOS Artemis partition;disk_guid: the header's DiskGUID (bytes 56..71), as on disk.- All zero when the probe found no whole-disk Block I/O, or no GPT.
-
void gpt_disk_guid(uint8_t guid[16]): the DiskGUID of the lastgpt_readthat returnedGPT_OK. -
The probe:
- Take the
EFI_DEVICE_PATH_PROTOCOLofloaded_image->DeviceHandle, and copy it up to its first Media/HardDrive node (type 4, subtype 1), ending the copy there (an End node). LocateDevicePath(&BlockIoGuid, ...)on the copy. Accept the result only if the whole copy was consumed andMedia->LogicalPartition == 0andMedia->BlockSize == 512.- Read it through a loader-side
blkio_vtable_t: block n is LBAs 2n and 2n+1, withinfo.phys_sector_size = 512. gpt_readandgpt_findfor both types;gpt_disk_guidfor the GUID.
- Take the
-
Kernel, before the
System:line:Boot disk: a StarshipOS stickorBoot disk: not a StarshipOS stick;- on a stick with
system=v3:System: v3 is not on this stick -- StarForth v4, then v4 runs (STARFORTH_V4builds only; the stick is one).
-
Step 1: Failing tests.
stick_v3(amd64,--target qemu-stick,KERNEL_ARGS=system=v3; the lastsystem=wins incmdline.c): showsBoot disk: a StarshipOS stickand the refusal line, thenPOST: PASSED(v4's POST); noSystem: StarForth v3; no fault.stick_bootgainsBoot disk: a StarshipOS stick.zusegainsBoot disk: not a StarshipOS stickon all three ISAs.- Today: FAIL (no such lines;
stick_v3boots v3).
-
Step 2: Run: FAIL.
-
Step 3: Implement. One check before relying on it:
gpt.cprints oneconsole_printlnline, for a disk whose sectors are not 512 bytes. Confirmconsole_printlnis safe before the kernel's console is set up (kernel/src/hal/console.c); if it is not, the probe refuses non-512 media itself before callinggpt_read. -
Step 4:
stick_v3,stick_boot,zuse(three ISAs) pass;v3_keysstill boots v3 on clean qemu;make -f kernel/Makefile ARCH=amd64 MONOLITHIC=0builds (run it before the change too; if it does not build there, report it and go on). -
Step 5: Suite; full acceptance; commit
feat(boot): the loader tells the kernel its boot disk; system=v3 is refused on a stickwith the logs.
Task 4: Artemis and Zuse from one disk; virtio Artemis off v4
Files:
- Modify:
kernel/src/kernel_main.c(sk_v4_block_chain: no virtio Artemis; its comment) - Modify:
kernel/src/v4/sk_v4_identity.c:- choosing the disk;
- an Artemis partition device per slot;
- Artemis attached first and its signature checked;
- the root key read after Artemis attaches (not in
sk_v4_identity_boot); - two detaches on a pull; other drives not used.
- Modify:
kernel/include/starkernel/v4/sk_v4_identity.h(sk_v4_identity_boottakes theBootDisk) - Modify:
kernel/src/v4/sk_v4.c(passes it) - Modify:
kernel/Makefile(ARTDISK=empty means no virtio disk, on all threeqemutargets) - Modify:
scripts/qemu_session.py(the modes below) - Modify:
docs/v4.0.0/STICK.md§4 (R2 as ruled),boards/BRINGUP.md(the acceptance disks, one line)
Interfaces:
-
sk_v4_drive_tgainsblkio_dev_t art; gpt_part_dev_t art_state; blkio_dev_t *art_used;. -
A drive that arrives is the stick if:
gpt_readgivesGPT_OK, and- both StarshipOS types are present, and
- either the boot disk is a stick and the drive's
gpt_disk_guidequalsBootDisk.disk_guid, or the boot disk is not a stick and no stick is attached now.
-
Then:
- Open Artemis's partition.
artemis_sig_checkmust beARTEMIS_SIG_OK, elseArtemis: the stick's signature is bad -- not usedand the drive is not used.blk_subsys_attach_device(Artemis) →Artemis: the stick's partition attached.capsule_zuse_boot_root_pubkey→ the root key.- The identity partition:
homeblocks_sig_check, attach,capsule_zuse_boot_check, exactly as today.
-
Any other drive:
USB: a drive that is not this system's stick -- not used, once per drive; nothing attached.- The GPT messages for damaged or unreadable tables stay as they are.
-
On a pull: detach
used(identity), thenart_used(Artemis).- When the session's drive leaves, the session ends as today.
-
Step 1: Failing tests (harness; each
--make "KERNEL_ARGS=--log-level=info"where block lines are counted):stick_artemis(three ISAs,--make "ARTDISK= ...", Zuse stick on port 1):Artemis: the stick's partition attached;- the first
blk: diskline has 785938 user blocks (the pair's Artemis), the next 48724 (its identity); [zuse@Hera] ok>,3 4 + .gives7 ok.- Today: FAIL (no marker without the virtio disk: headless).
stick_zuse(amd64,--target qemu-stick,STICK_PAIR=disk/zuse-scratch):Boot disk: a StarshipOS stick, the Artemis line,[zuse@Hera] ok>. Today: FAIL (headless).two_sticks(amd64; replacesdatastick; Zuse stick on port 1, a blank data stick viaDATASTICK=1on port 2): Zuse logs in;not this system's stick -- not usedonce; noblk: diskline between 300000 and 512000 user blocks. Today: FAIL (the data stick's identity is attached).artemis_unsigned(amd64; the Zuse stick with its Artemis signature devblocks zeroed in the session's copy):signature is bad -- not used; noblk: diskline; nook>for 60 s. Today: FAIL (Zuse logs in through virtio).raw_ident(amd64; the rawzuse-thumb-ident.imgon port 1, as before this increment):not this system's stick -- not used; nook>for 60 s. Today: FAIL (she logs in).- Changed expectations:
stick_boot: the Artemis line, and an Artemisblk: diskline before the identity's;Zuse: no genesis on this disk; headless.gpt_zuse/gpt_hotplug(a GPT disk with only an identity partition):not used, headless. Was: login; that case is the stick now.midpull: after the replug, two Artemis lines in all, and Zuse recognised again.
-
Step 2: Run each: FAIL as listed.
-
Step 3: Implement.
-
Step 4: The five new sessions pass (
stick_artemison three ISAs). Then regression:stick_boot,entropy,stick_v3,v3_keys;headless,zuse,hotplug,midpull,waitpull,typeahead,screen_dark_until_login,screen_dark_after_logout,kbd_typeahead,emergency;- the seven GPT modes.
-
Step 5: Suite; full acceptance; commit
feat(kernel): Artemis and Zuse from one disk -- the stick; virtio Artemis leaves v4with the logs.
Task 5: screen=boot -- the console on the screen until login (added 2026-10-28)
Captain Bob, 2026-10-28: "yes, add screen=boot at the end of increment 4" -- for the SER5 smoke
test after Task 4, which has no serial port: today the v4 boot turns the screen's console mirror off
after POST (sk_v4_block_chain, console_fb_mirror(0)), so a failure after POST shows a black
screen.
Files:
- Modify:
kernel/include/starkernel/kernel_args.h(int screen_boot;inKernelArgs, beforeraw; checkboot_info_offsets.h--argsis the last field it may name,BootDiskcomes after) - Modify:
kernel/src/boot/cmdline.c(screen=boot, with or without--, assystem=is parsed; the last one wins; any otherscreen=value is ignored) - Modify:
kernel/src/kernel_main.c(sk_v4_block_chain: withscreen_boot, the mirror stays on and the lineScreen: the console stays on until login (screen=boot); without it, as today) - Modify:
scripts/qemu_session.py(modescreen_boot) - Modify:
boards/BRINGUP.md(the PC section: the smoke test'sstarforth.cfgissystem=v4 screen=boot, Secure Boot off)
Behaviour:
-
Before the first login only. Login draws the console as today; a logout darkens the screen as today (
screen=bootis for watching a boot, not a way round the identity gate: the screen shows what serial shows, and nothing can be typed without Zuse). -
Default (no argument): unchanged --
screen_dark_until_loginstill passes. -
v3 (
system=v3): unchanged. -
Step 1: Failing test. Mode
screen_boot(amd64,KERNEL_ARGS=screen=boot, no drive): theScreen:line; 30 s after POST the screen is not black (at least 500 lit pixels, asscreen_console_after_logincounts); nook>. Today: FAIL. -
Step 2: Run: FAIL.
-
Step 3: Implement.
-
Step 4:
screen_bootpasses on amd64 and aarch64 (riscv64's ramfb too, if the mode's screendump works there);screen_dark_until_login,screen_dark_after_logout,zusepass. -
Step 5: Suite; full acceptance; commit
feat(kernel): screen=boot keeps the console on the screen until login -- for watching a PC bootwith the logs.