25cb3eb9c1
gates / gates (push) Successful in 29s
- R-463 CLOSED: rallly, outline, sparkyfitness -> PostgreSQL 18 (catalog 25ffd89 / aeb0cd6 / 1666572), each proven on the bench and on 9202 through the guarded Update with one undo case; zipline, adventurelog, immich stay on 16 by 09 decision 42's rule (upstream runs 16 / 16 / 14). - Part F: bookstack, kimai, audiobookshelf, n8n, navidrome, grafana, komga moved on both venues; immich not (R-732: its first start OOM-killed its database on the bench). - R-687 item 4 PROVEN LIVE on demo-hp: two deferrals while the leg stepped two apps, the whole-guest backup on the first poll after, success; config + window put back and read back. - Catalog currency audit (Part D): 25/53 behind inside a major, 19 across; night-updatable 28 -> 31 (+1). - R-446 and R-440 narrowed (measured on demo-hp); R-624 corrected (outline, rallly, zipline have routes); R-548 note (demo-hp's local tier refused for space since 09-27). - New rows: R-730 (the ISO 1.29.0 build commit cannot be proven -> no tag; installer-v* is the script's line), R-731 (tag-shape switches), R-732. Register 361 -> 364. - 09 §3: the 2026-09-30 operator notes (by day; Tester-2 pre-checks done; decision 52 not needed, not recorded); §6.4 dated currency note. STATUS, CONTEXT, REPORT-pg-last-six-2026-09-30.md. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
63 lines
3.9 KiB
Python
63 lines
3.9 KiB
Python
#!/usr/bin/env python3
|
|
"""undocase.py <app> <sub> <svc> <pg-user> <db> — the brief's ONE undo case per app on 9202 (scratch guest).
|
|
|
|
The load failing is the case (the docmost method, `audits/night-2026-09-26/D/Da-*`): an object the OLD major
|
|
has and 18 does not — the `adminpack` extension (removed in PostgreSQL 17) — is created in the app's own
|
|
database, the guarded Update is pressed (the drill catalog's step carries the conversion mark), the load
|
|
into 18 must fail, and the BOX must undo it by itself: old pin, old datadir, `undone`, the seed read back.
|
|
The extension is dropped again afterwards, so the real move that follows converts clean data.
|
|
|
|
Evidence: box/<app>/undo.txt + box/<app>/undo-verdict.json. Never prints a secret.
|
|
"""
|
|
import json, os, sys, time
|
|
import walk as w
|
|
sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts")
|
|
import upgrade_fixtures_box as fixtures, upgrade_fixtures_box28 as _f28
|
|
FX = dict(_f28.FIXTURES28); FX.update(fixtures.FIXTURES) # the box set wins, as upgrade_boxport.get() decides
|
|
|
|
app, sub, svc, user, db = sys.argv[1:6]
|
|
EVD = f"{w.EV}/box/{app}"; os.makedirs(EVD, exist_ok=True)
|
|
log = open(f"{EVD}/undo.txt", "a", buffering=1)
|
|
|
|
|
|
def say(*a):
|
|
w.say(*a); log.write(" ".join(map(str, a)) + "\n")
|
|
|
|
|
|
w.login()
|
|
toks = json.load(open(w.SC + "/seed-tokens-box.json"))
|
|
if isinstance(toks.get(app), dict) and toks[app].get("__generated"):
|
|
w.GENERATED[app] = toks[app]["__generated"]
|
|
psql = lambda sql: w.guest(f"docker exec {svc} psql -U {user} -d {db} -Atc \"{sql}\" 2>&1").strip()
|
|
pg = lambda: w.guest(f"docker exec {svc} sh -c 'cat $PGDATA/PG_VERSION' 2>&1").strip()
|
|
say(f"# undo case (the load fails) — {time.strftime('%Y-%m-%dT%H:%M:%S%z')}")
|
|
st = w.stack(app); before = (st.get("app_config") or {}).get("pinned_images")
|
|
say(f"before: PG_VERSION={pg()} pinned={before}")
|
|
say("before: seed reads back through the front door =", FX[app].verify(w, sub, toks[app], say))
|
|
say("setup: CREATE EXTENSION adminpack ->", psql("CREATE EXTENSION IF NOT EXISTS adminpack"))
|
|
say("setup: extensions now:", psql("select string_agg(extname, ' ' order by extname) from pg_extension"))
|
|
ctrl = w.sh(["ssh", "demo-hp", "pct exec 9202 -- docker run --rm postgres:18-alpine sh -c 'ls /usr/local/share/postgresql/extension/ | grep -c ^adminpack || true'"]).stdout.strip()
|
|
say(f"control: 18 has no adminpack: {ctrl}")
|
|
since = w.guest("date -u +%Y-%m-%dT%H:%M:%SZ").strip()
|
|
res = w.press_update(app, poll=1, cap_s=1800)
|
|
for p in res.get("phases", []):
|
|
say(f" phase +{p['t']}s {p['phase']} | err={p['error']}")
|
|
say("result:", json.dumps({k: res.get(k) for k in ("accepted", "http", "duration_s", "final_phase", "update_error", "hold_reason", "state")}))
|
|
time.sleep(10)
|
|
st = w.stack(app); after = (st.get("app_config") or {}).get("pinned_images")
|
|
after_pg = pg()
|
|
read = FX[app].verify(w, sub, toks[app], say)
|
|
say(f"after: PG_VERSION={after_pg} pinned={after} state={st.get('state')}")
|
|
say("after: seed reads back through the front door =", read)
|
|
conv = w.guest(f"docker logs --since {since} felhom-controller 2>&1 | grep -E 'update {app}|CONVERT|UNDO' | grep -v DEBUG | cut -c1-400")
|
|
log.write("controller lines:\n" + conv + "\n")
|
|
say("cleanup: DROP EXTENSION adminpack ->", psql("DROP EXTENSION IF EXISTS adminpack"))
|
|
verdict = {"app": app, "case": "the load into 18 fails (adminpack, an object 18 lacks)", "venue": "box 9202, the product's guarded Update",
|
|
"final_phase": res.get("final_phase"), "duration_s": res.get("duration_s"),
|
|
"pg_version_after": after_pg, "pinned_before": before, "pinned_after": after, "seed_read_after": read,
|
|
"undone_line": next((l.split("] ", 2)[-1] for l in conv.splitlines() if "UNDONE" in l), ""),
|
|
"ok": res.get("final_phase") == "undone" and read is True and after == before,
|
|
"measured_at": since}
|
|
json.dump(verdict, open(f"{EVD}/undo-verdict.json", "w"), indent=2)
|
|
say(f"RESULT undo case ok={verdict['ok']} final_phase={res.get('final_phase')} PG_VERSION={after_pg} seed_after={read}")
|