tools: live272.py (the v0.272.0 live proofs)
gates / gates (push) Successful in 24s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-09-25 11:34:04 +02:00
parent 7195a6342e
commit 3386041e62
@@ -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)