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
67 lines
2.7 KiB
Python
67 lines
2.7 KiB
Python
#!/usr/bin/env python3
|
|
"""live262.py — the v0.262.0 scenarios, on scratch guest 9202, through the product's endpoints.
|
|
|
|
A: R-630 — paperless-ngx has a probe that resolves to no container WITHOUT the explicit field, and
|
|
an explicit one WITH it. Both are exercised: the catalog now carries the field, so the Update
|
|
must reach `done`; the drill catalog lets the field be removed to show the OTHER half.
|
|
B: R-633 — a remove sent during a restore is refused; a remove after it is VERIFIED clean.
|
|
C: R-634 — a half-state (app.yaml, no deployed flag) is removable.
|
|
F: R-614 — a redeploy reads no stale phase.
|
|
"""
|
|
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 = {}
|
|
|
|
|
|
def save(tag, rec):
|
|
OUT[tag] = rec
|
|
json.dump(OUT, open(os.path.join(HERE, "live262.json"), "w"), ensure_ascii=False, indent=2)
|
|
print(json.dumps({tag: rec}, ensure_ascii=False, indent=2)[:1400], flush=True)
|
|
|
|
|
|
def scenario_A():
|
|
"""paperless-ngx: the update must now END `done`, where v0.261.0 held it at +313 s."""
|
|
app, sub = "paperless-ngx", "paperless"
|
|
rec = {"scenario": "A (R-630)", "app": app}
|
|
w.deploy(app, sub)
|
|
w.wait_app(sub, "/", tries=60)
|
|
st = w.stack(app)
|
|
rec["before"] = {"state": st.get("state"),
|
|
"front_door": w.app_curl(sub, "/")[1],
|
|
"containers": w.guest("docker ps --format '{{.Names}}|{{.Status}}' | grep -i paperless").strip().split("\n")}
|
|
# the probe now resolves — the controller's own log says which container
|
|
t0 = time.time()
|
|
rec["phases"] = w.press_update(app)
|
|
rec["wall_s"] = round(time.time() - t0, 1)
|
|
rec["after"] = {"state": w.stack(app).get("state"), "front_door": w.app_curl(sub, "/")[1]}
|
|
rec["controller_says"] = w.guest(
|
|
"docker logs --since 15m felhom-controller 2>&1 | grep -iE 'paperless' | "
|
|
"grep -iE 'no probe container|settling|phase |health probe|FAILED after' | tail -8").strip()
|
|
save("A", rec)
|
|
return app
|
|
|
|
|
|
def scenario_F(app):
|
|
"""R-614: remove clears the phase; a redeploy reads none."""
|
|
rec = {"scenario": "F (R-614)", "app": app}
|
|
rec["phase_before_remove"] = w.stack(app).get("update_phase")
|
|
w.remove(app)
|
|
time.sleep(5)
|
|
rec["after_remove_deployed"] = w.stack(app).get("deployed")
|
|
w.deploy(app, "paperless")
|
|
time.sleep(5)
|
|
st = w.stack(app)
|
|
rec["phase_after_redeploy"] = st.get("update_phase")
|
|
rec["verdict"] = "clean" if not st.get("update_phase") else "STALE PHASE SURVIVED"
|
|
save("F", rec)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
w.login()
|
|
which = sys.argv[1] if len(sys.argv) > 1 else "A"
|
|
if which == "A":
|
|
scenario_F(scenario_A())
|