From bfe3506aec46b251b438e3c81c22aa5c28ccda65 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 8 Nov 2026 03:43:33 +0000 Subject: [PATCH] wip(kernel): the proxy Zuse -- a stick's first boot mints its own Zuse (#92, Task 1) [skip ci] 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 Claude-Session: https://claude.ai/code/session_01GE3SDvQn5EoxdGufk84zz2 --- .../runs/doe-amd64-20261108-032532.csv | 0 .../runs/doe-amd64-20261108-032631.csv | 0 .../runs/doe-amd64-20261108-033258.csv | 0 .../runs/doe-amd64-20261108-033933.csv | 0 .../runs/doe-amd64-20261108-034104.csv | 0 kernel/Makefile | 4 +- kernel/include/starkernel/capsule_zuse_boot.h | 22 +++ kernel/include/starkernel/v4/sk_v4_marker.h | 30 ++++ .../include/starkernel/zuse_genesis_marker.h | 22 +++ kernel/src/capsule/capsule_zuse_boot.c | 66 ++++--- kernel/src/v4/sk_v4_identity.c | 119 ++++++++++--- kernel/src/v4/sk_v4_marker.c | 60 +++++++ .../amd64/qemu-amd64-20261108-032631.log | 158 +++++++++++++++++ .../amd64/qemu-amd64-20261108-033258.log | 158 +++++++++++++++++ .../amd64/qemu-amd64-20261108-033933.log | 161 ++++++++++++++++++ .../amd64/qemu-amd64-20261108-034104.log | 161 ++++++++++++++++++ scripts/qemu_session.py | 131 +++++++++----- 17 files changed, 996 insertions(+), 96 deletions(-) create mode 100644 experiments/bare_metal/runs/doe-amd64-20261108-032532.csv create mode 100644 experiments/bare_metal/runs/doe-amd64-20261108-032631.csv create mode 100644 experiments/bare_metal/runs/doe-amd64-20261108-033258.csv create mode 100644 experiments/bare_metal/runs/doe-amd64-20261108-033933.csv create mode 100644 experiments/bare_metal/runs/doe-amd64-20261108-034104.csv create mode 100644 kernel/include/starkernel/v4/sk_v4_marker.h create mode 100644 kernel/src/v4/sk_v4_marker.c create mode 100644 logs/20261108-032631/amd64/qemu-amd64-20261108-032631.log create mode 100644 logs/20261108-033258/amd64/qemu-amd64-20261108-033258.log create mode 100644 logs/20261108-033933/amd64/qemu-amd64-20261108-033933.log create mode 100644 logs/20261108-034104/amd64/qemu-amd64-20261108-034104.log diff --git a/experiments/bare_metal/runs/doe-amd64-20261108-032532.csv b/experiments/bare_metal/runs/doe-amd64-20261108-032532.csv new file mode 100644 index 00000000..e69de29b diff --git a/experiments/bare_metal/runs/doe-amd64-20261108-032631.csv b/experiments/bare_metal/runs/doe-amd64-20261108-032631.csv new file mode 100644 index 00000000..e69de29b diff --git a/experiments/bare_metal/runs/doe-amd64-20261108-033258.csv b/experiments/bare_metal/runs/doe-amd64-20261108-033258.csv new file mode 100644 index 00000000..e69de29b diff --git a/experiments/bare_metal/runs/doe-amd64-20261108-033933.csv b/experiments/bare_metal/runs/doe-amd64-20261108-033933.csv new file mode 100644 index 00000000..e69de29b diff --git a/experiments/bare_metal/runs/doe-amd64-20261108-034104.csv b/experiments/bare_metal/runs/doe-amd64-20261108-034104.csv new file mode 100644 index 00000000..e69de29b diff --git a/kernel/Makefile b/kernel/Makefile index 8c6fcbd6..71d9bb12 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -714,8 +714,8 @@ V4_ENGINE_SRCS := $(addprefix v4/src/,node.c exec.c stack.c iword.c heat.c guard V4_ROM_C := $(BUILD_DIR)/v4_rom_64.c V4_MAP_C := $(BUILD_DIR)/v4_map_64.c -LOADER_EXTRA_SRCS += $(KERNEL_SRC)/v4/sk_v4.c $(KERNEL_SRC)/v4/sk_v4_identity.c -KERNEL_EXTRA_SRCS += $(KERNEL_SRC)/v4/sk_v4.c $(KERNEL_SRC)/v4/sk_v4_identity.c +LOADER_EXTRA_SRCS += $(KERNEL_SRC)/v4/sk_v4.c $(KERNEL_SRC)/v4/sk_v4_identity.c $(KERNEL_SRC)/v4/sk_v4_marker.c +KERNEL_EXTRA_SRCS += $(KERNEL_SRC)/v4/sk_v4.c $(KERNEL_SRC)/v4/sk_v4_identity.c $(KERNEL_SRC)/v4/sk_v4_marker.c # v4/system/boot.c is the boot the hosted v4 system runs too: the ROM # assembles the system, then the capsules from this kernel's own capsule # directory, then the prompt. diff --git a/kernel/include/starkernel/capsule_zuse_boot.h b/kernel/include/starkernel/capsule_zuse_boot.h index 2d647c16..a9d1db0f 100644 --- a/kernel/include/starkernel/capsule_zuse_boot.h +++ b/kernel/include/starkernel/capsule_zuse_boot.h @@ -50,6 +50,28 @@ zuse_drive_t capsule_zuse_boot_check(struct blkio_dev *dev, const homeblocks_sig_t *sig, uint8_t seed[32], uint8_t pubkey[32]); +/** + * capsule_zuse_boot_check_key - The identity-record half of + * capsule_zuse_boot_check(): the drive's identity record (magic, version, + * CRC-64) and its public key against @p want, with no marker read + * (plan 2026-10-29-stick-5, Task 1: the v4 boot checks against either of the + * version 2 marker's keys). ZUSE_DRIVE_HERS, with @p seed and @p pubkey + * filled in, or ZUSE_DRIVE_NOT_HERS. Writes nothing. + */ +zuse_drive_t capsule_zuse_boot_check_key(struct blkio_dev *dev, + homeblocks_sig_result_t sig_rc, + const homeblocks_sig_t *sig, + const uint8_t want[32], + uint8_t seed[32], uint8_t pubkey[32]); + +/** + * capsule_zuse_boot_genesis_mint - The mint half of v3's genesis, with no VM: + * Zuse's identity minted onto the blank drive @p dev (genesis mode, + * capsule_mint_identity()). Returns 0 with @p seed and @p pubkey filled in, + * or -1. Writes no marker: each caller writes its own. + */ +int capsule_zuse_boot_genesis_mint(struct blkio_dev *dev, uint8_t seed[32], uint8_t pubkey[32]); + /** * capsule_zuse_boot_root_pubkey - Zuse's public key from the genesis * marker. Returns 0 with @p pubkey filled in, or -1 (no genesis yet, or the diff --git a/kernel/include/starkernel/v4/sk_v4_marker.h b/kernel/include/starkernel/v4/sk_v4_marker.h new file mode 100644 index 00000000..b5f5ae65 --- /dev/null +++ b/kernel/include/starkernel/v4/sk_v4_marker.h @@ -0,0 +1,30 @@ +/* sk_v4_marker.h -- the genesis marker, as the v4 boot reads and writes it: + * version 2, the stick's proxy Zuse and the real Zuse (docs/v4.0.0/STICK.md 6, + * 6a; plan 2026-10-29-stick-5, Task 1). On Artemis's metadata fence, + * devblock 0 from the top (blk_meta_zone_read/write), as v3's version 1. + * + * Built only with STARFORTH_V4=1 (Kconfig.kernel). + */ +#ifndef STARKERNEL_V4_SK_V4_MARKER_H +#define STARKERNEL_V4_SK_V4_MARKER_H + +#include + +typedef struct { + int present; /* a marker is on the fence: genesis has happened */ + uint8_t proxy[32]; /* the stick's proxy Zuse */ + int has_real; /* the real Zuse is minted, on a second stick */ + uint8_t real[32]; /* her key, when has_real */ +} sk_v4_marker_t; + +/* The marker on the first disk slot's fence. A version 1 marker (v3's, the + * minted pair's) reads as a proxy with no real Zuse. Returns 0 with @p m + * filled in; -1 when there is none or it fails its checks (@p m then reads + * present = 0). */ +int sk_v4_marker_read(sk_v4_marker_t *m); + +/* Write @p m to the fence, always as version 2. Returns 0, or -1 on a write + * error. */ +int sk_v4_marker_write(const sk_v4_marker_t *m); + +#endif /* STARKERNEL_V4_SK_V4_MARKER_H */ diff --git a/kernel/include/starkernel/zuse_genesis_marker.h b/kernel/include/starkernel/zuse_genesis_marker.h index cfa0afe6..7b8aafa3 100644 --- a/kernel/include/starkernel/zuse_genesis_marker.h +++ b/kernel/include/starkernel/zuse_genesis_marker.h @@ -43,4 +43,26 @@ typedef struct { _Static_assert(sizeof(zuse_genesis_marker_t) == 4096, "zuse_genesis_marker_t must be exactly one 4 KiB devblock"); +/* Version 2 (docs/v4.0.0/STICK.md 6, 6a; plan 2026-10-29-stick-5, Task 1): the + * same magic and devblock, two keys -- the stick's proxy Zuse, minted at its + * first boot, and the real Zuse, minted onto a second stick (zero, and + * has_real 0, until she is). Written and read only by the v4 boot + * (sk_v4_marker.c), which reads a version 1 marker as a proxy with no real + * Zuse. v3 reads version 1 only, and never runs from a stick (STICK.md 5.5). */ +#define ZUSE_GENESIS_MARKER_VERSION_2 2u + +typedef struct { + uint32_t magic; /* ZUSE_GENESIS_MARKER_MAGIC */ + uint32_t version; /* ZUSE_GENESIS_MARKER_VERSION_2 */ + uint8_t proxy_pubkey[32]; /* the stick's proxy Zuse */ + uint8_t real_pubkey[32]; /* the real Zuse, when has_real */ + uint32_t has_real; /* 1 once the real Zuse is minted */ + uint32_t _reserved; /* zero: makes the padding before crc explicit */ + uint64_t crc; /* CRC-64/ISO over every byte before this field */ + uint8_t _pad[4096 - (4 + 4 + 32 + 32 + 4 + 4 + 8)]; +} zuse_genesis_marker_v2_t; + +_Static_assert(sizeof(zuse_genesis_marker_v2_t) == 4096, + "zuse_genesis_marker_v2_t must be exactly one 4 KiB devblock"); + #endif /* STARKERNEL_ZUSE_GENESIS_MARKER_H */ diff --git a/kernel/src/capsule/capsule_zuse_boot.c b/kernel/src/capsule/capsule_zuse_boot.c index 3703ccd0..415e2e9e 100644 --- a/kernel/src/capsule/capsule_zuse_boot.c +++ b/kernel/src/capsule/capsule_zuse_boot.c @@ -113,6 +113,36 @@ static void install_and_activate(VM *mama_vm, struct blkio_dev *dev, * "ACL-ZUSE-BOOT") above already sets. Nothing to call here anymore. */ } +/* The identity-record half of the check (split out 2026-10-29 so the v4 boot + * can check against either of the version 2 marker's keys: plan + * 2026-10-29-stick-5, Task 1). */ +zuse_drive_t capsule_zuse_boot_check_key(struct blkio_dev *dev, + homeblocks_sig_result_t sig_rc, + const homeblocks_sig_t *sig, + const uint8_t want[32], + uint8_t seed[32], uint8_t pubkey[32]) { + if (!dev || !want) return ZUSE_DRIVE_NOT_HERS; + if (sig_rc != HOMEBLOCKS_SIG_OK || !sig) return ZUSE_DRIVE_NOT_HERS; + if (sig->identity_src_offset == 0 || sig->identity_src_devblocks < 1) return ZUSE_DRIVE_NOT_HERS; + + user_identity_seed_t idrec; + if (read_devblock(dev, sig->identity_src_offset, (uint8_t *)&idrec) != 0) return ZUSE_DRIVE_NOT_HERS; + /* Same magic -> version -> CRC-64 discipline as genesis_marker_read(): + * this record carries Zuse's private key (the seed), so a corrupt or + * format-mismatched record must be refused, never loaded -- a bad CRC + * could otherwise install a garbage seed as Zuse's identity. */ + if (idrec.magic != USER_IDENTITY_SEED_MAGIC) return ZUSE_DRIVE_NOT_HERS; + if (idrec.version != USER_IDENTITY_SEED_VERSION) return ZUSE_DRIVE_NOT_HERS; + uint64_t want_crc = compute_crc64((const uint8_t *)&idrec, + offsetof(user_identity_seed_t, crc)); + if (want_crc != idrec.crc) return ZUSE_DRIVE_NOT_HERS; + if (memcmp(idrec.pubkey, want, 32) != 0) return ZUSE_DRIVE_NOT_HERS; /* not Zuse's drive */ + + memcpy(seed, idrec.seed, 32); + memcpy(pubkey, idrec.pubkey, 32); + return ZUSE_DRIVE_HERS; +} + /* The check, with no VM (split out 2026-10-26 so the v4 boot makes the same * one: docs/v4.0.0/plans/2026-10-26-engine-step4-identity-gate.md, D1). */ zuse_drive_t capsule_zuse_boot_check(struct blkio_dev *dev, @@ -129,25 +159,18 @@ zuse_drive_t capsule_zuse_boot_check(struct blkio_dev *dev, /* Marker present: genesis already happened, on some thumbdrive. * Only this drive if it is that drive. */ - if (sig_rc != HOMEBLOCKS_SIG_OK || !sig) return ZUSE_DRIVE_NOT_HERS; - if (sig->identity_src_offset == 0 || sig->identity_src_devblocks < 1) return ZUSE_DRIVE_NOT_HERS; + return capsule_zuse_boot_check_key(dev, sig_rc, sig, marker.zuse_pubkey, seed, pubkey); +} - user_identity_seed_t idrec; - if (read_devblock(dev, sig->identity_src_offset, (uint8_t *)&idrec) != 0) return ZUSE_DRIVE_NOT_HERS; - /* Same magic -> version -> CRC-64 discipline as genesis_marker_read(): - * this record carries Zuse's private key (the seed), so a corrupt or - * format-mismatched record must be refused, never loaded -- a bad CRC - * could otherwise install a garbage seed as Zuse's identity. */ - if (idrec.magic != USER_IDENTITY_SEED_MAGIC) return ZUSE_DRIVE_NOT_HERS; - if (idrec.version != USER_IDENTITY_SEED_VERSION) return ZUSE_DRIVE_NOT_HERS; - uint64_t want_crc = compute_crc64((const uint8_t *)&idrec, - offsetof(user_identity_seed_t, crc)); - if (want_crc != idrec.crc) return ZUSE_DRIVE_NOT_HERS; - if (memcmp(idrec.pubkey, marker.zuse_pubkey, 32) != 0) return ZUSE_DRIVE_NOT_HERS; /* not Zuse's drive */ - - memcpy(seed, idrec.seed, 32); - memcpy(pubkey, idrec.pubkey, 32); - return ZUSE_DRIVE_HERS; +/* The mint half of v3's genesis, with no VM (split out 2026-10-29: plan + * 2026-10-29-stick-5, Task 1). */ +int capsule_zuse_boot_genesis_mint(struct blkio_dev *dev, uint8_t seed[32], uint8_t pubkey[32]) { + MintResult r = capsule_mint_identity(dev, (VM *)0, "Zuse", "zuse", + (const char *)0, (const char *)0, + pubkey, seed, + MINT_PERSONALITY_DEFAULT, /* Zuse is the superuser -- never restricted */ + 1 /* the caller confirmed the drive BLANK */); + return r == MINT_OK ? 0 : -1; } int capsule_zuse_boot_root_pubkey(uint8_t pubkey[32]) { @@ -176,12 +199,7 @@ void capsule_zuse_boot_try_attach(struct blkio_dev *dev, which = capsule_zuse_boot_check(dev, sig_rc, sig, seed, pubkey); if (which == ZUSE_DRIVE_GENESIS) { - MintResult r = capsule_mint_identity(dev, (VM *)0, "Zuse", "zuse", - (const char *)0, (const char *)0, - pubkey, seed, - MINT_PERSONALITY_DEFAULT, /* Zuse is the superuser -- never restricted */ - 1 /* sig_rc already confirmed BLANK above */); - if (r != MINT_OK) { + if (capsule_zuse_boot_genesis_mint(dev, seed, pubkey) != 0) { console_println("Zuse: genesis mint failed"); return; } diff --git a/kernel/src/v4/sk_v4_identity.c b/kernel/src/v4/sk_v4_identity.c index 95282e77..e6a0e721 100644 --- a/kernel/src/v4/sk_v4_identity.c +++ b/kernel/src/v4/sk_v4_identity.c @@ -7,7 +7,7 @@ * and Hera are the kernel's (ENGINE.md 2, Q1), so a drive that arrives is * given to the block subsystem here, and Zuse's session is kept here, for * the first node, Hera. The check that the drive is hers is v3's own - * (capsule_zuse_boot_check()). + * (capsule_zuse_boot_check_key(), against the genesis marker's key). * * Artemis and Zuse come from one disk, this system's stick * (docs/v4.0.0/STICK.md 4; plan 2026-10-27-stick-4-one-disk.md, Task 4): its @@ -16,9 +16,14 @@ * identity drive always has been. No other drive is used on v4; drives of * users minted by this system's Zuse come with the users design after INSTALL. * + * Genesis (plan 2026-10-29-stick-5, Task 1; STICK.md 6): a stick whose + * Artemis fence has no marker and whose identity partition is blank mints its + * own Zuse at its first boot -- the stick's proxy Zuse, full Zuse while no real + * Zuse exists -- reads her back, then writes the version 2 marker + * (sk_v4_marker.c) naming her. + * * Not here (the plan's "not in this gate"): WIREBIND identities, EJECT, - * ACL-ZUSE-BOOT, and genesis -- which writes Artemis's disk, and the v4 boot - * does not write it yet (the stick's increment 5). + * ACL-ZUSE-BOOT. */ #include "starkernel/v4/sk_v4_identity.h" #include "starkernel/console.h" @@ -33,6 +38,7 @@ #include "starkernel/capsule_zuse_boot.h" #include "starkernel/kbd_input.h" #include "starkernel/gpt.h" +#include "starkernel/v4/sk_v4_marker.h" #ifdef ARCH_AMD64 #include "starkernel/ioapic.h" #include "starkernel/i8042.h" @@ -51,6 +57,12 @@ static uint8_t g_zuse_seed[32], g_zuse_pubkey[32]; static uint8_t g_zuse_root_pubkey[32]; static int g_zuse_root_known; static struct blkio_dev *g_zuse_dev; +/* The genesis marker on the stick's Artemis fence (version 2: the proxy and + * the real Zuse), read when the stick's Artemis partition attaches. */ +static sk_v4_marker_t g_marker; +/* The proxy is recognised but a real Zuse exists: no session for the proxy + * (STICK.md 6a; the fixed screen comes with the plan's Task 5). */ +static int g_proxy_fallback; /* A drive in a USB slot: the raw disk; when it is this system's stick, its * StarshipOS identity and Artemis partitions as devices of their own; and @@ -76,7 +88,6 @@ static struct { static gpt_part_t g_gpt_parts[SK_V4_GPT_PARTS]; static uint64_t g_last_beat; -static int g_said_no_genesis; static const BootDisk *g_boot_disk; /* the loader's: the disk it booted from */ static int g_stick_in; /* a stick's Artemis partition is attached */ @@ -138,6 +149,68 @@ static int sk_v4_is_this_stick(unsigned nparts) return !g_stick_in; } +/* Zuse's session begins on @p dev: the console is hers, and the screen with + * it (docs/v4.0.0/STICK.md 2). @p line says how she was found. */ +static void sk_v4_login(blkio_dev_t *dev, const uint8_t seed[32], const uint8_t pubkey[32], + const char *line) +{ + console_set_vm_name("Hera"); + console_set_user_prefix_provider(sk_v4_user_prefix); + console_fb_mirror(1); + console_fb_enable_ttf(); + console_println(line); + if (!g_zuse_cert_installed) { + memcpy(g_zuse_seed, seed, 32); + memcpy(g_zuse_pubkey, pubkey, 32); + g_zuse_cert_installed = 1; + } + memcpy(g_zuse_root_pubkey, pubkey, 32); /* as v3's install_and_activate() */ + g_zuse_root_known = 1; + g_zuse_dev = dev; + g_zuse_session = 1; +} + +/* Genesis on the stick (STICK.md 6): the proxy Zuse minted onto the blank + * identity partition @p dev, read back, and only then named in the version 2 + * marker on @p art's fence. A failure anywhere leaves no marker, and the + * system headless. Returns 1 when she is logged in. */ +static int sk_v4_genesis(blkio_dev_t *dev, blkio_dev_t *art) +{ + uint8_t seed[32], pubkey[32], rseed[32], rpub[32]; + homeblocks_sig_t sig; + homeblocks_sig_result_t sig_rc; + uint32_t lbn, nblk; + + /* Artemis takes the marker: its volume is formatted at build (STICK.md 4); + * confirmed here only because its signature checked out on attach, and a + * no-op on a volume already formatted */ + if (blk_get_device_range(art, &lbn, &nblk) != BLK_OK || blk_subsys_confirm_format(lbn) != BLK_OK) { + console_println("Zuse: genesis mint failed"); + return 0; + } + if (capsule_zuse_boot_genesis_mint(dev, seed, pubkey) != 0) { + console_println("Zuse: genesis mint failed"); + return 0; + } + /* read her back: what the marker will name must be on the stick */ + sig_rc = homeblocks_sig_check(dev, HOMEBLOCKS_SIG_START_FBLOCK, &sig); + if (capsule_zuse_boot_check_key(dev, sig_rc, &sig, pubkey, rseed, rpub) != ZUSE_DRIVE_HERS || + memcmp(rseed, seed, 32) != 0) { + console_println("Zuse: genesis mint failed -- not read back"); + return 0; + } + memset(&g_marker, 0, sizeof(g_marker)); + g_marker.present = 1; + memcpy(g_marker.proxy, pubkey, 32); + if (sk_v4_marker_write(&g_marker) != 0) { + memset(&g_marker, 0, sizeof(g_marker)); + console_println("Zuse: genesis marker write failed -- headless"); + return 0; + } + sk_v4_login(dev, seed, pubkey, "Zuse: genesis minted onto attached thumbdrive"); + return 1; +} + /* A drive that has arrived. This system's stick: its Artemis partition is * attached (the first disk slot, the block subsystem's), Zuse's public key is * read from its genesis marker, then its identity partition is attached and, @@ -189,8 +262,11 @@ static int sk_v4_drive_arrived(sk_v4_drive_t *d) d->art_used = &d->art; g_stick_in = 1; console_println("Artemis: the stick's partition attached"); - /* Zuse's public key, from the genesis marker on Artemis's fence */ - g_zuse_root_known = capsule_zuse_boot_root_pubkey(g_zuse_root_pubkey) == 0; + /* the genesis marker on Artemis's fence: the proxy's key (and the real + * Zuse's, once she is minted) */ + (void)sk_v4_marker_read(&g_marker); + g_zuse_root_known = g_marker.present; + if (g_marker.present) memcpy(g_zuse_root_pubkey, g_marker.proxy, 32); /* then the identity partition, as an identity drive always has been */ dev = &d->part; @@ -202,31 +278,18 @@ static int sk_v4_drive_arrived(sk_v4_drive_t *d) d->used = dev; if (g_zuse_session) return 1; - which = capsule_zuse_boot_check(dev, sig_rc, &sig, seed, pubkey); - if (which == ZUSE_DRIVE_GENESIS) { - if (!g_said_no_genesis) { - console_println("Zuse: no genesis on this disk -- the system stays headless"); - g_said_no_genesis = 1; - } + /* no marker: a blank identity partition is the stick's first boot */ + if (!g_marker.present) { + if (sig_rc == HOMEBLOCKS_SIG_BLANK) (void)sk_v4_genesis(dev, &d->art); return 1; } + which = capsule_zuse_boot_check_key(dev, sig_rc, &sig, g_marker.proxy, seed, pubkey); if (which != ZUSE_DRIVE_HERS) return 1; - - console_set_vm_name("Hera"); - console_set_user_prefix_provider(sk_v4_user_prefix); - /* the screen is the console from here (docs/v4.0.0/STICK.md 2) */ - console_fb_mirror(1); - console_fb_enable_ttf(); - console_println("Zuse: identity confirmed from attached thumbdrive"); - if (!g_zuse_cert_installed) { - memcpy(g_zuse_seed, seed, 32); - memcpy(g_zuse_pubkey, pubkey, 32); - g_zuse_cert_installed = 1; + if (g_marker.has_real) { + g_proxy_fallback = 1; /* a real Zuse exists: the proxy gets no session */ + return 1; } - memcpy(g_zuse_root_pubkey, pubkey, 32); /* as v3's install_and_activate() */ - g_zuse_root_known = 1; - g_zuse_dev = dev; - g_zuse_session = 1; + sk_v4_login(dev, seed, pubkey, "Zuse: identity confirmed from attached thumbdrive"); return 1; } @@ -245,6 +308,8 @@ static void sk_v4_drive_left(sk_v4_drive_t *d) blk_subsys_detach_device(d->art_used); d->art_used = (blkio_dev_t *)0; g_stick_in = 0; + g_proxy_fallback = 0; + memset(&g_marker, 0, sizeof(g_marker)); } if (!dev || dev != g_zuse_dev) return; g_zuse_session = 0; diff --git a/kernel/src/v4/sk_v4_marker.c b/kernel/src/v4/sk_v4_marker.c new file mode 100644 index 00000000..405c0797 --- /dev/null +++ b/kernel/src/v4/sk_v4_marker.c @@ -0,0 +1,60 @@ +/* sk_v4_marker.c -- the genesis marker, version 2, for the v4 boot + * (docs/v4.0.0/STICK.md 6, 6a; plan 2026-10-29-stick-5, Task 1). + * + * Version 1 (v3's, zuse_genesis_marker_t) holds one key; it reads here as + * the proxy, with no real Zuse, so the minted pair logs in as it always has. + * What is written is always version 2. v3 reads only version 1, and v3 never + * runs from a stick (STICK.md 5.5), so v3 never meets a version 2 marker. + */ +#include "starkernel/v4/sk_v4_marker.h" +#include "starkernel/zuse_genesis_marker.h" +#include "block_subsystem.h" /* blk_meta_zone_read/write, compute_crc64 */ +#include +#include + +/* One 4 KiB devblock, read as either version */ +static union { + uint8_t bytes[4096]; + zuse_genesis_marker_t v1; + zuse_genesis_marker_v2_t v2; +} g_buf; + +int sk_v4_marker_read(sk_v4_marker_t *m) +{ + memset(m, 0, sizeof(*m)); + if (blk_meta_zone_read(0, g_buf.bytes) != 0) return -1; + if (g_buf.v1.magic != ZUSE_GENESIS_MARKER_MAGIC) return -1; + + if (g_buf.v1.version == ZUSE_GENESIS_MARKER_VERSION) { + if (compute_crc64(g_buf.bytes, offsetof(zuse_genesis_marker_t, crc)) != g_buf.v1.crc) + return -1; + memcpy(m->proxy, g_buf.v1.zuse_pubkey, 32); + m->present = 1; + return 0; + } + if (g_buf.v2.version == ZUSE_GENESIS_MARKER_VERSION_2) { + if (compute_crc64(g_buf.bytes, offsetof(zuse_genesis_marker_v2_t, crc)) != g_buf.v2.crc) + return -1; + if (g_buf.v2.has_real > 1u) return -1; + memcpy(m->proxy, g_buf.v2.proxy_pubkey, 32); + m->has_real = (int)g_buf.v2.has_real; + if (m->has_real) memcpy(m->real, g_buf.v2.real_pubkey, 32); + m->present = 1; + return 0; + } + return -1; +} + +int sk_v4_marker_write(const sk_v4_marker_t *m) +{ + memset(g_buf.bytes, 0, sizeof(g_buf.bytes)); + g_buf.v2.magic = ZUSE_GENESIS_MARKER_MAGIC; + g_buf.v2.version = ZUSE_GENESIS_MARKER_VERSION_2; + memcpy(g_buf.v2.proxy_pubkey, m->proxy, 32); + if (m->has_real) { + memcpy(g_buf.v2.real_pubkey, m->real, 32); + g_buf.v2.has_real = 1u; + } + g_buf.v2.crc = compute_crc64(g_buf.bytes, offsetof(zuse_genesis_marker_v2_t, crc)); + return blk_meta_zone_write(0, g_buf.bytes) == 0 ? 0 : -1; +} diff --git a/logs/20261108-032631/amd64/qemu-amd64-20261108-032631.log b/logs/20261108-032631/amd64/qemu-amd64-20261108-032631.log new file mode 100644 index 00000000..fedaa068 --- /dev/null +++ b/logs/20261108-032631/amd64/qemu-amd64-20261108-032631.log @@ -0,0 +1,158 @@ +[=3h[=3h[=3hBdsDxe: loading Boot0001 "UEFI QEMU DVD-ROM QM00005 " from PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x2,0xFFFF,0x0) +BdsDxe: starting Boot0001 "UEFI QEMU DVD-ROM QM00005 " from PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x2,0xFFFF,0x0) +[=3hStarKernel UEFI Loader +Loading kernel from ESP... +[CKPT 001] Entered efi_main - ConOut live +RAW SERIAL UP +[CKPT 002] Serial (COM1) initialized +Monolithic build - kernel linked directly +Collecting boot information... +CmdLine: parsed OK +[CKPT 004] Command line parsed +[CKPT 005] Kernel stack allocation decided +[CKPT 006] Boot info collected (ACPI table located) +GOP: linear framebuffer found +[CKPT 007] GOP: linear framebuffer found +[CKPT 008] About to enter ExitBootServices retry loop +EBS... +EBS OK +Calling kernel_main (monolithic)... + + + _____ _ _ __ _ + / ____| | | |/ / | | + | (___ | |_ __ _ _ __| ' / ___ _ __ _ __ ___| | + \___ \| __/ _` | '__| < / _ \ '__| '_ \ / _ \ | + ____) | || (_| | | | . \ __/ | | | | | __/ | + |_____/ \__\__,_|_| |_|\_\___|_| |_| |_|\___|_| + +LithosAnanke v4.0.0-alpha.3 amd64 +Architecture: amd64 +Build: Nov 8 2026 03:25:59 + +UEFI BootServices: EXITED +Console: serial line is a 16550 at port 0x00000000000003f8 (COM1, probed) + +=== StarKernel Boot Information === +Memory map entries: 126 +Total memory: 1023 MB +Usable memory: 967 MB +=================================== + +PMM initialized. +PMM statistics: + Total pages: 248951 + Free pages : 247424 + Used pages : 1527 + Total MB : 972 + Free MB : 966 + Used MB : 5 + +VMM initialized (mapped RAM, CR3 switched) +VMM initialized (mapped RAM, CR3 switched) +VMM self-test: mapped OK at 0xffff800000000000 +VMM self-test complete. + +IDT installed. + +APIC: init... +APIC: IA32_APIC_BASE MSR=0x00000000fee00900 +APIC: stale-ISR drain: 0 EOI(s) issued +APIC: initialized (xAPIC MMIO, TPR=0, SIVR=0x1FF, EOI-clear) +APIC: init done + +I/O APIC: init... +I/O APIC: base=0xfec00000, gsi_base=0, overrides=5 + override: source=00 gsi=2 flags=0x0000 + override: source=05 gsi=5 flags=0x000d + override: source=09 gsi=9 flags=0x000d + override: source=0a gsi=10 flags=0x000d + override: source=0b gsi=11 flags=0x000d +i8042: keyboard ACKed enable-scanning +i8042: IRQ1 enabled in controller config byte +I/O APIC: keyboard IRQ1 routed (masked) + +Timer: init... +Timer: init start +Timer: PM_TMR_BLK discovered from FADT at port 1544 +Timer: VM mode detected (hypervisor present). +Timer: HPET calibration disabled (VM-exit MMIO would poison timing). +Timer: WARNING: invariant TSC not present under hypervisor. +Timer: continuing in RELATIVE mode (no determinism guarantees). +Timer: RDTSCP not present; using RDTSC (less serialized). +Timer: CPUID frequency unavailable; trying PM Timer... +Timer: trust=1 (0=NONE,1=REL,2=ABS), TSC=2092610026 Hz +Timer: init done + +Kernel heap initialized. +Heap statistics: + Total bytes: 536870872 + Free bytes: 536870872 + Used bytes: 0 + Peak bytes: 0 + Heap base addr: 24641536 + Heap end addr: 561512448 + +Heartbeat: init... +APIC Timer: calibrating... +APIC Timer: apic_hz=1000682012, tick_hz=100, initial_count=10006820 +APIC Timer: configured (masked, ready to start) +Heartbeat: init done +Kernel initialization complete. +Boot successful! + +Boot disk: not a StarshipOS stick +System: StarForth v4 (this build's default) +StarForth v4: one host node, the F18-derived engine +V4: capsule v4:sys:map-64.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:map-64.f18 capsule_hash=0x949eb6a1e9260e49 +V4: capsule v4:sys:core.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:core.f18 capsule_hash=0xb8f167892ec18c28 +V4: capsule v4:sys:input.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:input.f18 capsule_hash=0x85d3ec486d929a79 +V4: capsule v4:sys:dict.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:dict.f18 capsule_hash=0x1379bd3b5de811dc +V4: capsule v4:sys:codegen.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:codegen.f18 capsule_hash=0x36d2ae84d2911b90 +V4: capsule v4:sys:compile.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:compile.f18 capsule_hash=0x11145c4c2ab819d8 +V4: capsule v4:sys:quit.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:quit.f18 capsule_hash=0x43cadf3b21ddbf3e +V4: capsule v4:sys:forth.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:forth.f18 capsule_hash=0x16cab4a1268b85da +V4: capsule v4:sys:numout.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:numout.f18 capsule_hash=0x85a240ad3fb2b299 +V4: capsule v4:sys:words.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:words.f18 capsule_hash=0xb1d381f927d9f14e +V4: capsule v4:sys:system.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:system.f18 capsule_hash=0xd92203664c2915bd +V4: capsule v4:sys:qmath.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:qmath.f18 capsule_hash=0xdb4140713dd857f3 +V4: capsule v4:sys:blocks.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:blocks.f18 capsule_hash=0x76307ef48678dc2a +V4: capsule v4:sys:log.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:log.f18 capsule_hash=0xfc6540e6b824a52b +V4: capsule v4:sys:acl.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:acl.f18 capsule_hash=0x6845aa048db7eb22 +V4: capsule v4:sys:end.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:end.f18 capsule_hash=0x5ef01312c07b79cc +PARITY:V4_ROM words=2372 rom_hash=0x87b23d3ae2643c8d instructions=59145258 +V4: capsule v4:forth79.4th signature: missing (unsigned) +PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x4055641ee17d176b capsule_hash=0x4055641ee17d176b dict_hash=0x033e1c05c09a1df8 +PARITY:V4_POST tests=538 pass=538 fail=0 +PARITY:V4_SYSTEM word_count=317 dict_hash=0xfec8c47cfc6f437f +PARITY:OK +POST: PASSED +PCI: init... +PCI: ECAM mapped (amd64) +virtio-rng: no device on PCI bus 0 +rng: backend = rdrand +entropy: ready +virtio-input: no device on PCI bus 0 +Starting heartbeat... +APIC Timer: started (LVT=0x00020020 ICR=10006820) +I/O APIC: keyboard IRQ1 unmasked +Heartbeat running. +blkio_usb: MSC device ready +Artemis: the stick's partition attached +Zuse: no genesis on this disk -- the system stays headless diff --git a/logs/20261108-033258/amd64/qemu-amd64-20261108-033258.log b/logs/20261108-033258/amd64/qemu-amd64-20261108-033258.log new file mode 100644 index 00000000..2734eae6 --- /dev/null +++ b/logs/20261108-033258/amd64/qemu-amd64-20261108-033258.log @@ -0,0 +1,158 @@ +[=3h[=3h[=3hBdsDxe: loading Boot0001 "UEFI QEMU DVD-ROM QM00005 " from PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x2,0xFFFF,0x0) +BdsDxe: starting Boot0001 "UEFI QEMU DVD-ROM QM00005 " from PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x2,0xFFFF,0x0) +[=3hStarKernel UEFI Loader +Loading kernel from ESP... +[CKPT 001] Entered efi_main - ConOut live +RAW SERIAL UP +[CKPT 002] Serial (COM1) initialized +Monolithic build - kernel linked directly +Collecting boot information... +CmdLine: parsed OK +[CKPT 004] Command line parsed +[CKPT 005] Kernel stack allocation decided +[CKPT 006] Boot info collected (ACPI table located) +GOP: linear framebuffer found +[CKPT 007] GOP: linear framebuffer found +[CKPT 008] About to enter ExitBootServices retry loop +EBS... +EBS OK +Calling kernel_main (monolithic)... + + + _____ _ _ __ _ + / ____| | | |/ / | | + | (___ | |_ __ _ _ __| ' / ___ _ __ _ __ ___| | + \___ \| __/ _` | '__| < / _ \ '__| '_ \ / _ \ | + ____) | || (_| | | | . \ __/ | | | | | __/ | + |_____/ \__\__,_|_| |_|\_\___|_| |_| |_|\___|_| + +LithosAnanke v4.0.0-alpha.3 amd64 +Architecture: amd64 +Build: Nov 8 2026 03:25:59 + +UEFI BootServices: EXITED +Console: serial line is a 16550 at port 0x00000000000003f8 (COM1, probed) + +=== StarKernel Boot Information === +Memory map entries: 128 +Total memory: 1023 MB +Usable memory: 967 MB +=================================== + +PMM initialized. +PMM statistics: + Total pages: 248953 + Free pages : 247426 + Used pages : 1527 + Total MB : 972 + Free MB : 966 + Used MB : 5 + +VMM initialized (mapped RAM, CR3 switched) +VMM initialized (mapped RAM, CR3 switched) +VMM self-test: mapped OK at 0xffff800000000000 +VMM self-test complete. + +IDT installed. + +APIC: init... +APIC: IA32_APIC_BASE MSR=0x00000000fee00900 +APIC: stale-ISR drain: 0 EOI(s) issued +APIC: initialized (xAPIC MMIO, TPR=0, SIVR=0x1FF, EOI-clear) +APIC: init done + +I/O APIC: init... +I/O APIC: base=0xfec00000, gsi_base=0, overrides=5 + override: source=00 gsi=2 flags=0x0000 + override: source=05 gsi=5 flags=0x000d + override: source=09 gsi=9 flags=0x000d + override: source=0a gsi=10 flags=0x000d + override: source=0b gsi=11 flags=0x000d +i8042: keyboard ACKed enable-scanning +i8042: IRQ1 enabled in controller config byte +I/O APIC: keyboard IRQ1 routed (masked) + +Timer: init... +Timer: init start +Timer: PM_TMR_BLK discovered from FADT at port 1544 +Timer: VM mode detected (hypervisor present). +Timer: HPET calibration disabled (VM-exit MMIO would poison timing). +Timer: WARNING: invariant TSC not present under hypervisor. +Timer: continuing in RELATIVE mode (no determinism guarantees). +Timer: RDTSCP not present; using RDTSC (less serialized). +Timer: CPUID frequency unavailable; trying PM Timer... +Timer: trust=1 (0=NONE,1=REL,2=ABS), TSC=2089746389 Hz +Timer: init done + +Kernel heap initialized. +Heap statistics: + Total bytes: 536870872 + Free bytes: 536870872 + Used bytes: 0 + Peak bytes: 0 + Heap base addr: 24641536 + Heap end addr: 561512448 + +Heartbeat: init... +APIC Timer: calibrating... +APIC Timer: apic_hz=999267242, tick_hz=100, initial_count=9992672 +APIC Timer: configured (masked, ready to start) +Heartbeat: init done +Kernel initialization complete. +Boot successful! + +Boot disk: not a StarshipOS stick +System: StarForth v4 (this build's default) +StarForth v4: one host node, the F18-derived engine +V4: capsule v4:sys:map-64.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:map-64.f18 capsule_hash=0x949eb6a1e9260e49 +V4: capsule v4:sys:core.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:core.f18 capsule_hash=0xb8f167892ec18c28 +V4: capsule v4:sys:input.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:input.f18 capsule_hash=0x85d3ec486d929a79 +V4: capsule v4:sys:dict.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:dict.f18 capsule_hash=0x1379bd3b5de811dc +V4: capsule v4:sys:codegen.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:codegen.f18 capsule_hash=0x36d2ae84d2911b90 +V4: capsule v4:sys:compile.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:compile.f18 capsule_hash=0x11145c4c2ab819d8 +V4: capsule v4:sys:quit.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:quit.f18 capsule_hash=0x43cadf3b21ddbf3e +V4: capsule v4:sys:forth.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:forth.f18 capsule_hash=0x16cab4a1268b85da +V4: capsule v4:sys:numout.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:numout.f18 capsule_hash=0x85a240ad3fb2b299 +V4: capsule v4:sys:words.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:words.f18 capsule_hash=0xb1d381f927d9f14e +V4: capsule v4:sys:system.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:system.f18 capsule_hash=0xd92203664c2915bd +V4: capsule v4:sys:qmath.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:qmath.f18 capsule_hash=0xdb4140713dd857f3 +V4: capsule v4:sys:blocks.f18 signature: missing (unsigned) +PARITY:V4_F18 naame=v4:sys:blocks.f18 capsule_hash=0x76307ef48678dc2a +V4: capsule v4:sys:log.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:log.f18 capsule_hash=0xfc6540e6b824a52b +V4: capsule v4:sys:acl.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:acl.f18 capsule_hash=0x6845aa048db7eb22 +V4: capsule v4:sys:end.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:end.f18 capsule_hash=0x5ef01312c07b79cc +PARITY:V4_ROM words=2372 rom_hash=0x87b23d3ae2643c8d instructions=59145258 +V4: capsule v4:forth79.4th signature: missing (unsigned) +PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x4055641ee17d176b capsule_hash=0x4055641ee17d176b dict_hash=0x033e1c05c09a1df8 +PARITY:V4_POST tests=538 pass=538 fail=0 +PARITY:V4_SYSTEM word_count=317 dict_hash=0xfec8c47cfc6f437f +PARITY:OK +POST: PASSED +PCI: init... +PCI: ECAM mapped (amd64) +virtio-rng: no device on PCI bus 0 +rng: backend = rdrand +entropy: ready +virtio-input: no device on PCI bus 0 +Starting heartbeat... +APIC Timer: started (LVT=0x00020020 ICR=9992672) +I/O APIC: keyboard IRQ1 unmasked +Heartbeat running. +blkio_usb: MSC device ready +Artemis: the stick's partition attached +Zuse: no genesis on this disk -- the system stays headless diff --git a/logs/20261108-033933/amd64/qemu-amd64-20261108-033933.log b/logs/20261108-033933/amd64/qemu-amd64-20261108-033933.log new file mode 100644 index 00000000..79555e8c --- /dev/null +++ b/logs/20261108-033933/amd64/qemu-amd64-20261108-033933.log @@ -0,0 +1,161 @@ +[=3h[=3h[=3hBdsDxe: loading Boot0001 "UEFI QEMU DVD-ROM QM00005 " from PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x2,0xFFFF,0x0) +BdsDxe: starting Boot0001 "UEFI QEMU DVD-ROM QM00005 " from PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x2,0xFFFF,0x0) +[=3hStarKernel UEFI Loader +Loading kernel from ESP... +[CKPT 001] Entered efi_main - ConOut live +RAW SERIAL UP +[CKPT 002] Serial (COM1) initialized +Monolithic build - kernel linked directly +Collecting boot information... +CmdLine: parsed OK +[CKPT 004] Command line parsed +[CKPT 005] Kernel stack allocation decided +[CKPT 006] Boot info collected (ACPI table located) +GOP: linear framebuffer found +[CKPT 007] GOP: linear framebuffer found +[CKPT 008] About to enter ExitBootServices retry loop +EBS... +EBS OK +Calling kernel_main (monolithic)... + + + _____ _ _ __ _ + / ____| | | |/ / | | + | (___ | |_ __ _ _ __| ' / ___ _ __ _ __ ___| | + \___ \| __/ _` | '__| < / _ \ '__| '_ \ / _ \ | + ____) | || (_| | | | . \ __/ | | | | | __/ | + |_____/ \__\__,_|_| |_|\_\___|_| |_| |_|\___|_| + +LithosAnanke v4.0.0-alpha.3 amd64 +Architecture: amd64 +Build: Nov 8 2026 03:38:58 + +UEFI BootServices: EXITED +Console: serial line is a 16550 at port 0x00000000000003f8 (COM1, probed) + +=== StarKernel Boot Information === +Memory map entries: 129 +Total memory: 1023 MB +Usable memory: 967 MB +=================================== + +PMM initialized. +PMM statistics: + Total pages: 248953 + Free pages : 247426 + Used pages : 1527 + Total MB : 972 + Free MB : 966 + Used MB : 5 + +VMM initialized (mapped RAM, CR3 switched) +VMM initialized (mapped RAM, CR3 switched) +VMM self-test: mapped OK at 0xffff800000000000 +VMM self-test complete. + +IDT installed. + +APIC: init... +APIC: IA32_APIC_BASE MSR=0x00000000fee00900 +APIC: stale-ISR drain: 0 EOI(s) issued +APIC: initialized (xAPIC MMIO, TPR=0, SIVR=0x1FF, EOI-clear) +APIC: init done + +I/O APIC: init... +I/O APIC: base=0xfec00000, gsi_base=0, overrides=5 + override: source=00 gsi=2 flags=0x0000 + override: source=05 gsi=5 flags=0x000d + override: source=09 gsi=9 flags=0x000d + override: source=0a gsi=10 flags=0x000d + override: source=0b gsi=11 flags=0x000d +i8042: keyboard ACKed enable-scanning +i8042: IRQ1 enabled in controller config byte +I/O APIC: keyboard IRQ1 routed (masked) + +Timer: init... +Timer: init start +Timer: PM_TMR_BLK discovered from FADT at port 1544 +Timer: VM mode detected (hypervisor present). +Timer: HPET calibration disabled (VM-exit MMIO would poison timing). +Timer: WARNING: invariant TSC not present under hypervisor. +Timer: continuing in RELATIVE mode (no determinism guarantees). +Timer: RDTSCP not present; using RDTSC (less serialized). +Timer: CPUID frequency unavailable; trying PM Timer... +Timer: trust=1 (0=NONE,1=REL,2=ABS), TSC=2094449913 Hz +Timer: init done + +Kernel heap initialized. +Heap statistics: + Total bytes: 536870872 + Free bytes: 536870872 + Used bytes: 0 + Peak bytes: 0 + Heap base addr: 24641536 + Heap end addr: 561512448 + +Heartbeat: init... +APIC Timer: calibrating... +APIC Timer: apic_hz=1002197406, tick_hz=100, initial_count=10021974 +APIC Timer: configured (masked, ready to start) +Heartbeat: init done +Kernel initialization complete. +Boot successful! + +Boot disk: not a StarshipOS stick +System: StarForth v4 (this build's default) +StarForth v4: one host node, the F18-derived engine +V4: capsule v4:sys:map-64.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:map-64.f18 capsule_hash=0x949eb6a1e9260e49 +V4: capsule v4:sys:core.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:core.f18 capsule_hash=0xb8f167892ec18c28 +V4: capsule v4:sys:input.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:input.f18 capsule_hash=0x85d3ec486d929a79 +V4: capsule v4:sys:dict.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:dict.f18 capsule_hash=0x1379bd3b5de811dc +V4: capsule v4:sys:codegen.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:codegen.f18 capsule_hash=0x36d2ae84d2911b90 +V4: capsule v4:sys:compile.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:compile.f18 capsule_hash=0x11145c4c2ab819d8 +V4: capsule v4:sys:quit.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:quit.f18 capsule_hash=0x43cadf3b21ddbf3e +V4: capsule v4:sys:forth.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:forth.f18 capsule_hash=0x16cab4a1268b85da +V4: capsule v4:sys:numout.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:numout.f18 capsule_hash=0x85a240ad3fb2b299 +V4: capsule v4:sys:words.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:words.f18 capsule_hash=0xb1d381f927d9f14e +V4: capsule v4:sys:system.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:system.f18 capsule_hash=0xd92203664c2915bd +V4: capsule v4:sys:qmath.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:qmath.f18 capsule_hash=0xdb4140713dd857f3 +V4: capsule v4:sys:blocks.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:blocks.f18 capsule_hash=0x76307ef48678dc2a +V4: capsule v4:sys:log.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:log.f18 capsule_hash=0xfc6540e6b824a52b +V4: capsule v4:sys:acl.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:acl.f18 capsule_hash=0x6845aa048db7eb22 +V4: capsule v4:sys:end.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:end.f18 capsule_hash=0x5ef01312c07b79cc +PARITY:V4_ROM words=2372 rom_hash=0x87b23d3ae2643c8d instructions=59145258 +V4: capsule v4:forth79.4th signature: missing (unsigned) +PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x4055641ee17d176b capsule_hash=0x4055641ee17d176b dict_hash=0x033e1c05c09a1df8 +PARITY:V4_POST tests=538 pass=538 fail=0 +PARITY:V4_SYSTEM word_count=317 dict_hash=0xfec8c47cfc6f437f +PARITY:OK +POST: PASSED +PCI: init... +PCI: ECAM mapped (amd64) +virtio-rng: no device on PCI bus 0 +rng: backend = rdrand +entropy: ready +virtio-input: no device on PCI bus 0 +Starting heartbeat... +APIC Timer: started (LVT=0x00020020 ICR=10021974) +I/O APIC: keyboard IRQ1 unmasked +Heartbeat running. +blkio_usb: MSC device ready +Artemis: the stick's partition attached +[Hera] Zuse: genesis minted onto attached thumbdrive +[zuse@Hera] ok> 3 4 + . +[zuse@Hera] 7 ok +[zuse@Hera] ok>  \ No newline at end of file diff --git a/logs/20261108-034104/amd64/qemu-amd64-20261108-034104.log b/logs/20261108-034104/amd64/qemu-amd64-20261108-034104.log new file mode 100644 index 00000000..f9075006 --- /dev/null +++ b/logs/20261108-034104/amd64/qemu-amd64-20261108-034104.log @@ -0,0 +1,161 @@ +[=3h[=3h[=3hBdsDxe: loading Boot0001 "UEFI QEMU DVD-ROM QM00005 " from PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x2,0xFFFF,0x0) +BdsDxe: starting Boot0001 "UEFI QEMU DVD-ROM QM00005 " from PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x2,0xFFFF,0x0) +[=3hStarKernel UEFI Loader +Loading kernel from ESP... +[CKPT 001] Entered efi_main - ConOut live +RAW SERIAL UP +[CKPT 002] Serial (COM1) initialized +Monolithic build - kernel linked directly +Collecting boot information... +CmdLine: parsed OK +[CKPT 004] Command line parsed +[CKPT 005] Kernel stack allocation decided +[CKPT 006] Boot info collected (ACPI table located) +GOP: linear framebuffer found +[CKPT 007] GOP: linear framebuffer found +[CKPT 008] About to enter ExitBootServices retry loop +EBS... +EBS OK +Calling kernel_main (monolithic)... + + + _____ _ _ __ _ + / ____| | | |/ / | | + | (___ | |_ __ _ _ __| ' / ___ _ __ _ __ ___| | + \___ \| __/ _` | '__| < / _ \ '__| '_ \ / _ \ | + ____) | || (_| | | | . \ __/ | | | | | __/ | + |_____/ \__\__,_|_| |_|\_\___|_| |_| |_|\___|_| + +LithosAnanke v4.0.0-alpha.3 amd64 +Architecture: amd64 +Build: Nov 8 2026 03:38:58 + +UEFI BootServices: EXITED +Console: serial line is a 16550 at port 0x00000000000003f8 (COM1, probed) + +=== StarKernel Boot Information === +Memory map entries: 128 +Total memory: 1023 MB +Usable memory: 967 MB +=================================== + +PMM initialized. +PMM statistics: + Total pages: 248954 + Free pages : 247427 + Used pages : 1527 + Total MB : 972 + Free MB : 966 + Used MB : 5 + +VMM initialized (mapped RAM, CR3 switched) +VMM initialized (mapped RAM, CR3 switched) +VMM self-test: mapped OK at 0xffff800000000000 +VMM self-test complete. + +IDT installed. + +APIC: init... +APIC: IA32_APIC_BASE MSR=0x00000000fee00900 +APIC: stale-ISR drain: 0 EOI(s) issued +APIC: initialized (xAPIC MMIO, TPR=0, SIVR=0x1FF, EOI-clear) +APIC: init done + +I/O APIC: init... +I/O APIC: base=0xfec00000, gsi_base=0, overrides=5 + override: source=00 gsi=2 flags=0x0000 + override: source=05 gsi=5 flags=0x000d + override: source=09 gsi=9 flags=0x000d + override: source=0a gsi=10 flags=0x000d + override: source=0b gsi=11 flags=0x000d +i8042: keyboard ACKed enable-scanning +i8042: IRQ1 enabled in controller config byte +I/O APIC: keyboard IRQ1 routed (masked) + +Timer: init... +Timer: init start +Timer: PM_TMR_BLK discovered from FADT at port 1544 +Timer: VM mode detected (hypervisor present). +Timer: HPET calibration disabled (VM-exit MMIO would poison timing). +Timer: WARNING: invariant TSC not present under hypervisor. +Timer: continuing in RELATIVE mode (no determinism guarantees). +Timer: RDTSCP not present; using RDTSC (less serialized). +Timer: CPUID frequency unavailable; trying PM Timer... +Timer: trust=1 (0=NONE,1=REL,2=ABS), TSC=2100055482 Hz +Timer: init done + +Kernel heap initialized. +Heap statistics: + Total bytes: 536870872 + Free bytes: 536870872 + Used bytes: 0 + Peak bytes: 0 + Heap base addr: 24641536 + Heap end addr: 561512448 + +Heartbeat: init... +APIC Timer: calibrating... +APIC Timer: apic_hz=1009310739, tick_hz=100, initial_count=10093107 +APIC Timer: configured (masked, ready to start) +Heartbeat: init done +Kernel initialization complete. +Boot successful! + +Boot disk: not a StarshipOS stick +System: StarForth v4 (this build's default) +StarForth v4: one host node, the F18-derived engine +V4: capsule v4:sys:map-64.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:map-64.f18 capsule_hash=0x949eb6a1e9260e49 +V4: capsule v4:sys:core.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:core.f18 capsule_hash=0xb8f167892ec18c28 +V4: capsule v4:sys:input.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:input.f18 capsule_hash=0x85d3ec486d929a79 +V4: capsule v4:sys:dict.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:dict.f18 capsule_hash=0x1379bd3b5de811dc +V4: capsule v4:sys:codegen.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:codegen.f18 capsule_hash=0x36d2ae84d2911b90 +V4: capsule v4:sys:compile.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:compile.f18 capsule_hash=0x11145c4c2ab819d8 +V4: capsule v4:sys:quit.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:quit.f18 capsule_hash=0x43cadf3b21ddbf3e +V4: capsule v4:sys:forth.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:forth.f18 capsule_hash=0x16cab4a1268b85da +V4: capsule v4:sys:numout.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:numout.f18 capsule_hash=0x85a240ad3fb2b299 +V4: capsule v4:sys:words.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:words.f18 capsule_hash=0xb1d381f927d9f14e +V4: capsule v4:sys:system.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:system.f18 capsule_hash=0xd92203664c2915bd +V4: capsule v4:sys:qmath.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:qmath.f18 capsule_hash=0xdb4140713dd857f3 +V4: capsule v4:sys:blocks.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:blocks.f18 capsule_hash=0x76307ef48678dc2a +V4: capsule v4:sys:log.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:log.f18 capsule_hash=0xfc6540e6b824a52b +V4: capsule v4:sys:acl.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:acl.f18 capsule_hash=0x6845aa048db7eb22 +V4: capsule v4:sys:end.f18 signature: missing (unsigned) +PARITY:V4_F18 name=v4:sys:end.f18 capsule_hash=0x5ef01312c07b79cc +PARITY:V4_ROM words=2372 rom_hash=0x87b23d3ae2643c8d instructions=59145258 +V4: capsule v4:forth79.4th signature: missing (unsigned) +PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x4055641ee17d176b capsule_hash=0x4055641ee17d176b dict_hash=0x033e1c05c09a1df8 +PARITY:V4_POST tests=538 pass=538 fail=0 +PARITY:V4_SYSTEM word_count=317 dict_hash=0xfec8c47cfc6f437f +PARITY:OK +POST: PASSED +PCI: init... +PCI: ECAM mapped (amd64) +virtio-rng: no device on PCI bus 0 +rng: backend = rdrand +entropy: ready +virtio-input: no device on PCI bus 0 +Starting heartbeat... +APIC Timer: started (LVT=0x00020020 ICR=10093107) +I/O APIC: keyboard IRQ1 unmasked +Heartbeat running. +blkio_usb: MSC device ready +Artemis: the stick's partition attached +[Hera] Zuse: identity confirmed from attached thumbdrive +[zuse@Hera] ok> 3 4 + . +[zuse@Hera] 7 ok +[zuse@Hera] ok>  \ No newline at end of file diff --git a/scripts/qemu_session.py b/scripts/qemu_session.py index 31867d9d..f11bdf0b 100755 --- a/scripts/qemu_session.py +++ b/scripts/qemu_session.py @@ -46,7 +46,13 @@ the stick's, plan 2026-10-26-stick-1-screen-and-keyboard.md): said so, not attached 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 + blank identity partition attached (500 MiB, not the 2 GiB disk); the proxy Zuse + minted onto it (genesis), [zuse@Hera] ok> (plan 2026-10-29-stick-5, Task 1) + genesis a fresh data stick (DATASTICK=1, no Zuse drive), booted twice, one QEMU at a time: + first the genesis line, [zuse@Hera] ok>, 7 ok; then the same copy again: + identity confirmed, no genesis, the prompt (plan 2026-10-29-stick-5, Task 1) + stick_genesis --target qemu-stick, the PC stick as built (no STICK_PAIR): its first boot mints + the proxy Zuse and reaches [zuse@Hera] ok> with nothing set up (plug and play) 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) @@ -88,7 +94,8 @@ 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", "stick_zuse", "screen_boot"} + "kbd_typeahead", "stick_boot", "stick_v3", "stick_zuse", "screen_boot", "genesis", + "stick_genesis"} label = a.label or f"{a.mode}-{a.arch}" work = os.path.join(ROOT, "build", "qemu-session", label) shutil.rmtree(work, ignore_errors=True) @@ -128,38 +135,18 @@ if a.mode == "screen_boot": make_args.append("KERNEL_ARGS=screen=boot") 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) -deadline = time.time() + 1800 -ok = True - - +if a.mode == "genesis": + # a fresh data stick, made once into this session's directory and booted twice: the second + # boot is the same copy, so it finds what the first wrote (plan 2026-10-29-stick-5, Task 1) + dstick = os.path.join(work, "data.img") + subprocess.run(["make", "-f", "kernel/Makefile", f"ARCH={a.arch}", "datastick", f"DATASTICK_IMG={dstick}"], + cwd=ROOT, check=True, stdout=subprocess.DEVNULL) + make_args += ["DATASTICK=1", f"DATASTICK_IMG={dstick}", "DATASTICK_DEP="] def newest(pattern): c = [p for p in glob.glob(pattern) if os.path.getmtime(p) >= start] return max(c, key=os.path.getmtime) if c else None -log = ser = qmp = None -while time.time() < deadline and not (log and ser and qmp): - if mk.poll() is not None: - print(f"MAKE FAILED (exit {mk.returncode}); see {make_log.name}", flush=True) - sys.exit(1) - log = log or newest(f"{ROOT}/logs/*/{a.arch}/qemu-{a.arch}-*.log") - ser = ser or newest(f"{ROOT}/build/{a.arch}/kernel/qemu-serial-{a.arch}-*.sock") - qmp = qmp or newest(f"{ROOT}/build/{a.arch}/kernel/qemu-qmp-{a.arch}-*.sock") - time.sleep(1) -print(f"LOG: {os.path.relpath(log, ROOT)}", flush=True) - -s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) -for _ in range(60): - try: - s.connect(ser) - break - except OSError: - time.sleep(1) - - def drain(): while True: try: @@ -169,14 +156,6 @@ def drain(): return -threading.Thread(target=drain, daemon=True).start() - -q = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) -q.connect(qmp) -qf = q.makefile("r") -qf.readline() - - def qcmd(cmd, **args): msg = {"execute": cmd} if args: @@ -190,7 +169,48 @@ def qcmd(cmd, **args): return r -qcmd("qmp_capabilities") +def boot(args): + """One `make ... qemu` boot: QEMU in the foreground of make, its serial log, serial and QMP + sockets found and connected (the globals every check reads).""" + global start, mk, log, s, q, qf + start = time.time() + make_log = open(os.path.join(work, "make.txt"), "a") + mk = subprocess.Popen(args, cwd=ROOT, stdout=make_log, stderr=subprocess.STDOUT) + deadline = time.time() + 1800 + log = ser = qmp = None + while time.time() < deadline and not (log and ser and qmp): + if mk.poll() is not None: + print(f"MAKE FAILED (exit {mk.returncode}); see {make_log.name}", flush=True) + sys.exit(1) + log = log or newest(f"{ROOT}/logs/*/{a.arch}/qemu-{a.arch}-*.log") + ser = ser or newest(f"{ROOT}/build/{a.arch}/kernel/qemu-serial-{a.arch}-*.sock") + qmp = qmp or newest(f"{ROOT}/build/{a.arch}/kernel/qemu-qmp-{a.arch}-*.sock") + time.sleep(1) + print(f"LOG: {os.path.relpath(log, ROOT)}", flush=True) + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + for _ in range(60): + try: + s.connect(ser) + break + except OSError: + time.sleep(1) + threading.Thread(target=drain, daemon=True).start() + q = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + q.connect(qmp) + qf = q.makefile("r") + qf.readline() + qcmd("qmp_capabilities") + + +def shutdown(): + """Quit QEMU over QMP and let make finish: one QEMU at a time (.claude/CLAUDE.md).""" + q.send(json.dumps({"execute": "quit"}).encode()) + mk.wait() + time.sleep(2) + + +ok = True +boot(make_args) def text(): @@ -371,12 +391,12 @@ elif m in ("stick_boot", "two_sticks"): check("System: StarForth v4", re.search(r"System: StarForth v4", text()) is not None) 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) + check("Zuse: genesis minted onto attached thumbdrive", + wait_for(r"Zuse: genesis minted onto attached thumbdrive", 120)) + check("[zuse@Hera] ok>", wait_for(PROMPT, 60)) 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)) check("[zuse@Hera] ok>", wait_for(PROMPT, 60)) @@ -418,6 +438,32 @@ elif m in ("artemis_unsigned", "raw_ident"): check("no blk: disk line (nothing attached)", count(r"blk: disk ") == 0) check("no fault", count(r"EXCEPTION|PANIC|panic") == 0) +elif m in ("genesis", "stick_genesis"): + if m == "stick_genesis": + check("Boot disk: a StarshipOS stick", re.search(r"Boot disk: a StarshipOS stick", text()) is not None, + near=r"Boot successful!") + check("Zuse: genesis minted onto attached thumbdrive", + wait_for(r"Zuse: genesis minted onto attached thumbdrive", 180), near=r"Artemis: |Zuse: ") + check("[zuse@Hera] ok>", wait_for(PROMPT, 60)) + check("no identity confirmed on a first boot", count(r"Zuse: identity confirmed") == 0) + 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) + if m == "genesis": + shutdown() + print("-- the second boot, the same copy of the data stick", flush=True) + boot([x for x in make_args if x != "clean"]) + check("POST: PASSED (second boot)", wait_for(r"POST: PASSED", 900)) + check("Zuse: identity confirmed (second boot)", + wait_for(r"Zuse: identity confirmed from attached thumbdrive", 120), near=r"Artemis: |Zuse: ") + check("no genesis on the second boot", count(r"Zuse: genesis minted") == 0) + check("[zuse@Hera] ok> (second boot)", wait_for(PROMPT, 60)) + n = count(PROMPT) + type_line("3 4 + .") + check("7 ok (second boot)", wait_for(PROMPT, 60, n) and re.search(r"7\s+ok", text()[-400:]) is not None) + check("no fault (second boot)", count(r"EXCEPTION|PANIC|panic") == 0) + elif m == "stick_v3": t = text() check("Boot disk: a StarshipOS stick", re.search(r"Boot disk: a StarshipOS stick", t) is not None) @@ -662,8 +708,7 @@ else: check(f"known mode {m}", False) print(f"RESULT: {'PASS' if ok else 'FAIL'}", flush=True) -q.send(json.dumps({"execute": "quit"}).encode()) -mk.wait() +shutdown() for f in ("v3/include/version.h", "capsules/BLOCK_MAP.md"): subprocess.run(["git", "checkout", "-q", "--", f], cwd=ROOT) sys.exit(0 if ok else 2)