All checks were successful
Check / eval (push) Successful in 3m6s
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>
99 lines
4.0 KiB
Python
Executable File
99 lines
4.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""compose-lock.py contract check (flake check `installer-compose-lock`).
|
|
|
|
Usage: check-compose-lock.py <compose-lock.py>
|
|
|
|
Exercises the offline lock-composer the installer runs on the target with
|
|
no network: given nomarchy's own flake.lock and the baked locked/original
|
|
node metadata, it must emit a well-formed downstream lock where
|
|
|
|
root ─▶ nomarchy (locked to the baked rev+narHash)
|
|
└─▶ every upstream node, verbatim, one level down
|
|
|
|
and where `follows` paths (list-valued inputs, absolute from the old root)
|
|
are rebased under the new `nomarchy` node. Also pins the fail-closed
|
|
guards: a locked node without a narHash, and an upstream lock that already
|
|
carries a `nomarchy` node, must both abort.
|
|
"""
|
|
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
tool = sys.argv[1]
|
|
|
|
|
|
def run(src_lock, locked, original):
|
|
"""Run compose-lock on a fixture; return (returncode, stderr, out_json|None)."""
|
|
with tempfile.TemporaryDirectory() as d:
|
|
src = Path(d) / "upstream.lock"
|
|
out = Path(d) / "out.lock"
|
|
src.write_text(json.dumps(src_lock))
|
|
p = subprocess.run(
|
|
[sys.executable, tool, str(src), str(out),
|
|
json.dumps(locked), json.dumps(original)],
|
|
capture_output=True, text=True,
|
|
)
|
|
got = json.loads(out.read_text()) if p.returncode == 0 and out.exists() else None
|
|
return p.returncode, p.stderr, got
|
|
|
|
|
|
def check(cond, msg):
|
|
if not cond:
|
|
sys.exit(f"compose-lock contract: {msg}")
|
|
|
|
|
|
# A representative upstream lock: a plain input, and one that `follows`
|
|
# it (list-valued input — the case the rebasing logic exists for).
|
|
upstream = {
|
|
"nodes": {
|
|
"root": {"inputs": {"nixpkgs": "nixpkgs", "home-manager": "home-manager"}},
|
|
"nixpkgs": {"locked": {"type": "github", "narHash": "sha256-NP"}},
|
|
"home-manager": {
|
|
"inputs": {"nixpkgs": ["nixpkgs"]},
|
|
"locked": {"type": "github", "narHash": "sha256-HM"},
|
|
},
|
|
},
|
|
"root": "root",
|
|
"version": 7,
|
|
}
|
|
locked = {"type": "path", "path": "/nix/store/x", "narHash": "sha256-SELF", "lastModified": 1}
|
|
original = {"type": "git", "url": "https://example.invalid/nomarchy.git", "ref": "v1"}
|
|
|
|
rc, err, got = run(upstream, locked, original)
|
|
check(rc == 0, f"valid inputs were rejected (rc={rc}): {err}")
|
|
check(got is not None, "no output lock written on the happy path")
|
|
|
|
check(got["version"] == 7 and got["root"] == "root", "output is not a v7 lock rooted at 'root'")
|
|
check(got["nodes"]["root"] == {"inputs": {"nomarchy": "nomarchy"}},
|
|
f"root node must point only at nomarchy, got {got['nodes']['root']}")
|
|
|
|
nomarchy = got["nodes"].get("nomarchy")
|
|
check(nomarchy is not None, "no 'nomarchy' node in output")
|
|
check(nomarchy["locked"] == locked, "nomarchy node lost its baked locked metadata")
|
|
check(nomarchy["original"] == original, "nomarchy node lost its baked original metadata")
|
|
check(nomarchy["inputs"] == upstream["nodes"]["root"]["inputs"],
|
|
"nomarchy node did not inherit the upstream root's input set")
|
|
|
|
check(got["nodes"]["nixpkgs"] == upstream["nodes"]["nixpkgs"],
|
|
"a plain upstream node was mutated")
|
|
check(got["nodes"]["home-manager"]["inputs"]["nixpkgs"] == ["nomarchy", "nixpkgs"],
|
|
"a follows path was not rebased under the nomarchy node "
|
|
f"(got {got['nodes']['home-manager']['inputs']['nixpkgs']})")
|
|
|
|
# Fail-closed: no narHash in the baked locked metadata.
|
|
rc, err, _ = run(upstream, {"type": "path", "path": "/x"}, original)
|
|
check(rc != 0, "a locked node without a narHash was accepted")
|
|
check("narHash" in err, f"missing-narHash abort did not mention narHash: {err}")
|
|
|
|
# Fail-closed: an upstream lock that already names a nomarchy input.
|
|
poisoned = json.loads(json.dumps(upstream))
|
|
poisoned["nodes"]["nomarchy"] = {"locked": {"narHash": "sha256-X"}}
|
|
rc, err, _ = run(poisoned, locked, original)
|
|
check(rc != 0, "an upstream lock already carrying a 'nomarchy' node was accepted")
|
|
|
|
print("compose-lock contract: happy-path shape, follows rebasing, and both "
|
|
"fail-closed guards hold")
|