Ruled (Captain Bob, 2026-10-27): the data stick is built and wired behind DATASTICK=1, off by default; increment 5 turns it on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
12 KiB
The stick, increment 3: the images — Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:executing-plans (Captain Bob prefers native execution). Steps use checkbox (
- [ ]) syntax for tracking.
Goal: make boot_image TARGET=PC writes the stick (ESP + StarshipOS identity + StarshipOS Artemis,
Artemis formatted and signed at build); make ... datastick writes the data stick (identity + Artemis,
no ESP); QEMU boots the stick as a USB stick on amd64, and the clean qemu targets can attach the data
stick over USB on every ISA.
Architecture: A host tool, tools/mkartemis.c, formats an Artemis partition inside a disk image
with the kernel's own code -- v3's block_subsystem.c (blk_subsys_confirm_format) and
kernel/src/artemis_sig.c (artemis_sig_genesis_stamp) -- through a small blkio backend over a
byte range of the image file, and checks one the same way. scripts/mkdiskimage.sh gains the two
StarshipOS partitions and calls it. The kernel is not changed: it reads these images with increment 2's
GPT reader as they are.
Tech Stack: host C99 (cc, as mkcapsule), sfdisk/mkfs.fat/mtools, QEMU q35 + OVMF with
usb-storage on qemu-xhci; scripts/qemu_session.py.
Spec: docs/v4.0.0/STICK.md §4 (layout, sizes, "the Artemis partition is formatted when the image
is built"), §9 (QEMU boots the stick image itself; the data stick), §11 increment 3, §12 (GUIDs in
mkdiskimage.sh).
Ruled (Captain Bob, 2026-10-27: "yes" -- the recommendation below, and the plan)
When the clean qemu boots switch to the data stick. §11 puts "every clean qemu boot attaches the
data stick" in this increment, and §9 says it replaces the virtio disk and the scratch identity drive
and is "fresh for each boot, so the first boot mints". The kernel cannot use it that way until
increment 4 (Artemis from the stick, virtio Artemis off v4) and increment 5 (genesis, so a fresh
stick's first boot mints Zuse). Switched now, every clean qemu boot would be headless and acceptance
([zuse@Hera] ok> ) would fail until increment 5. Attached beside today's disks, it would put a blank
500 MiB identity partition into every acceptance boot's block chain ahead of nothing it serves, and
renumber the chain's LBNs, for no test it passes.
Recommended: this increment builds the data stick and wires it into all three qemu targets behind
DATASTICK=1 (off by default), tested by a session on each ISA; increment 5, which makes the first boot
mint, turns it on by default and retires the scratch pair (D4) and the virtio disk in the same commit.
STICK.md §11 is amended to say so. The plan below is written that way.
Global Constraints
- As increment 2's plan: branch
StarForth-v4.0.0, commits, strict C99,-Werrorfor kernel code, v3 unchanged, QEMU/bare-metal parity, no fix outside the plan, full acceptance on every task (threeclean qemuboots to[zuse@Hera] ok>, POST 538/538,dict_hash0xfec8c47cfc6f437fequal on all three,make -C v4 test-320 failures, logs committed). - Partition type GUIDs (fixed in increment 2,
kernel/src/gpt.c):- StarshipOS identity
23BEBFE1-4817-4CF3-A537-9069BF159788 - StarshipOS Artemis
BCD7B586-1C8E-4EEF-86A3-273A8D8EE624 - EFI System
C12A7328-F81F-11D2-BA4B-00A0C93EC93B
- StarshipOS identity
- Sizes (STICK.md §4), set once in
kernel/Makefile:STICK_IMAGE_MIB := 2048,STICK_ESP_MIB := 64,STICK_IDENT_MIB := 500. Data stick (my call: identity as the stick's, Artemis 1 GiB as today'sdisk/artemis.img):DATASTICK_MIB := 1536, identitySTICK_IDENT_MIB, Artemis the rest. - Layout: partition 1 at LBA 2048; each partition starts where the previous ends; every start and every
size a whole MiB (so even LBAs and even sector counts, as the GPT reader requires); the last
partition (Artemis) ends on the last whole MiB before the backup GPT's 33 sectors. Partition names
EFI System,StarshipOS identity,StarshipOS Artemis. - The identity partition is all zeros in both images (sparse).
- Artemis is formatted exactly as the kernel formats a disk -- the same
block_subsystem.ccode, the volume headerSTFRv2 at the partition's start, its BAM and relocation table -- and stamped with anartemis_sig_t(artemis_sig_genesis_stamp,disk_uuidfrom the host'sgetrandom(2)). Each built image has its owndisk_uuid. Zuse's genesis marker is not written (increment 5 writes it on first boot). - The stick's ESP is today's PC image's ESP:
EFI/BOOT/BOOTX64.EFI,startup.nsh,starforth.cfg(system=v4 $(KERNEL_ARGS)). OnlyBOARD=pcgains the StarshipOS partitions; Milk-V'suefi-espimage and the Pi's are unchanged. - Images are sparse files; every copy is
cp --sparse=always.
Review Focus
- A partition that is not a whole number of 1 KiB blocks -- an odd sector count from
"the rest of the disk" -- would be refused by the GPT reader on the stick. Pinned by
check_images.sh(every start and size even, Artemis ending on a MiB boundary). - mkartemis writing outside its partition -- the BAM or the signature landing in the identity
partition or the backup GPT. Pinned by
check_images.sh: after formatting, the identity partition is still all zeros andsfdisk --verifyreports the GPT (both headers, both CRCs) clean. - A
blkiorange backend that reads past its range returnsBLKIO_EINVAL, never the next partition's bytes. Pinned bymkartemis --checkreading the signature at the partition's top (the last devblocks), which fails if the range's end is wrong. - The stick booted twice behaves the same (nothing written yet): pinned by
stick_bootrun on a fresh copy; the second-boot case is increment 5's, when the first boot writes. - A DATASTICK boot that loses Zuse -- the data stick enumerated where Zuse's drive should be, or
its blank identity taken for hers -- pinned by
datastickon all three ISAs: Zuse still logs in.
Task 1: mkartemis, and the two images
Files:
- Create:
tools/mkartemis.c - Create:
scripts/check_images.sh(the test) - Modify:
scripts/mkdiskimage.sh(StarshipOS partitions) - Modify:
kernel/Makefile(sizes;MKARTEMIS_BINrule besideMKCAPSULE_BIN;boot_imageforBOARD_STICK=1; newdatasticktarget),boards/pc/board.mk(BOARD_STICK := 1) - Modify:
disk/README.md(the two images, one paragraph)
Interfaces (produces):
-
mkartemis --format IMG --first-lba L --sectors N— formats and stamps the Artemis partition at[L, L+N)512-byte sectors ofIMG; printsmkartemis: formatted <N/2> blocks, STFR v2, signature stamped; exit 0, else a message and exit 1. -
mkartemis --check IMG --first-lba L --sectors N— attaches the range withblk_subsys_attach_deviceand reportsmkartemis: STFR v2, <user> user blocks, signature ok(exit 0) only ifblk_get_volume_metagives magic0x53544652version 2 andartemis_sig_checkgivesARTEMIS_SIG_OK; otherwise names what failed, exit 1. -
The range backend: a
blkio_vtable_twhose block n is the file's bytesL*512 + n*1024,pread/pwrite, refusingn >= N/2withBLKIO_EINVAL.rng_get_bytes(uint8_t *buf, size_t n)(the signaturekernel/include/starkernel/rng.hdeclares) is defined inmkartemis.cwithgetrandom(2). -
Built from
tools/mkartemis.c, v3'sV3_BLOCK_SRCSasv4/Makefilelists them (block_subsystem.c blkio_ram.c blkio_file.c log.c platform/platform_init.c platform/linux/time.c) andkernel/src/artemis_sig.c,-std=gnu99asv4/Makefile'sV3_CFLAGS, into$(BUILD_DIR)/tools/mkartemis. -
mkdiskimage.sh ... --starship IDENT_MIB --mkartemis TOOL [--esp-mib E]: with--esp-mib E, an ESP of E MiB holding the SRC:DEST files, then identity, then Artemis; with--esp-mib 0and no files, identity and Artemis only. Without--starship, exactly today's single-ESP image. -
make boot_image TARGET=PC→build/boards/pc/lithos-pc.img,STICK_IMAGE_MIB. -
make -f kernel/Makefile ARCH=<arch> datastick→$(BUILD_DIR)/datastick.img,DATASTICK_MIB, rebuilt every time it is made (fresh, as §9 asks). -
Step 1: Failing test —
scripts/check_images.shbuilds both images (make boot_image TARGET=PC,make -f kernel/Makefile ARCH=amd64 datastick) and checks, withsfdisk --json,sfdisk --verify,mtools(-i IMG@@OFFSET) andcmp -nagainst/dev/zero:- stick: size 2048 MiB; three partitions; types EFI System / identity / Artemis in that order; starts
2048, 2048+131072, 2048+131072+1024000; ESP 131072 sectors, identity 1024000; every start and size
even and a multiple of 2048; Artemis ends at or before the last usable LBA and within 2048 sectors
of it;
sfdisk --verifyclean; the ESP holdsEFI/BOOT/BOOTX64.EFI,startup.nsh, andstarforth.cfgwhose first word issystem=v4; the identity partition is all zeros;mkartemis --checkon the Artemis partition passes. - data stick: size 1536 MiB; two partitions, identity then Artemis, identity at 2048 of 1024000
sectors; the same evenness, verify, zeros and
--check. - two data sticks built in a row have different
disk_uuids (mkartemis --checkprints it). PrintsPASS:/FAIL:per line andRESULT:; exit 0 only on all PASS.
- stick: size 2048 MiB; three partitions; types EFI System / identity / Artemis in that order; starts
2048, 2048+131072, 2048+131072+1024000; ESP 131072 sectors, identity 1024000; every start and size
even and a multiple of 2048; Artemis ends at or before the last usable LBA and within 2048 sectors
of it;
-
Step 2: Run it: FAIL (no
datasticktarget, the PC image has one partition). -
Step 3: Implement
mkartemis.c, themkdiskimage.shoptions, the Makefile rules. -
Step 4:
check_images.shpasses. A mutation check, once, not committed: shift the range backend's base by one block and see--checkfail. -
Step 5: Suite; full acceptance; commit
feat(build): the stick and the data stick, Artemis formatted and signed at buildwith the logs.
Task 2: QEMU boots the stick, and the data stick over USB
Files:
- Modify:
kernel/Makefile(qemu-sticktarget, amd64;DATASTICKin the threeqemutargets) - Modify:
scripts/qemu_session.py(modesstick_boot,datastick; a--targetoption, defaultqemu) - Modify:
docs/v4.0.0/STICK.md§11 (the amendment, as ruled),boards/BRINGUP.md§3.2 (how the stick is rehearsed in QEMU, one paragraph)
Interfaces:
-
make -f kernel/Makefile ARCH=amd64 clean qemu-stick [KERNEL_ARGS=...]: builds the PC stick ($(MAKE) ... BOARD=pc boot_image), copies it to$(BUILD_DIR)/stick-run.img, boots q35 + OVMF (-cpu qemu64, asqemu) with the copy asusb-storage,bus=xhci0.0,port=1,bootindex=0; no-cdrom, no virtio disk, noZUSEDISK; serial, QMP andlogs/<TS>/amd64/exactly asqemu.ARCHother than amd64: a message and exit 1 (ARM and RISC-V sticks are a non-goal, §10). -
DATASTICK=1onqemu(all three ISAs): makesdatastick, then adds-drive if=none,id=datadrv,file=$(BUILD_DIR)/datastick.img,format=raw -device usb-storage,bus=xhci0.0,port=2,drive=datadrv. Default empty: the command line is unchanged. -
Step 1: Failing tests (harness):
stick_boot(amd64,--target qemu-stick,KERNEL_ARGS=--log-level=info): the loader runs from the USB stick (System: v4andPOST: PASSED); ablk: diskline whose user-block count is that of the 500 MiB identity partition (between 300000 and 512000 -- the whole 2 GiB disk would be over 1.5 million); nook>for 30 s (the identity partition is blank: headless, as increment 2 left it); no fault.datastick(amd64, aarch64, riscv64;--make "DATASTICK=1 KERNEL_ARGS=--log-level=info", Zuse's scratch drive attached as usual):[zuse@Hera] ok>,3 4 + .gives7 ok; the log shows two USB drives found; ablk: diskline for the data stick's identity partition (the same count asstick_boot's). Today: FAIL (noqemu-sticktarget;DATASTICKdoes nothing, one drive found).
-
Step 2: Run each; record the failures.
-
Step 3: Implement the target and the
DATASTICKarguments. -
Step 4:
stick_bootpasses;datastickpasses on all three ISAs; the identity gate's sessions, increment 1's and increment 2's GPT sessions still pass. -
Step 5: Suite; full acceptance; commit
feat(build): QEMU boots the stick; the data stick over USBwith the logs.