Second recursive pass: two adversarial re-reviewers verified the iteration-1 fixes.
Fuzz/empirical checks cleared the freshness min-budget rewrite (200k random trials),
the config-lint reg-token scanner, its UTF-8 boundary safety, and the build-kernel.sh
trap (5 exit scenarios) — no defects. Three items corrected here:
- config-lint is_known_safe_loader: match the WHOLE normalized loader name, not an
unanchored substring. The iteration-1 allowlist swap kept `contains()`, so a future
coprocessor whose name merely contained a boot word ("AudioLoader" ⊃ "loader",
"SplRtos" ⊃ "spl", "Bl32" ≠ "bl31") would have been waved through — reopening the
0x40000-brick false-negative the fail-closed change exists to prevent. Regression
test added with those exact adversarial names.
- docs/architecture.md §3: the `cru` bullet no longer claims flared's devmem `Bus`
seam is shipped — it lands when flare-edge consumes warden-sdk ([maintainer]-gated), which
is what §7 item 3 already said. Resolves an in-document contradiction.
- drivers/README.md: modbus "11 pty scenarios" -> "8 pty scenarios + 3 wire/daemon
checks (11 total)", matching flare-edge tools/modbus-sim's actual SCENARIOS list.
- docs/decisions/0002-mcdc-tiering.md: Consequences now describe the shared
drivers/enforce-mcdc.sh + drivers/<name>/test/ layout actually built (not the
per-driver dirs the ADR first anticipated); Rust MC/DC tooling reality noted.
config-lint: 9 tests pass; clippy clean under -D warnings; gitleaks clean. C drivers
untouched (still relays 40/40, freshness 66/66 MC/DC).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017wB8KB3MMQztRDXCMCkPrf
config-lint — static target-config gates
Catches flash-time config faults the behavioural sim cannot: mistakes in the
memory map, not the logic. The first check is the one that would have caught the
c8a3 brick — a boot-loaded coprocessor firmware dropped at 0x40000, which is
a reserved-memory carve-out on Thunder-Boot boards but plain kernel RAM on ours,
so the MCU and the kernel fought over the same DRAM and the board hung before eth0.
The check
Every address the idblock loader drops MCU firmware to must sit inside a
reserved-memory node in the target devicetree.
- MCU loads come from the rkbin loader
.ini: eachLOADERn=Hpmcu(any hpmcu/mcu/amp entry) in[LOADER_OPTION], with itsLOAD_ADDRfrom[LOADERn_PARAM]. - Reserved ranges come from the devicetree: every
reg = <addr size>inside areserved-memory { … }node.
A load outside all reservations is a failure (non-zero exit).
Use
cargo run -p warden-config-lint -- --ini <loader.ini> --dt <devicetree.dts>
In CI, feed the flattened devicetree so includes and overlays are resolved:
dtc -I dtb -O dts build/.../rv1106g-warden.dtb > /tmp/warden.dts
config-lint --ini .../RKBOOT/RV1106MINIALL*.ini --dt /tmp/warden.dts
Exit 0 = every MCU load is reserved (or there are none); 1 = a collision was
found; 2 = usage/IO error.
Test
cargo test -p warden-config-lint
The suite encodes the brick as a regression: the real Thunder-Boot .ini
(Hpmcu @ 0x40000) fails against a DT with no rtos@40000 node and passes
once the reservation is added — and our board's non-TB loader (no boot-loaded MCU)
always passes. See ../../docs/architecture.md §5 and, for the hardware hazard,
the boot-loaded-mcu-0x40000-hazard note.