diff --git a/docs/v4.0.0/plans/2026-10-29-stick-5-proxy-zuse-and-real-zuse.md b/docs/v4.0.0/plans/2026-10-29-stick-5-proxy-zuse-and-real-zuse.md new file mode 100644 index 00000000..e69ba474 --- /dev/null +++ b/docs/v4.0.0/plans/2026-10-29-stick-5-proxy-zuse-and-real-zuse.md @@ -0,0 +1,345 @@ +# The stick, increments 5 and 5a: plug and play -- the proxy Zuse, growth, and the real Zuse stick — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans (Captain Bob prefers native execution). Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** a freshly written stick boots to `[zuse@Hera] ok> ` with nothing set up (a proxy Zuse +minted at first boot, Artemis grown to fill the stick); `MINT-ZUSE` mints the real Zuse onto a +second stick; with a real Zuse, the system stick alone gives a fixed screen whose only action is +minting a replacement, and a replacement kills the old Zuse stick. + +**Architecture:** +- The genesis marker on Artemis's fence gains version 2 (the proxy's key, the real Zuse's). v4 reads + it in a new `sk_v4_marker.c`; v3 keeps reading only version 1, unchanged. The identity check that + compares a drive with a key is split out of `capsule_zuse_boot_check` (as D1 split the check from + the VM), so v4 can check against either key. +- Genesis, growth, the GPT writer, the Zuse-stick mint and the fixed screen are all on the v4 path: + `sk_v4_identity.c` decides; new focused files do the work (`gpt.c` gains the writer, + `sk_v4_grow.c`, `sk_v4_mint.c`). +- Acceptance moves from the scratch pair to a fresh data stick: every `clean qemu` boot is a first + boot, so every acceptance run tests plug and play. + +**Tech Stack:** kernel C99 (`-Werror`), the block subsystem (`v3/src/block_subsystem.c`, unchanged +behaviour for v3), `tools/mkartemis.c`, `scripts/mkdiskimage.sh`, QEMU q35/pc/virt + OVMF/EDK2 with +xHCI USB sticks, `scripts/qemu_session.py`. + +**Spec:** `docs/v4.0.0/STICK.md` §2, §3, §4 (the 2026-10-29 amendment), §5a, §6, §6a, §7, §9, §10, +§11 increments 5 and 5a. + +## Rulings (Captain Bob) + +**R1. How far Artemis grows. Ruled 2026-10-29: "256 GiB is ruled".** +- **The problem:** §5a caps the Artemis partition at 2 TiB. The block subsystem keeps two bytes of + kernel heap per 1 KiB block (`blk_bam_entry_t`, `calloc(user_blocks, ...)`), and the kernel heap + is 2 GiB (`KMALLOC_DEFAULT_HEAP_SIZE`). A 2 TiB Artemis would need about 3 GiB; it cannot attach. +- **Recommended: cap growth at 256 GiB** (about 384 MiB of heap for the block map). Sticks on the + shelf are 16-64 GB (§4); a 512 GB stick runs at 256 GiB and the boot says so. §5a's 2 TiB is + amended to 256 GiB. +- **Its on-disk side:** the free map (BAM) sits at the volume's start and its size is fixed at + format, so growth could not add pages to it without moving the payload. The image builder + formats Artemis with a BAM sized for the cap (24 MiB of zeroed pages, sparse in the image); growth + then writes metadata only, as §5a requires. + +**My calls (no ruling needed unless you disagree):** +- **The fixed screen and `MINT-ZUSE` show the offered stick's size, not its USB vendor and product.** + §6a says both; the xHCI driver logs `idVendor` at enumeration but keeps neither it nor a product + string, and adding that is a driver change this design does not need. §6a is amended to "its + size". +- **On v4 the installed Zuse key is the logged-in Zuse's,** set at each login (proxy or real). v3's + one-way `vm_zuse_cert_install` is v3's VM's and is not touched. +- **`MINT-ZUSE` is a kernel word of the lone node** (`sk_v4_words[]`, request 2), as `BYE` is. It + adds one word: v4's `PARITY:V4_SYSTEM word_count` goes from 317 to 318 and `dict_hash` changes, + equal on all three ISAs. It has no POST case (it waits for a stick and a typed `YES`), as `BYE` + has none. +- **The Zuse stick's identity partition** is 500 MiB, or the stick less 2 MiB if smaller; a stick + under 64 MiB is refused. Before minting, the partition's first 8 devblocks are zeroed (a used + stick's old bytes must not read as an identity), then `capsule_mint_identity(..., NULL, "Zuse", + "zuse", ..., drive_known_blank=1)`. +- **Waiting for the stick, any key stops `MINT-ZUSE`.** Pulling the system stick at any point + before the marker is written stops it too; nothing is written to the marker. + +## Global Constraints + +- **As increment 4'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 (`.claude/CLAUDE.md`, as it stands when the task starts): three + `clean qemu` boots to `[zuse@Hera] ok> `, `PARITY:V4_POST tests=538 pass=538 fail=0`, `POST: + PASSED`, `dict_hash` equal on all three; `qemu-stick-high`; `make -C v4 test-32` 0 failures; + logs and DoE CSVs committed (LFS filter off). +- **Every new console or log message is at most 63 characters.** +- **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, the system stick's Artemis partition (the + first disk slot), its identity partition, then the real Zuse stick's identity partition when she + is logged in. Detach in reverse (`blk_subsys_detach_device` removes only the tail): pulling the + system stick lets the Zuse stick go first. +- **The marker is written only after what it names has been read back.** A failure anywhere before + that leaves the marker as it was. +- **Sizes, in `kernel/Makefile` only:** image 2 GiB, ESP 64 MiB, identity 500 MiB, minimum stick for + growth 8 GB (8000000000 bytes), Artemis cap 256 GiB (R1), Zuse stick minimum 64 MiB. +- **Exact messages** (the tests match them): + - `Zuse: genesis minted onto attached thumbdrive` (as v3's) + - `Artemis: grown to fill the stick` / `Artemis: the stick is under 8 GB -- not grown` / + `Artemis: past 256 GiB of the stick is left unused` + - `MINT-ZUSE: plug in the stick for the real Zuse` / `MINT-ZUSE: a stick of MiB -- all of it + is erased` / `MINT-ZUSE: type YES to write it` / `MINT-ZUSE: stopped -- nothing written` / + `MINT-ZUSE: failed -- nothing changed` / `MINT-ZUSE: the stick is under 64 MiB` / + `MINT-ZUSE: the stick's sectors are not 512 bytes` + - `Zuse: real Zuse minted onto the second stick` / `Zuse: not this system's Zuse stick -- not used` + - the fixed screen, four lines: `Zuse's stick is not here.` / `Plug it in to log in.` / `Or plug + in another stick and press R to mint a replacement` / `-- the old Zuse stick will stop + working.` + +## Review Focus + +1. **The Zuse stick enumerates before the system stick** (both in at power-on, hers on the lower + port): the marker is not readable yet. She must still be logged in once the system stick's + Artemis attaches. Pinned by `zuse_stick_first` (Task 5). +2. **A used stick offered to `MINT-ZUSE`** -- one holding an old GPT and an old identity (the pair's + Zuse stick): nothing on it changes before `YES`, and after `YES` its old identity is never taken + for the new one. Pinned by `mint_zuse_refuse` (bytes unchanged) and `mint_zuse` run on the pair's + Zuse stick image (Task 5). +3. **The system stick pulled while `MINT-ZUSE` waits:** it stops, nothing is written, the system is + headless, and replugging the system stick logs the proxy in again. Pinned by + `mint_pull_system` (Task 5). +4. **A write failure on the new stick:** "failed -- nothing changed", the marker untouched, the old + Zuse stick still logs in. Pinned by `mint_fail` (blkdebug write errors on the new stick, Task 6). +5. **Keys typed on the fixed screen:** `3 4 + .` and Enter do not run, before or after a stick + arrives; `R` with no stick plugged asks for one. Pinned by `fallback` (Task 6). + +--- + +### Task 1: the proxy Zuse -- genesis on the stick, marker version 2 + +**Files:** +- Modify: `kernel/include/starkernel/zuse_genesis_marker.h` (version 2 layout, beside version 1) +- Modify: `kernel/src/capsule/capsule_zuse_boot.c`, `kernel/include/starkernel/capsule_zuse_boot.h` + (split out `capsule_zuse_boot_check_key`; split out the VM-free genesis mint; v3's two callers + unchanged in behaviour) +- Create: `kernel/src/v4/sk_v4_marker.c`, `kernel/include/starkernel/v4/sk_v4_marker.h` +- Modify: `kernel/src/v4/sk_v4_identity.c` (genesis instead of "no genesis"; proxy login) +- Modify: `kernel/Makefile` (the new source) +- Modify: `scripts/qemu_session.py` (modes below; `add_stick(name, img, port)` generalising + `add_zuse`) + +**Interfaces (produces):** +- **Marker version 2** (`ZUSE_GENESIS_MARKER_VERSION_2 = 2`, same magic, one 4 KiB devblock): + `uint32_t magic, version; uint8_t proxy_pubkey[32]; uint8_t real_pubkey[32]; uint32_t has_real; + uint64_t crc;` (CRC-64 as version 1, over every byte before `crc`), padded to 4096. +- **`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])`:** + the identity-record half of `capsule_zuse_boot_check` (magic, version, CRC, pubkey == `want`). + `capsule_zuse_boot_check` calls it with the version 1 marker's key. +- **`int capsule_zuse_boot_genesis_mint(struct blkio_dev *dev, uint8_t seed[32], uint8_t + pubkey[32])`:** the mint half of v3's genesis (0 or -1); v3's genesis calls it, then writes its + version 1 marker as today. +- **`sk_v4_marker.h`:** `typedef struct { int present; uint8_t proxy[32]; int has_real; uint8_t + real[32]; } sk_v4_marker_t;` `int sk_v4_marker_read(sk_v4_marker_t *m)` (version 1 reads as + `present=1, proxy=its key, has_real=0`; 0 on success, -1 if none); `int + sk_v4_marker_write(const sk_v4_marker_t *m)` (always version 2; 0 or -1). +- **On the stick's arrival** (`sk_v4_drive_arrived`, after Artemis attaches): no marker and a blank + identity partition → genesis: mint, write the version 2 marker (`has_real=0`), log the genesis + line, log in as Zuse. A marker → `capsule_zuse_boot_check_key` against `proxy`; a match with + `has_real=0` logs in (the line `Zuse: identity confirmed from attached thumbdrive`, as today); a + match with `has_real=1` sets `g_proxy_fallback=1` and no session (Task 6 gives it the fixed + screen; until then it stays headless). +- **The Artemis partition must be formatted to take the marker:** `blk_subsys_confirm_format` only + if the slot is provisional and its `artemis_sig_t` checked out (§4). + +- [ ] **Step 1: Failing tests** (harness): + - **`genesis`** (three ISAs): a fresh data stick (`DATASTICK=1 ZUSEDISK=`), built once into the + session directory (`DATASTICK_IMG=/data.img DATASTICK_DEP=`) and booted twice, one QEMU at + a time. First boot: the genesis line, `[zuse@Hera] ok> `, `3 4 + .` → `7 ok`. Second boot: + `identity confirmed`, no genesis line, the prompt. Today: FAIL (`no genesis on this disk`). + - **`stick_genesis`** (amd64, `--target qemu-stick`, no `STICK_PAIR`): the PC stick's first boot + reaches the prompt. Today: FAIL (headless). + - **Changed:** `stick_boot` expected the blank stick headless; it now expects the genesis line and + the prompt. +- [ ] **Step 2:** Run each: FAIL as listed. +- [ ] **Step 3:** Implement. +- [ ] **Step 4:** The new sessions pass; regression: `zuse` (the pair, version 1: logs in as the + proxy), `stick_zuse`, `stick_artemis`, `two_sticks`, `artemis_unsigned`, `raw_ident`, `hotplug`, + `midpull`, `waitpull`, `typeahead`, the GPT modes, `v3_keys` (v3's genesis and marker unchanged). +- [ ] **Step 5:** Suite; full acceptance; commit `feat(kernel): the proxy Zuse -- a stick's first + boot mints its own Zuse; the genesis marker's version 2` with the logs. + +### Task 2: Artemis grows to fill the stick + +**Files:** +- Modify: `kernel/src/gpt.c`, `kernel/include/starkernel/gpt.h` (the writer) +- Create: `kernel/src/v4/sk_v4_grow.c`, `kernel/include/starkernel/v4/sk_v4_grow.h` +- Modify: `kernel/src/v4/sk_v4_identity.c` (grow before the partitions are opened) +- Modify: `tools/mkartemis.c` (`--bam-for SECTORS`), `scripts/mkdiskimage.sh` (passes it), + `kernel/Makefile` (`STICK_ARTEMIS_CAP_GIB := 256`, `STICK_GROW_MIN_BYTES := 8000000000`, the BAM + size for the cap on the PC stick and the data stick) +- Modify: `scripts/qemu_session.py` (`--stick-gib N`: the session's copy of the stick extended, + sparse, to N GiB) +- Modify: `docs/v4.0.0/STICK.md` §5a (R1 as ruled) + +**Interfaces:** +- **`int gpt_grow_last(blkio_dev_t *disk, const uint8_t type[16], uint64_t max_part_sectors, uint64_t + *new_last_lba)`:** moves the backup entry array and header to the disk's end, extends the last + partition of `type` to the last usable LBA (capped at `max_part_sectors`), rewrites both headers + and entry CRCs, backup before primary. `GPT_OK`, or `GPT_EIO`/`GPT_BAD` with nothing written + after the failure point; a disk whose GPT already ends at the disk's end returns `GPT_OK` with + `*new_last_lba` unchanged. +- **`int sk_v4_grow_artemis(blkio_dev_t *art)`:** for an Artemis partition whose `STFR` header's + `total_devblocks` is less than the partition's: sets `total_devblocks`, recomputes the totals as + `compute_totals_from_B` does (same formula; the BAM already covers it -- `mkartemis --bam-for`), + writes the header, moves the `artemis_sig_t` and the fence's devblocks to the new top. Metadata + only. Returns 0, or -1 with the old header still valid (the header is written last). +- **`mkartemis --format IMG --first-lba L --sectors N [--bam-for M]`:** the BAM is sized as for a + volume of M sectors (`choose_B`), the rest as today; `--check` unchanged. +- **In `sk_v4_drive_arrived`, for this system's stick, before opening its partitions:** if the disk + is at least 8 GB and its GPT ends short of the disk → `gpt_grow_last` (cap 256 GiB), re-read the + GPT, open Artemis, `sk_v4_grow_artemis`, then on as Task 1. Under 8 GB → the "not grown" line, + once. Past the cap → the "left unused" line. + +- [ ] **Step 1: Failing tests:** + - **`stick_grow`** (amd64, `--target qemu-stick --stick-gib 16`): first boot: `grown to fill the + stick`, Artemis's `blk: disk` line over 12000000 user blocks (`KERNEL_ARGS=--log-level=info`), + the genesis line, the prompt; the first boot's time printed by the harness (POST to prompt). + Second boot of the same copy: no `grown` line, the same Artemis size, `identity confirmed`. + `sfdisk --verify` on the copy afterwards: clean. Today: FAIL (no growth). + - **`stick_small`** (amd64, `--stick-gib 4`): `under 8 GB -- not grown`, Artemis's 2 GiB size, the + prompt. Today: FAIL (no line). + - **`check_images.sh`:** the PC stick's and data stick's Artemis BAM is the cap's size + (`mkartemis --check` reports it). Today: FAIL. +- [ ] **Step 2:** Run each: FAIL as listed. +- [ ] **Step 3:** Implement. +- [ ] **Step 4:** They pass; regression: `genesis`, `stick_genesis`, `stick_zuse`, `two_sticks`, + `artemis_unsigned`, `midpull` (replug: not grown again). +- [ ] **Step 5:** Suite; full acceptance; commit `feat(kernel): Artemis grows to fill the stick at + its first boot -- metadata only, capped at 256 GiB` with the logs and the first-boot time. + +### Task 3: acceptance on a fresh data stick + +**Files:** +- Modify: `kernel/Makefile` (`DATASTICK` on by default for the three `qemu` targets; `ZUSEDISK` + empty by default on v4 boots; `ARTDISK`/`ZUSEDISK` kept for `system=v3` boots and named in their + comments as such) +- Modify: `scripts/qemu_session.py` (the default Zuse is the fresh data stick; modes that need the + pair -- `stick_zuse`, `v3_keys`, `raw_ident`, the GPT modes -- ask for it by name) +- Modify: `.claude/CLAUDE.md` ("The disks" paragraph; the `qemu-stick-high` line runs + `stick_genesis` on the pc machine at 3 GiB), `disk/README.md` (the pair: tests and v3 only), + `docs/v4.0.0/STICK.md` §11 (increments 5 and 3's amendment done), the acceptance-rules memory + +**Interfaces:** `make -f kernel/Makefile ARCH= clean qemu` boots v4 with a fresh data stick +on port 2 and no other drive: a first boot every time, the proxy minted, the prompt. + +- [ ] **Step 1: Failing test:** `zuse` with the new default (no `--make`): the genesis line and the + prompt on all three ISAs. Today: FAIL (the pair is the default; no genesis line). +- [ ] **Step 2:** Run it: FAIL. +- [ ] **Step 3:** Change the defaults and the docs. +- [ ] **Step 4:** `zuse` passes on three ISAs; `qemu-stick-high` reaches the prompt from a fresh + PC stick; regression: `v3_keys`, `stick_zuse`, `raw_ident`, the GPT modes, `entropy`. +- [ ] **Step 5:** Suite; full acceptance as newly written; commit `test(build): acceptance boots a + fresh data stick -- every run is a first boot` with the logs. + +### Task 4: `MINT-ZUSE` -- the real Zuse onto a second stick + +**Files:** +- Create: `kernel/src/v4/sk_v4_mint.c`, `kernel/include/starkernel/v4/sk_v4_mint.h` +- Modify: `kernel/src/gpt.c`, `gpt.h` (a one-partition writer) +- Modify: `kernel/src/v4/sk_v4.c` (`sk_v4_words[]` gains `"MINT-ZUSE"`, request 2) +- Modify: `kernel/src/v4/sk_v4_identity.c` (a stick offered while a mint waits; the real Zuse + stick recognised and attached; her session) +- Modify: `kernel/Makefile`, `scripts/qemu_session.py` + +**Interfaces:** +- **`int gpt_write_one(blkio_dev_t *disk, const uint8_t type[16], uint64_t part_sectors, uint8_t + disk_guid[16])`:** a protective MBR, primary and backup GPT with one partition of `type` at LBA + 2048 of `part_sectors`, a fresh disk GUID (from the RNG); refuses (`GPT_BAD`) a disk whose sectors + are not 512 bytes. +- **`int sk_v4_mint_zuse(void)`:** the whole flow of §6a, run from the `MINT-ZUSE` word and (Task 5) + from the fixed screen's R. Prints the plug-in line; beats (`sk_v4_identity_beat`) until a stick is + offered or a key is pressed or the system stick leaves; shows its size; reads a line; on exactly + `YES`: `gpt_write_one`, zero the partition's first 8 devblocks, `capsule_mint_identity(..., + drive_known_blank=1)`, read back with `capsule_zuse_boot_check_key` against the new key, then + `sk_v4_marker_write` with `real=` the new key, `has_real=1`, `proxy` kept. Then + `sk_v4_identity_recheck_offered()`, and her session begins on the new stick by the ordinary path + below. Returns 0 when minted, -1 otherwise (each failure's line from the list). +- **`sk_v4_identity.h` gains** (the drive type stays private to `sk_v4_identity.c`): + - `int sk_v4_identity_offer_wait(blkio_dev_t **raw, uint64_t *bytes)`: beats until a drive that + is not this system's stick arrives (1, its raw device and size), or a key is pressed or the + system stick leaves (0); + - `void sk_v4_identity_recheck_offered(void)`: runs the arrival check again on the offered + drive, now that it carries a GPT and an identity. +- **A drive that arrives and is not this system's stick:** if a mint waits for one, it is offered + (not attached). Else, if it is a GPT disk with a StarshipOS identity partition and no Artemis + partition, the system stick is in, and the marker has a real key: check against `real`; a match + is attached (the chain's tail) and she is logged in; no match → `not this system's Zuse stick -- + not used`. If the system stick is not in yet, the drive is remembered and checked when Artemis + attaches (Review Focus 1). Anything else: `not this system's stick -- not used`, as today. +- **Pulling the real Zuse stick:** her session ends as today's line says; with Task 5, the fixed + screen. + +- [ ] **Step 1: Failing tests** (amd64 unless said; a blank 600 MiB sparse stick image in the session + directory; `--make "DATASTICK_IMG=/data.img DATASTICK_DEP="` where two boots share one + copy): + - **`mint_zuse`** (three ISAs): fresh data stick, prompt; type `MINT-ZUSE`; the plug-in line; + `add_stick` the blank stick on port 3; the size line names 600 MiB; type `YES`; the minted line; + the prompt; `3 4 + .` → `7 ok`; `sfdisk --verify` on the new stick's image clean, one + StarshipOS identity partition. Then the same with the pair's Zuse stick image as the offered + stick (Review Focus 2). Today: FAIL (`MINT-ZUSE` unknown). + - **`mint_zuse_refuse`:** as `mint_zuse` but type `no`: `stopped -- nothing written`; the offered + image's SHA-256 unchanged; the prompt; still the proxy (a second `MINT-ZUSE` is offered again). + - **`mint_pull_system`:** type `MINT-ZUSE`, then pull the data stick: `stopped -- nothing + written`, `session ended`, headless; replug it: the proxy logs in again (no real key was + written). + - **`zuse_stick_first`:** after a `mint_zuse` boot, boot the same data stick copy with the minted + Zuse stick on port 1 and the data stick on port 2: she is logged in (`identity confirmed` once, + the prompt). + - **`mint_small`:** a 32 MiB stick offered: `under 64 MiB`, nothing written, the prompt. +- [ ] **Step 2:** Run each: FAIL as listed. +- [ ] **Step 3:** Implement. +- [ ] **Step 4:** They pass; regression: `genesis`, `two_sticks` (a blank data stick on port 2 is + still not used when no mint waits), `hotplug`, `midpull`, `typeahead`. +- [ ] **Step 5:** Suite; full acceptance (`word_count=318`, the new `dict_hash`, equal on three); + commit `feat(kernel): MINT-ZUSE -- the real Zuse minted onto a second stick, read back before the + marker names her` with the logs. + +### Task 5: the fixed screen, its replacement, and the old stick refused + +**Files:** +- Modify: `kernel/src/v4/sk_v4_identity.c` (`sk_v4_identity_wait`: the fixed screen while + `g_proxy_fallback`; R; the screen again when her stick is pulled) +- Modify: `kernel/src/v4/sk_v4_mint.c` (nothing new: R calls `sk_v4_mint_zuse`) +- Modify: `scripts/qemu_session.py` +- Modify: `docs/v4.0.0/STICK.md` §6a (my call on the size line), §11 (increment 5a done) + +**Interfaces:** +- **The fixed screen** (`console_fb_clear`, `console_fb_mirror(1)`, the four lines; on serial too): + shown when the system stick's proxy is recognised and the marker has a real key, and again when + the real Zuse stick is pulled. While it shows, every key is read; `R` or `r` runs + `sk_v4_mint_zuse` (which asks for a stick if none is offered); every other key is dropped. The real + Zuse stick arriving logs her in (Task 4's path) and the console takes the screen. +- **After a replacement** the marker's `real` is the new key; the old stick then reads `not this + system's Zuse stick -- not used`. + +- [ ] **Step 1: Failing tests** (amd64; the data stick copy from a `mint_zuse` boot, booted again): + - **`fallback`:** no Zuse stick: the four lines; no `ok> ` for 30 s; `3 4 + .` and Enter typed, + then the Zuse stick added: the prompt, and no `7` in the output before `3 4 + .` is typed + again after it. Then `R` typed with no stick offered asks for one (the plug-in line); any key + stops it; the fixed screen again. + - **`replace`:** no Zuse stick; type `R`; offer a blank stick; `YES`: the minted line, the + prompt; pull it: the fixed screen; add the old Zuse stick: `not this system's Zuse stick -- not + used`, no prompt for 30 s; add the new one: the prompt. + - **`mint_fail`** (Review Focus 4): from the fixed screen, `R`, offer a stick whose writes fail + (blkdebug `iotype: write`, errno 5): `failed -- nothing changed`; the fixed screen; add the old + Zuse stick: the prompt. + - **`real_pull`:** logged in as the real Zuse; pull her stick: `session ended`, the fixed screen + (the screen's pixels change, `screen()`); add it: the prompt. + - **`system_pull_real`:** logged in as the real Zuse; pull the data stick: her stick is let go + first (no `detach` refusal in the log), `session ended`, headless; replug the data stick (hers + still in): she is logged in again, by the remembered-drive check of Task 4. + - Today: FAIL (the proxy with a real key stays headless; no fixed screen). +- [ ] **Step 2:** Run each: FAIL as listed. +- [ ] **Step 3:** Implement. +- [ ] **Step 4:** They pass; regression: all of Task 4's sessions, `genesis`, `stick_genesis`, + `screen_dark_until_login`, `screen_dark_after_logout`, `kbd_typeahead`, `emergency`. +- [ ] **Step 5:** Suite; full acceptance; commit `feat(kernel): the fixed screen -- with a real Zuse, + the system stick alone can only mint a replacement; the old stick dies` with the logs. +- [ ] **Step 6:** A new PC stick (`make boot_image TARGET=PC`, no `STICK_PAIR`) for the SER5, and + the bench steps written in `boards/BRINGUP.md`: plug in, boot, the prompt; `MINT-ZUSE` onto the + second stick; boot with the system stick alone (the fixed screen); with both (the prompt).