#!/usr/bin/env python3 """B2 — prove the BUSY guard specifically (R-633). B's first run refused the remove with `409 still running — stop it first`, which is the PRE-EXISTING running check, not v0.262.0's new guard: the restore had already finished. To reach the new guard the app must be STOPPED (so the running check passes) while the backup side is busy. """ import json, os, sys, time HERE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, os.path.join(os.path.dirname(HERE), "update-night-2026-09-21")) import walk as w # noqa: E402 OUT = json.load(open(os.path.join(HERE, "live262.json"))) app, sub = "privatebin", "paste" rec = {"scenario": "B2 (R-633) — the BUSY guard, reached deliberately", "app": app} w.login() w.deploy(app, sub) w.wait_app(sub, "/", tries=30) # stop it, so the pre-existing "still running" check cannot answer first w.ctl("POST", f"/api/stacks/{app}/stop") for _ in range(24): time.sleep(5) if w.stack(app).get("state") != "running": break rec["state_before"] = w.stack(app).get("state") # box-wide backup: `POST /api/backup/run` holds the single-flight the guard consults code, d = w.ctl("POST", "/api/backup/run") rec["backup_started"] = {"http": code, "answer": str(d)[:120]} time.sleep(3) code, d = w.ctl("POST", f"/api/stacks/{app}/remove", {"remove_hdd_data": False, "remove_backups": False}) rec["remove_during_backup"] = {"http": code, "answer": d} rec["refused_by_busy_guard"] = (code == "409" and "ment" in json.dumps(d, ensure_ascii=False).lower()) rec["controller_says"] = w.guest( f"docker logs --since 5m felhom-controller 2>&1 | grep -iE 'RemoveStack {app}.*(REFUSED|busy)' | tail -3").strip() OUT["B2"] = rec json.dump(OUT, open(os.path.join(HERE, "live262.json"), "w"), ensure_ascii=False, indent=2) print(json.dumps(rec, ensure_ascii=False, indent=2))