evidence · 5.0 KiB

implementation.md

Committed at docs/evidence/storage-capacity/implementation.md. This is what grounds the requirements that cite it.

Open raw source

Guarded root growth: host and disposable guest proof

The helper derives the mounted root from findmnt and sysfs, checks the actual DOS/512-byte partition table, requires the fixed boot/root starts and boot size, exactly two Linux partitions, matching kernel/table geometry and the expected ext4 labels/identities. Its installed CLI has no target-device override. Read-only preflight is the default; --apply owns a runtime lock and gates both growpart and resize2fs. A changed identity, failed command or incomplete postcondition is a failure, not a successful expansion.

The Nix service is wanted by multi-user, after local filesystems and before the shell, without making shell startup require growth success. It bounds the operation at 180 seconds and stops its entire control group. Independent stock partition/filesystem auto-growth is prohibited so it cannot bypass the guard. The board and RVV configurations include it; the unrelated netboot guest does not. configuration.json records the successful actual Nix evaluation.

python3 tests/test_root_growth.py
nix build .#root-growth --max-jobs 1 --cores 8 --no-link --print-out-paths
python3 tools/check-root-growth-config.py

All 13 host checks pass. They cover refused layouts before writes, tool failures, post-partition identity changes before filesystem mutation, false success, interrupted growth/retry, repeat no-op and 1 KiB ext4 page alignment. These are policy tests, not filesystem or boot proof. The narrow cross-build produces /nix/store/6sca423wcjlq1v9ynjpbv4ai48jf53l9-k230-root-growth.

The package uses only the unmodified growpart script from pinned cloud-utils 0.33, with its declared DOS-layout utilities. The first packaging attempt was stopped because building cloud-utils’ split guest output also built unused QEMU/CD-image tools from its sibling output. The final closure excludes those tools. Relative to the current board system, the growth helper adds four paths and approximately 197 KB of NAR data; the integrated system adds about 1.19 MB. No store deletion is required to stage this system.

Actual filesystem tests in a RISC-V guest

python3 tools/test-root-growth.py --qemu --output /tmp/new-root-growth-proof

The fixture boots a real RISC-V Linux 6.18.52 kernel with the cross-built helper and pinned utilities. Its only writable QEMU disk is a new 512 MiB regular file; no host block device can be supplied. The guest refuses to run outside marked PID 1. It mounts an actual ext4 test root and invokes the production discovery, guard and growth logic against that mountpoint.

qemu/result.json, manifest.json, serial.log and provenance.json retain the exact artifacts, fresh run identity, commands, helper hash, comparisons and limits. Seven cases pass: growth plus repeat, already-full disk, wrong root label, a later partition, unsupported filesystem, a partition-tool failure, and retry after partition growth followed by an injected pre-filesystem failure. The entire firmware gap after the MBR and the complete 112 MiB boot partition retain their hashes. Root sentinel contents and identities survive. Negative cases retain the original partition table and complete root-filesystem hash. The first successful growth increases a 128 MiB filesystem to 393196 KiB; repeat runs leave the layout unchanged.

The first guest attempt correctly failed its overly strict postcondition: pinned resize2fs rounds the default target down to a system page when filesystem blocks are smaller. e2fsprogs 1.47.4 resize/main.c, the sys_page_size and new_size calculation, establishes that rule. A 1 KiB filesystem can therefore leave 3584 bytes unused on this test partition. The corrected check uses the larger of page and filesystem block size. Pinned cloud-utils also reserves 33 trailing sectors even for DOS, which the partition postcondition explicitly allows. qemu/previous-failure.json preserves the failed attempt and original transcript hash; it is not relabeled as passing.

The fixture exercises a mounted disposable filesystem, not production / or NixOS service startup. Failure injection occurs before the named utility runs; it does not simulate power loss during a metadata write. A normal exit of its PID 1 produces the expected final kernel panic after the complete report; the host requires the fresh complete report and every assertion, not that panic. At the time of this host/guest checkpoint, physical K230 growth and recovery were still unverified. The matching system and compact image also cross-built; image-build.json records artifact hashes, unchanged initial offsets, exact kernel/initrd payloads, valid U-Boot CRCs and matching bootargs. Together these records completed tasks 1.1, 1.2, 2.1 and 2.2.

Subsequent physical first and repeat boots now pass. See the board evidence, board-before.json, board-after.json and board-repeat.json for automatic growth, preserved boot/root identities, available capacity, service results and shell/Wi-Fi recovery. Those separate observations complete the physical gates; they do not change the guest fixture’s limits described above.