diff --git a/documentation/audits/night-2026-09-25/tools/live272.py b/documentation/audits/night-2026-09-25/tools/live272.py new file mode 100644 index 00000000..a21dd275 --- /dev/null +++ b/documentation/audits/night-2026-09-25/tools/live272.py @@ -0,0 +1,69 @@ +"""Live proofs for controller v0.272.0 on scratch 9202 (drill catalog). Evidence, not product; every act is the +endpoint the UI invokes. R-670 + R-671: navidrome installed at 0.64.0 with a WRONG probe port (9999) so a step to +0.64.1 fails AND its undo fails (the old probe is wrong too) -> HOLD with the undo copies kept; then the backup +page's restore lifts the hold -> the copies must be gone, and no undo may log the false 'unreadable' ERROR. +R-677: the probe put right, navidrome climbed to the head, then the drill RE-TESTS the head at a new digest -> +the badge must say '— ma' / '— today', not the template's catalog_since age.""" +import json, re, subprocess, time, os +import walk as w, fixtures +EVD = w.EV.replace("night-2026-09-25", "retire-peti-2026-09-25") + "/B/live" +os.makedirs(EVD, exist_ok=True) +T = f"{w.DRILL}/templates/navidrome" +LOG = [] +def say(s): + w.say(s); LOG.append(time.strftime("%H:%M:%S ") + s); open(EVD + "/run.log", "w").write("\n".join(LOG) + "\n") +def drill(edit, msg): + w.sh(["git", "-C", w.DRILL, "pull", "-q", "--rebase", "origin", "main"], timeout=120) + edit() + w.sh(["git", "-C", w.DRILL, "commit", "-qam", "LIVE-272 " + msg]); w.sh(["git", "-C", w.DRILL, "push", "-q", "origin", "main"], timeout=120) + head = w.sh(["git", "-C", w.DRILL, "rev-parse", "--short", "HEAD"]).stdout.strip() + for k in range(40): + w.sync_rescan() + if w.guest("cd /var/lib/docker/volumes/felhom-controller-data/_data/data/catalog-cache && git rev-parse --short HEAD").strip() == head: + say(f"drill: {msg} (box at {head})"); return + time.sleep(8) + raise SystemExit("box never caught up") +def sub(path, a, b): + s = open(path).read(); assert a in s, (path, a); open(path, "w").write(s.replace(a, b, 1)) +def copies(): + return w.guest("docker volume ls -q --filter label=felhom.undo-copy-of=navidrome").split() +w.login() +since = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) +# --- R-670 / R-671 ------------------------------------------------------------------------------- +drill(lambda: (sub(f"{T}/docker-compose.yml", "image: deluan/navidrome:0.64.1", "image: deluan/navidrome:0.64.0"), + sub(f"{T}/.felhom.yml", " port: 4533", " port: 9999")), "navidrome at 0.64.0, probe port 9999 (wrong on purpose)") +ok = w.deploy("navidrome", "c-navi"); say(f"deploy navidrome -> {ok}; state={w.stack('navidrome').get('state')}") +tok = fixtures.FIXTURES["navidrome"].seed(w, "c-navi", say); say(f"seed ok={tok is not None}") +say(f"C1 read before: {fixtures.FIXTURES['navidrome'].verify(w, 'c-navi', tok, say)}") +drill(lambda: sub(f"{T}/docker-compose.yml", "image: deluan/navidrome:0.64.0", "image: deluan/navidrome:0.64.1"), "navidrome head 0.64.1 (probe still wrong)") +w.sync_rescan(expect_app="navidrome", expect_ref="deluan/navidrome:0.64.1") +res = w.press_update("navidrome"); st = w.stack("navidrome") +say(f"update -> final {res.get('final_phase')} hold={st.get('hold_reason')!r}") +c1 = copies(); say(f"undo copies kept by the hold: {c1}") +lg = w.guest(f"docker logs --since {since} felhom-controller 2>&1 | grep -E 'UNDO|the UNDO failed|HOLDING|backup block rejected|unreadable|removed .* undo cop'") +open(EVD + "/r670-r671-log-before-restore.txt", "w").write(lg) +say(f"R-670: 'docker-compose.yml unreadable' lines during the update+undo: {lg.count('unreadable')}; UNDO lines: {lg.count('UNDO')}") +r = w.restore("navidrome"); say(f"restore -> {json.dumps({k: r.get(k) for k in ('ok','http','seconds','state_after','hold_after')}, ensure_ascii=False)}") +c2 = copies(); say(f"undo copies AFTER the restore: {c2}") +lg2 = w.guest(f"docker logs --since {since} felhom-controller 2>&1 | grep -E 'update hold .* CLEARED|removed .* undo cop|unreadable'") +open(EVD + "/r671-log-after-restore.txt", "w").write(lg2); say("after-restore lines:\n" + lg2) +say(f"read after restore: {fixtures.FIXTURES['navidrome'].verify(w, 'c-navi', tok, say)}") +# --- R-677 ------------------------------------------------------------------------------------------- +drill(lambda: sub(f"{T}/.felhom.yml", " port: 9999", " port: 4533"), "navidrome probe put right") +w.sync_rescan(expect_app="navidrome", expect_ref="deluan/navidrome:0.64.1") +res2 = w.press_update("navidrome"); say(f"update to the head -> {res2.get('final_phase')}") +time.sleep(5) +def retest(): + p = f"{T}/.felhom.yml"; lines = open(p).read().split("\n") + idx = max(i for i, l in enumerate(lines) if l.startswith(" - {") and '"deluan/navidrome:0.64.1"}, "digest"' in l) + e = json.loads(lines[idx][4:]); e["digest"] = {"navidrome": "sha256:" + "b" * 64} + e["tested_at"] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()); lines[idx] = " - " + json.dumps(e, ensure_ascii=False) + open(p, "w").write("\n".join(lines)) +drill(retest, "navidrome head re-tested at a new digest (tested now)") +for i in range(6): + w.sync_rescan(); b = w.badges("navidrome") + if "Frissítés elérhető" in json.dumps(b, ensure_ascii=False): break + time.sleep(10) +say(f"R-677 badges: {json.dumps(b, ensure_ascii=False)}") +json.dump({"update": res, "restore": r, "copies_kept": c1, "copies_after": c2, "update2": res2, "badges": b}, + open(EVD + "/live272.json", "w"), indent=2, ensure_ascii=False, default=str)