186546d562
gates / gates (push) Successful in 27s
All 28 walked on scratch guest 9202 against the private drill catalog. 26 deployed, 6 proven, 5 inconclusive, 14 with no upstream edge, 1 failed honestly (outline 1.9.1->1.10.1, HELD with the right sentence), 2 undeployable - one (plant-it) by design, refused by the lifecycle gate, proven live for the first time. Each app also got the half the update night skipped: a restore from its own copy with the seed read back again - 21 restored, 2 correctly REFUSED per 07 6.2. R-630 RAISED TO P1 by measurement: a stack with NO probe container does not skip verifying - it waits out the full health timeout and HOLDS, stopping an app whose three containers read healthy. The controller's own words: "not healthy within 5m0s (last: no probe container)". R-633 opened: a remove sent during a restore reports success and leaves a container restarting with a live public route. The product already refuses that clash for update and for restore, naming the blocker; remove has no such guard. R-634 opened: an app can be running, healthy and serving while recorded as deployed=false, and is then unremovable. Reproducible alone on sparkyfitness; concurrency-linked on two others. R-631 and R-632 CLOSED. Register 321 -> 323. Seven interventions, six of them my own harness - named, with what each cost. No product code. The live catalog's image: lines are byte-identical to the start of the night. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
63 lines
3.0 KiB
Python
63 lines
3.0 KiB
Python
#!/usr/bin/env python3
|
|
"""R-630 — what does the guarded Update do when NO probe answers, because none was ever built?
|
|
|
|
`findProbeContainer` (healthprobe.go:297) takes the container whose name EQUALS the stack name,
|
|
else the first with it as a PREFIX. paperless-ngx's containers are `paperless-webserver`,
|
|
`paperless-postgres`, `paperless-redis` — none matches `paperless-ngx`. The function returns "",
|
|
`:62` counts the stack in `skippedNoContainer` and continues, so no probe is ever built.
|
|
|
|
The open question this measures, and it is the one that decides which fix is right:
|
|
**the `verifying` phase waits on that probe. With no probe at all, does it pass at once, wait out
|
|
`update.health_timeout`, or HOLD?**
|
|
|
|
No edge exists upstream for paperless-ngx tonight, so this presses the Update on the SAME version —
|
|
which is exactly what a household does when they press it on an up-to-date app, and it still walks
|
|
the whole phase machine. That difference is stated in the record rather than glossed.
|
|
"""
|
|
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
|
|
|
|
APP, SUB = "paperless-ngx", "paperless"
|
|
rec = {"app": APP, "question": "what does `verifying` do with no probe target at all?"}
|
|
|
|
w.login()
|
|
try:
|
|
rec["deployed"] = w.deploy(APP, SUB)
|
|
w.wait_app(SUB, "/", tries=60)
|
|
st = w.stack(APP)
|
|
rec["controller_state"] = st.get("state")
|
|
rec["front_door"] = w.app_curl(SUB, "/")[1]
|
|
rec["containers"] = w.guest(
|
|
"docker ps --format '{{.Names}}|{{.Status}}' | grep -i paperless").strip().split("\n")
|
|
# what the HEALTH page says for a stack the prober skips
|
|
code, d = w.ctl("GET", f"/api/stacks/{APP}")
|
|
dd = (d.get("data") or {})
|
|
rec["health_detail"] = dd.get("health") or dd.get("health_checks") or dd.get("health_detail")
|
|
rec["state"] = dd.get("state")
|
|
# the app page, as the household reads it
|
|
import re
|
|
for lang, suf in (("hu", ""), ("en", "?lang=en")):
|
|
h = w.page(f"/apps/{APP}{suf}")
|
|
h = re.sub(r"<script.*?</script>|<style.*?</style>", "", h, flags=re.S)
|
|
txt = re.sub(r"\s+", " ", re.sub(r"<[^>]+>", " ", h))
|
|
i = txt.find("←")
|
|
rec[f"page_{lang}"] = txt[i:i + 200] if i >= 0 else None
|
|
# THE MEASUREMENT: press Update and time every phase
|
|
t0 = time.time()
|
|
rec["phases"] = w.press_update(APP)
|
|
rec["update_wall_s"] = round(time.time() - t0, 1)
|
|
rec["state_after"] = w.stack(APP).get("state")
|
|
rec["front_door_after"] = w.app_curl(SUB, "/")[1]
|
|
print(json.dumps(rec, ensure_ascii=False, indent=2)[:2500])
|
|
finally:
|
|
try:
|
|
w.remove(APP)
|
|
except Exception as e:
|
|
rec["remove_error"] = str(e)
|
|
json.dump(rec, open(os.path.join(HERE, "sidejobs", "r630.json"), "w"),
|
|
ensure_ascii=False, indent=2)
|
|
open(os.path.join(HERE, "sidejobs", "r630-log.txt"), "w").write("\n".join(w.LOG) + "\n")
|
|
print("\nwritten sidejobs/r630.json")
|