Files
felhom.eu/documentation/audits/v0262-live-2026-09-22/live262.py
T
admin 22439b0e43
gates / gates (push) Successful in 30s
v0.262.0 + v0.262.1 live on 9202: four scenarios proven, R-630/R-633/R-621/R-614 closed
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
2026-09-22 21:53:53 +02:00

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())