Skip to content

toyos-gpt: a Partition is only what parsing proved, and a bent GPT is refused (B1) - #547

Merged
Japabu merged 8 commits into
mainfrom
wt/toyos-gptfix
Sep 27, 2026
Merged

Japabu merged 8 commits into
mainfrom
wt/toyos-gptfix

Conversation

@Japabu

@Japabu Japabu commented Sep 27, 2026 •

Copy link
Copy Markdown
Collaborator

Stage F of the trust-boundary design. It fixes audit B1: a CRC-valid GPT whose entry has first_lba > last_lba panicked the kernel at toyos-gpt/src/lib.rs:171 on the next SYS_DEVICE_INVENTORY. list handed the entry back as a Partition with public fields, and lba_count underflowed.

Every gate and control below ran at head 8d854908, which is merged with main c9ed0125.

Review round 2

  • The BLOCKER. src/metal.rs's one_partition and src/image.rs's only_partition each ran the same locate_type scan into an identical three-way match on (scan.matched, out[0]). Pulled the match into image::one_partition_of, returning Result<toyos_gpt::Partition, OnePartitionError> where OnePartitionError carries either the Stated of an unplaced entry or the matched count. only_partition turns that into its String; metal::one_partition turns it into Refusal::Table or Refusal::Partitions { what, matched } — the count src/metal.rs's own test (an_image_admits_only_the_table_the_installed_rule_names) needs back. Each caller still builds its own [None; 2] and calls locate_type itself; only the match moved.
  • The REMOVE. Deleted the PR body's unmeasured codegen sentence about aarch64 and x86_64-unknown-none lookup compilation. No objdump/godbolt output backed it.

What changed, per decision

  • A partition type that carries its proof. Partition's fields are private, and there is one constructor, place.
    • place proves first_usable ≤ first ≤ last ≤ last_usable, where last_usable is below the device's block count.
    • It computes lba_count once, as a NonZeroU64, and the accessors return it. Nothing outside the crate subtracts LBAs any more.
  • The overlap proof travels only as Located. A Partition from list or locate_type is range-proven only. Only locate checks overlap, and only locate makes a Located.
    • The bootloader now carries Located into read_root, so a scan result cannot reach the ROOT read.
    • The kernel still drops to a raw Volume after locate.
  • Scans hand back what the table states, typed. list and locate_type fill &mut [Option<Entry>], where Entry = Result<Partition, Stated>.
    • Stated is the entry as the table states it. place hands it back when its blocks are no partition.
    • Every consumer has to handle that entry.
    • The kernel logs it once, in list, and lists it nowhere: not in the inventory, not as a DATA candidate.
    • blockd still refuses it as Unusable.
    • The slice is cleared before it is filled, including before a retry against the backup.
  • TypeScan is matched, disk_guid and used_entries. How many fit the slice is derived by each caller.
  • The domain lint line is in toyos-gpt/src/lib.rs (arithmetic_side_effects, indexing_slicing, unwrap_used, expect_used, panic, as_conversions), with no local allow.
  • The CRC is table-driven. const TABLE: [u32; 256] is built by a split_first_mut walk and read with TABLE.get(usize::from(low)).
  • Consumers adapted:
    • kernel: gpt.rs, inventory.rs;
    • bootloader: rootimage.rs;
    • blockd;
    • host tools: src/image.rs (with one only_partition helper), src/metal.rs;
    • tests/common.
  • Mutator (toyos-gpt/tests/mutate.rs, tests/table/mod.rs). Seeded layouts get one to three header or entry values bent, and both CRC32s of both copies are resealed. 5,000 iterations run in the normal host run.
    • The properties are judged against the test's own raw reading of the image, never against the parser's.
    • Properties: no parse panics; every partition handed back is an entry of a copy of the table, inside that copy's usable range, with count = last − first + 1; every Stated handed back really is not a partition; every locate answer and refusal is true of the table.
    • The run has to reach every post-CRC refusal, and fewer than 10% of parses may stop at a checksum.
  • One image builder. parse.rs builds its fixed disks as a table::Layout and flips bytes of the image, so the test crate has one Image: Sectors.
  • Differential oracle (toyos-gpt/tests/oracle.rs) against the gpt crate, which is already resolved for the host. It is now a dev-dependency of toyos-gpt, and Cargo.lock gains one line.
    • On 2,000 UEFI-laid tables the two readers agree entry for entry.
    • On 4,000 bent tables every difference has to be one of 12 named ones, such as "gpt checks no range" or "gpt reads no protective MBR". An unnamed difference is a red.
  • Guest test. inspect_reads_its_owners (Fast tier) runs inspect dev.*, which is SYS_DEVICE_INVENTORY.

