Files
LithosAnanke/docs/v4.0.0/plans/2026-10-27-stick-4-one-disk.md
T
rajamesandClaude Opus 5.5 38b2b9693a test(build): Zuse's minted pair as a stick -- the acceptance boots and the sessions use it
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>
2026-10-28 08:41:51 -04:00

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 in BootInfo: the disk's GUID, and whether it is a StarshipOS stick.
  • The kernel chooses the system with that. In sk_v4_identity.c it 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 is artemis.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 becomes ZUSEDISK'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_image with STICK_PAIR=disk/zuse-scratch, for the recognition boot from USB.
  • Increment 5 retires it with the pair, as increment 3's ruling already says of the pair.
  • ARTDISK stays on the qemu command line for system=v3 boots 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 qemu boots 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 under enforce), and the amd64 qemu target 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=v3 boots on amd64 QEMU then take entropy from RDRAND too; v3's code is unchanged.
  • An Artemis partition whose artemis_sig_t does 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; -Werror for kernel code;
    • v3's behaviour unchanged; QEMU/bare-metal parity; no fix outside the plan;
    • full acceptance on every task: three clean qemu boots to [zuse@Hera] ok> , POST 538/538, dict_hash 0xfec8c47cfc6f437f equal on all three, make -C v4 test-32 0 failures, logs and DoE CSVs committed.
  • Every new console or log message is at most 63 characters (LOG_LINE_MAX 64 cuts longer log_message lines; 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
  • 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_device removes 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

  1. 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 (and two_sticks): it is "not used", and the first blk: disk line is Artemis's.
  2. Pull and replug of the stick detaches identity and then Artemis, then attaches Artemis first again and recognises Zuse. Pinned by midpull, hotplug and waitpull on the Zuse stick, and by midpull checking two "Artemis: the stick's partition attached" lines.
  3. 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 qemu boot showing "Boot disk: not a StarshipOS stick" on all three ISAs.
  4. system=v3 on a clean-qemu boot still boots v3 (only a stick refuses it). Pinned by v3_keys.
  5. 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.sh comparing each partition with its file (cmp), and mkartemis --check on 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 zusestick target that writes $(BUILD_DIR)/zusestick.img and is rebuilt every time;
    • ZUSEDISK defaults to that image, and qemu depends on zusestick while it is the default;
    • STICK_PAIR for boot_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 as ident for the GPT-mode payloads)
  • Modify: .claude/CLAUDE.md ("The disks" paragraph: ZUSEDISK is 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| + 1 MiB;
    • 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.img and $(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.sh gains:
      • zusestick: two partitions, identity then Artemis; identity 131072 sectors at LBA 2048, Artemis 2097152 sectors right after it; sfdisk --verify clean; each partition cmp-equal to its file; mkartemis --check passes on Artemis.
      • The PC stick with STICK_PAIR: three partitions, its ESP as the stick's; identity and Artemis cmp-equal to the pair.
    • The harness's Zuse drive becomes the Zuse stick.
    • Expected: check_images.sh FAILs (no zusestick target, 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.sh PASSes.

    • 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 (with EMERGENCY_CONSOLE_ENABLED=1), the seven GPT modes, stick_boot.
    • Then zuse and datastick on aarch64 and riscv64.
  • 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 it with the logs.

Task 2: entropy on the v4 path

Files:

  • Modify: kernel/src/kernel_main.c (sk_v4_block_chain: rng_init() after pci_init, with v3's two lines "entropy: ready" / "entropy: not available (continuing without)")

  • Modify: kernel/Makefile:

    • amd64 qemu and qemu-esp: -cpu qemu64,+rdrand, and virtio-rng removed;
    • qemu-stick: -cpu qemu64,+rdrand.
  • Modify: scripts/qemu_session.py (mode entropy)

  • Step 1: Failing test. Mode entropy (all three ISAs, plus amd64 --target qemu-stick):

    • after System: StarForth v4, the log shows rng: backend = rdrand on amd64 (both targets) and rng: backend = virtio-rng on aarch64 and riscv64, then entropy: ready;
    • on the clean-qemu targets: [zuse@Hera] ok> .
    • Today: FAIL (no rng: line on the v4 path).
  • Step 2: Run it on all four: FAIL.

  • Step 3: Implement.

  • Step 4: entropy passes on all four. v3_keys (amd64, system=v3) still passes, now with rng: backend = rdrand.

  • Step 5: Suite; full acceptance; commit feat(kernel): entropy on the v4 path; QEMU's amd64 CPU has RDRAND, as a PC's with 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_PROTOCOL with EFI_BLOCK_IO_MEDIA, its GUID;
    • a typed LocateDevicePath;
    • BootDisk, appended to BootInfo after args (so boot_info_offsets.h is unchanged).
  • Modify: kernel/src/boot/uefi_loader.c (the probe, before ExitBootServices)
  • 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 builds gpt.c)
  • Modify: scripts/qemu_session.py (mode stick_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 read GPT_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 last gpt_read that returned GPT_OK.

  • The probe:

    1. Take the EFI_DEVICE_PATH_PROTOCOL of loaded_image->DeviceHandle, and copy it up to its first Media/HardDrive node (type 4, subtype 1), ending the copy there (an End node).
    2. LocateDevicePath(&BlockIoGuid, ...) on the copy. Accept the result only if the whole copy was consumed and Media->LogicalPartition == 0 and Media->BlockSize == 512.
    3. Read it through a loader-side blkio_vtable_t: block n is LBAs 2n and 2n+1, with info.phys_sector_size = 512.
    4. gpt_read and gpt_find for both types; gpt_disk_guid for the GUID.
  • Kernel, before the System: line:

    • Boot disk: a StarshipOS stick or Boot disk: not a StarshipOS stick;
    • on a stick with system=v3: System: v3 is not on this stick -- StarForth v4, then v4 runs (STARFORTH_V4 builds only; the stick is one).
  • Step 1: Failing tests.

    • stick_v3 (amd64, --target qemu-stick, KERNEL_ARGS=system=v3; the last system= wins in cmdline.c): shows Boot disk: a StarshipOS stick and the refusal line, then POST: PASSED (v4's POST); no System: StarForth v3; no fault.
    • stick_boot gains Boot disk: a StarshipOS stick.
    • zuse gains Boot disk: not a StarshipOS stick on all three ISAs.
    • Today: FAIL (no such lines; stick_v3 boots v3).
  • Step 2: Run: FAIL.

  • Step 3: Implement. One check before relying on it: gpt.c prints one console_println line, for a disk whose sectors are not 512 bytes. Confirm console_println is 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 calling gpt_read.

  • Step 4: stick_v3, stick_boot, zuse (three ISAs) pass; v3_keys still boots v3 on clean qemu; make -f kernel/Makefile ARCH=amd64 MONOLITHIC=0 builds (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 stick with 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_boot takes the BootDisk)
  • Modify: kernel/src/v4/sk_v4.c (passes it)
  • Modify: kernel/Makefile (ARTDISK= empty means no virtio disk, on all three qemu targets)
  • 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_t gains blkio_dev_t art; gpt_part_dev_t art_state; blkio_dev_t *art_used;.

  • A drive that arrives is the stick if:

    • gpt_read gives GPT_OK, and
    • both StarshipOS types are present, and
    • either the boot disk is a stick and the drive's gpt_disk_guid equals BootDisk.disk_guid, or the boot disk is not a stick and no stick is attached now.
  • Then:

    1. Open Artemis's partition.
    2. artemis_sig_check must be ARTEMIS_SIG_OK, else Artemis: the stick's signature is bad -- not used and the drive is not used.
    3. blk_subsys_attach_device (Artemis) → Artemis: the stick's partition attached.
    4. capsule_zuse_boot_root_pubkey → the root key.
    5. 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), then art_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: disk line has 785938 user blocks (the pair's Artemis), the next 48724 (its identity);
      • [zuse@Hera] ok> , 3 4 + . gives 7 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; replaces datastick; Zuse stick on port 1, a blank data stick via DATASTICK=1 on port 2): Zuse logs in; not this system's stick -- not used once; no blk: disk line 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; no blk: disk line; no ok> for 60 s. Today: FAIL (Zuse logs in through virtio).
    • raw_ident (amd64; the raw zuse-thumb-ident.img on port 1, as before this increment): not this system's stick -- not used; no ok> for 60 s. Today: FAIL (she logs in).
    • Changed expectations:
      • stick_boot: the Artemis line, and an Artemis blk: disk line 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_artemis on 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 v4 with 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; in KernelArgs, before raw; check boot_info_offsets.h -- args is the last field it may name, BootDisk comes after)
  • Modify: kernel/src/boot/cmdline.c (screen=boot, with or without --, as system= is parsed; the last one wins; any other screen= value is ignored)
  • Modify: kernel/src/kernel_main.c (sk_v4_block_chain: with screen_boot, the mirror stays on and the line Screen: the console stays on until login (screen=boot); without it, as today)
  • Modify: scripts/qemu_session.py (mode screen_boot)
  • Modify: boards/BRINGUP.md (the PC section: the smoke test's starforth.cfg is system=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=boot is 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_login still passes.

  • v3 (system=v3): unchanged.

  • Step 1: Failing test. Mode screen_boot (amd64, KERNEL_ARGS=screen=boot, no drive): the Screen: line; 30 s after POST the screen is not black (at least 500 lit pixels, as screen_console_after_login counts); no ok> . Today: FAIL.

  • Step 2: Run: FAIL.

  • Step 3: Implement.

  • Step 4: screen_boot passes on amd64 and aarch64 (riscv64's ramfb too, if the mode's screendump works there); screen_dark_until_login, screen_dark_after_logout, zuse pass.

  • Step 5: Suite; full acceptance; commit feat(kernel): screen=boot keeps the console on the screen until login -- for watching a PC boot with the logs.