Skip to content

In QEMU, the loader writes boot variables on the running system's request and a failed pass falls to the entry behind its own or powers off; toyos-metal drives a boot through a machine running ToyOS alone - #539

Closed
Japabu wants to merge 37 commits into
mainfrom
wt/toyos-install

Conversation

@Japabu

@Japabu Japabu commented Sep 27, 2026 •

Copy link
Copy Markdown
Collaborator

Part of stage 2 of issues/boot-media/the-machine-updates-itself-without-ubuntu.md, proven in QEMU with ToyOS on an emulated USB stick and claiming nothing of the T14. The loader writes the firmware's boot variables on a request the running system leaves in the slot table. It boots a slot or another ESP once, and when a pass fails it hands the machine to the entry behind its own, or powers it off where there is none; that power-off is proven by nothing, and the section below it says why. toyos-metal drives a whole metal boot against a machine running ToyOS alone (the bench), with the old path behind --via-ubuntu until the installer. The T14's switch-over stays owed in the track, as item 1, and is proven later by the orchestrator's runbook.

What changed, per decision

  • The request is the slot table's (format 2). The kernel never maps the runtime services, so the running system writes the request and the next pass writes the variables. slots::Request is next: Option<Next> (a slot once, an ESP by unique GUID, a slot on trial) and first: bool; each field is held to the one value a writer leaves, and written away (table rewritten, flushed) before it is acted on. The loader finds the table as toyos_gpt proves it: locate_type for the one TOYOS-SLOTS entry, refused where its blocks are no partition, then locate. Entries the loader writes are HD(…)/File(\EFI\BOOT\BOOTX64.EFI) for an ESP it found, never a path the running system names. update --boot-first, update --boot-next <esp guid> (both unsigned), update --once < image. --boot-next refuses by name to replace a slot asked for once (Request::boot_next), and --once refuses by the ESP's GUID to replace another ESP's boot (Request::install, the one rule for what an install leaves asked), before any byte is written.
  • A trial writes nothing of the image the machine keeps. The pass that boots a slot once leaves it on the table as Next::Trial (kind 3); while it stands slots::grant refuses the running slot's trial, so nothing on the trial holds the marked slot. The next pass that chooses a slot takes the trial off. A slot booted once raises no floor (record::Booted::once). The kernel logs a once boot from slot-refused=<marked>:once, whose word is toyos_abi::boot::SLOT_ONCE. What the kernel is told of the chosen slot is policy::told, pure, for both of the loader's tries.
  • One HARDDRIVE rule, one enumerator, one writer. entry::partition walks a device path's bytes: its one HARDDRIVE node (42 bytes, GPT, GUID-signed) or a refusal, and the disk's path, the bytes before that node. The boot partition, the loader's own ESP, an ESP a request names, every option naming and after read, and rootimage::boot_disk (the handle whose path is that disk's and the end node) all take it. bootvars::entries reads Boot#### (an unreadable entry refuses), bootvars::boot_next writes BootNext, and bootnext::point_at_us uses both. The other decisions are pure in toyos_update::entry: naming (lowest active entry naming an ESP), after (first active entry after BootCurrent's last place in BootOrder not naming the loader's own ESP; never earlier, so no loop), free (a number neither an entry nor the order names), first (refuses an order past 256), number (uppercase only).
  • The loader's own panic handler (uefi-services' is off) sets BootNext to entry::after's entry and resets, or powers off where there is none. The no-slot path is this path. The loader's own ESP is taken once, right after uefi_services::init and before anything can panic (bootnext::take_ours), so the handler never opens LoadedImage while the pass it interrupted may hold it. A pass that failed before that powers off.
  • loader-previous.log keeps the last chain: deleted first, then copied in chunks, so no refusal leaves an earlier chain's file. The loader names its ESP's removable-media file by SHA-256, which the bench holds every image's loader to.
  • The bench path (src/metalbench.rs): update --once < image, reboot, /log in one sftp session. A readback is this boot's: the machine is back only once loader.log differs from the one fetched before the reboot; the loader's text is loader-previous.log held to the image's signed-header digest; the kernel's is the logd boot naming the image's ROOT with no name /log held before the delivery. A wait that runs out says what its last ask came to. The bench's own log (a --nic boot's MAC) is found by the ROOT its pass handed the kernel.
  • One path per run, named. --install-sudoers, --fat32-check, --device, --host and --resident reach the machine through Ubuntu and are refused by name without --via-ubuntu; --swap beside --via-ubuntu is refused, with or without --image; --resident beside --readback, --nic or --fat32-check is refused.
  • The bench key is on the bench image's ROOT, built by cargo run -- --bench-image <keys>, and build::bench_keys refuses an owner-signed bench. date on the bench answers -u +%s and refuses every other form.
  • Harness: BootOptions::recovery_stick, BootOptions::stick_readonly, vars::global, vars::plant_global and an independent EFI_LOAD_OPTION reader, metal::stage, image::put_files_on, tests/common/bench.rs, Rig::bend_kernel. wait_for_ready reads the UART for its marker when QEMU exits before the console closes, as its timeout arm already did: the loader's panic handler resets at once, and a -no-reboot QEMU then exits before the timeout arm looks.
  • No QEMU test holds the power-off, and update_no_entry_powers_off is deleted. Its premise was a pass with nothing active behind the stick's entry. The pinned OVMF (edk2-gf0064ac3af) writes two such entries at every boot. The first is its EFI Internal Shell (BdsPlatform.c:1712, LOAD_OPTION_ACTIVE). The firmware matches an existing option on its attributes too (BmLoadOption.c:558), so a Shell entry made inactive is replaced by a new active one. The second is its Boot Manager Menu, UiApp (BdsEntry.c:991, CATEGORY_APP|ACTIVE|HIDDEN, BmBoot.c:2533). SetBootOrderFromQemu rebuilds BootOrder from active options only: the bootindex devices first, the firmware's applications behind them. That identifies the log's entries: Boot0000 is UiApp, Boot0001 the stick, and Boot0002 and Boot0003 the Shell, which takes the two numbers in turn because BmGetFreeOptionNumber treats as free a number BootOrder does not list. The orchestrator's run measured it: over 4 passes the firmware wrote both back active each time the test cleared them. So the stick always has the Shell behind it. From the disk or the variables, the no-entry arm is reached only by overriding the firmware's order at every boot. The arm is filed as proven by nothing, with its exit (issues/boot-media/the-loaders-power-off-with-no-entry-behind-it-is-proven-by-nothing.md).
  • netd is main's. netd: a listener's socket that left Listen is handed over or listens again, and a refused accept leaves its owner a wake #559 landed the listener fix and its tests on their own. This branch takes main's userland/netd whole and carries no netd change.

High-risk: the two checks

Negative controls. Each is a checked patch: the mutant was shown to build, then restored, and the tree was left clean.

decision test mutation exit at
the disk is the path before the one HARDDRIVE node a_partitions_disk_is_the_path_before_its_hard_drive_node, a_path_names_the_partition_of_its_one_hard_drive_node the disk cut before the path's last node, boot_disk's old rule 101 be959b5d
one HARDDRIVE node same the second node taken, not refused 101 be959b5d
--resident beside --fat32-check refused the_ubuntu_path_is_named_and_nothing_falls_back_to_it out.fat32_check out of the refusal 101 5cd13850
installing the sudoers rule is not also a boot installing_the_rule_is_not_also_a_boot RESIDENT out of ABOUT_A_BOOT 101 5cd13850
a silent machine is refused with its last ask a_machine_that_never_answers_is_refused_with_the_last_ask wait refusing with last: None 101 5cd13850
the Ubuntu-only flags the_ubuntu_path_is_named_and_nothing_falls_back_to_it each of --fat32-check, --device, --host, --resident, --install-sudoers out of UBUNTUS; the --swap refusal if false; the resident refusal if false 101 each 1f576344
the recovery fall passes the loader's own ESP update_no_slot_boots_the_recovery_stick (an active HD(<machine ESP>)/\EFI\BOOT\BOOTX64.EFI right behind BootCurrent) after_this_one(rt, None) 1: the machine loops and never reaches the recovery stick 1f576344
loader-previous.log is deleted first bench_loop_drives_a_toyos_machine (a 384 KiB stale file ending in a marker) the delete-first block removed 1: the readback carries the marker 1f576344
consumed before acted on update_boot_next_boots_the_entry_once (read-only stick; serial and store) consume below the BootNext write 1 1f576344
--boot-next never replaces a slot asked once a_boot_next_replaces_an_esp_and_never_a_slot_asked_once the refusal answers Ok 101 1f576344
--once never replaces another ESP's boot an_install_answers_a_slot_asked_once_and_never_drops_an_esp (Some(Next::Esp(_)), true) => Some(Next::Slot(idle)), the old rule 101: left: Ok(Request { next: Some(Slot(B)), first: true }), right: Err([1, …]) 29ec77fa
a fetch that never succeeds says why a_machine_that_never_gives_back_its_log_is_refused_with_the_last_ask last: None 101 1f576344
date answers one form bench_loop_drives_a_toyos_machine (date +%Y) the refusal if false 1 1f576344
a trial is told once a_trial_is_told_once_and_a_fall_back_is_told_its_refusal the trial told (None, None) 101 1f576344
a readback is this boot's a_boots_log_is_the_one_that_names_its_root, a_readback_is_of_a_pass_since_and_of_this_image no before skip in kernel_log; the digest check if false; rebooted without the comparison 101 each 9f9da32b…4de6c673
a trial holds nothing of the kept slot a_grant_claims_only_an_idle_slot_on_the_running_disk, update_trial_writes_nothing_of_the_kept_slot the grant's trial refusal if false 101; 1 9f9da32b…4de6c673
no owner-signed bench a_bench_is_throwaway_signed_and_authorizes_ed25519_keys_alone no owner refusal in bench_keys 101 9f9da32b…4de6c673
the order is written once update_boot_first_puts_the_loader_first first: asked.first 1 9f9da32b…4de6c673
a once boot raises no floor bench_loop_drives_a_toyos_machine once: false 1 9f9da32b…4de6c673
the entry policy the_entry_after_is_later_in_the_order_and_boots_something_else, an_esp_is_booted_by_its_lowest_active_entry, a_number_is_four_uppercase_hex_digits_and_a_free_one_is_named_by_nothing, the_order_puts_ours_first_once no own-ESP skip in after; naming without active; lowercase numbers; a stale order number free; a duplicate order; put_first past the bound 101 each 9f9da32b…4de6c673
the loader's own panic handler update_no_slot_boots_the_recovery_stick uefi-services' panic handler back 1 9f9da32b…4de6c673
a boot that ends before its console closes is read on the UART root_candidate_malformed, root_named_but_absent, root_candidate_overlaps the wait at 2d6d228f, whose Disconnected arm does not read the UART red 3 of 3 in the orchestrator's Fast run at 2d6d228f: QEMU died before Slot A: REFUSED, 2d6d228f

No whole-change control: the tests name slots::Next and the harness fields this change adds, so they do not compile against the base; each control reverts one decision whole. The handler's no-entry power-off has no control, because no test reaches it.

Independent oracles: OVMF's boot manager (it booted the HD(…)/File(…) entry the loader wrote and the one the test planted, honoured BootNext once and resumed BootOrder, took the fall to the recovery stick); the variable store read and written by EDK2's VariableFormat.h layout, and the load option read and built by the specification's tables in the test, not by the loader's encoder; EDK2's source at the pinned OVMF's commit f0064ac3af, for the entries that firmware writes at every boot; the GPT entry array read by UEFI §5.3.3's layout; QEMU's read-only drive. For the bench: the machine's own kernel record of the once boot, init's refusal of the trial's grant, and the slot table read off the disk after each cycle.

Gates

At b7299abb, the head, the last three rows re-run there; b7299abb changes only tests/ and issues/ from 133d5dcd, where the first three ran. 133d5dcd is the merge of main at fb34346e, which takes #554, #574, #575 and #576.

gate exit
cargo run -- --build-only 0
cargo test --lib 0
cargo test --workspace --exclude toyos-build (toyos-update, toyos-gpt among them) 0
cargo run -- --clippy 0
cargo run -- --ci host 0
cargo test --test toyos-build -- --list (builds every guest binary, boots nothing) 0

Guest runs by the orchestrator, one cargo test --test toyos-build -- [--nightly] --jobs 1 <name> each, at 29ec77fa. Since then the branch changed update_no_entry_powers_off, then deleted it, and took main's merges. At 133d5dcd the Fast tier passed 394 of 394.

  • The Fast tier: 396 of 396 passed, the three root_* tests among them.
  • All 21 of these passed: the 11 update_* tests the branch keeps, bench_loop_drives_a_toyos_machine, and the nightlies that exercise the loader's report pass or its panic (blackbox_panic_chain, panic_outlives_the_deadline, panic_reboots, watchdog_resets, loader_watchdog_arms, boot_deadline_ends_a_wedge, usb_reset_hands_devices_back, hard_lockup_ends_a_deaf_cpu, usb_reset_records_the_phase_it_cut).
  • usb_transport_break is disabled on main (Disable four flaky tests behind their filed defects #565), so its run printed only the disabled line.

Unsure

  • No test panics the loader while main holds LoadedImage. That the handler never opens it is shown only by reading the code.
  • boot_disk takes its disk from entry::partition, but no QEMU test gives the loader a boot path whose HARDDRIVE node is not the last one. OVMF never makes such a path. So the loader's wiring of the rule is held only by reading, and the rule itself by the host test.
  • A panic inside the panic handler spins until the firmware watchdog resets into the same stick. This is read from the code, not measured.
  • An entry for the loader's own stick that carries no HARDDRIVE node, such as OVMF's own auto entry, is not recognised by entry::after as the loader's own.
  • --resident, take_the_machine and metalbench::wire have never run: they run only on the T14.
  • metal::invocation's Reach::ViaUbuntu line is first parsed on the T14. tests/toyos.rs is harness = false with no host test site, and only the bench test parses Reach::Bench's line.
  • lan_swap is on the redlist (issues/build/a-swaps-redial-races-a-hard-dial-ceiling-against-an-unbounded-guest-gap.md), so the T14 does not run it. The bench's --swap path is measured only by bench_loop_drives_a_toyos_machine. That test redials the same way after its netd swap, so the redial race that issue names can reach it too.

Filed

  • issues/boot-media/a-loader-change-reaches-a-machine-only-by-writing-its-stick.md
  • issues/boot-media/the-bench-reads-no-quiescent-log-volume.md
  • issues/boot-media/the-benchs-cable-is-read-by-the-driver-under-test.md
  • issues/boot-media/the-bench-runs-with-no-bound-on-its-own-boot.md
  • issues/boot-media/the-bench-sometimes-comes-back-two-minutes-late.md
  • issues/boot-media/the-loaders-power-off-with-no-entry-behind-it-is-proven-by-nothing.md
  • issues/boot-media/a-failed-pass-can-fall-to-an-entry-the-firmware-never-boots-from-its-order.md

🤖 Generated with Claude Code

https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j

Japabu and others added 5 commits September 27, 2026 13:34
…bench replaces Ubuntu

The loader writes the firmware's boot variables on a request the running
system leaves in the slot table (format 2): `update --boot-first` puts its
own entry first in BootOrder, `update --boot-next <esp guid>` sets BootNext
to another ESP, and `update --once < image` boots the idle slot once and
never marks it. Each request is taken off the table, flushed, before it is
acted on. A pass whose every slot is refused falls to the entry after its
own in BootOrder instead of dying. A slot booted once raises no floor.

toyos-metal drives a T14 running ToyOS alone (the bench): the image goes in
with `update --once`, the reboot over ssh, the boot's loader passes and
logd files come back over sftp in one session — the loader keeps the last
chain's file as loader-previous.log, and the boot's logd file is the one
naming its ROOT — and the old judges read them. `--via-ubuntu` names the
old path; `--via-ubuntu --resident` hands the machine to a bench image.
The bench refuses an image built with another loader, which it names by
its file's hash.

Measured in QEMU on OVMF: BootOrder was 0003,0002,0000 and the loader's
written entry Boot0001 stayed first across the reset after it; the machine
stick is Boot0001 beside a recovery stick, which the fall-through reached as
Boot0003.

tests/updatecase gave toybox `syscap = ["power"]` where power is a
connector since the log change, so its `reboot` was refused (exit 1) and
every update test's reboot stalled; it receives the connector now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
…L_CONFIGS

`as_chunks` for the two-byte walks, a type for a boot entry, no redundant
clones, `rfind` for the variable store's last copy; `toyos_tmpdir::TempDir`
for the bench's scratch and its tests; tests/benchcase and
tests/benchvirtiocase in the list every config gate walks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
After the recovery stick hands the machine back, and again after the
machine reboots itself, the wait ends at the machine's own kernel or the
recovery stick's a second time; the second is the red, by name, where it
used to be the wait's own stall.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
An entry for an ESP is written only where none names it, and only for its
removable-media loader; one that names it — the owner's own entry for
Ubuntu's ESP, efibootmgr's for a stick — is reused whatever file it boots,
and an ESP with neither is refused by name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
…rule tested

`date -u +%s` answers a second on the bench and a bare `date` is refused
as 2; `--metal-via-ubuntu` alone is refused as reaching no machine.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
@Japabu Japabu changed the title The T14 updates without Ubuntu: loader boot variables, the bench, and recovery The T14 updates without Ubuntu: the loader writes boot variables, the bench replaces Ubuntu, a machine with no slot falls to its recovery stick Sep 27, 2026
@Japabu
Japabu marked this pull request as ready for review September 27, 2026 12:41
@Japabu

Japabu commented Sep 27, 2026

Copy link
Copy Markdown
Collaborator Author

Review of fce54926 against origin/main (16d2e645), round 1.

Gate. CI at fce54926 is green: abi-split success in run 36319840382, and host is skipped on pull requests by ci.yml. The fast tier has not run at this head. No T14 reading exists. Under reviewer.md the firmware claims are NOT READY FOR REVIEW. I reviewed the rest because the brief asked for it.

BLOCKER

  • B1 src/metalbench.rs:234 — [Q4] kernel_log takes the newest logd stem whose first part names the image's ROOT UUID. That UUID comes from the content (src/image.rs:137), so an earlier delivery of the same image, such as a rerun from the same request.txt, left a file that matches. A delivery that never reached logd is then read back with that earlier boot's kernel log. before (metalbench.rs:326) already lists the names present before delivery; exclude them. Test: a unit test where before holds the only file that matches the ROOT must get the empty text.
  • B2 src/metalbench.rs:369 — [Q4] "Gone down" is a single failed probe. The PR body measured the bench's listener resetting connects in 3 of 4 runs. A reset probe before the reboot takes effect lets wait_for_the_log (398) fetch the bench while it still runs. Its loader-previous.log is then the previous delivery's chain: with the same image the digest check (265) passes, and with B1 the whole readback is an earlier boot's. bootloader/src/loaderlog.rs:73 keep_previous also leaves the older loader-previous.log in place on every refusal (the 4 MiB bound at :88, a write error). Required: the fetched loader.log must differ from before's, and keep_previous must delete PREVIOUS before it copies. A mutation survives today: metalbench.rs:269 if !text.contains(&named) → if false, and no test turns red.
  • B3 toyos-update/src/slots.rs:394 (userland/init/src/main.rs:1293) — [Q2] During a once boot the running slot is the unmarked one, so idle is the marked slot, the image the machine keeps. update < Y on the trial overwrites that slot and marks it. update --once < Y on the trial overwrites it too, and bootloader/src/slot.rs:85 then boots it as the marked slot, for good. The claim "every pass after boots the marked slot" breaks as soon as a trial runs update. Refuse the grant (or update) on a once boot; the kernel knows it from slot-refused=<m>:once. Test in updatecase: update --once < X, reboot, then update < Y on the trial must be refused, with slot A's signed header unchanged.
  • B4 src/main.rs:155 / src/main.rs:259 — [Q3] cargo run -- --owner-key --bench-image k.pub builds the bench with the runner key on ROOT and signs it with the owner's key and a Machine-scope loader. --update-image with --bench-image does the same. Nothing refuses either pair. The result is a valid update for every owner machine, and it authorizes the runner key there. "No image anything publishes carries one" holds only while nobody types this. Refuse --bench-image once use_owner has run (or have bench_image refuse unless key().whose() == Throwaway), with a test.
  • B5 bootloader/src/request.rs:83 — [Q1/Q5] No test can fail on the "consumed before it is acted on" ordering. Move consume(...) below the SetVariable block (act first, consume after) and every listed test stays green. Test: in the boot-next test, kill QEMU on "is taken off the slot table before it is acted on", then assert with vars::global that the store holds no BootNext and that the next boot is the machine's stick. That test goes red under the patch.
  • B6 bootloader/src/request.rs:77 — [Q5] Change first: false to first: asked.first and update_boot_first_puts_the_loader_first (tests/common/update.rs:771) stays green. Under the patch every later pass rewrites BootOrder, one NVRAM write per boot. Assert that the pass after the one that wrote the order carries no Request: line and no is first: line.
  • B7 bootloader/src/main.rs:1116 — [Q2/Q5] once: chosen.once.is_some() → once: false survives every test. The only control is on record::account, and the bench's Scope::Image floor refuses nothing when it is raised for B. In bench_loop_drives_a_toyos_machine (tests/common/bench.rs:163), assert that the readback's loader passes say Anti-rollback floor: not raised, because slot B's image was booted once and never raise the floor for B.
  • B8 bootloader/src/bootvars.rs:182 — [Q1/Q5] Delete || ours.is_some_and(|guid| entry::names(option, guid)) and update_no_slot_boots_the_recovery_stick (tests/common/update.rs:813) stays green: no second entry naming the machine's ESP follows BootCurrent, and the test checks BootNext=Boot without the number. Plant an active HD entry for the machine's ESP behind BootCurrent with vars::plant, and assert that BootNext names the recovery stick's entry.
  • B9 bootloader/src/bootvars.rs:123 — [Q1/Q5] Delete entry::active(o) && and every test stays green, so reusing an entry the boot manager skips goes unseen. Plant an inactive entry naming the recovery ESP before update --boot-next; the (written now) assertion then goes red.
  • B10 bootloader/src/bootnext.rs:98 — There is a second enumeration of Boot#### beside bootvars::entries (bootvars.rs:101), with the opposite policy: it skips unreadable entries and ignores ACTIVE. There is also a second BootNext writer (bootnext.rs:46 beside bootvars.rs:166) with its own attribute set. point_at_us can choose an inactive entry that the request path refuses. Keep one enumerator and one writer.
  • B11 toyos-update/src/entry.rs:217 — GuidText re-implements toyos_gpt::Guid's Display, and its test (:372) exists only to keep the two equal. request.rs already uses toyos_gpt::Guid. Use it in update as well, and put parse_guid beside the Display it inverts.

NOTE

  • N1 Gate — The fast tier is owed at fce54926: swap_on now hands back with fire where it used exec, and Talking::prepare changed its signature on the old path too, while lan_swap and swap_* are Tier::Fast.
  • N2 [Q5] — No whole-change negative control is named. The new update_* tests on 16d2e645 should go red (update refuses the flags); name that run and its exit. The five single-line controls were measured at 0f9b5b7e, not at the head.
  • N3 bootloader/src/bootvars.rs:156 — put_first can write MAX_ORDER+1 entries, but words (:144) refuses more than MAX_ORDER. after_this_one, the recovery path, then fails on an order the loader wrote itself.
  • N4 toyos-update/src/entry.rs:202 — after cycles on a BootOrder that holds duplicates ([3,5,3] with both entries failing: 3→5→3). Untrusted order, recovery path.
  • N5 toyos-update/src/entry.rs:211 — free ignores numbers that BootOrder names but no variable holds. A new entry can take such a stale number and so enter the order at its old position.
  • N6 toyos-update/src/entry.rs:255 — number accepts lowercase hex, which UEFI does not treat as a load option. Reusing such an entry sets a BootNext that names nothing.
  • N7 [Q1] — After --boot-first the stick heads BootOrder. Every loader panic other than a slot refusal (main.rs:965 floor, the RSDP expect, the cmdline UTF-8 check, load_kernel_elf) then leaves a machine that boots only through F12. fall_through covers slot refusal alone.
  • N8 bootloader/src/slot.rs:121 — When both slots are dead, the dead loop can boot the once slot with once left false, so its handover raises the floor to the trial's version.
  • N9 bootloader/src/slot.rs:100 — The refused = None arm for a refused trial has no test; deleting it is invisible.
  • N10 bootloader/src/main.rs:148 — LOADER_IS says "the file firmware loaded", but own_file hashes REMOVABLE_PATH, not LoadedImage's file path.
  • N11 src/build.rs:2011 — Duplicates tests/common/ssh.rs:325 KEYS_ON_ROOT.
  • N12 src/metalbench.rs:57 — ONCE restates the wording in update's output with no shared constant.
  • N13 toyos-update/src/slots.rs:38 — FORMAT 2. A machine at main that installs this branch's image with update keeps a format-1 table, which the new update refuses. The machine then cannot update until its stick is rewritten. Record this in the loader-change issue.
  • N14 [Q3] — update --boot-first and --boot-next are unsigned. Whoever holds the update grant (on the bench, the runner key) can reorder the firmware or boot any ESP that is present once. Say so in update's header.
  • N15 src/metalbench.rs:214 — wire takes the newest logd name to be the bench's own. Those names come from the bench's clock, so a clock that stepped back makes the newest name another boot's.
  • N16 [Q7] kernel/src/params.rs:53 + toyos-update/src/policy.rs:107 + the bootlog gate row — One word lives in two crates and a source gate keeps them equal. One constant in toyos_abi::boot beside SLOT_REFUSED_PARAM deletes the copy and the gate row.
  • N17 [Q7] toyos-update/src/entry.rs:57 — Unwritable (NotAscii, TooLong) and its test police two compile-time constants. Build the option infallibly.
  • N18 [Q7] — These only police flag forms on a path slated for deletion and are candidates to cut: the UBUNTUS/resident/swap refusal matrix in src/metal.rs with the_ubuntu_path_is_named_and_nothing_falls_back_to_it; the --metal-via-ubuntu "add --metal" refusal in src/testargs.rs with its test; the refusal of other date forms in userland/toybox/src/date.rs, with the bare-date assertion at tests/common/bench.rs:117.
  • N19 [Q6] — OVMF is the only firmware measured. The T14 run must show:
    • after --resident, Boot entries: Boot####, this loader's ESP …, is first, and then this pass was booted as Boot#### after a cold power-off by hand, not only after a warm reset;
    • an update --once boot logging boot: slot B, once…, with the next pass booting A;
    • update --boot-next <NVMe ESP> booting Ubuntu once, then the bench again: Lenovo deletes BootNext and expands the HD() short form for both the NVMe and the USB stick;
    • one fall_through on the T14 reaching the next entry. The runbook marks this optional, but it is the recovery the owner relies on.
  • N20 [Q8] — git diff --shortstat: 46 files, +3197 −256. Production, excluding in-file mod tests: about +2048 −201, net about +1847. Tests: tests/ +700 −21 plus in-file tests +339 −20, net about +998. Issues net +95. Deletions still owed: the bootnext.rs enumerator and writer, GuidText, params::ONCE with its gate row, and Unwritable.

REMOVE

  • bootloader/src/bootnext.rs:88 — The doc paragraph is duplicated verbatim at :92.
  • bootloader/src/bootnext.rs:5 — "on the owner's laptop the next entry in the boot order is Ubuntu" is false after --boot-first.
  • bootloader/src/main.rs:870 — "BootNext was consumed by this pass and this pass sets none" is false for the Fired::Next pass that ends here.
  • bootloader/src/loaderlog.rs:64 — "a file past this is not one this loader wrote" is false: a long chain the loader wrote can pass the bound.
  • src/build.rs:2014 — "past the largest metal image's" is a measurement that rots.
  • tests/toyos.rs:877 — "a minute and more of boots" is a measurement.
  • src/metalbench.rs:14 — The Ubuntu-to-bench table in the module header is the old path's story and rots when --via-ubuntu goes.

Brief questions

  1. NVRAM safety. No path deletes a variable. Entries it writes are HD+removable only, and the order is bounded (with the N3 off-by-one). "At most once" is crash-safe as ordered (table flushed, then SetVariable), but no test can fail on it (B5, B6). The recovery path has untested skips (B8, B9), a duplicate-order cycle (N4), and a panic surface outside slot refusal (N7).
  2. Anti-rollback floor. --once cannot lower the floor, and the pure path does not raise it for a trial. The loader's wiring of that is untested (B7). A trial can replace the kept image (B3). The runner key reaches only throwaway-signed images on a Scope::Image floor, unless the owner-signed bench is built (B4).
  3. Bench key. No gate stops an owner-signed bench (B4). No pipeline publishes images today.
  4. Stale log. Yes, a stale /log can pass as this boot's: the kernel log by ROOT UUID on a rerun (B1), and both files through an early "gone down" together with a stale loader-previous.log (B2).
  5. Negative controls. Surviving mutations: B2, B5, B6, B7, B8, B9, and N9.
  6. Independent oracle. OVMF is independent, but it is not the T14. The owed readings are in N19.
  7. Zookeeping. N16, N17, N18.
  8. Net lines. See N20.

SEND BACK

Japabu and others added 5 commits September 27, 2026 15:46
One conflict, the ssh client's usage block: main's `fire` answer `exited <n>` and
this branch's `probe` line, both kept. `tests/updatecase/system.toml`
carries main's side.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
…his boot's, one entry policy

The blockers, each with the test that goes red without it:

- B1: a boot's kernel log is never a stem whose names `/log` held before
  the delivery (`metalbench::kernel_log`), so a rerun of one image cannot
  read the last run's file.
- B2: the machine is back only once its `loader.log` differs from the one
  fetched before the reboot (`rebooted`), in the one wait that fetches
  `/log`; `keep_previous` deletes `loader-previous.log` first and copies in
  chunks, so no refusal leaves an earlier chain's file and no bound refuses.
- B3: a slot booted once stays on the table as `Next::Trial` (request kind
  3) until the next pass that chooses a slot takes it off; while it stands
  `slots::grant` refuses the running slot's trial, so `update` on the trial
  holds nothing of the slot the machine keeps.
- B4: `bench_image` refuses the owner's key (`bench_keys`), so neither
  `--owner-key` nor `--update-image` beside `--bench-image` signs a bench.
- B5: a pass that cannot take a request off the slot table sets no
  `BootNext`: the boot-next test runs one pass on a read-only stick
  (`BootOptions::stick_readonly`) and reads the variable store after it.
- B6: the pass after the one that wrote the boot order says no `Request:`
  and no `is first:` line.
- B7: the bench test asserts the report pass's "not raised, because slot
  B's image was booted once" and no raise to the trial's version.
- B8, B9, B10: one enumerator (`bootvars::entries`) and one `BootNext`
  writer (`bootvars::boot_next`); `bootnext::point_at_us` uses both, and the
  decisions are `toyos_update::entry::naming` and `entry::after`, each
  skipping an inactive entry and `after` every entry naming the loader's own
  ESP, host-tested.
- B11: `GuidText` and `entry::parse_guid` are gone; `toyos_gpt::Guid::parse`
  sits beside the Display it inverts, and `update` uses both.

NOTEs: `BootOrder` is written only where it fits `MAX_ORDER` (N3);
`entry::after` falls behind the current entry's last place, so a duplicated
order ends rather than cycles (N4); a free number is one neither an entry nor
the order names (N5); lowercase hex is no `Boot####` (N6); every loader panic
falls to the next entry through the loader's own panic handler, and powers
off where there is none (N7), which is also the no-slot path now; the chosen
slot's once and refusal are set in one place for both loops (N8, N9); the
loader line names the removable-media file it hashes (N10); the tests use
`build::AUTHORIZED_ON_ROOT` (N11); the restated `update` wording is gone
(N12); the table format change is recorded in the loader-change issue (N13);
`update`'s header says the two asks are unsigned (N14); the bench's own log
is found by the ROOT its pass handed the kernel (N15); the once word is
`toyos_abi::boot::SLOT_ONCE`, and the two copies and their gate row are gone
(N16); `entry::Unwritable` is gone and the load option is built infallibly
(N17); the Ubuntu-flag matrix, the `--metal-via-ubuntu` rule and `date`'s
refusal of other forms are gone with their tests (N18).

REMOVEs: the duplicated doc and the laptop clause in `bootnext.rs`, the
consumed-BootNext clause in `end_this_pass`, the loader-log bound's comment
with the bound, the ROOT room's measurement, the registration's duration and
the Ubuntu table in `metalbench`'s header.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
`update --boot-next` reads its GUID with `toyos_gpt::Guid::parse`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
…before the exit

The grant's mutant went red on the refusal's wording while `update` was
stopped by the version rule instead (200 is not newer than 200). An image
newer than the trial's leaves the grant as the only thing between it and
slot A, and slot A's signed header is read before the exit code is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
The owner's ruling: stale or false prose is deleted, never corrected, and new
prose is the one-clause invariant at the edit site. Every doc and comment this
review round rewrote is back to the text it had with only the false part
deleted, and every one it added is a clause at the line it guards.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
@Japabu

Japabu commented Sep 27, 2026

Copy link
Copy Markdown
Collaborator Author

Review round 2 at 22d5e97.

CI is red at this head: run 36327751879, job abi-split exit 1 ("toyos-abi changed and its version did not: toyos-abi/Cargo.toml still says 0.16.0, and the next one is 0.17.0" — the branch adds 4 lines to toyos-abi/src/boot.rs with no bump), so host was skipped and no host test ran at this head; the previous head 4de6c67 was red too.

NOT READY FOR REVIEW

Japabu and others added 2 commits September 27, 2026 17:17
The branch added four lines to toyos-abi/src/boot.rs, and abi-split
refused a changed crate republished under its old version. toyos-abi
moves to 0.17.0; toyos and toyos-window each name it by version, so
both are touched and each takes its own minor bump (0.19.0, 0.21.0),
and every lockfile that locks any of the three without a registry
source is re-locked with `cargo update --offline -p <crate>`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Japabu

Japabu commented Sep 27, 2026

Copy link
Copy Markdown
Collaborator Author

Review of fa94514f against origin/main (6f0729ab, merge base c79d409b), round 2. This supersedes the gate stop in 5857002896.

Gate. CI is green at fa94514f: run 36329382702, abi-split success, and host is skipped on pull requests by ci.yml. The green arms of update_* and bench_loop_drives_a_toyos_machine were measured at 9f9da32b and 4de6c673. Since 4de6c673 the branch's own code has changed only in comments and the toyos-abi version (checked with git diff 4de6c673 HEAD). But merge f4b90415 brought in #540, QEMU 11.1.1, so those arms predate the instrument they will run on. The fast tier has not run, and there is no T14 reading. I reviewed at the brief's request; the verdict says whether the branch is ready for those runs.

Round-1 BLOCKERs

  • B1 CLOSED. kernel_log skips a boot that before names. With that skip removed, cargo test --lib exits 101 (a_boots_log_is_the_one_that_names_its_root).
  • B2 CLOSED. rebooted requires a changed loader.log, and the digest check stands. Both mutations (the check as if false, and rebooted without the comparison) exit 101. keep_previous's delete-first has no control; see the NOTE on loaderlog.rs:70.
  • B3 CLOSED. slots::grant refuses Next::Trial(running). The mutation exits 101 in toyos-update and 1 in update_trial_writes_nothing_of_the_kept_slot. I checked the one bypass I suspected, a trial running update --boot-next to overwrite next and then update < Y. It is shut too: the refusal withholds the table claim, so no form of update runs on a trial.
  • B4 CLOSED. bench_keys refuses Whose::Owner; the mutation exits 101.
  • B5 CLOSED. A read-only stick is used, and moving consume below BootNext exits 1 (BootNext [04, 00]). The test has a race; see the NOTE on update.rs:754.
  • B6 CLOSED. first: asked.first exits 1.
  • B7 CLOSED. The bench test asserts the not-raised line and the absence of a raise; once: false exits 1.
  • B8 CLOSED on its named mutation, which now lives in entry::after and exits 101. The loader's wiring of it is unguarded; see the NOTE on main.rs:900.
  • B9 CLOSED. naming without active exits 101.
  • B10 CLOSED. There is one enumerator (bootvars::entries) and one BootNext writer (bootvars::boot_next). No other set_variable of a global boot variable is left in bootloader/src.
  • B11 CLOSED. toyos_gpt::Guid::parse sits beside its Display; GuidText, parse_guid and Unwritable are gone.

N8 does not block. Both loops now end in the one told closure (bootloader/src/slot.rs:94), so the second path N8 named is gone. A surviving mutation would have to rebuild a loop-local path, and review would see that. Making it testable costs about five lines (see NOTE).

BLOCKER

  • src/metal.rs:2067: deleting UBUNTUS and the resident refusal means the bench path takes --fat32-check, --device, --host and --resident, then drops them. toyos-metal --image i --readback r --fat32-check returns a verdict with the outside FAT judge never run. This branch's issues/boot-media/the-bench-reads-no-quiescent-log-volume.md:12 records a refusal that is not there. Round 1's N18 was wrong to call these checks flag-form policing. Refuse each of them without --via-ubuntu, by name, and restore their assertions. The test must go red when the refusal is removed.
  • src/metal.rs:2055: --via-ubuntu --swap netd --binary n --talk k --readback r --image x.img used to be refused (IMAGE in NOT_A_SWAP, then the via_ubuntu && swap refusal). Now it flashes x.img through Ubuntu, runs --talk's conversation with the swap's key, never swaps, and can come back green. Refuse --swap beside --via-ubuntu with --image, and restore the "no Ubuntu half" assertion, which must go red when the refusal is removed.

NOTE

  • src/metal.rs:2766: a_swap_is_not_a_boot… dropped --fat32-check from its refusal loop, but FAT32_CHECK is still in NOT_A_SWAP (:1570), so removing it from NOT_A_SWAP turns nothing red. Restore vec!["--fat32-check"]. The deleted assertion that --image x.img without --readback is refused tested a live refusal (:2067); restore it too.
  • bootloader/src/main.rs:900: after_this_one(rt, None) survives every test, because OVMF's own entries carry no HD node. Round 1's B8 test guards it. In update_no_slot_boots_the_recovery_stick, plant under EFI_GLOBAL_VARIABLE an active HD(<machine ESP>)/File(\EFI\BOOT\BOOTX64.EFI) entry, ordered right after the boot stick, and assert that BootNext names the recovery stick's entry. Termination does not depend on this, since after only moves later in the order.
  • bootloader/src/loaderlog.rs:70: nothing guards delete-first. Remove the open(PREVIOUS, ReadWrite) block and CreateReadWrite writes over the old file from offset 0. A shorter chain then keeps an older chain's tail, which can carry HANDED_BACK for bootlog::handed_back to take as this boot's. Test: before the delivery in bench_loop_drives_a_toyos_machine, stage a loader-previous.log longer than any chain, holding a marker line, on the bench's log partition. Assert that the readback's loader text lacks the marker.
  • tests/common/update.rs:754: after the marker, the correct unwritable pass goes on to point_at_us (main.rs:1174), and if QEMU is still alive it sets BootNext to its own entry. That makes a false red. Also assert that the serial up to the marker lacks BOOT_NEXT_SET, which is deterministic under the B5 mutation, and hold the store to no BootNext naming the recovery stick's entry.
  • userland/update/src/main.rs:120: --boot-next silently replaces a standing Next::Slot(idle) left by update --once, so the installed trial is dropped. Refuse by name, or say what it replaced.
  • src/metalbench.rs:156: .ok() discards every fetch error, so a steady failure ends as Silent { "come back" } after wait_secs. Carry the last error into the refusal.
  • src/metalbench.rs:51, :343 and src/metal.rs:2457: machine.txt is written and read by no judge and no test. Delete it.
  • tests/benchcase/system.toml:41 and tests/benchvirtiocase/system.toml:40: nothing asks the bench to run echo. The probe only authenticates, and echo {PHRASE} is asked of the delivered boot. Delete the bin/echo symlink.
  • tests/common/metal.rs:838: stage repeats run's Arm-to-Batch literal (:626). Make one constructor.
  • bootloader/src/bootvars.rs:70: this adds a third rule for choosing a device path's HARDDRIVE node, "last". It sits beside boot_partition's "exactly one" (main.rs:232) and bootnext::hard_drive_guid's "first" (bootnext.rs:66), and ours for requests and the panic handler comes from "first". Choose the node in one function.
  • userland/toybox/src/date.rs:4: date answers any argument with Unix seconds, so date +%Y prints seconds. Refuse everything but -u +%s. Round 1's N18 was wrong to cut that refusal.
  • bootloader/src/slot.rs:94: move told's decision into toyos_update::policy as a pure function, and N8's dead-loop case gets a host test.
  • Gate: rerun update_* and bench_loop_drives_a_toyos_machine at the head, under QEMU 11.1.1. The loader's own panic handler now ends every loader panic in a reset or a power-off rather than a halt, so the fast tier must cover every test that stages a loader refusal.

REMOVE

  • tests/benchcase/system.toml:7: "the two differ by the one devices row" is false; benchvirtiocase also starts test-runner.
  • tests/benchcase/system.toml:35 and tests/benchvirtiocase/system.toml:31: "echo is how the loop asks the bench to answer" is false.
  • src/metal.rs:8: "until the installer puts ToyOS on the NVMe" is a plan in a module header, and it will rot.
  • PR body: "Every loader failure hands the machine on" is false for a panic inside the handler, which spins until the firmware watchdog resets into the same stick.

Net lines

git diff --shortstat origin/main...HEAD: 60 files, +3291 −345. Split at each file's mod tests against the merge base:

part net lines
production +1751
in-file tests +317
tests/ +779
issues +98

Round 1 had about +1847 of production. The feature earns the growth. What is still left to delete is the list above: machine.txt, bin/echo, the duplicated Batch literal, and one of the HARDDRIVE rules.

Owed on the T14

  1. Resident mode. --via-ubuntu --resident must show:

    • TAKEN:;
    • in loader-previous.log, Boot entries: Boot####, this loader's ESP (…), is first: BootOrder was … and is …;
    • the next pass saying this pass was booted as Boot####, with the same number and no Request: or is first:.

    Record whether the line said written now or already there. The resident path's own efibootmgr --create-only entry makes it already there, in which case Lenovo has never booted an entry the loader wrote. To measure one written now entry: from step 6's Ubuntu, delete the stick's entry with efibootmgr -B, F12 to the stick, run update --boot-first, reboot, and check that the pass says this pass was booted as the new number.

  2. Cold power-off by hand. The pass says this pass was booted as the same Boot####, and BootOrder is is still headed by it.

  3. update --once.

    • Loader: Slot B: asked for once; the table marks A and Request: a boot of slot B once is taken off the slot table.
    • Kernel: boot: slot B, once, as the running system asked; the slot table marks A.
    • Report pass: Anti-rollback floor: not raised, because slot B's image was booted once, and no Anti-rollback floor: <B's version>, raised.
    • Next pass: Request: slot B's trial, which is over, is taken off the slot table, then boot: slot A, the one the slot table marks.
    • The metalcase judge is green.
  4. --boot-next to Ubuntu and back. update --boot-next <NVMe ESP PARTUUID> gives BootNext=Boot####, ESP …: the firmware boots it once, and Ubuntu boots. Ubuntu's efibootmgr shows BootCurrent as that entry and no BootNext. Ubuntu's reboot returns to the bench, whose pass says this pass was booted as the bench's entry.

  5. One fall-through. With TOYOS-SLOTS zeroed:

    • the loader says Slots: … and this pass failed, so BootNext=Boot####, the entry after Boot#### in BootOrder;
    • that Boot#### is Ubuntu's, not another entry for the stick;
    • Ubuntu boots with no F12, and the machine does not power off;
    • read from that Ubuntu, the stick's loader.log carries the line.

SEND BACK

Japabu and others added 2 commits September 27, 2026 17:44
…and what earns nothing goes

BLOCKERs:

- A flag only Ubuntu carries out (`--install-sudoers`, `--fat32-check`,
  `--device`, `--host`, `--resident`) is refused by name without
  `--via-ubuntu` rather than dropped by the bench path, so no run returns a
  verdict with the outside FAT judge never run.
- `--swap` beside `--via-ubuntu` is refused ("a swap has no Ubuntu half"),
  with or without `--image`, and `--resident` beside `--readback`, `--talk`
  or `--nic` is refused ("judges no boot"). The three refusals sit after the
  swap's own, so `--swap … --fat32-check` is still the swap's refusal and
  `FAT32_CHECK` in `NOT_A_SWAP` is live and tested again.

NOTEs:

- `metal::tests`: the swap loop names `--fat32-check` again, and
  `--image x.img` with no `--readback` is refused, asserted again.
- `update_no_slot_boots_the_recovery_stick` plants an active
  `HD(<the machine's ESP>)/\EFI\BOOT\BOOTX64.EFI` entry right behind the
  entry that boots the machine, and holds the pass to one fall, to the entry
  behind the planted one.
- `bench_loop_drives_a_toyos_machine` stages a `loader-previous.log` longer
  than any chain, ending in a marker line, before the bench's first pass, and
  holds the readback's loader text to lacking it (`image::create_file_on`).
- `update_boot_next_boots_the_entry_once` holds the unwritable pass's serial
  up to its marker to no `BootNext=Boot`, and the store to no `BootNext`
  naming the recovery stick's entry, since the pass goes on past the marker.
- `update --boot-next` refuses by name to replace a slot asked for once
  (`slots::Request::boot_next`, host-tested).
- `metalbench::wait_for_the_log` carries what its last ask came to into
  `Refusal::Silent`.
- The HARDDRIVE node is chosen in `bootvars::hard_drive` alone (exactly one,
  GPT, GUID-signed); `bootnext`'s "first" and `esp`'s "last" are gone.
- `date` refuses every form but `-u +%s`; the bench test asserts `date +%Y`
  ends 2.
- `told` is `toyos_update::policy::told`, pure and host-tested with the dead
  trial of N8.

Deleted: `machine.txt` with its writer and its readback entry, the bench
configs' `bin/echo` symlink, the second Arm-to-Batch literal (`Batch::of`).
REMOVEs: the benchcase "differ by the one devices row" clause, both "echo is
how the loop asks" clauses, and the installer plan in `metal.rs`'s header.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Japabu

Japabu commented Sep 27, 2026

Copy link
Copy Markdown
Collaborator Author

Review of 1f576344 against origin/main (merge base 6f0729ab), round 3.

Gate. CI is green at 1f576344: run 36333871353, abi-split success, and host is skipped on pull requests. The PR body gives exit 0 at this head for toyos-update, --lib, clippy, --ci host, the 11 update_* tests and bench_loop_drives_a_toyos_machine under QEMU 11.1.1. The fast tier is left to the queue. There is no T14 reading yet; this review says whether the branch is ready for one.

Round-2 findings

  • BLOCKER metal.rs:2067 CLOSED. UBUNTUS refuses --install-sudoers, --fat32-check, --device, --host and --resident without --via-ubuntu. Taking each one out of the list exits 101 (the_ubuntu_path_is_named_and_nothing_falls_back_to_it).
  • BLOCKER metal.rs:2055 CLOSED. --via-ubuntu beside --swap is refused, with and without --image. The refusal as if false exits 101.
  • Resident refusal CLOSED. The refusal as if false exits 101 for --readback and for --nic.
  • NOTEs CLOSED, each by its measured red:
    • swap loop and unread --image: 101;
    • own-ESP entry planted behind BootCurrent: 1, the machine loops;
    • stale loader-previous.log: 1;
    • B5 serial/store: 1;
    • Request::boot_next: 101;
    • Refusal::Silent { last }: 101;
    • date: 1;
    • policy::told: 101.
  • Deletions CLOSED. machine.txt, bin/echo and the Batch literal are gone, and nothing reads them.
  • REMOVEs CLOSED.
  • NOTE bootvars.rs:70 (one HARDDRIVE rule) still OPEN. It is now the BLOCKER below.

Q2, the refusal set. Nothing is refused that the bench needs. By reading, every line the harness writes still parses:

  • invocation for Reach::Bench: --image --readback [--nic] [--talk] [--swap --binary --talk --hand-back];
  • invocation for Reach::ViaUbuntu: --via-ubuntu --fat32-check …;
  • swap_invocation: no --via-ubuntu;
  • runbook step 2: --via-ubuntu --resident --image.

On the bench path --key, --dry-run, --wait-secs, --nic and --talk are all read, so none of them is dropped there. Two combinations are still taken and then dropped (NOTEs below). Neither can return a verdict about a boot.

Q3. Yes, there is a cheap red, and without it the claim has no test (BLOCKER).

Q4. It does not matter for the pin.

  • The rust gitlink is fbf6ad14 on both origin/main and the head.
  • The build treats library/Cargo.lock as stale by design. compiler.rs:265 and sysroot.rs:393 re-lock it and put it back.
  • compiler.rs's key hashes the fork's root Cargo.lock, not library/.
  • The diff is toyos 0.1.0→0.19.0 and toyos-abi 0.1.0→0.17.0, which is that re-lock left behind.

Restore it with git -C rust checkout library/Cargo.lock. Otherwise --pr refuses on m rust. Why the Restore did not run (a killed build is the likely cause) was not measured.

BLOCKER

  • toyos-update/src/entry.rs:121: hard_drive_guid is a fourth HARDDRIVE rule, "first", which names and so naming/after use. It sits beside bootvars::hard_drive's "exactly one" (bootloader/src/bootvars.rs:61). The PR body says "one HARDDRIVE rule", and that claim has no test that can fail: the loader's rule lives only in uefi-typed code. The cheap red: make one pure entry function over device-path bytes, meaning exactly one HD node, GPT, GUID-signed, returning the existing entry::Partition. Have bootvars::hard_drive call it on path.as_bytes() (uefi 0.26 has DevicePath::as_bytes), which also removes the hand-built Partition at bootvars.rs:93. Have names call it too, and add a host test with a two-HD path. Mutation: return the first HD node without checking for a second; cargo test -p toyos-update must go red. This is loader code, so it lands before the T14 run, or that reading is of a loader that is not the one that lands.

NOTE

  • src/metal.rs:1794: --via-ubuntu --resident --image b.img --fat32-check is parsed and the check is never run (hand_to_the_bench does not read fat32_check). --via-ubuntu --install-sudoers pw --resident installs the rule and drops --resident, which is not in ABOUT_A_BOOT. Add --fat32-check to the resident refusal, and RESIDENT to ABOUT_A_BOOT.
  • src/metal.rs:1794: out.talk.is_some() can never decide, because --talk without --readback is already refused at :1730. Delete it.
  • src/metal.rs:1577: FAT32_CHECK and INSTALL_SUDOERS in NOT_A_SWAP are now a second refusal of inputs that UBUNTUS and the via_ubuntu && swap refusal already refuse. The swap test's --fat32-check case pins which message wins, not a behaviour. Delete both entries, and have that case assert the UBUNTUS refusal.
  • src/metalbench.rs:127: answers turns the probe's error into false, so Bench::wait ("answer as the delivered boot", "go down") still ends in Silent { last: None } for a key the boot refuses. Return the probe's Result and carry it the way wait_for_the_log now does.
  • src/image.rs:590: create_file_on is the third copy of read the volume, mount, write, sync and write back (install_slot at :476–:508 is another), added for one test. Fold them into one volume round-trip.
  • tests/common/bench.rs:136: the bench test parses words built by hand rather than metal::invocation(…, Reach::Bench). A request line the parser refuses is therefore first found on the T14. Add a unit test that parses invocation for both reaches, with nic, talk and swap set.

REMOVE

  • PR body: "A flag is refused, never taken and dropped." This is false for the two resident cases above.
  • PR body: "one HARDDRIVE rule". This is false while entry::hard_drive_guid stands.

Net lines

git diff --shortstat origin/main...HEAD: 60 files, +3608 −420. Split at each file's #[cfg(test)] mod tests against 6f0729ab:

part net lines
production +1815 (round 2: +1751)
in-file tests +395
tests/ +879
issues +98

This round's production growth is the refusals, policy::told, Request::boot_next, date's refusal and create_file_on, less the two deleted HARDDRIVE walkers. Deletions still owed:

  • the second HARDDRIVE walker;
  • the two redundant NOT_A_SWAP entries;
  • the dead talk condition;
  • one of the three FAT volume round-trips.

SEND BACK

Japabu and others added 3 commits September 27, 2026 18:50
The loader's `bootvars::hard_drive` took exactly one HARDDRIVE node off a
uefi `DevicePath`, and `entry::hard_drive_guid` took the first one off an
option's bytes for `names`, `naming` and `after`: two rules for which
partition a path names, and no test could fail on their disagreeing.

`entry::partition` is the one: a walk of a device path's bytes to its end
node, refusing a path with no HARDDRIVE node, with two, or with one that is
not a 42-byte GPT node signed by GUID, and returning the `Partition` it
names. `boot_partition`, `our_partition` and `bootvars::esp` call it on
`DevicePath::as_bytes`; `names` calls it on an option's path. The uefi
walker and `hard_drive_guid` are deleted.

Mutation, taking the first node without looking for a second:
`cargo test -p toyos-update` exits 101
(`a_path_names_the_partition_of_its_one_hard_drive_node`); with the fix,
exit 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- `--resident` beside `--fat32-check` is refused, as beside `--readback`
  and `--nic`: the resident path never ran the check, so the run could
  come back green with the outside judge never asked. `--resident` joins
  `ABOUT_A_BOOT`, so `--install-sudoers` beside it is refused rather than
  installing the rule and dropping it. The resident refusal's
  `out.talk.is_some()` is gone: `--talk` without `--readback` is refused
  before it.
- `FAT32_CHECK` and `INSTALL_SUDOERS` leave `NOT_A_SWAP`: `UBUNTUS` and
  the `--via-ubuntu`/`--swap` refusal already refuse both beside a swap.
  The swap test asserts `--fat32-check` beside it meets the `UBUNTUS`
  refusal.
- `Bench::answers` returns the probe's `Result`, and `Bench::wait` carries
  the last one into `Refusal::Silent`, so "answer as the delivered boot"
  and "go down" end with a reason.
- One FAT volume round-trip: `image::put_files_on` reads the volume,
  replaces or removes each named file, syncs and writes it back.
  `stage_slot` and the bench test use it; `create_file_on` is deleted.
- The bench test takes its command line from `metal::invocation` for
  `Reach::Bench`, the line `request.txt` carries, and `metal::stage` no
  longer hands back the key that line names.

Mutations, each a checked patch, built, restored:
- `RESIDENT` out of `ABOUT_A_BOOT`: `cargo test --lib
  installing_the_rule_is_not_also_a_boot` exits 101.
- `out.fat32_check` out of the resident refusal: `cargo test --lib
  the_ubuntu_path_is_named_and_nothing_falls_back_to_it` exits 101.
- `Bench::wait` refusing with `last: None`: `cargo test --lib
  a_machine_that_never_answers_is_refused_with_the_last_ask` exits 101.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Japabu

Japabu commented Sep 27, 2026

Copy link
Copy Markdown
Collaborator Author

Review round 4, at 5cd13850.

No CI has run at 5cd13850: GitHub shows the PR as CONFLICTING/DIRTY against origin/main (6c9e2cb2), gh pr checks 539 reports no checks, and the last CI run on this branch is at 1f576344. git merge-tree --write-tree HEAD origin/main exits 1 with conflicts in bootloader/src/rootimage.rs (BootDisk::table_at, against #547's locate_type returning Option<Result<…>>), toyos-abi/Cargo.toml, toyos/Cargo.toml and userland/toyos-window/Cargo.toml (#550).

NOT READY FOR REVIEW

Japabu and others added 4 commits September 27, 2026 19:30
BootDisk::table_at takes #547's scan: `locate_type` fills
`[Option<Entry>; 2]`, an entry whose blocks are no partition is refused by
name, and the one slot table is then `locate`d and read through
`Partition`'s accessors. The SDK manifests take main's side: the publisher
assigns their versions (#550).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… under toyos-gpt's lints

The merge took main's manifests (#550) but kept this branch's bumped
toyos, toyos-abi and toyos-window versions in eight lockfiles; they now say
what main says, and userland's keeps only the update binary's toyos-gpt
dependency.

#547 forbids indexing, `as` and unchecked arithmetic in toyos-gpt outside
tests, and `Guid::parse` used all three. It now reads the dashes with `get`,
the digits with `char::from`, and a byte with checked arithmetic.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`rootimage::boot_disk` cut the boot device's path at its last node and
required that node to be HARDDRIVE, beside `entry::partition`'s "exactly
one GPT HARDDRIVE node, GUID-signed", and the two disagreed:
`disk/HD/File` was a partition to `partition` and no disk to `boot_disk`,
and `disk/HD/HD` or `disk/HD(MBR)` was a disk to `boot_disk` that
`partition` refuses. `partition` now hands back the path before its one
HARDDRIVE node as the disk's, and `boot_disk` takes the handle whose path
is those bytes and the end node.

Test: `a_partitions_disk_is_the_path_before_its_hard_drive_node`.
Negative controls, each a checked patch built and restored:
- the disk cut before the path's last node, the old rule: 101;
- the second HARDDRIVE node taken rather than refused: 101.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t of the boot

bench_loop_drives_a_toyos_machine went red once on the dev host at
be959b5 and green on the harness's alone re-run; the red attempt's sshd
accepted twice on the new netd and then no connect reached it for 46 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Japabu and others added 3 commits September 27, 2026 21:47
…again

A listener is one smoltcp socket that becomes the connection it accepts,
so port 22 listens only while that socket is in Listen. netd announced a
connection to the owner only when a pass saw the socket Established, and
accept took only Established. A peer whose handshake-closing ACK and FIN
land in one pass moves the socket SynReceived -> CloseWait, and no pass
ever sees it Established. The owner is never woken and never accepts, the
socket never listens again, and every later SYN on the port is answered
with a reset. Nothing on either side logs it. An ACK and a reset in one
pass leave it Closed, with the same result.

QEMU's user network produces the first pattern. It takes a host connect at
once and finishes the guest's handshake afterwards, and when the host
socket is already closed it sends the FIN straight behind the ACK. Measured
with a host actuator, applied to tests/ as a checked patch and not
committed (40 rounds of one connect closed at once and one
closed with a zero linger, then `echo` asked up to ten times a second
apart), on 4def9c8's netd with a log line per listener state change:
every attempt logged `listener <id> now CloseWait notified=false`, 6 of
6 (3 invocations with their alone re-runs). Without that logging, 5 of 5 invocations were red, 10 of 10 attempts
counting the harness's alone re-runs. With this change 5 of 5 were green,
`echo` answered on the first ask each time, and sshd logged 31 to 33
connections, the `echo`'s among them.

`bench_loop_drives_a_toyos_machine` is where this showed. After the netd
swap, port 22 answered every connect with a reset for 46 s and sshd logged
nothing. That run was not caught with the state logging, which saw 23
attempts green. With this change the bench test exited 0 in 25 of 25
invocations, none re-run. The trigger is not identified: no dial the
bench host is known to make fell in that window. A connect this host closed before its
guest handshake finished fits the signature, for example a `probe` cut
off by its 10 s bound while the machine rebooted.

The rule is now `listen::Listening`, one type used by the pass and by
accept. A socket in Established or CloseWait holds a connection its owner
is woken for and takes. A Closed one listens again. An accept spends the
owner's wake whatever it finds, so a wake written for a connection its
peer then reset still leaves the next connection announced. A CloseWait
connection handed over reads its data and then EOF, which is how sshd
ends such a peer's session.

This is main's defect: the branch touches no netd, sshd or network SDK
code.

Negative controls, each a checked patch on userland/netd/src/listen.rs,
shown to build, run, and restored:
- CloseWait back with Listen and SynReceived: host suite exit 101
  (a_peer_that_closes_with_its_last_ack_is_a_connection)
- Closed not listening again: exit 101
  (a_peer_that_resets_before_it_is_taken_frees_the_port,
  a_wake_spent_on_a_reset_connection_announces_the_next)
- accept keeping the wake: exit 101
  (a_wake_spent_on_a_reset_connection_announces_the_next)

A handshake that nobody finishes keeps the socket SynReceived: 600 s
were measured. That is filed as
issues/hardware/a-handshake-nobody-finishes-holds-a-listeners-port-shut.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Seen across the 48 bench runs this branch's netd investigation made: 7
of them took 126 to 132 s for the bench to give back its /log, and the
other 41 took 13 to 34 s. The late runs came with the base netd and with
the fixed one alike. The cause is not measured.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Japabu

Japabu commented Sep 27, 2026

Copy link
Copy Markdown
Collaborator Author

Review round 6 of abdb6783 (the merge of c5518949 onto d94ae082) against origin/main c5518949.

Gate. CI host succeeded at abdb6783 (run 36345994089, conclusion success). Its log shows userland/netd's four listen::tests and toyos-update's two entry::tests HARDDRIVE tests passing. That job boots no guest (ci.yml: "the host tests, and no guest"). No guest test has run at this head: the 11 update_* were last green at be959b5d, bench_loop_drives_a_toyos_machine at bdfc2c52's tree, and the fast tier has never run on this branch. There is no T14 reading. Under reviewer.md the hardware claims are NOT READY FOR REVIEW. I reviewed the code because the brief asked for it.

Earlier BLOCKERs

  • Round 3, entry.rs:121, a fourth HARDDRIVE rule: CLOSED. entry::partition is the one walker. names, boot_partition, our_partition, bootvars::esp and rootimage::boot_disk all call it, and rg finds no other HARDDRIVE match in bootloader/src. Taking a second node instead of refusing it exits 101 (a_path_names_the_partition_of_its_one_hard_drive_node, install-r5 mutation-twohd.log). boot_disk's old last-node rule exits 101 as well.
  • Round 4, merge conflicts: CLOSED. The merge is clean and CI is green at abdb6783.

BLOCKER

  • userland/netd/src/main.rs:1435 — No committed test goes red when netd's own wiring of the rule is reverted. The four host tests call Listening directly. The actuator that went red 5 of 5 at base was not committed. The bench test caught the defect at best 1 in 24. Mutation: - let owed = listener.listening.owes_wake(socket); + let owed = socket.state() == tcp::State::Established && listener.listening.owes_wake(socket);. This restores the defect in the pass exactly (no CloseWait wake, no Closed re-listen) and every committed test stays green. Commit hasty-actuator.patch as the machine test sshd_hasty_peers (Tier::Fast); it must go red under this patch. It is also the only test here that outlives smoltcp: issues/design-debt/toyos-has-its-own-network-stack.md stage 5 deletes listen/tests.rs together with smoltcp.
  • Gate — Required before landing: the 11 update_*, bench_loop_drives_a_toyos_machine and the fast tier, all at abdb6783. The merge brings Parse every ELF value into a type that cannot leave its image (E1-E4, C1) #544's load_kernel_elf rewrite (rela::parse, layout.extent()) into bootloader/src/main.rs, beside this branch's panic handler and request path, and Test suite, first pass: a disabled list replaces the quarantine, sysret waits on its report, update_* reboot through the power connector #542's harness changes (Tally, the disabled list, no ALONE re-run). This loader has never booted. CI boots no guest, and the merge queue boots none either, so the first boot of it would be main's nightly.
  • Hardware — The title's claim "The T14 updates without Ubuntu" rests on no T14 reading. Runbook steps 2–7 are owed at this branch's head before LAND. The other way out is a title and body that claim QEMU and nothing more.

NOTE

  • userland/netd/src/main.rs:1242, :1251 — The room refusal and the DataPipes refusal return before listening.accept. The owner has already read its wake byte, but woken stays set. A std owner refused for room then blocks in its next accept on a wake netd never writes, while the Established socket holds the port. sshd escapes only because it rebinds on any accept error. This is pre-existing, but it is the same "owner never woken" class this change sets out to close, and the rule "an accept spends the owner's wake whatever it finds" is false at these two sites. Spending the wake on a refusal makes each pass announce the connection again, so the fix is a design decision. Either decide it in the netd PR or file it.

  • Brief Q2, where the netd fix belongs: land it on its own, first. Reasons:

    • It is main's defect, and lan_swap (Tier::Fast, on main) swaps netd over the same sshd port.
    • This branch touches no other netd code.
    • Its open BLOCKER above should not hold the T14 work at round 6, and the T14 gates should not hold a netd fix.
    • The owner of the network-stack track should review it with that track's acceptance bar.

    Order: the netd PR with sshd_hasty_peers lands, this branch merges main, then the bench test runs on a main that has the fix. While the fix stays in this diff, the BLOCKER above applies here.

  • userland/netd/src/listen.rs:51 — Brief Q1. The rule is correct and complete for smoltcp 0.12. A listener reaches without Established:

    • Listen;
    • SynReceived, where an RST returns it to Listen (tcp.rs:1738);
    • CloseWait, from one FIN|ACK in SynReceived (tcp.rs:1797), not only from an ACK and a FIN in one pass;
    • Closed, from an RST after the handshake.

    SynSent, FinWait*, Closing, LastAck and TimeWait need a local close/connect. netd only aborts a listener and removes it (main.rs:840, :1453), so the panic arm is unreachable from a peer. timeout and keep_alive are never set, so no timer closes a listener. Each of M1 and M2 switches off one whole rule at its one site. M3 does too, except for the two early returns above.

  • userland/netd/src/listen.rs:51 — connection_waiting re-listens a Closed socket as a side effect of a question that owes_wake asks on every pass. That is correct today, but the name hides a write. settle or take_back says what it does.

  • Brief Q3, the attribution. The Unsure bullet says it plainly: the trigger was not re-caught. The netd bullet and the control paragraph state it as fact (REMOVE below). The 25 of 25 green runs after the fix are no evidence against a 1-in-24 base rate: (23/24)^25 ≈ 0.35. The deleted after-a-netd-swap-… issue is closed on the actuator's matching signature, which is acceptable only because Test suite, first pass: a disabled list replaces the quarantine, sysret waits on its report, update_* reboot through the power connector #542's rule now disables the test at once if it recurs.

  • Brief Q4, the two issues. Both are real.

    • a-handshake-nobody-finishes-holds-a-listeners-port-shut.md is a one-packet remote denial of sshd's port. Its exit is stack-independent, and its evidence has a measurement (600 s, 72 SYN-ACKs). status: open with no holder is legal, but it belongs beside a-connect-between-two-accepts-is-reset.md under whoever takes the network-stack track, and the netd PR should name that owner.
    • the-bench-sometimes-comes-back-two-minutes-late.md is about this branch's own Bench::wait_for_the_log (src/metalbench.rs:153). One fetch can overrun secs by the client's whole 120 s bound. As a finding with an exit condition it is correctly filed. The cheap measurement it defers is to log each ask's elapsed time and error.
  • toyos-gpt/src/guid.rs:107, :125 — A second impl Guid block beside the one at :15, and let h = hex; is a rename for nothing. Fold both.

REMOVE

  • PR body, netd bullet: "this branch's bench test is what showed it" — inferred from a signature, not measured.
  • PR body, control paragraph: "the test that exposed it, the bench loop," — the same inference, stated as fact.
  • PR body, netd bullet: "an accept spends the owner's wake whatever it finds" — false at main.rs:1242 and :1251.
  • PR body, Unsure: "The listener reset (…a-connect-between-two-accepts-is-reset.md) on the connections before a delivery." — a fragment that says nothing.
  • PR body, Gates: "Gates at d94ae082, the head" — no longer the head. The table is re-measured at abdb6783, not corrected.
  • issues/boot-media/the-machine-updates-itself-without-ubuntu.md:41 — The "Built, and proven in QEMU" bullets describe the tree and rot with the next flag rename. The Owed list and the exit are the track.

Brief Q5, the checklist

check result
one rule, one function HARDDRIVE: yes. netd: yes, but for the accept's two early returns (NOTE).
no silently dropped flag or input nothing new since round 3's closures
every claimed property has a red arm no: netd's wiring (BLOCKER)
no unmeasured numbers the numbers checked trace to install-r6 logs
no review chronology in the source or the body none in the added source lines or in the body; it is in the commit subjects, where it may be
no external binaries none added (the actuator uses the libc crate)
PR body fit for main's record not until the REMOVEs, the re-measured Gates table and the title

Net lines (brief Q6)

git diff --shortstat origin/main...HEAD: 54 files, +4041 −454.

part lines net
production +2230 −371 +1859 (round 3: +1815)
in-file tests +460 −23 +437
tests/ and listen/tests.rs +1145
issues +145

netd is +66 production and +281 tests; it leaves with the split. Nothing else is left to delete beyond the Guid fold and the track bullets.

SEND BACK

Japabu and others added 3 commits September 28, 2026 10:42
The branch continues from a worktree made from main, so main is this
merge's first parent and every hash of wt/toyos-install stays reachable.
netd takes main's side whole: #559 landed the listener fix and its machine
test on its own, which supersedes this branch's copy of listen.rs, its
tests and its main.rs wiring. The listener issue takes main's text, which
names its owner and `listen::settle`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
Brings #569 (log_ring_keeps_the_owners_slots on the redlist); no conflict.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
`Guid::parse` joins the first `impl Guid` block and loses the `let h = hex`
rename. The Stage 2 track drops its "built, and proven in QEMU" bullets,
which described the tree and would rot with the next flag rename; the Owed
list and the exit are the track.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
@Japabu

Japabu commented Sep 28, 2026

Copy link
Copy Markdown
Collaborator Author

Review round 7 of 2d6d228f against origin/main (merge base 225dacbc).

Gate. CI host succeeded at 2d6d228f (run 36400973914, headSha 2d6d228, conclusion success). The orchestrator's guest runs at this head: the 12 named --nightly tests pass (EXIT=0 per the brief). The Fast tier is red: 393 passed, 4 failed, none known-red (orch-runs/539r7-fast.log). There is no T14 reading.

Earlier BLOCKERs

BLOCKER

  1. tests/common/qemu.rs:5175 with bootloader/src/main.rs:867 — root_candidate_malformed, root_named_but_absent and root_candidate_overlaps fail 3 of 3 at this head, and the branch caused it.
    • Mechanism: the new panic handler resets at once. QEMU (-no-reboot) exits with status 0 before the 1 s Timeout arm reads the UART. The Disconnected arm then panics "QEMU died before Slot A: REFUSED," without reading the UART, which does hold the marker (539r7-fast.log:1699–1844).
    • Main passes all three in every other Fast run in orch-runs/ (536* through 571r2).
    • Owed: fix it in the one wait. The Disconnected arm checks the UART for ready as the Timeout arm does. No per-test workaround.
  2. Gate — the nightly tests that exercise the loader's report pass or its panic have never run on this loader: blackbox_panic_chain, panic_outlives_the_deadline, panic_reboots, watchdog_resets, loader_watchdog_arms, boot_deadline_ends_a_wedge and usb_reset_hands_devices_back (Tier::Nightly, tests/toyos.rs:1029–1065).
    • The body's list of every test that stages a loader refusal was wrong once already (REMOVE 1).
    • Neither CI nor the merge queue boots a guest, so without these runs main's nightly would be their first.
    • Owed at the next head, together with the Fast tier.
  3. bootloader/src/main.rs:889 — nothing tests "powers off where there is none".
    • This patch passes every committed test: - ResetType::SHUTDOWN → + ResetType::WARM. A WARM reset there is the "resetting into the same failure" loop the handler exists to prevent.
    • Only update_no_slot_boots_the_recovery_stick reaches the fall, and it always has an entry behind the stick.
    • Owed: a guest test with takes_the_reset: true whose variables leave no active entry behind the stick. It asserts "there is no entry to fall to" and exactly one loader banner, and goes red under the patch.
  4. PR title — "The T14 updates without Ubuntu" and "the bench replaces Ubuntu" rest on no T14 reading, and the track lists "The T14 switched over" as owed. Either cut the title to what QEMU shows, or record runbook steps 2–7 at the head before landing.

NOTE

  • quiesce_wakes_on_the_last_park — main's, not this branch's.
    • The signature is identical ("3 of 5 … gave up on 2 thread(s)") in 555r2-fast.log and 559-fast.log. Neither branch touches quiesce, and this one changes kernel/src/main.rs by one log line.
    • The filed issues/build/quiesce-wakes-on-the-last-park-lost-its-serial-ready-beside-other-guests.md has a different signature.
    • Per CLAUDE.md, disable it now and file this signature. This is the orchestrator's to do, not this branch's.
  • Whole-change negative control — accepted as the PR argues.
    • A revert of the whole branch reads no format-2 table and would be red for a reason that measures nothing.
    • Each per-decision control removes that decision's whole mechanism: the panic handler, the order of consume, the order write, the trial refusal.
    • The oracles hold for QEMU only: OVMF's boot manager, and the load option built by hand from UEFI §3.1.3/§10.3.6. For the title, the oracle is Lenovo's firmware.
  • userland/update/src/main.rs:186 — update --once silently drops a standing Next::Esp request, while --boot-next refuses by name to drop a slot asked for once. Refuse by name here too.
  • bootloader/src/main.rs:869–873 — the handler derives ours again, through open_protocol_exclusive::<LoadedImage>. If a panic lands while main holds that protocol, ours is None and the fall can take the loader's own entry (the after_this_one(rt, None) control loops). Take ours once, before anything can panic.
  • Runbook step 0 — record which active entries name NVME_ESP.
    • entry::naming takes the lowest active one, so step 6 and every recovery boot whatever that entry is.
    • Where no entry names the ESP, the loader writes one for the NVMe's \EFI\BOOT\BOOTX64.EFI. On Ubuntu that file is shim, whose fallback can rewrite BootOrder.
    • Stop unless the lowest entry is Ubuntu's.
  • Runbook step 7 — the root dd beyond the sudoers rule needs the owner's explicit yes for this use, since the password-file option uses root the owner granted only for the rule. Before dd, check the target is the SanDisk, removable, the way hand_to_the_bench checks it.
  • Runbook, the rest:
    • No step writes the NVMe, or any firmware state beyond Boot####, BootOrder and BootNext, so nothing risks bricking the machine.
    • F12 stays available throughout.
    • The only data lost is the stick's, and every metal boot already rewrites it.
    • Step 2's "owner is told first" gate stands.
    • Stop conditions are sound: a refused BootOrder write lands in Ubuntu, and step 2 stops there.
    • From step 2 on, a bench kernel that hangs on a cold boot needs hands (F12), as the filed the-bench-runs-with-no-bound-on-its-own-boot.md says.
  • The orch-runs/539r7-*.log files carry no exit code and no head. The twelve EXIT=0 rest on the brief's word; each log should record EXIT= and the head.
  • Net lines (git diff --shortstat origin/main...HEAD): 50 files, +3638 −442.
    • Outside tests/ and issues/: +2612 −382. Of the added lines, 561 sit after a mod tests/#[cfg(test)] line.
    • tests/: +910 −46.
    • issues/: +115 −14.
    • The production growth is the feature, and bootnext.rs gives back 151 lines. I see no further deletion.

REMOVE

  • PR body, Gates: "Every test that stages a loader refusal (the floor refused, or no slot verifies) is among update_* …" — false; the three root_* tests stage "no slot verifies".
  • PR body, Gates: the rows reading "owed at this head" — stale once the runs were made; the next head re-measures.
  • issues/boot-media/the-machine-updates-itself-without-ubuntu.md:50–53 — "on the Mac … through DiskArbitration … Never dd or diskutil" designs a tool that is not built yet and ties it to one host OS. The owed item and the exit are enough.

SEND BACK

Japabu and others added 6 commits September 28, 2026 13:00
The loader's panic handler resets at once, so a guest whose ready marker is
the loader's own line can have QEMU (-no-reboot) exit before the one-second
Timeout arm reads the UART. The Disconnected arm then panicked "QEMU died
before ..." with the marker in the file it printed: root_candidate_malformed,
root_named_but_absent and root_candidate_overlaps went red 3 of 3. Both arms
now ask the one closure whether the UART carries the marker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
The panic handler derived the loader's own ESP again through an exclusive
LoadedImage open. A panic while main held that protocol left it None, and the
fall could then take the loader's own entry: the loop the handler exists to
prevent. main takes it right after uefi_services::init into a once-set cell;
the handler reads the cell, and a pass that failed before the cell was set
powers off rather than guessing. The request pass and point_at_us take the
same value, so our_partition has one caller.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
An install asked for once replaced a standing Next::Esp with its own slot,
silently, while --boot-next refuses to drop a slot asked for once.
Request::install is the one rule for what an install leaves asked: a slot asked
for once is answered, an ESP's boot is kept, and --once over one is refused by
the ESP's GUID before any byte is written.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
update_no_entry_powers_off stages the no-slot machine with every entry behind
the stick's in BootOrder made inactive, and holds the pass to saying there is
no entry to fall to, QEMU (which takes every reset here) exiting, and exactly
one loader banner. A WARM reset in place of SHUTDOWN loops into the same
failure and turns it red. Rig::bend_kernel is the one way the tests here bend
a slot's kernel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
Takes #567, #570 and #571 at af817e5.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
@Japabu Japabu changed the title The T14 updates without Ubuntu: the loader writes boot variables, the bench replaces Ubuntu, a machine with no slot falls to its recovery stick In QEMU, the loader writes boot variables on the running system's request and a failed pass falls to the entry behind its own or powers off; toyos-metal drives a boot through a machine running ToyOS alone Sep 28, 2026
Japabu and others added 4 commits September 28, 2026 15:29
…l a pass reads none active

The setup cleared the entries behind the stick's in the BootOrder the
firmware's first boot wrote, a list fixed in advance. At the next boot the
firmware wrote an active entry the setup never saw (Boot0003, in the pass's
"BootOrder is 0001,0003,0000"), and the failed pass fell to it.

The machine now boots good passes until one reads a BootOrder whose every
entry behind its own is inactive: after each, the host parses the loader's own
"this pass was booted as ...; BootOrder is ..." line, clears every active
entry behind the pass's in that order, and boots again. A firmware that still
puts an active entry there after four passes reds the test by name, with
every pass's order and which of its followers were active. Only then is slot
A's kernel bent for the failed pass, which is held, as before, to "there is no
entry to fall to", QEMU's exit, and one loader banner.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
…ctive entry behind the stick's

The test needed a pass with nothing active behind the stick's entry. Round 9
set this up by clearing, over four passes, every follower the pass had read.
The orchestrator's run at 133d5dc measured the result: at each of the 4
passes the firmware had put back an active entry behind the stick's, first
Boot0002 and Boot0000, then Boot0003 and Boot0000, and so on. The
SHUTDOWN->WARM control arm died on the same setup line, so it measured
nothing.

EDK2's source at the pinned OVMF's commit (edk2-gf0064ac3af,
f0064ac3afa28e1aa3b6b9c22c6cf422a4bb8771) says why. The firmware writes two
active entries behind every device at every boot:

- The EFI Internal Shell (BdsPlatform.c:1712, LOAD_OPTION_ACTIVE). The firmware
  matches an existing option on its attributes too (BmLoadOption.c:558), so a
  Shell entry made inactive is replaced by a new active one.
- The Boot Manager Menu, UiApp (BdsEntry.c:991, CATEGORY_APP|ACTIVE|HIDDEN,
  BmBoot.c:2533). The firmware registers it again whenever BootOrder lists
  none.

SetBootOrderFromQemu then rebuilds BootOrder from the active options alone,
the bootindex devices first. That makes Boot0000 UiApp, Boot0001 the stick,
and Boot0002 and Boot0003 the Shell. The Shell takes those two numbers in turn
because BmGetFreeOptionNumber treats as free a number BootOrder does not list.

The firmware owns its entries. Without overriding the firmware's order at
every boot, no disk and no variable setup reaches the loader's no-entry arm.
So the test goes. The arm is filed as proven by nothing, with its exit. The
firmware's order also shows a second finding: entry::after counts a
CATEGORY_APP entry, which the firmware's own BootOrder walk skips. That is
filed too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
@Japabu

Japabu commented Sep 28, 2026

Copy link
Copy Markdown
Collaborator Author

Closed in favour of the loader track in #582 (issues/boot-media/the-loader-does-only-what-must-precede-the-handover.md), per the owner's decision to slim the loader and redo this work inside that track. The branch wt/toyos-install stays: the track takes the one HARDDRIVE rule (stage 2), update --once as a trial of tries = 1 and --boot-first (stage 4), and the T14 bench tooling (stage 5) from it; --boot-next, the panic handler's fall to the next boot entry and the bootvars::state line are not carried.

@Japabu Japabu closed this Sep 28, 2026
Japabu added a commit that referenced this pull request Sep 28, 2026
… rebuilt on the owner's rulings

CLAUDE.md: ToyOS calls no UEFI runtime service; every UEFI call is the
loader's. PSCI on ARM64 and the xHCI legacy-ownership handshake are not
UEFI calls, which closes both CLAUDE.md blockers.

The track:
- Stage 1 is what #583 lands: UTC, the hex dump, ten comments, the
  KernelArgs layout identity, IA32_TSC_ADJUST read by the kernel. The
  loader's TCO arm stays; wall_clock_utc is the test a zone reds.
- Stage 2 scopes every volume lookup to the boot disk with exactly one
  match, and asks firmware once per pass.
- Stage 3 compares one floor per key on a signed security version; the
  accepted cost of refusing older builds is gone with the build time.
- Stage 4 is new: current uefi, the loader's own panic handler, the
  unsound allocations and relocation unsafes gone, typed KernelArgs,
  one CRC32.
- Stage 5 adopts the Android/libabr tries rules as pure host-tested
  toyos-update decisions: fresh slot A untried with 3 tries, no bootable
  slot powers off, the good flag set only past a health gate, and
  controls for a good flag left set and a floor raised to the table's
  version.
- Stage 8 is new: the kernel arms the TCO before mm::init and takes the
  read-back the TCO issue's exit needs; only then does the loader's arm go.
- Stage 9 rewrites the wedged-report issue's exit and gives the kernel
  harvest's stale-record check.
- Every #539 piece is placed or listed as deleted.

the-machine-updates-itself-without-ubuntu.md: stage 2 gets its exit back,
and names its wait on the track's --boot-first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
Japabu added a commit that referenced this pull request Sep 28, 2026
…s controls can fail

CLAUDE.md's Firmware paragraph now reads as the orchestrator ruled: the
kernel calls no UEFI service, and every UEFI call is the loader's, before
ExitBootServices. The loader's GetVariable, SetVariable, GetTime and
ResetSystem come before the handover, so the old wording was false of it.

The track:
- Stage 1 matches #583 at 8565cc5: KernelArgs::layout (0x5459_0001),
  kernel_args_layout_refused with the loader-writes-no-layout actuator,
  the probe's realtime=, and the kernel's IA32_TSC_ADJUST line. No test
  fails without that line, and the stage says so.
- Stage 3 drops "why nothing is weaker". A security version admits an
  older build the same key signed at that version. The floor issue records
  that as the owner's accepted cost. Raising the version is a reviewed PR
  that edits one constant and names the security fix. The loader deletes
  the build-time ToyOSImageFloor- variables instead of leaving them behind.
- Stage 4's panic handler writes loader.log and powers off, never resets.
  Its test panics on a floor planted in 9 bytes, a failure the machine
  causes. KernelArgs' layout word rises to 0x5459_0002, and
  kernel_args_last_layout_refused fails if it does not.
- Stage 5 refuses a signed kernel the loader cannot load inside verify,
  so the other slot boots instead of the pass bricking the machine. An
  install's priority rises above the kept slot's. update --good, run by
  init at the health gate under the slots claim, writes the good flag, and
  an image without that claim is never good. Each rule gets a named guest
  control: update_floor_waits_for_good, update_readonly_stick_boots_nothing
  (red under `let persisted = true;`) and
  update_unloadable_kernel_boots_the_other_slot. The slot-table oracle is
  decoded without production code.
- Every #539 piece the review listed is placed or deleted. The #539-only
  issue names and the stack-offset closure (#584's) are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Japabu
Japabu deleted the wt/toyos-install branch September 29, 2026 13:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant