22439b0e43
gates / gates (push) Successful in 30s
A (R-630, the P1): paperless-ngx, same app same button - failed at +313.0s with the app stopped under v0.261.0, done at +53.4s now, with no "no probe container" warning because the explicit healthcheck.container resolved the target. B (R-633): the busy guard fires - RemoveStack REFUSED (busy): a backup or restore is running - and the live proof caught it answering HTTP 500, because router.go maps remove errors by grepping the error TEXT. v0.262.1 makes it a typed error and a 409; re-proven live. C (R-634 half): a half-state with app.yaml on disk and deployed=false answered 200, leftovers NONE. Under v0.261.0 the same call said "not deployed". F (R-614): phase done before the remove, no phase at all after redeploying the same name. Also: the first B run proved NOTHING and nearly went down as a pass - the refusal came from the pre-existing "still running" check, not the new guard. Recorded. 09 6.1 and 8.8, the capability map, and STATUS updated. R-625 and R-634's mechanism are named as owed, not half-done. Register 325. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
40 lines
1.8 KiB
Python
40 lines
1.8 KiB
Python
#!/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))
|