gates / gates (push) Successful in 13m17s
Every catalog app installed, used, backed up, removed keeping data, reinstalled, restored and removed with data on a fresh box from the public ISO (demo-hp VM 9360, Tester4), endpoint-level. Worst: a reinstall's capture rewrites the kept unit so its restore breaks the app (R-1269, P2), the restore still reports success (R-1270, P2), and „use my kept data" loaded another box's future-dated database (R-1271, P2). Drill catalog fixes for the morning list (n8n usage data; OpenGist 256M, proven by a cgroup probe). VM 9360 and LXC 9361 destroyed; hub host Tester4-aa9466 left for the operator. REPORT-catalog-sweep-2026-10-24.md.
114 lines
11 KiB
Python
114 lines
11 KiB
Python
#!/usr/bin/env python3
|
||
"""file_rows.py [--apply] — the sweep's register rows A–I into OPEN-ITEMS.md, each in its category section in
|
||
severity order, IDs from the highest R-number in BOTH registers + 1, section heading counts updated; a note on R-739.
|
||
Prints the letter → ID map (README, table.py and the report use it). Without --apply it changes nothing."""
|
||
import json, os, re, sys
|
||
|
||
FE = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../.."))
|
||
OPEN = os.path.join(FE, "documentation/backlog/OPEN-ITEMS.md")
|
||
CLOSED = os.path.join(FE, "documentation/backlog/CLOSED-ITEMS.md")
|
||
EV = "`audits/catalog-sweep-2026-10-24/"
|
||
|
||
ROWS = [
|
||
("A", "Backup & restore", "P2",
|
||
"**A reinstall's own capture rewrites the unit that „remove — keep backups” kept, so restoring that unit breaks the app and the household's item is gone.** "
|
||
f"Catalog sweep 2026-10-24, reproduced on its own ({EV}_unitprobe/tandoor/`): install Tandoor, one account, „Mentés most”, remove keeping data and backups, "
|
||
"install again (a volume app: no kept-data question) → 19:50:32 „Recovery unit captured for tandoor” rewrote ONLY the unit's `app.yaml` (checksum `0f366c97…` → `4438c1c5…`, "
|
||
"`created_at` 19:45:26 → 19:50:32) beside the OLD data → „Visszaállítás” paired the NEW install's database password with the OLD data: „password authentication failed” ×42 (app) / ×22 (postgres), "
|
||
"state `starting`, the account not there. Seen three times (the sweep's Tandoor cycle, this probe, AdventureLog's re-run 20:03:18 → 20:05:32 → restore); AdventureLog's first run worked only because no capture fell between. "
|
||
"The comment at `felhom-controller/controller/internal/backup/recovery_unit.go:101-103` says the refresh never rewrites the definition away from its data. Household order: „I reinstalled, my recipes are gone — restore the backup”. "
|
||
"The data itself is not deleted (the old volumes are in the unit).",
|
||
"OPEN", "—", "Design + fix in the controller: a capture of a NEW install must not adopt a unit whose data belongs to an earlier install (a new unit, or the old one set aside); a test in this order that fails today; then `tools/unitprobe.py` again", "CC"),
|
||
("B", "Backup & restore", "P2",
|
||
"**A unit restore reports success while the app never came up.** Catalog sweep 2026-10-24: the controller logged „tandoor restored but health check failed: stack tandoor did not reach running state within 1m30s after restore”, "
|
||
"and the status the restore page reads said `ok: true` — „A(z) tandoor: 3 adatkötet és az adatbázis visszaállítva — az alkalmazás újraindult.” (17:32:41; again in the unitprobe, 19:52:38). "
|
||
f"The household is told it worked ({EV}tandoor/`, `_unitprobe/tandoor/record.json`).",
|
||
"OPEN", "—", "The restore's verdict consults the health result; the message says the app did not start and what to do; a test that fails today", "CC"),
|
||
("C", "Backup & restore", "P2",
|
||
"**„Use my kept data” loaded ANOTHER box's database — a future-dated off-site copy of the rehearsal box — under today's kept files.** Catalog sweep 2026-10-24: Tester4's off-site store holds copies from the 2026-10-20 rehearsal, "
|
||
"whose box clock was set to 2026-10-28. On the new box the question said „Az adatbázist innen töltjük vissza: távoli mentés, 2026-10-28 07:39” for Paperless-ngx, Nextcloud and Immich, and loaded it: the seeded account / user / album "
|
||
"was not there, and Immich never answered again (404 for 20 min). RomM, whose newest copy was its own („saját mentés, 2026-10-24 19:17”), got its item back. "
|
||
"`07` §6.5 says the load is refused when the unit „was taken of another drive” — the rehearsal box's drive was another disk, so that check did not hold here (which field it compares: not measured). "
|
||
f"The household's own local unit stays (nothing deleted). Same future-dated copies as R-1206 (that row: retention only). Evidence {EV}paperless-ngx/`, `nextcloud/`, `immich/`.",
|
||
"OPEN", "—", "Read which drive identity the load compares and why the rehearsal unit passed; refuse a unit of another drive or one dated after now (or say so); a test with both; then the sweep's kept-data cycle on one app", "CC"),
|
||
("D", "Apps & catalog", "P3",
|
||
"**Homepage answers a stranger with no gate at all**, while Glance — the same class of dashboard — is behind the family gate (decision 221, S26). A stranger who reaches `homepage.<domain>` sees the box's CPU, memory and disk figures "
|
||
f"and, once the family edits it, their own links ({EV}homepage/`; the stranger request → 200).",
|
||
"WAITING-ON-OPERATOR", "the operator's word", "Decide: `family_gate: true` like Glance (CC's pick: one line in the template, proven on the drill first) or leave it open", "operator"),
|
||
("E", "Install & onboarding", "P3",
|
||
"**An install that fails on the image registry's rate limit tells the household only „exit code 1”.** Catalog sweep 2026-10-24, code-server 16:01:37: `lscr.io … toomanyrequests` → the product cleaned up right (R-649) but the event and the card say "
|
||
f"„Alkalmazás telepítése nem sikerült: Code-Server — exit code 1”; nothing says the download server is busy and a later try works. The same install went through later ({EV}code-server/`).",
|
||
"OPEN", "—", "Recognise `toomanyrequests` (and a pull timeout) in the deploy error and say „the app's download server is busy — try again in an hour”; a test with the stderr line", "CC"),
|
||
("F", "Backup & restore", "P3",
|
||
"**The restore refusal tells the household to „turn on remote backup, or connect a second drive” when the box has both and their first copy has not run yet.** Catalog sweep 2026-10-24, Grimmory and Calibre-Web: „Mentés most” makes the local copy only "
|
||
"(`07` §8, by design), so the drive files had no copy yet; the refusal's sentence is `restore.refuse.files.none` (`felhom-controller/controller/internal/web/handlers.go:1835`) whatever the box has. "
|
||
"The Hungarian sentence is frozen by `i18n_go_parity`, so the fix is a NEW key („the first copy is made in tonight's backup”) chosen when a tier is set up but holds nothing yet.",
|
||
"OPEN", "—", "The new key + the branch + a test", "CC"),
|
||
("G", "Storage & devices", "P4",
|
||
"**An English household sees the drive names in Hungarian** on the install page (and wherever a storage label shows): „Belső tárhely — 52.0 GB free (default)”, „Külső tárhely (hdd_1)” (catalog sweep 2026-10-24, "
|
||
f"{EV}navidrome/page-install-en.txt`). The label is stored once, in Hungarian, when the drive is found (`felhom-controller/controller/internal/settings/settings.go` `InferStorageLabel`); a household may rename it.",
|
||
"OPEN", "—", "Render a label that still equals the inferred default in the reader's language; a test per language", "CC"),
|
||
("H", "Storage & devices", "P4",
|
||
"**„Remove with data” leaves the app's empty parent folder** (`<drive>/appdata/<app>`) when the template binds a subfolder: Grimmory, RomM, Paperless-ngx (catalog sweep 2026-10-24; 4 KB each; the bound folders were removed). "
|
||
"Harmless (empty, not counted as old data), untidy for a household that browses the drive.",
|
||
"OPEN", "—", "Remove the parent when it is empty after the bound folders go; a test", "CC"),
|
||
("I", "Process & tooling", "P4",
|
||
"**The catalog's box fixtures drifted from the apps they test** — fixed only in the sweep's copy "
|
||
f"({EV}tools/upgrade_fixtures_box.py`, marked SWEEP COPY): OpenGist 1.15 signs up at `/-/register`; Calibre-Web's admin is the generated ADMIN_USER; Mealie's after_install replaces the factory password (the seed raced it); "
|
||
"Papra 26.6 needs an Origin header; Rallly 4.15 closed password sign-up (no fixture possible by password); Vaultwarden 1.37.4 answers 404 on `POST /identity/accounts/register` (the fixture was written on 1.36; the admin invite works, the invited sign-up has no route — the newer clients' register/finish flow);the read-back through a family gate needs a fresh cookie after the setup gate opens (Grimmory stayed inconclusive). "
|
||
"New fixtures for Homebox, Immich, Komga, Jellyfin, Ghost, Radarr, Sonarr in `tools/sweep_fixtures.py`; `box_walk.py` gained a `sweep` target.",
|
||
"OPEN", "—", "Port them into `app-catalog-felhom.eu/scripts/` with the bench tests", "CC"),
|
||
]
|
||
R739_NOTE = (" **2026-10-24 (catalog sweep):** seen again on a box without DNS — Wanderer unhealthy and 404 for this reason "
|
||
"(`audits/catalog-sweep-2026-10-24/wanderer/`).")
|
||
R1267_NOTE = (" **2026-10-24 (catalog sweep):** the same class for OnlyOffice and RomM (RomM's image volume `/romm` holds only the "
|
||
"empty mount points of its binds); after every catalog app had been removed WITH data, the sweep box still held 52 "
|
||
"such volumes (PrivateBin 21, OnlyOffice 8, RomM 8, Immich 3, and 12 from 12:50–12:52 before the batch, holding the same nginx folders as PrivateBin's) — remove-with-data "
|
||
"does not see them (no compose project label). All small (4–72 KB), no household data "
|
||
"(`audits/catalog-sweep-2026-10-24/_box/anon-volumes.txt`).")
|
||
SEV_ORDER = {"P1": 1, "P2": 2, "P3": 3, "P4": 4}
|
||
|
||
|
||
def main(apply):
|
||
text = open(OPEN).read()
|
||
ids = [int(x) for x in re.findall(r"\bR-(\d+)\b", text + open(CLOSED).read())]
|
||
nxt = max(ids) + 1
|
||
lines = text.split("\n")
|
||
idmap = {}
|
||
for letter, cat, sev, what, state, blocked, action, owner in ROWS:
|
||
rid = f"R-{nxt}"; nxt += 1
|
||
idmap[letter] = rid
|
||
row = f"| **{rid}** | {cat} | {sev} | {what} | {state} | {blocked} | {action} | {owner} |"
|
||
h = next(i for i, l in enumerate(lines) if re.match(rf"^## {re.escape(cat)} — \d+ rows", l))
|
||
end = next((i for i in range(h + 1, len(lines)) if lines[i].startswith("## ")), len(lines))
|
||
rows_at = [i for i in range(h, end) if lines[i].startswith("| **R-")]
|
||
pos = None
|
||
for i in rows_at:
|
||
s = lines[i].split(" | ")[2].strip()
|
||
if SEV_ORDER.get(s, 9) > SEV_ORDER[sev]:
|
||
pos = i; break
|
||
if pos is None:
|
||
pos = rows_at[-1] + 1 if rows_at else h + 4
|
||
lines.insert(pos, row)
|
||
# heading: total + per-sev counts, recomputed from the rows of THIS category in the section
|
||
m = re.match(r"^(## .+? — )(\d+) rows \((.*)\)$", lines[h])
|
||
counts = {}
|
||
for p in re.findall(r"(P\d) (\d+)", m.group(3)):
|
||
counts[p[0]] = int(p[1])
|
||
counts[sev] = counts.get(sev, 0) + 1
|
||
lines[h] = f"{m.group(1)}{int(m.group(2)) + 1} rows ({', '.join(f'{k} {counts[k]}' for k in sorted(counts))})"
|
||
for rid, note in (("R-739", R739_NOTE), ("R-1267", R1267_NOTE)):
|
||
i = next(i for i, l in enumerate(lines) if l.startswith(f"| **{rid}** |"))
|
||
cells = lines[i].split(" | ")
|
||
assert len(cells) == 8, (rid, len(cells))
|
||
if "catalog sweep" not in lines[i]:
|
||
cells[4] = cells[4] + note # the State cell, dated
|
||
lines[i] = " | ".join(cells)
|
||
print(json.dumps(idmap))
|
||
if apply:
|
||
open(OPEN, "w").write("\n".join(lines))
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main("--apply" in sys.argv)
|