Card capacity and boot-time growth
Original capacity limit
The read-only commands in preflight.json found a 127,934,660,608-byte card with a 117,440,512-byte boot partition and a 2,173,693,952-byte root partition. Only 77,070,336 filesystem bytes were available. The built RVV system added 16 paths / 112,720,944 NAR bytes relative to the running normal system, so it did not fit. The original audit changed neither the installed system nor storage. Its 62 unrooted store paths were reported, not collected.
The guarded system growth implementation and seven actual disposable RISC-V QEMU filesystem cases are described in implementation.md. image-build.json identifies the complete system and compact 2,308,960,256-byte image. QEMU is separate from physical-board evidence.
Physical trial preparation
staging.json preserves an unexpected staging failure: the card
ran out of inodes, despite available bytes. Only this trial’s failed partial
system-path import was removed. nix-store --optimise then shared 8,599
identical files, reported 92.1 MiB saved, and allowed the complete closure import
to succeed. This was an operator preparation step; no new garbage-collection
policy or automatic deletion service was added.
board-selection.json identifies the deployment method:
the complete system closure from the built image was imported and its exact
boot files selected on the existing compact root. All eight selected boot-file
hashes match the image. The kernel and initrd were already identical; only
bootargs.txt and k230-tdisplay.dtb needed replacement. Their old contents
remain under /var/lib/k230-root-growth-evidence/ with .before suffixes, and
the prior Nix system generation remains available. This was not a full-image
reflash and did not restore a home directory or manually resize the root.
The preserved recovery image is
/nix/store/b384ag314xp1gprqy3i5h7sbsci3fmm0-k230-sd-image.img, pinned with a host
GC root. Its SHA256 still matches the previously tested artifact in
the offline Wi-Fi image evidence:
c510362955d208bc449fcb62f9d419e597c337e873512eb4f95e65c84b00f867.
The normal system image was also rebuilt at its original store path; its new
filesystem-container bytes do not match the historical image checksum, so it
is not substituted for the byte-verified recovery artifact above.
board-before.json records the physical compact layout,
root/boot identity, MBR boot-code hash, firmware-gap hash (bytes 512 through
4 MiB), all eight boot-file hashes and a public root-file sentinel. The baseline
was captured after deliberate boot selection and before reboot/growth.
It confirms the old system still running, the new system selected, normal shell,
Wi-Fi association and HTTPS over wlan0, and protected credential permissions.
The credential contents, network names and private addresses are never emitted
by the checker and never enter the repository.
The first checker attempt is retained as board-before-control-path-failure.json. HTTPS succeeded, but its WPA association query used the wrong client socket location. The corrected checker uses both of the service-specific control and client paths documented by the credential procedure. The failed report remains failed; the subsequent capture passed.
The host checker refuses stale boot IDs, missing/failed growth service results, changed protected data and failed recovery checks. Its three host tests exercise first/repeat acceptance and more than twenty rejection cases. Those tests validate the evidence checker; they do not substitute for board observations.
Runtime rollback never shrinks root: restore the backed-up boot selection and old system profile, then reboot. The previously tested U-Boot UMS flashing route and the pinned recovery image remain the full-image fallback. Provision Wi-Fi only through the protected credential procedure. No routine whole-image readback was performed.
First physical boot
board-after.json passes against the pre-boot capture with a
new boot ID and the exact selected system running. The boot service reported
grown: root expanded from 4,245,496 to 249,610,207 sectors without changing
its start or identity. Ext4 grew from 2,173,693,952 to 127,800,422,400 bytes.
statvfs reported 125,742,698,496 total usable filesystem bytes,
118,826,979,328 available bytes and 7,540,210 available inodes.
The service exited successfully; all protected hashes and the root sentinel
matched, shell and seatd were active, and Wi-Fi association plus HTTPS through
wlan0 passed. Credential permissions remained root:root 0600. The checker
captures the actual current-boot service journal record, rather than inferring
a resize from the resulting capacity alone.
first-boot-selected.log contains selected actual serial kernel/service/login observations; provenance identifies the complete private transcript by its hash and command. Serial login alone is not treated as growth completion. These observations prove a normal system boot and recovery, not physical-finger input, a camera observation, RVV execution or GPU/card-shell performance.
Second physical boot
board-repeat.json passes with another new boot ID. The
service reported no-change; root boundaries and filesystem size were stable.
The firmware, MBR boot code, all eight boot files and sentinel still matched.
The selected system, shell, seatd, Wi-Fi association and HTTPS over wlan0
returned successfully. Selected serial lines and
their provenance accompany the current-boot
journal result. No manual grow command was run on the physical board.
Reproduce the evidence capture
Use tools/check-root-growth.py --board with the helper and system store paths
recorded by image-build.json. The checker holds /tmp/k230-board.lock across
its UART upload and capture. It never resizes or changes boot selection. Before
capture creates one public preservation sentinel; later captures only read it.
growth_helper=/nix/store/6sca423wcjlq1v9ynjpbv4ai48jf53l9-k230-root-growth
growth_system=/nix/store/gnr36q39hmy4pq7ipwac1r1rpfbyqxd4-nixos-system-nixos-26.11.20260919.20b1ddd
python3 tools/check-root-growth.py --board --phase before \
--helper "$growth_helper" --target-system "$growth_system" \
--recovery-image /path/to/preserved/recovery-image \
--recovery-sha256 c510362955d208bc449fcb62f9d419e597c337e873512eb4f95e65c84b00f867 \
--output NEW_EVIDENCE/board-before.json
# Reboot with serial capture; wait for the selected system and shell.
python3 tools/check-root-growth.py --board --phase after \
--helper "$growth_helper" --target-system "$growth_system" \
--before NEW_EVIDENCE/board-before.json --output NEW_EVIDENCE/board-after.json
# Reboot again with serial capture.
python3 tools/check-root-growth.py --board --phase repeat \
--helper "$growth_helper" --target-system "$growth_system" \
--before NEW_EVIDENCE/board-after.json --output NEW_EVIDENCE/board-repeat.json
A before capture deliberately refuses an already-expanded root. Repeating the
entire growth experiment requires a compact starting card; it must not be
simulated by shrinking the live filesystem or editing earlier proof. The checker
also refuses to overwrite a prior evidence file.