Gates at f6b675cb (exit codes)

Gate Exit
cargo run -- --ci host (48 steps: host workspace, every clippy shape including kernel x86/aarch64 and the bootloader) 0
cargo test -p toyos-gpt (parse 43, mutate 1, oracle 3, unit 7) 0
cargo clippy -p toyos-gpt --all-targets -- -D warnings 0
cargo test --test toyos-build -- inspect_reads_its_owners 0

Gates at 8d854908 (round 2, exit codes)

Gate Exit
cargo test --lib 0
cargo test -p toyos-gpt 0
cargo test --workspace --exclude toyos-build 0
cargo run -- --clippy 0

CRC timing

The bench times crc32 over one 16 KiB array: the median of 5 interleaved rounds, each round the best of 5 × 20,000 calls, built with rustc -O --edition 2021. It ran on this host (aarch64), whose load average was 32.

crc32.rs from µs per 16 KiB
main 6f0729ab (indexed table) 31.1
61ab2201 (bit at a time) 106.2
f6b675cb (table read with get) 31.2

Command, per version v in a scratch directory holding that version's crc32.rs (git show <rev>:toyos-gpt/src/crc32.rs):

rustc -O --edition 2021 -o bench main.rs && ./bench
#[path = "crc32.rs"]
#[allow(dead_code)]
mod crc32;

fn main() {
    let data: Vec<u8> = (0..16384u32).map(|i| (i.wrapping_mul(2_654_435_761) >> 24) as u8).collect();
    let rounds = 20_000u32;
    let mut best = std::time::Duration::MAX;
    let mut sum = 0u32;
    for _ in 0..5 {
        let began = std::time::Instant::now();
        for _ in 0..rounds {
            sum ^= crc32::crc32(std::hint::black_box(&data));
        }
        best = best.min(began.elapsed() / rounds);
    }
    println!("{best:?} per 16 KiB (best of 5 x {rounds}), crc {:08x}, check {:08x}", sum, crc32::crc32(b"123456789"));
}

Negative controls at f6b675cb

Each is a checked script: the patch is checked, the mutated tree is shown to build, then run, then reverted, and the tree is checked to be back where it was.

  • Guest, whole change reverted. Every file this PR changes is reverted onto main 6f0729ab, except the test's premise: inspect.rs, inspectcase/system.toml, the GopUsbDisk profile, and craft_stick and rewrite_gpt made pub(super). The harness builds (exit 0). inspect_reads_its_owners exits 1, the same failure wide and alone: inspect *.state exited Some(-1), attempt to subtract with overflow. That is B1, reached through a USB stick, and the kernel's syscall panic recovery killing the caller.
  • Removing out.fill(None): build 0, parse exit 101. Three tests go red: a_list_leaves_no_slot_it_did_not_fill, a_backup_retry_leaves_no_slot_of_the_primary, and a_type_scan_lists_every_entry_of_that_type.
  • The CRC as main's indexed table: build 0, cargo clippy -p toyos-gpt --all-targets -- -D warnings exit 101: indexing may panic and using a potentially dangerous silent as conversion.
  • Deleting place's usable-range check: build 0, mutate exit 101. The oracle stays 0.
  • Dropping check_no_overlap from locate: build 0, mutate exit 101. The oracle stays 0, because it reads through list, which makes no overlap claim.

Independent oracles

  • The gpt crate, above.
  • The recorded failure: the audit's host reproduction of B1, which the guest control reproduces.

