Files
Nomarchy/tools/check-hardware-db.py
Bernardo Magri e01303851d
All checks were successful
Check / eval (push) Successful in 3m6s
test(checks): pure-contract guards batch (#49)
Four new no-VM checks.* + CI py_compile expansion:
- hardware-db-modules: installer DB module names ∈ pinned
  nixos-hardware.nixosModules (a lock bump can rename a module and break
  profiled installs on just the matching DMI, invisible to any VM).
- installer-compose-lock: offline lock-composer contract on fixtures.
- installer-disko: pure Nix assert — swapSize "0"/"0G" → no @swap, "2G"
  → sized, withLuks wraps root (permanently guards the #46 install fix).
- windowrule-syntax: builds the generated hyprland.conf and fails on the
  pre-0.53 grammar / windowrulev2 keyword (guards ed7fd93).
CI py_compile now covers all tracked *.py; docs/TESTING.md §1 synced.

Implemented by a worktree agent; diff reviewed. Verified V0 (flake check)
+ V1 (each checks.x86_64-linux.* built on main, exit 0); agent's negative
tests confirm each guard fails on a regression.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 19:10:45 +01:00

54 lines
2.2 KiB
Python
Executable File

#!/usr/bin/env python3
"""Installer hardware-DB ↔ nixos-hardware drift check (flake check
`hardware-db-modules`).
Usage: check-hardware-db.py <hardware-db.sh> <nixos-hardware-modules.txt>
Every nixos-hardware module name the installer references — the third
`|`-field of each HARDWARE_DB table row, plus the literal `common-*`
modules the CPU/GPU/chassis autodetect emits — must exist in the pinned
`nixos-hardware.nixosModules`. A lock bump can rename a module upstream
(the X1 Carbon → `x1-Nth-gen` churn is the canonical example) and silently
break profiled installs on exactly the machines whose DMI matches — a
class of bug that never shows up in a VM. `nixos-hardware-modules.txt` is
generated at eval time in flake.nix from `attrNames
nixos-hardware.nixosModules`, so the "what actually exists" side can't go
stale by itself.
"""
import re
import sys
db_file, modules_file = sys.argv[1], sys.argv[2]
available = {l.strip() for l in open(modules_file) if l.strip()}
db = open(db_file).read()
referenced = {} # module name -> short reason (first seen)
# HARDWARE_DB rows: "sys_vendor_regex | product_regex | module". The first
# two fields are bash regexes (may contain anything but a literal quote);
# the module name is a plain nixos-hardware attr (lower-case, dashes).
for vendor, product, module in re.findall(
r'"([^"|]*)\|([^"|]*)\|([a-z0-9][a-z0-9-]*)"', db
):
referenced.setdefault(module, f"HARDWARE_DB row ({vendor.strip()} {product.strip()})")
# Literal `common-*` (and any other bare-literal) modules emitted by the
# autodetect via `echo "MODULE <name>"`. The `$mod` variable form is the
# HARDWARE_DB table above and is skipped here.
for module in re.findall(r'echo "MODULE ([a-z0-9][a-z0-9-]*)"', db):
referenced.setdefault(module, "autodetect MODULE line")
if not referenced:
sys.exit("hardware-db: parsed no module references — parser or file changed")
missing = sorted(m for m in referenced if m not in available)
if missing:
print(f"hardware-db: {len(missing)} module(s) not in nixos-hardware.nixosModules")
for m in missing:
print(f" {m} — referenced by {referenced[m]}")
sys.exit(1)
print(f"hardware-db: all {len(referenced)} referenced modules exist in nixos-hardware")