feat(kernel): Artemis and Zuse from one disk -- the stick; virtio Artemis leaves v4

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>
This commit is contained in:
rajames
2026-10-28 16:10:33 -04:00
co-authored by Claude Opus 5.5
parent 1b45f5f85f
commit 1e8a6fed34
99 changed files with 16418 additions and 103 deletions
+85 -34
View File
@@ -47,8 +47,17 @@ the stick's, plan 2026-10-26-stick-1-screen-and-keyboard.md):
virtio_kbd aarch64/riscv64: QEMU's virtio keyboard types 7 ok
stick_boot --target qemu-stick (amd64): the PC stick booted as a USB stick through OVMF; its
blank identity partition attached (500 MiB, not the 2 GiB disk); headless
datastick --make "DATASTICK=1 ...": the data stick over USB beside Zuse's drive; Zuse
still logs in; the data stick's identity partition attached
stick_artemis ARTDISK= (no virtio disk): Artemis and Zuse from the Zuse stick alone -- the
stick's Artemis partition attached first (785938 user blocks), then its identity
(48724); [zuse@Hera] ok> (plan 2026-10-27-stick-4-one-disk.md, Task 4)
stick_zuse --target qemu-stick, the PC stick with Zuse's pair in it: a stick, Artemis attached,
[zuse@Hera] ok>
two_sticks --make "DATASTICK=1 ...": Zuse's stick on port 1, a blank data stick on port 2;
Zuse logs in; the data stick is "not this system's stick -- not used" (replaces
datastick)
artemis_unsigned the Zuse stick with its Artemis signature zeroed: "signature is bad -- not
used", nothing attached, headless
raw_ident Zuse's raw identity image, no GPT: "not this system's stick -- not used", headless
stick_v3 --target qemu-stick, system=v3 in the stick's starforth.cfg: "Boot disk: a StarshipOS
stick", the refusal, then v4's POST; no v3 (plan 2026-10-27-stick-4-one-disk.md, Task 3)
entropy after "System: StarForth v4", the entropy backend -- rdrand on amd64 (qemu and
@@ -76,7 +85,7 @@ NO_ZUSE_MODES = {"gpt_zuse", "gpt_hotplug", "gpt_foreign", "gpt_bad_crc", "gpt_b
"gpt_read_error", "headless", "hotplug", "midpull", "waitpull", "typeahead", "emergency",
"alttab_after_logout", "alttab_before_pull", "emergency_pull",
"screen_dark_until_login", "screen_console_after_login", "screen_dark_after_logout",
"kbd_typeahead", "stick_boot", "stick_v3"}
"kbd_typeahead", "stick_boot", "stick_v3", "stick_zuse"}
label = a.label or f"{a.mode}-{a.arch}"
work = os.path.join(ROOT, "build", "qemu-session", label)
shutil.rmtree(work, ignore_errors=True)
@@ -91,15 +100,29 @@ zimg = os.path.join(work, "zusestick.img")
subprocess.run(["bash", os.path.join(ROOT, "scripts/mkdiskimage.sh"), "--out", zimg, "--table", "gpt",
"--label", "ZUSESTICK", "--esp-mib", "0", "--starship-from", ident, art],
check=True, stdout=subprocess.DEVNULL)
if a.mode == "artemis_unsigned":
# the Zuse stick with the 4 KiB artemis_sig_t zeroed: it is devblock total - 1 - 64
# (artemis_sig.c, ARTEMIS_SIG_DEVBLOCK_FROM_TOP), i.e. 65 4 KiB devblocks below the top of the
# Artemis partition, which starts 1 MiB + |ident| in
unsigned = os.path.join(work, "unsigned.img")
subprocess.run(["cp", "--sparse=always", zimg, unsigned], check=True)
sig_at = 1048576 + os.path.getsize(ident) + os.path.getsize(art) - 65 * 4096
with open(unsigned, "r+b") as f:
f.seek(sig_at)
f.write(bytes(4096))
attach_zuse = not (a.no_zuse or a.mode in NO_ZUSE_MODES)
# v3 reads identity drives raw, never through a GPT (only the v4 path reads one: sk_v4_identity.c),
# so a system=v3 boot gets Zuse's raw identity image, not her stick (plan 2026-10-27-stick-4, R2)
zdrive = ident if a.mode == "v3_keys" else zimg
zdrive = {"v3_keys": ident, "raw_ident": ident}.get(a.mode, zimg)
if a.mode == "artemis_unsigned":
zdrive = unsigned
make_args = ["make", "-f", "kernel/Makefile", f"ARCH={a.arch}", "clean", a.target, f"ARTDISK={art}",
f"ZUSEDISK={zdrive if attach_zuse else ''}"] + shlex.split(a.make)
if a.mode in ("v3_keys", "stick_v3"):
make_args.append("KERNEL_ARGS=system=v3")
if a.mode == "stick_zuse":
make_args.append(f"STICK_PAIR={work}") # the PC stick with this session's copy of the pair
start = time.time()
make_log = open(os.path.join(work, "make.txt"), "w")
mk = subprocess.Popen(make_args, cwd=ROOT, stdout=make_log, stderr=subprocess.STDOUT)
@@ -307,20 +330,23 @@ elif m == "zuse":
type_line("3 4 + .")
check("7 ok, and a prompt", wait_for(PROMPT, 60, n) and re.search(r"7\s+ok", text()) is not None)
elif m in ("stick_boot", "datastick"):
# a 500 MiB identity partition: about 380000 user blocks (64 MiB gave 48724); the whole
# 2 GiB stick would be over 1.5 million
def ident_attached():
# "use", not "user": the kernel cuts log messages at 63 characters (LOG_LINE_MAX 64,
# v3/include/vm.h), and with a seven-digit LBN the attach line ends "(383542 use"
users = [int(u) for u in re.findall(r"blk: disk .*?\((\d+) use", text())]
return any(300000 < u < 512000 for u in users), users
elif m in ("stick_boot", "two_sticks"):
def disks():
# "us", not "user": the kernel cuts log messages at 63 characters (LOG_LINE_MAX 64,
# v3/include/vm.h), and with seven-digit LBNs the attach line ends "(383542 us"
return [int(u) for u in re.findall(r"blk: disk .*?\((\d+) us", text())]
if m == "stick_boot":
# the PC stick: Artemis is the rest of 2 GiB (well over 1 million user blocks), its blank
# identity 500 MiB (about 380000)
check("Boot disk: a StarshipOS stick", re.search(r"Boot disk: a StarshipOS stick", text()) is not None)
check("System: StarForth v4", re.search(r"System: StarForth v4", text()) is not None)
time.sleep(20)
found, users = ident_attached()
check(f"the stick's identity partition attached, not the disk (user blocks {users})", found)
check("Artemis: the stick's partition attached",
wait_for(r"Artemis: the stick's partition attached", 120))
check("Zuse: no genesis on this disk", wait_for(r"Zuse: no genesis on this disk", 60))
time.sleep(5)
d = disks()
check(f"Artemis attached first, then the identity partition (user blocks {d})",
len(d) >= 2 and d[0] > 1000000 and 300000 < d[1] < 512000)
check("no ok> for 30 s (a blank identity: headless)", not wait_for(ANYPROMPT, 30))
else:
check("Zuse: identity confirmed", wait_for(r"Zuse: identity confirmed from attached thumbdrive", 120))
@@ -330,8 +356,37 @@ elif m in ("stick_boot", "datastick"):
check("7 ok", wait_for(PROMPT, 60, n) and re.search(r"7\s+ok", text()[-400:]) is not None)
check(f"two USB drives found ({count(r'blkio_usb: MSC device ready')})",
count(r"blkio_usb: MSC device ready") >= 2)
found, users = ident_attached()
check(f"the data stick's identity partition attached (user blocks {users})", found)
time.sleep(5)
nu = count(r"USB: a drive that is not this system's stick -- not used")
check(f"the data stick: not this system's stick -- not used (said {nu} times)", nu == 1)
d = disks()
check(f"nothing of the data stick attached (user blocks {d})", not any(300000 < u < 512000 for u in d))
check("no fault", count(r"EXCEPTION|PANIC|panic") == 0)
elif m in ("stick_artemis", "stick_zuse"):
if m == "stick_zuse":
check("Boot disk: a StarshipOS stick", re.search(r"Boot disk: a StarshipOS stick", text()) is not None)
check("Artemis: the stick's partition attached", wait_for(r"Artemis: the stick's partition attached", 120))
check("Zuse: identity confirmed", wait_for(r"Zuse: identity confirmed from attached thumbdrive", 120))
check("[zuse@Hera] ok>", wait_for(PROMPT, 60))
if m == "stick_artemis":
d = [int(u) for u in re.findall(r"blk: disk .*?\((\d+) us", text())]
check(f"Artemis first (785938 user blocks), then the identity (48724): {d}", d[:2] == [785938, 48724])
n = count(PROMPT)
type_line("3 4 + .")
check("7 ok", wait_for(PROMPT, 60, n) and re.search(r"7\s+ok", text()[-400:]) is not None)
check("no fault", count(r"EXCEPTION|PANIC|panic") == 0)
elif m in ("artemis_unsigned", "raw_ident"):
if m == "artemis_unsigned":
check("Artemis: the stick's signature is bad -- not used",
wait_for(r"Artemis: the stick's signature is bad -- not used", 120))
else:
check("USB: a drive that is not this system's stick -- not used",
wait_for(r"USB: a drive that is not this system's stick -- not used", 120))
check("no ok> for 60 s", not wait_for(ANYPROMPT, 60))
check("no identity", count(r"Zuse: identity confirmed") == 0)
check("no blk: disk line (nothing attached)", count(r"blk: disk ") == 0)
check("no fault", count(r"EXCEPTION|PANIC|panic") == 0)
elif m == "stick_v3":
@@ -387,6 +442,13 @@ elif m == "midpull":
t = text()
check("the line typed after the pull did not run (no 11)", re.search(r"\b11\s+ok", t) is None)
check("no ok> after the session ended", "ok> " not in t[t.rfind("Zuse: session ended"):])
# the replug: identity and Artemis were both let go; Artemis is attached first again
qcmd("blockdev-del", **{"node-name": "zuse1"})
p = count(PROMPT)
add_zuse(2)
check("[zuse@Hera] ok> again after the replug", wait_for(PROMPT, 120, p))
na = count(r"Artemis: the stick's partition attached")
check(f"Artemis attached again: two Artemis lines in all ({na})", na == 2)
elif m == "waitpull":
add_zuse(1)
@@ -543,23 +605,12 @@ elif m in ("gpt_zuse", "gpt_hotplug", "gpt_foreign", "gpt_bad_crc", "gpt_bad_ent
else:
add_zuse(1, gimg)
if m in ("gpt_zuse", "gpt_hotplug"):
check("Zuse: identity confirmed", wait_for(r"Zuse: identity confirmed from attached thumbdrive", 120))
check("[zuse@Hera] ok>", wait_for(PROMPT, 60))
# the identity partition (64 MiB), not the whole 72 MiB disk: a 64 MiB identity drive
# shows 48724 user blocks (the raw minted drive, logs/20261026-091010); the disk, 54868
users = re.findall(r"blk: disk .*?\((\d+) user", text())
check(f"the partition, not the disk, was attached (blk: lines {b0} -> {blk_lines()}, "
f"user blocks {users[-1] if users else None})",
blk_lines() > b0 and users and users[-1] == "48724")
n = count(PROMPT)
type_line("3 4 + .")
check("7 ok", wait_for(PROMPT, 60, n) and re.search(r"7\s+ok", text()[-400:]) is not None)
if m == "gpt_hotplug":
del_zuse(1)
check("session ended on detach", wait_for(r"Zuse: session ended -- reattach to re-authenticate", 60))
p = count(PROMPT)
add_zuse(2, gimg)
check("[zuse@Hera] ok> again", wait_for(PROMPT, 120, p))
# a GPT disk with only an identity partition is not a stick: on v4 it is not used
# (plan 2026-10-27-stick-4-one-disk.md, R2; Zuse's own case is the stick now)
check("not this system's stick -- not used",
wait_for(r"USB: a drive that is not this system's stick -- not used", 60))
check("no ok> for 30 s", not wait_for(ANYPROMPT, 30))
check(f"not attached (blk: lines {b0} -> {blk_lines()})", blk_lines() == b0)
else:
if m in ("gpt_bad_crc", "gpt_bad_entries", "gpt_no_header"):
check("the damaged table is said so", wait_for(r"GPT: a USB drive has a damaged partition table -- not used", 60))