9c69b3ff07
gates / gates (push) Successful in 27s
Evidence off the machine at the end of the phases that produced it (R-320). Teardown follows. PHASE 2 — the two database engines, through the REAL Update button: - MariaDB 11.6 -> 12.3 on nextcloud: PROVEN, and pressed through the button for the first time. All four SPIKE-r459 observables: the datadir's own record moved 11.6.2 -> 12.3.3; the engine itself says "already upgraded ... no need to run mariadb-upgrade again"; the entrypoint says "Major version upgrade detected ... Check required!" and then STARTED and FINISHED it (not the `skipped due to $MARIADB_AUTO_UPGRADE` line R-459 feared); and the engine took its own pre-upgrade backup, 631 905 B. The seeded Nextcloud account read back. - PostgreSQL 16 -> 17 on docmost: FAILED exactly as R-463 predicted and nobody had measured. 5.1 s to held; the pin named 17 while nothing ran; the restore brought it back in 29.1 s. The engine's REFUSAL LINE was destroyed by failAndHold before any probe could read it, so it was REPRODUCED INDEPENDENTLY with a control on every step (R-320). PHASE 3 — the bad days. B1 produced THE UNATTENDED HOLD, which this project has never had: the caller pressed once with nobody watching, the app held after 312.9 s, and passes 2 and 3 pressed nothing. B2 put the pin back on a pull failure in 1.0 s. B3 refused `busy` six times. B4 showed there is NO single-flight — 5 of 5 updates ran at once and all ended honest. B5 cut the power in `backing-up` and the box recovered itself and said so. B7 refused under the 2 GB floor. B9 found R-458's risk narrower than the row states. PHASE 4 — every badge on the box is TRUE, and the held app answers all four of Q4's questions. FINDINGS, five new and three corrections to existing rows. The one that matters: R-618 is P1 — two templates name a health probe the app does not answer, and because the guarded update waits on that same probe, a SUCCESSFUL update ends by STOPPING a working app. Measured: tandoor served HTTP 200 on the new version at four samples across five minutes and was then stopped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
83 lines
3.3 KiB
Python
83 lines
3.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Measure `wger` — the last unmeasured candidate in R-618.
|
|
|
|
The static sweep convicts three templates and two were confirmed live (tandoor: wrong PORT;
|
|
zipline: wrong PATH). `wger` is the third: its `.felhom.yml` probes `type: http, port: 80` while
|
|
`wger/server` serves **8000** and the template's own traefik label says 8000. If that is right, a
|
|
household running wger sees it as permanently UNHEALTHY and — worse, per tonight's escalation — a
|
|
SUCCESSFUL update of it would be stopped by its own health wait.
|
|
|
|
This does not walk the whole edge. It answers ONE question with a control on each side:
|
|
|
|
does the container listen on the port the probe names, and what does the box then say?
|
|
|
|
The app is then removed. Deliberately narrow: the point is to turn a suspicion into a fact or to
|
|
clear it, not to add a fourteenth edge at midnight.
|
|
"""
|
|
import json, os, sys, time
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
sys.path.insert(0, HERE)
|
|
import walk as w # noqa: E402
|
|
|
|
OUT = os.path.join(HERE, "22-wger-probe-measured.txt")
|
|
APP, SUB = "wger", "fitness"
|
|
|
|
|
|
def main():
|
|
w.login()
|
|
lines = []
|
|
|
|
def say(s):
|
|
w.say(s)
|
|
lines.append(s)
|
|
|
|
say("=== wger — R-618's last unmeasured candidate")
|
|
say(f" template probe : type=http port=80 (.felhom.yml)")
|
|
say(f" traefik label : loadbalancer.server.port=8000 (the SAME template's compose)")
|
|
|
|
sub = w.guest("grep -m1 '^subdomain:' /dev/null 2>/dev/null") or ""
|
|
st = w.stack(APP)
|
|
if not st.get("deployed"):
|
|
if not w.deploy(APP, SUB):
|
|
say(" wger never came up — the candidate stays UNMEASURED, and that is the result")
|
|
open(OUT, "w").write("\n".join(lines) + "\n")
|
|
return
|
|
# give the app time to actually serve, not merely to run
|
|
w.wait_app(SUB, "/", want=("200", "302", "301"), tries=90)
|
|
|
|
probe = w.guest("""
|
|
echo "--- what wger actually LISTENS on, asked inside its own container:"
|
|
docker exec wger sh -c 'netstat -ltn 2>/dev/null || ss -ltn 2>/dev/null' | head -8
|
|
echo "--- the container's own docker healthcheck, if it has one:"
|
|
docker inspect wger --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}(no docker healthcheck){{end}}'
|
|
echo "--- port 80 from inside (the port the probe names):"
|
|
docker exec wger sh -c 'wget --spider -q -T 4 http://127.0.0.1:80/ 2>&1 && echo "ANSWERED on 80" || echo "refused on 80"'
|
|
echo "--- port 8000 from inside (the port the compose says):"
|
|
docker exec wger sh -c 'wget --spider -q -T 4 http://127.0.0.1:8000/ 2>&1 && echo "ANSWERED on 8000" || echo "refused on 8000"'
|
|
""", timeout=420)
|
|
for ln in probe.strip().split("\n"):
|
|
if ln.strip():
|
|
say(" " + ln)
|
|
|
|
rc, code, _ = w.app_curl(SUB, "/", timeout=25)
|
|
say(f" the household's own front door: http={code}")
|
|
st = w.stack(APP)
|
|
say(f" the box's own verdict: state={st.get('state')!r}")
|
|
|
|
verdict = ("CONFIRMED DEFECT — the same shape as tandoor"
|
|
if st.get("state") == "unhealthy" and code in ("200", "302", "301")
|
|
else "NOT a defect on this measurement"
|
|
if st.get("state") == "running"
|
|
else "INCONCLUSIVE — see the lines above")
|
|
say(f" VERDICT: {verdict}")
|
|
open(OUT, "w").write("\n".join(lines) + "\n")
|
|
w.say(f" written -> {OUT}")
|
|
|
|
if st.get("deployed"):
|
|
w.remove(APP)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|