Files
felhom.eu/scripts/iso_bootstrap_gate.py
T
admin 9d39faabf8 R-502: the ISO first-boot harness is a gate, full runs only, "not checked" without docker
iso_bootstrap_gate.py runs scripts/iso/test/bootstrap-modes.sh in felhom-iso-assistant:trixie
(staged copy, read-only mount, --network none), registered fast=False in repo_gates.py so the
pre-push hook and CI (both --fast) never run it (decision 147). No docker / no image / docker
error -> exit 2 NOT CHECKED. Every green run is followed by a built-in decoy: the harness must
FAIL a bootstrap whose pairing banner never paints (R-496 shape), or the gate convicts the
instrument as blind. Docker-free decoys in test_iso_bootstrap_gate.py (fake docker on a
one-directory PATH), run from test_gate_decoys.py (COVERS).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-06 11:29:17 +02:00

158 lines
7.5 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""iso_bootstrap_gate.py — the ISO first-boot harness, run as a gate (R-502, decision 147).
Usage: python3 scripts/iso_bootstrap_gate.py
Exit 0 harness green AND its planted-broken copy convicted · 1 a harness check failed, or the harness
could not see a planted broken banner · 2 NOT CHECKED (no docker, no image, docker itself failed).
WHAT IT RUNS. scripts/iso/test/bootstrap-modes.sh — felhom-bootstrap.sh's two modes, the network gate,
the console banners against their goldens and the postinst — inside `felhom-iso-assistant:trixie`.
Until this gate it ran only by hand, and it was RED for two days before anyone saw (R-586).
WHERE IT RUNS. Full runs only — `fast=False` in repo_gates.py, so never in --fast, never in the
pre-push hook, never in CI (both call --fast; CI has no docker). Operator ruling 2026-10-06, `09` §3
decision 147. Pinned by scripts/test_iso_bootstrap_gate.py (`test_registered_full_runs_only`).
NOT CHECKED IS NEVER A PASS. No docker binary, no image, or a docker error (exit 125-127, a timeout)
is exit 2 with the words "not checked". The image is built by hand:
docker build -f scripts/iso/Dockerfile.assistant -t felhom-iso-assistant:trixie scripts/iso
THE TREE STAYS CLEAN. The harness writes into /work (fakes, logs, /etc/felhom). It never sees the repo:
the four inputs are copied to a temp dir, mounted READ-ONLY at /src, and copied to /work INSIDE the
container. `--network none`: the harness fakes curl and needs no network.
THE BUILT-IN DECOY (the positive control, every run). After a green run the gate runs the harness a
second time against a copy of felhom-bootstrap.sh whose print_pairing_banner returns at once — the
exact shape that left the household's first screen untested until ISO 1.27.0 (R-496). The harness
MUST fail it, naming the banner. If it passes, the instrument is blind and the green run proved
nothing: exit 1. A green run is also required to show its own pass line AND at least MIN_OK `ok:`
lines — a harness that printed the pass line and checked nothing is not green.
"""
import os
import re
import shutil
import subprocess
import sys
import tempfile
import uuid
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
ISO = os.path.join(ROOT, "scripts", "iso")
IMAGE = "felhom-iso-assistant:trixie"
TIMEOUT_S = 300
# The harness carried 76 `ok:` checks on 2026-10-06. Far below that, deliberately: this is the floor
# of "it ran", not a count to keep in step.
MIN_OK = 40
PASS_LINE = "ALL BOOTSTRAP-MODE TESTS PASSED"
# The planted decoy: the banner function returns before painting.
MUTANT_ANCHOR = "print_pairing_banner() {\n"
MUTANT_TEXT = MUTANT_ANCHOR + " return 0 # R-502 planted decoy: the banner never paints\n"
# A FAIL line the mutant must produce (ASCII fragment of the harness's own check name).
MUTANT_MUST_FAIL = re.compile(r"^\s*FAIL: R-496: banner painted", re.M)
INPUTS = [ # (repo path, path under /work) — what the harness reads, per its own header
(os.path.join(ISO, "felhom-bootstrap.sh"), "felhom-bootstrap.sh"),
(os.path.join(ISO, "pkg", "debian", "postinst"), "postinst"),
(os.path.join(ISO, "test", "golden"), "golden"),
(os.path.join(ISO, "test", "bootstrap-modes.sh"), os.path.join("test", "bootstrap-modes.sh")),
]
def not_checked(why):
print("iso-bootstrap: NOT CHECKED — %s" % why)
print("iso-bootstrap: INCONCLUSIVE (exit 2) — an unrun harness is never a pass")
return 2
def stage(dest, mutate=False):
"""Copy the harness inputs into dest. Returns None, or a reason the staging failed."""
for src, rel in INPUTS:
if not os.path.exists(src):
return "harness input missing: %s" % src
out = os.path.join(dest, rel)
os.makedirs(os.path.dirname(out), exist_ok=True)
if os.path.isdir(src):
shutil.copytree(src, out)
else:
shutil.copy2(src, out)
if mutate:
p = os.path.join(dest, "felhom-bootstrap.sh")
with open(p, encoding="utf-8") as f:
text = f.read()
if text.count(MUTANT_ANCHOR) != 1:
return ("the decoy anchor %r occurs %d time(s) in felhom-bootstrap.sh (want 1) — the "
"built-in decoy cannot be planted" % (MUTANT_ANCHOR.strip(), text.count(MUTANT_ANCHOR)))
with open(p, "w", encoding="utf-8") as f:
f.write(text.replace(MUTANT_ANCHOR, MUTANT_TEXT))
return None
def run_harness(docker, mutate):
"""Return (rc, output) of one harness run; rc None means docker itself failed (output says why)."""
tmp = tempfile.mkdtemp(prefix="felhom-iso-gate-")
name = "felhom-iso-gate-%s" % uuid.uuid4().hex[:12]
try:
err = stage(tmp, mutate)
if err:
return None, err
cmd = [docker, "run", "--rm", "--name", name, "--network", "none",
"-v", "%s:/src:ro" % tmp, IMAGE, "bash", "-c",
"mkdir -p /work && cp -a /src/. /work/ && exec bash /work/test/bootstrap-modes.sh"]
try:
p = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=TIMEOUT_S)
except subprocess.TimeoutExpired:
subprocess.run([docker, "rm", "-f", name], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
return None, "the harness did not finish in %d s (container %s removed)" % (TIMEOUT_S, name)
out = p.stdout.decode("utf-8", "replace")
if p.returncode in (125, 126, 127):
return None, "docker run exited %d:\n%s" % (p.returncode, out[-600:])
return p.returncode, out
finally:
shutil.rmtree(tmp, ignore_errors=True)
def main():
docker = shutil.which("docker")
if not docker:
return not_checked("no docker on PATH")
p = subprocess.run([docker, "image", "inspect", IMAGE],
stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
if p.returncode != 0:
return not_checked("image %s is absent or docker is unreachable (%s). Build it with: docker build "
"-f scripts/iso/Dockerfile.assistant -t %s scripts/iso"
% (IMAGE, p.stderr.decode("utf-8", "replace").strip()[:200], IMAGE))
rc, out = run_harness(docker, mutate=False)
if rc is None:
return not_checked(out)
print(out.rstrip())
oks = len(re.findall(r"^\s*ok: ", out, re.M))
fails = re.findall(r"^\s*FAIL: .*$", out, re.M)
if rc != 0 or fails:
print("\niso-bootstrap: FAILED — the harness exited %d with %d failing check(s):" % (rc, len(fails)))
for f in fails:
print(" " + f.strip())
return 1
if PASS_LINE not in out or oks < MIN_OK:
print("\niso-bootstrap: FAILED — exit 0 but %s (pass line %s, %d ok lines, want >= %d)"
% ("the harness proved nothing", "present" if PASS_LINE in out else "ABSENT", oks, MIN_OK))
return 1
mrc, mout = run_harness(docker, mutate=True)
if mrc is None:
return not_checked("the built-in decoy could not run: %s" % mout)
if mrc == 0 or not MUTANT_MUST_FAIL.search(mout):
print("\niso-bootstrap: FAILED — the harness PASSED a bootstrap whose pairing banner never paints "
"(exit %d). The instrument is blind; the green run above proves nothing." % mrc)
print(mout[-800:])
return 1
print("\niso-bootstrap: built-in decoy convicted (a banner that never paints -> harness exit %d)" % mrc)
print("iso-bootstrap gate OK — %d harness checks green in %s" % (oks, IMAGE))
return 0
if __name__ == "__main__":
sys.exit(main())