docs(plan): the stick, increments 5 and 5a -- the proxy Zuse, growth, MINT-ZUSE and the fixed screen

Five tasks, each closed by full acceptance: the proxy Zuse (a stick's first boot mints its own;
genesis marker version 2), Artemis grown to fill the stick (metadata only), acceptance on a fresh
data stick, MINT-ZUSE onto a second stick (read back before the marker names her), and the fixed
screen whose only action is a replacement that kills the old stick.

R1 ruled (Captain Bob, 2026-10-29: "256 GiB is ruled"): Artemis grows to at most 256 GiB, not
STICK.md §5a's 2 TiB -- the block map costs two bytes of the 2 GiB kernel heap per 1 KiB block.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-29 08:23:40 -04:00
co-authored by Claude Opus 5.5
parent 248179257a
commit 728b0a54eb
@@ -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 <N> 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=<work>/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=<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=<work>/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).