Unsure

🤖 Generated with Claude Code

Japabu and others added 4 commits September 27, 2026 16:54
…is refused

A USB stick whose CRC-valid GPT states an entry with first_lba > last_lba
panicked the kernel on the next SYS_DEVICE_INVENTORY: `list` handed the entry
back as a `Partition` with public fields, and `lba_count` underflowed
(audit B1).

- `Partition` has private fields and one constructor, `place`, which proves
  first_usable <= first <= last <= last_usable (< the device's block count)
  and computes `lba_count` once, as a `NonZeroU64`. `Located` is private too,
  so the overlap proof travels with it.
- `list` and `locate_type` hand back each entry of the type as
  `Result<Partition, Unplaced>`: every consumer sees what the table states
  and has to handle the entry that is no partition. The kernel logs it and
  lists it nowhere; blockd refuses it as Unusable, as before.
- The domain lint line forbids arithmetic side effects, indexing, unwrap,
  expect, panic and `as` in the crate; the CRC goes bit at a time, since a
  table lookup is an index.
- tests/mutate.rs bends header and entry values of seeded layouts and reseals
  both CRCs; tests/oracle.rs reads every table against the `gpt` crate.

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

Deleting `place`'s usable-range check left the oracle green: gpt checks no
range, so an out-of-range partition agreed. UEFI 2.11 §5.3.3 is now applied
to gpt's own decoding of the header, and that mutation reds it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…l lives

`inspect dev.*` is SYS_DEVICE_INVENTORY, the call B1 panicked the kernel on.
The crafted NVMe disk now states an entry at LBA 500..=400: the inventory
lists it nowhere and the kernel's log names the refusal.

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

The `gpt` crate reads TOYOS-DATA's type back as unused, so rewriting the
table through it handed DATA's slot to the backwards entry and dropped DATA.
The entry is now written into the first free slot of both copies and both
CRCs resealed; toyos-gpt reads the result as three partitions and one
Unplaced.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Japabu Japabu changed the title toyos-gpt: a Partition is only what parsing proved (B1) toyos-gpt: a Partition is only what parsing proved, and a bent GPT is refused (B1) Sep 27, 2026
@Japabu
Japabu marked this pull request as ready for review September 27, 2026 15:16
@Japabu

Japabu commented Sep 27, 2026

Copy link
Copy Markdown
Collaborator Author

Review of #547 at 61ab220, round 1.

CI at 61ab220: abi-split success. host skipped, because it runs only on merge_group. Net +1470 −488 (+982): production +149 (manifests +7 apart), tests +826. Stage F's budget in the design was +200.

BLOCKER

  • toyos-gpt/src/crc32.rs:22-30 — bit-at-a-time CRC, 54 µs against 32 µs per 16 KiB by the PR's own number (no command given). It runs on every walk: locate walks twice, and probe, collect and blockd walk once per candidate. — The lint does not require it. Keep a const TABLE: [u32; 256], built without indexing (for example a split_first_mut walk in the const block), and look it up with let [low, ..] = (self.0 ^ u32::from(byte)).to_le_bytes(); TABLE.get(usize::from(low)). The None arm cannot happen (a u8 is below 256) and compiles away. Measure the result with a named command.
  • toyos-gpt/src/lib.rs:469 — the patch - out.fill(None); stays green on every test. Every test passes a fresh [None; N], and the mutator's primary never fails mid-walk once its header passes, so no backup retry is left with stale slots. — Every consumer now iterates found.iter().flatten(), so this clear is the only thing between a caller and CRC-unverified entries. Add a parse.rs test that turns that patch red: fill out with Some(Err(bogus)) in every slot, list the default table, and assert exactly 4 Some. Also cover a primary that has one extra used entry and a broken array CRC, with a good backup: no stale slot may survive.
  • tests/common/inspect.rs:86-116 — state_backwards is a second edit-and-reseal of both GPT copies, beside rewrite_gpt (tests/common/volumes.rs:3430). — That is a sibling of something the tree already has. Make rewrite_gpt pub(super), then std::fs::read, rewrite_gpt(&mut image, image.len(), |entries, eb| …) and std::fs::write.
  • toyos-gpt/tests/table/mod.rs:218-330 — a second GPT image builder and a second Image: Sectors, in the same test crate as parse.rs:43-245 (Builder, Image). — One of them goes. Either build parse.rs's fixed cases as a table::Layout and flip bytes in Image.bytes, or build table::image through Builder. That deletes about 200 lines, which is most of question 4's answer.

NOTE

  • toyos-gpt/src/lib.rs:172 — Partition cannot be forged: private fields, no public constructor, no Default. But it means two different things. From list/locate_type it is only range-proven; from locate it is also overlap-proven. Every consumer calls .partition() at once, so the overlap proof does not travel. bootloader/src/rootimage.rs:226 read_root(&Partition) would accept a locate_type result, and the kernel drops to a raw Volume { start_lba, blocks } anyway. — A scan result should be its own type, or consumers should carry Located.
  • Question 1 — no unproven LBA reaches arithmetic outside the crate. Unplaced.first/last feed only format strings (kernel/src/gpt.rs:125,316, userland/blockd/src/main.rs:147, src/image.rs:360, src/metal.rs:963). Proven values still do raw arithmetic outside it (rootimage.rs:206, blockd % per and / per, src/image.rs * LBA, span_blocks, which is checked). All of it is bounded by first + count ≤ the device's lba_count, which place and parse_header prove.
  • toyos-gpt/src/lib.rs:245-251,307-313 — Unplaced and Stated have the same five fields, and place copies one into the other. — Make Stated the public type and give place the signature -> Result<Partition, Stated>.
  • toyos-gpt/src/lib.rs:52-53,839 — MIN_LBA_BYTES and MAX_LBA_BYTES have no reader left except the test that ties them to LbaSize. — LbaSize is now the one declaration. Delete the constants and that assertion.
  • toyos-gpt/src/lib.rs:382,491 — TypeScan.listed is min(matched, out.len()), which every caller can derive. — Delete it.
  • kernel/src/gpt.rs:125,316 — an unplaced DATA-typed entry is logged twice per probe, once by list and once by collect. A hostile stick gets up to 128 + MAX_PER_DEVICE lines per plug. — Log it in one place.
  • toyos-gpt/tests/oracle.rs:132-134 — the §5.3.3 arm applies the author's own predicate (mutate.rs sound) to gpt's decoded header. That is not independent, and it reds only what mutate.rs already reds. The (None, Some(stated)) arm at :140 names every Unplaced "gpt checks no range", whether or not it is out of range, so the bent-table oracle cannot see over-refusal. — Only the valid-table agreement and "toyos-gpt reads what gpt refuses" are independent. Cut the range arm and the usable plumbing.
  • Question 5, tests/common/inspect.rs:66 — NVMe is sufficient today. USB (kernel/src/fat32_adapter.rs:1164) and NVMe (kernel/src/main.rs:484) reach the same gpt::probe → DeviceSectors → list. — It does not survive Storage: file servers for DATA, the log and the boot volume; the kernel's NVMe and FAT go #536, whose head 0b7a2099 deletes this crafted NVMe disk and the kernel's NVMe path. Move the entry onto a stick now (partclaim::craft_stick). That is also the audit's vector.
  • Merge — clean against 9608f943 and against origin/main 6f0729ab, which moved only reviewer docs. It conflicts with Storage: file servers for DATA, the log and the boot volume; the kernel's NVMe and FAT go #536 (0b7a2099: kernel/src/gpt.rs, tests/common/inspect.rs, userland/blockd/src/main.rs) and with 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 (22d5e972: bootloader/src/rootimage.rs). Outside the hunks, both break on fields this branch makes private: Storage: file servers for DATA, the log and the boot volume; the kernel's NVMe and FAT go #536 p.unique_guid == guid on LISTED and listed.type_guid.0 in blockd; 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 part.first_lba in TableAt. — The later lander adapts them.
  • PR body — no gate or control names the head it ran at. 61ab220 rewrote the guest test's disk after 696ec53. — State the head for the guest arm and its negative control.

REMOVE

  • kernel/src/gpt.rs:69 — "Every entry each disk's table stated" — false; unplaced entries are no longer kept.
  • kernel/src/gpt.rs:86 — "Every GPT entry on every disk" — false for the same reason.
  • kernel/src/inventory.rs:7 — "the partitions are what each disk's table stated" — false.
  • src/image.rs:343-344 — "as its table states it" — the function now refuses an entry the table states.
  • toyos-gpt/src/lib.rs:170 — "after it has been checked against the disk it is on" — it implies the overlap check, which a scan result never had.
  • toyos-gpt/src/crc32.rs:26 — narration; it goes with the CRC blocker.
  • PR body and commit e0d1976 — "Located is private too, so the overlap proof travels with it" — false; see the first NOTE.
  • PR body — "Measured on this host: 54 µs against 32 µs per 16 KiB array" — no command; it goes with the CRC blocker.

SEND BACK

Japabu and others added 2 commits September 27, 2026 17:33
The CRC is table-driven again. The table is built by a split_first_mut walk
and read with `get`, so the crate's lint line still forbids indexing and
nothing in it is allowed locally. On this host's aarch64 and x86_64 the
lookup compiles to the loop the indexed table did, with no `None` branch.

A scan's slice is cleared before it is filled. Two parse.rs tests now hold
that: a slice of stale entries listed over, and a primary whose extra entry
and broken array CRC are retried against a good backup. Removing
`out.fill(None)` turns both red.

One image builder: parse.rs builds its fixed disks as a `table::Layout`, which
gains `Table::mirror` and a failing read, and loses its own `Builder`.

`Unplaced` and `Stated` are one public type, `Stated`; `place` hands it back
when an entry's blocks are no partition. `TypeScan.listed` is gone, since
every caller can derive it, and so are `MIN_LBA_BYTES` and `MAX_LBA_BYTES`,
whose reason moves to `LbaSize`. The kernel logs an unplaced entry once, in
`list`, not again in `collect`. The bootloader carries `Located` into
`read_root`, so the overlap proof reaches the one read that depends on it.

The oracle's usable-range arm is cut: it held toyos-gpt to its own predicate.

inspectcase states its backwards entry on a USB stick
(`Profile::GopUsbDisk`, `partclaim::craft_stick`), sealed by
`volumes::rewrite_gpt`, and no longer crafts an NVMe disk.

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

Japabu commented Sep 27, 2026

Copy link
Copy Markdown
Collaborator Author

Review of #547 at f6b675c, round 2.

CI at f6b675c: abi-split success. host skipped (only runs in merge_group; the workflow's own comment: "which a required check counts as passing").

Round 1 BLOCKERs

  • toyos-gpt/src/crc32.rs:22-30 (bit-at-a-time CRC) — CLOSED. TABLE is built by a split_first_mut walk and read with TABLE.get(usize::from(low)).copied().unwrap_or(0); #![forbid(clippy::indexing_slicing, ...)] stays crate-wide with no local allow. PR body's own bench: 31.2 µs/16 KiB against main's 31.1 and the bit-at-a-time 106.2, with the rustc -O command given.
  • toyos-gpt/src/lib.rs:469 (out.fill(None) untested) — CLOSED. parse.rs's a_list_leaves_no_slot_it_did_not_fill pre-fills out with Some(Err(bogus)) and asserts exactly 4 Somes survive; a_backup_retry_leaves_no_slot_of_the_primary covers a primary with a 5th used entry and a broken array CRC, good backup, asserting no stale slot survives the retry. Both fail without the fill(None), by inspection: the first because the primary walk only overwrites its own 4 matches, leaving the 4 bogus tail slots; the second because the backup's 4-slot overwrite leaves the primary's leftover 5th slot behind.
  • tests/common/inspect.rs:86-116 (state_backwards duplicating rewrite_gpt) — CLOSED. rewrite_gpt is pub(super) (tests/common/volumes.rs:3430) and state_backwards calls it directly; no second reseal.
  • toyos-gpt/tests/table/mod.rs (second image builder) — CLOSED. parse.rs's own Builder/Image are gone (grep finds no struct Builder anywhere in toyos-gpt/tests); parse.rs::disk() builds a table::Layout and table::image(), the same builder mutate.rs and oracle.rs use.

BLOCKER

  • src/metal.rs:952-972 — one_partition is a fresh sibling of src/image.rs:484-493's new only_partition: the identical locate_type into [None; 2], then match (scan.matched, out[0]) { (1, Some(Ok(_))) ..., (1, Some(Err(_))) ..., (n, _) ... } — same crate, same file this PR already edited to add the shared helper next to it. This branch built only_partition specifically to end this repeated shape (and used it in image.rs, tests/common/partclaim.rs, tests/common/volumes.rs) but left the pre-existing copy in metal.rs untouched apart from the mechanical accessor rename. — Pull the three-way match out of both into one function in image.rs that returns the matched count on failure (e.g. Result<toyos_gpt::Partition, OnePartitionError> carrying either unplaced: Stated or matched: u32), have only_partition turn that into its String, and have metal::one_partition turn it into Refusal::Table/Refusal::Partitions { what, matched } — the only reason it wasn't already reused verbatim is that only_partition's String error throws the count away, which Refusal::Partitions and its own test (src/metal.rs:3281) need back.

NOTE

  • PR body, "CRC timing" — "the lookup compiles to one table load per byte, with no None branch" (and the source comment crc32.rs:41 "A u8 is below 256: get is never None") is an unmeasured codegen claim: no objdump/godbolt output backs it, only the timing table (31.2 vs 31.1 µs), which is consistent with it but doesn't establish it. The source comment is the load-bearing safety invariant for unwrap_or(0) and stays; the PR-body codegen assertion adds nothing the comment doesn't already state.

REMOVE

  • PR body, "CRC timing" — "On aarch64 and on x86_64-unknown-none the lookup compiles to one table load per byte, with no None branch. The aarch64 loop is identical to the old indexed table's." — no command or log measures the generated code; the timing table is the only evidence and doesn't say this.

SEND BACK

Japabu and others added 2 commits September 27, 2026 18:15
src/metal.rs's one_partition and src/image.rs's only_partition each ran the
same locate_type scan into a three-way match on (matched, out[0]). Pull that
match into image::one_partition_of, returning Result<Partition,
OnePartitionError> where OnePartitionError carries either the Stated of an
unplaced entry or the matched count. only_partition turns that into its
String; metal::one_partition turns it into Refusal::Table or
Refusal::Partitions { what, matched }, keeping the count only metal.rs needed
back.

Also drops the PR body's unmeasured codegen sentence about aarch64 and
x86_64-unknown-none lookup compilation — no objdump or godbolt output backed
it.

Review round 2 of #547.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Japabu
Japabu added this pull request to the merge queue Sep 27, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 27, 2026
@Japabu
Japabu added this pull request to the merge queue Sep 27, 2026
Merged via the queue into main with commit 6c9e2cb Sep 27, 2026
2 checks passed
Japabu added a commit that referenced this pull request Sep 27, 2026
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>
Japabu added a commit that referenced this pull request Sep 27, 2026
… 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>
Japabu added a commit that referenced this pull request Sep 27, 2026
…gpt)

- toyos-abi, toyos and toyos-window take main's frozen versions and path
  dependencies; the lockfiles' SDK entries go back to them.
- kernel/src/gpt.rs: main's `Option<Entry>` listing, with the branch's
  deletions of `collect`, `locate_log` and `BLANK` kept; `log_place` reads
  `unique_guid()`.
- blockd: main's listing of an entry whose blocks are no partition, carrying
  the branch's `kind`.
- inspect: main's crafted USB stick with its free, granted and backwards
  entries and their checks, beside the branch's blockd and fsd rows and its
  two partitions fsd holds; test-runner's granted partition is back in the
  config.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Japabu
Japabu deleted the wt/toyos-gptfix branch September 28, 2026 09:46
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