Files
felhom.eu/documentation/audits/night-2026-09-23/spike_ladder.py
T
admin 0030cbd1de
gates / gates (push) Successful in 26s
night 2026-09-23: evidence so far (Parts A, B, C in progress)
The records the catalog's ladder entries cite (apps/<app>/bench and
apps/<app>/verdict.json), the drill tools, the spike, R-626's run,
R-612/R-613 red-proofs, the chaos schedule (seed 20260923) drawn
BEFORE round 1. Docs and register follow at the end of the night.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-23 21:33:54 +02:00

61 lines
3.5 KiB
Python

#!/usr/bin/env python3
"""Part B spike — does the controller tolerate a top-level `update_ladder:` key in .felhom.yml?
The drill catalog's navidrome carries the block (drill commit given as argv[1]). The app must sync,
deploy, probe and badge exactly as without it. Positive observables: the sync reaches the drill commit,
the metadata the page needs is read (display_name, deploy_fields), the deploy reaches `running`, the
controller's own health-probe line for navidrome appears, the badge renders in both languages."""
import sys, time, json, re
sys.path.insert(0, ".")
import walk as w
want = sys.argv[1]
tag = sys.argv[2] if len(sys.argv) > 2 else "v?"
w.login()
w.say(f"SPIKE on controller {tag}, drill commit {want}")
w.sync_rescan()
time.sleep(3)
head = w.guest("cat /var/lib/docker/volumes/felhom-controller-data/_data/catalog-cache/.git/HEAD 2>/dev/null; git -C /var/lib/docker/volumes/felhom-controller-data/_data/catalog-cache rev-parse --short=12 HEAD 2>/dev/null; ls /var/lib/docker/volumes/felhom-controller-data/_data/ | head -30")
w.say("catalog cache: " + head.replace("\n", " | "))
has = w.guest("grep -c '^update_ladder:' /opt/docker/stacks/navidrome/.felhom.yml")
w.say(f"update_ladder lines in the box's navidrome/.felhom.yml: {has.strip()}")
c, d = w.ctl("GET", "/api/stacks/navidrome/deploy-fields")
meta = ((d.get("data") or {}).get("metadata") or {})
w.say(f"deploy-fields -> {c}; display_name={meta.get('display_name')!r} fields={[f.get('env_var') for f in meta.get('deploy_fields') or []]}")
ok = w.deploy("navidrome", "navidrome")
w.wait_app("navidrome", "/")
probe = []
for _ in range(40):
c2, d2 = w.ctl("GET", "/api/debug/logs?level=DEBUG&lines=3000")
ents = ((d2.get("data") or {}).get("entries") or []) if isinstance(d2, dict) else []
probe = [e.get("timestamp", "") + " " + e.get("message", "") for e in ents
if "navidrome" in e.get("message", "") and "probe" in e.get("message", "").lower()]
if any("healthy=true" in p or "-> 200" in p or "→ 200" in p or " ok" in p for p in probe):
break
time.sleep(10)
w.say("probe lines for navidrome:\n " + "\n ".join(probe[-6:]))
st = w.stack("navidrome")
w.say(f"state={st.get('state')} deployed={st.get('deployed')} health={st.get('health')}")
import re as _re
b = {}
for i in range(12):
for lang, suf in (("hu", ""), ("en", "?lang=en")):
h = w.page(f"/apps/navidrome{suf}")
b[lang] = _re.findall(r'<span class="tag tag-[a-z]+" title="[^"]*">([^<]*)<', h)[:4]
if b["hu"] and b["en"]:
w.say(f"badge present after {i} rescan round(s)")
break
w.ctl("POST", "/api/stacks/rescan")
time.sleep(5)
w.say("badges: " + json.dumps(b, ensure_ascii=False))
logs = w.guest("docker logs --since 10m felhom-controller 2>&1 | grep -iE 'navidrome' | grep -iE 'probe|yaml|felhom.yml|error|WARN' | tail -15")
w.say("controller log lines for navidrome:\n" + logs)
errs = w.guest("docker logs --since 10m felhom-controller 2>&1 | grep -iE 'yaml: |unmarshal|cannot parse' | tail -5")
w.say("yaml/unmarshal errors in the controller log (want none): " + (errs.strip() or "(none)"))
w.remove("navidrome")
out = {"controller": tag, "drill_commit": want, "ladder_on_box": has.strip(), "deploy_fields_http": c,
"display_name": meta.get("display_name"), "deployed": ok, "state": st.get("state"),
"badges": b, "probe_lines": probe[-6:],
"yaml_errors": errs.strip()}
json.dump(out, open(f"B1-spike-{tag}.json", "w"), indent=2, ensure_ascii=False)
open(f"B1-spike-{tag}.log", "w").write("\n".join(w.LOG) + "\n")