Files
felhom.eu/documentation/audits/night-2026-09-23/r626.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.3 KiB
Python

#!/usr/bin/env python3
"""R-626 — does a removed app come back? Measured through the product on 9202.
deploy navidrome -> remove through the product -> `docker events` for the compose project for 5 min,
a controller restart at +150 s -> then a GUEST REBOOT and a look after it. Positive observable: the
events stream must show the remove's own destroy events (proves the watcher saw the project), and
any `create` after the remove's answer is the defect.
"""
import sys, time, json
sys.path.insert(0, ".")
import walk as w
APP = sys.argv[1] if len(sys.argv) > 1 else "navidrome"
w.login()
w.say(f"R-626 run on guest {w.GUEST}, app {APP}")
if not w.deploy(APP, APP):
sys.exit("deploy failed")
w.wait_app(APP, "/")
w.say("starting docker events watcher (400 s) in the guest")
w.guest(f"rm -f /root/r626-events.txt; nohup timeout 400 docker events --format '{{{{.Time}}}} {{{{.Type}}}} {{{{.Action}}}} {{{{.Actor.Attributes.name}}}} project={{{{index .Actor.Attributes \"com.docker.compose.project\"}}}}' --filter label=com.docker.compose.project={APP} > /root/r626-events.txt 2>&1 &\nsleep 1; echo started")
time.sleep(3)
t_remove = time.time()
code = w.remove(APP)
w.say(f"remove answered {code} at epoch {int(t_remove)}")
checks = {}
for at in (60, 150, 300, 390):
while time.time() - t_remove < at:
time.sleep(2)
if at == 150:
w.say("+150 s: restarting the controller (docker restart felhom-controller)")
w.guest("docker restart felhom-controller >/dev/null; echo restarted")
time.sleep(20)
w.login()
out = w.guest(f"docker ps -a --filter label=com.docker.compose.project={APP} --format '{{{{.Names}}}} {{{{.Status}}}}'; docker volume ls -q | grep -c '^{APP}_' || true")
st = w.stack(APP)
checks[at] = {"containers_and_volcount": out.strip(), "deployed": st.get("deployed")}
w.say(f"+{at} s: {checks[at]}")
ev = w.guest("cat /root/r626-events.txt")
w.say("events after the remove:\n" + ev)
w.say("rebooting guest 9202 (pct reboot)")
w.sh(["ssh", w.HP, f"pct reboot {w.GUEST}"], timeout=300)
for _ in range(60):
time.sleep(5)
r = w.sh(["ssh", w.HP, f"pct exec {w.GUEST} -- docker ps --format '{{{{.Names}}}}'"], timeout=30)
if "felhom-controller" in (r.stdout or ""):
break
time.sleep(60)
w.login()
after = w.guest(f"docker ps -a --filter label=com.docker.compose.project={APP} --format '{{{{.Names}}}} {{{{.Status}}}}'; docker volume ls -q | grep '^{APP}_' || true; ls -d /opt/docker/stacks/{APP} 2>&1")
st = w.stack(APP)
w.say(f"after reboot (+60 s): containers/volumes/dir = {after.strip()!r} deployed={st.get('deployed')}")
creates = [l for l in ev.splitlines() if " create " in l]
destroys = [l for l in ev.splitlines() if " destroy " in l]
verdict = {"app": APP, "remove_http": code, "checks": checks, "events_create_after_remove": creates,
"events_destroy": destroys, "after_reboot": after.strip(), "deployed_after_reboot": st.get("deployed"),
"positive_observable_destroy_events_seen": bool(destroys),
"came_back": bool(creates) or bool([l for l in after.splitlines() if l.strip() and "No such file" not in l and not l.startswith("ls:")])}
json.dump(verdict, open("A3-r626-verdict.json", "w"), indent=2)
w.say("VERDICT " + json.dumps({k: verdict[k] for k in ("came_back", "positive_observable_destroy_events_seen")}))
open("A3-r626-log.txt", "w").write("\n".join(w.LOG) + "\n")