From 321770d9d6ab6c3065c6fa0560c30f810b88d4c2 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Sun, 13 Sep 2026 19:42:42 +0200 Subject: [PATCH] =?UTF-8?q?R-452=20CLOSED:=20the=20catalog-since=20gate=20?= =?UTF-8?q?(hook-enforced);=2009=20=C2=A78.2=20limitation=20lifted;=20STAT?= =?UTF-8?q?US=20note=20updated?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- REPORT.md | 9 +++++---- STATUS.md | 7 ++++--- documentation/architecture/09-update-architecture.md | 2 +- documentation/audits/v0240-2026-09-13/rp-R452.txt | 8 ++++++++ documentation/backlog/CLOSED-ITEMS.md | 1 + documentation/backlog/OPEN-ITEMS.md | 1 - 6 files changed, 19 insertions(+), 9 deletions(-) create mode 100644 documentation/audits/v0240-2026-09-13/rp-R452.txt diff --git a/REPORT.md b/REPORT.md index 0e4b24b0..586f6934 100644 --- a/REPORT.md +++ b/REPORT.md @@ -20,10 +20,11 @@ rules cannot be invented byte-identically. The brief's own fences were applied i - `scripts/observations_gate.py` — R-471: every observations section is read (`scripts/CHANGELOG.md`). - `documentation/runbooks/target-selection.md` — R-461: `/mnt/hdd_1` is the NVMe; `drill-r50` exists on neither box (measured), fence kept with the fact beside it; R-93 carries the fact. -- `09-update-architecture.md` §6.1, `07-backup-architecture.md` §6, `CONTEXT.md` — v0.240.0 notes. -- Register: R-473, R-474, R-466, R-471, R-453, R-461, R-477, R-478, R-480, R-482, R-484, R-485, R-486 - closed and compressed; R-481, R-482..R-489 opened (R-482 closed the same night). 212 → 207 open, - 177 → 188 closed; 432 156 → 426 502 bytes open, 120 598 → 128 499 closed. +- `09-update-architecture.md` §6.1 + §8.2 (R-452 lifted), `07-backup-architecture.md` §6, `CONTEXT.md` — v0.240.0 notes. +- Catalog (`app-catalog-felhom.eu`): the `catalog-since` gate (R-452), see its REPORT. +- Register: R-452, R-473, R-474, R-466, R-471, R-453, R-461, R-477, R-478, R-480, R-482, R-484, R-485, R-486 + closed and compressed; R-481, R-482..R-489 opened (R-482 closed the same night). 212 → 206 open, + 177 → 189 closed (byte sizes: see the commit). - Evidence: `documentation/audits/nightly-2026-09-13-adventurelog/` (01–08), `audits/v0240-2026-09-13/` (release log, floor, validation, red-proofs incl. R-471). diff --git a/STATUS.md b/STATUS.md index 61670751..19e425cd 100644 --- a/STATUS.md +++ b/STATUS.md @@ -23,15 +23,16 @@ internet. Photo upload fails from any non-browser client. A glance fresh install **What I fixed and proved live.** Controller 0.240.0 (delivered by the floor in 16 s and 18 s): the forgotten copy, the PostGIS dump, the backup card, "delete backups" now deletes, the stale failure sentence, the slow off-site check, the old-install copy. Catalog: DEBUG off for AdventureLog; -glance now lands healthy on a fresh install. Docs: the observations gate reads every section; the -target-selection runbook names real paths. +glance now lands healthy on a fresh install. Docs and gates: the observations gate reads every section; the +target-selection runbook names real paths; the catalog now refuses an image move that forgets +its `catalog_since` date. **What I filed.** R-481 (scratch guest, your decision), R-483 (photo upload needs a browser check), R-487 (removed apps invisible on the backup pages), R-488 (a 5-minute test package), R-489 (the removal reports null over volumes it removed). **Register.** Before: 432 156 bytes open / 120 598 closed (212 / 177 rows). After: see the top of -`OPEN-ITEMS.md` — 207 open / 188 closed. +`OPEN-ITEMS.md` — 206 open / 189 closed. **Needs you.** (a) The rules file text — paste it and I create it in all three repos. **If you do nothing:** nights run on the brief's fences, as tonight. (b) R-481: how to make a scratch guest (a diff --git a/documentation/architecture/09-update-architecture.md b/documentation/architecture/09-update-architecture.md index ee7b2523..4ee14ed8 100644 --- a/documentation/architecture/09-update-architecture.md +++ b/documentation/architecture/09-update-architecture.md @@ -545,7 +545,7 @@ Version strings stay in the logs, the API and the hub. identical while the image behind it has moved. **Measured, not theorised:** spike §5 found `mariadb:11.4` and `mariadb:12.3` had both already moved upstream, with two fully-pinned controls holding. Digest-level comparison needs a registry query and is deferred — **R-446**. -2. **Nothing enforces `catalog_since`.** A commit that moves an `image:` line and forgets the date +2. **~~Nothing enforces `catalog_since`.~~ Enforced by the pre-push hook since 2026-09-13 (R-452, `app-catalog-felhom.eu/scripts/check-catalog-since.py`); CI's shallow clone still skips it out loud.** A commit that moves an `image:` line and forgets the date under-reports how far behind a box is. The gates runner fetches at `--depth 1` and has no parent commit to diff against, so the gate needs a deeper fetch — **R-452**. 3. ~~**The record only appears after the next lifecycle action.**~~ **CLOSED in v0.234.0, and the way diff --git a/documentation/audits/v0240-2026-09-13/rp-R452.txt b/documentation/audits/v0240-2026-09-13/rp-R452.txt new file mode 100644 index 00000000..b09e4dc4 --- /dev/null +++ b/documentation/audits/v0240-2026-09-13/rp-R452.txt @@ -0,0 +1,8 @@ +=== R-452 red-proof — the gate no longer compares catalog_since with the commit day === + ok FACT: kimai image moves, catalog_since set to a FUTURE year rc=1 (expected 1) + ok GENUINE: kimai image moves AND catalog_since = today rc=0 (expected 0) +FAIL: FACT: kimai image moves, catalog_since untouched: rc=0 expected 1; missing ['CATALOG-SINCE GATE FAILED', 'kimai', 'catalog_since is still'] +catalog-since gate OK — every image move in the range carries a catalog_since on or after its commit day +FAIL: DECOY: image moves; today's date lands in a COMMENT and README, the field stays: rc=0 expected 1; missing ['CATALOG-SINCE GATE FAILED'] +catalog-since gate OK — every image move in the range carries a catalog_since on or after its commit day +decoys-rc=1 diff --git a/documentation/backlog/CLOSED-ITEMS.md b/documentation/backlog/CLOSED-ITEMS.md index c2fbd2fc..fc33e6d0 100644 --- a/documentation/backlog/CLOSED-ITEMS.md +++ b/documentation/backlog/CLOSED-ITEMS.md @@ -277,3 +277,4 @@ Compressed here to title, shipping version, evidence, and the sentences that sta | **R-482** | **`adventurelog` ran Django with DEBUG=True on the public origin.** Closed in catalog `ed2c018`: `DEBUG=False` on the backend service. Proven live after the sync: the same CSRF failure renders the 300-byte production page, no debug text. `wger`, `tandoor`, `paperless-ngx` are recorded as unmeasured. `audits/v0240-2026-09-13/` (10-v0240-validation.txt; red-proofs rp-v240-*) | **CLOSED 2026-09-13 — PROVEN-LIVE** | full text: `git show 681c3d6:documentation/backlog/OPEN-ITEMS.md` | | **R-453** | **`~/.config/credentials` values are single-quoted, and a half-applied strip produced a confidently wrong "password is stale" verdict — twice.** Closed 2026-09-13: the instrument the row asked for exists — `felhom.eu/scripts/read_credential.py KEY <0600-file>` (`66156c6`, 2026-08-31) — and the whole 2026-09-13 night used it for every controller and hub password with zero quoting incidents; the memory `credentials-file-values-are-quoted` now names it. The instrumentation lesson (a discriminator that rules out one alternative does not rule in the rest) stays in the memory. | **CLOSED 2026-09-13 — INSTRUMENTED** | full text: `git show 681c3d6:documentation/backlog/OPEN-ITEMS.md` | | **R-461** | **`runbooks/target-selection.md` named a venue that does not exist and fenced a fixture that is gone.** Closed 2026-09-13, both halves checked against both boxes first: (a) no `/mnt/nvme-1tb` on demo-hp or demo-felhom; demo-hp's NVMe is `nvme0n1` at `/mnt/hdd_1` (demo-felhom's `/mnt/hdd_1` is `sdb`) — the runbook now names `/mnt/hdd_1` and says it is the same disk as the data drive; (b) `qm list` is empty on BOTH boxes — `drill-r50` (VM 300) exists nowhere; the fence text stays with the measured absence written beside it, and R-93 carries the fact. | **CLOSED 2026-09-13 — DOCUMENTED** | full text: `git show 681c3d6:documentation/backlog/OPEN-ITEMS.md` | +| **R-452** | **Nothing enforced `catalog_since`, so the badge's one number could silently under-report.** Closed 2026-09-13 (catalog): `scripts/check-catalog-since.py`, the fifth gate in `catalog_gates.py` — a `--range A..B` gate in the engine-major shape: an app whose per-service `image:` lines differ across the range must carry a `catalog_since` on or after the moving commit's day and not in the future; comments, README and CHANGELOG mentions are not the fact. Hook-enforced; the shallow CI clone skips it out loud (the CI-shape half the row named stays as is, by the same reasoning engine-major uses). Five decoy cases; red-proof: dropping the date comparison lets the untouched-date fact through (`audits/v0240-2026-09-13/rp-R452.txt`). | **CLOSED 2026-09-13 — GATED** | full text: `git show 681c3d6:documentation/backlog/OPEN-ITEMS.md` | diff --git a/documentation/backlog/OPEN-ITEMS.md b/documentation/backlog/OPEN-ITEMS.md index 485f6430..83affbd3 100644 --- a/documentation/backlog/OPEN-ITEMS.md +++ b/documentation/backlog/OPEN-ITEMS.md @@ -681,7 +681,6 @@ class (an image `VOLUME` at an unmounted path) is still live — `immich-server` | **R-446** | **[P2-MEDIUM] „Naprakész" can be FALSE, and the badge that says it cannot tell.** Slice 2 (controller v0.233.0, 2026-09-02) compares the RECORDED image reference per compose service against the reference the current template pins, and **queries no registry** — deliberately: a customer's box must not depend on reaching eight upstream registries to render a page (`felhom-controller/controller/internal/web/updatebadge.go`, `compareInstalledToTemplate`). **For the 23 floating pins that comparison is blind by construction:** `postgres:16-alpine`, `mariadb:11.6` and 21 others can carry an identical reference over an image that has moved. **MEASURED, not theorised — spike §5 found `mariadb:11.4` and `mariadb:12.3` had BOTH already moved upstream while two fully-pinned CONTROLS held.** So `romm` and `bookstack` on demo-hp would read „Naprakész" over a database engine build that is not the one the catalog now resolves to. **This is a KNOWN LIMITATION OF A SHIPPED FEATURE, filed the same session rather than left implicit**, and it is stated in the same words in `architecture/09-update-architecture.md` §8.1 and in the controller's `README.md`. The close is a digest comparison against the registry, which needs a network call, a cache and a failure posture — it is not a one-liner and it is not slice 2's job. **Depends on R-440**, whose fix (stop floating) would remove the problem instead of measuring it — take that route first if it is available. `architecture/09-update-architecture.md` | **OPEN — rank P2-MEDIUM; owner: CC** | | **R-450** | **[P2-MEDIUM] UPDATE ARC SLICE 6 — a version sequence: automatic WITHIN a major, never ACROSS one, and an engine change gets its OWN edge.** The first half is an operator ruling of 2026-09-02 and its justification is R-449's measurement: a cross-major jump can be refused by the app itself and cannot be undone. **The second half is a rule recorded now, while it is cheap:** an engine change must never be bundled with an app version bump. `bookstack`'s `0b73e5e` moved the application 25.02.2 → 26.05.2 **and** MariaDB 11.6 → 12.3 in one commit — **two migrations behind one edge**, and an unreadable failure when it breaks. Needs a catalog-side convention and, eventually, a gate. `architecture/09-update-architecture.md` §6 | **READY — rank P2-MEDIUM; owner: VIKTOR rules, CC implements** | | **R-451** | **[P3-LOW] UPDATE ARC SLICE 7 — a fleet sweep: the operator can SEE, and MOVE, how far behind every box is.** Slices 1 and 2 make one box's state visible on that box's own pages. The operator has no fleet view, and **it is not derivable from what is already reported: the hub's report payload carries container name, state, CPU and memory, and NO image field at all** (spike §5, which is why Peti's box could only be recorded UNKNOWN). So this is a hub-side change as well as a controller one. Rank LOW today because the fleet is two enrolled boxes; it rises with the fleet. `architecture/09-update-architecture.md` §6, §8.4 | **READY — rank P3-LOW; owner: CC** | -| **R-452** | **[P3-LOW] Nothing enforces `catalog_since`, so the one number the update badge shows can silently under-report.** `app-catalog-felhom.eu` `CLAUDE.md` now states the rule — any commit that changes an `image:` line must set that app's `catalog_since` to the same day — and all 53 apps were backfilled from git history on 2026-09-02 (`69761cf`). **A rule with no instrument is a wish; that is this project's most-repeated finding and this row exists so it is not repeated silently.** A stale `catalog_since` makes „Frissítés elérhető — N napja" under-report N, which is the single number the badge exists to give. **WHY IT WAS NOT BUILT IN THE SAME SESSION, stated rather than implied:** the gate would have to diff an `image:` line against the PARENT commit, and `catalog_gates.py` runs under a runner that fetches at `--depth 1` — there is no parent to diff against. The gate therefore needs a deeper fetch, which is a change to the CI shape and not to a script. **This is the R-421 class in advance: an enumerated gap becomes a row in the same session it is enumerated.** `architecture/09-update-architecture.md` §8.2 | **READY — rank P3-LOW; owner: CC** | | **R-454** | **[P3-LOW] Five `internal/web` test files have been `gofmt`-unclean for an unknown length of time, and nothing notices.** MEASURED 2026-09-02: `gofmt -l controller/internal/web/` reports `backups_split_test.go`, `claim_code_naming_test.go`, `disk_health_test.go`, `r400_debug_routes_test.go`, `recovery_test.go` — at the **baseline** commit `960d29b0612c`, i.e. not introduced by v0.233.0 (both files added that day are clean). **`go vet` does not check formatting and `controller_gates.py` has no formatting gate**, so the only thing that would ever surface this is someone running `gofmt -l` by hand, which is how it was found. **Not reformatted in the same session, deliberately** — the minimal-changes rule, and a five-file whitespace commit inside a feature release makes that release's diff unreadable. **Small, and the cost of NOT having the instrument is the row:** the count can only grow, and every future `gofmt -l` run produces noise that hides a real one. Fix is two lines: a `gofmt -l` gate in `controller_gates.py` plus one formatting commit, in that order (the gate first, so the commit is provably complete). Owner: **CC.** | **READY — rank P3-LOW; owner: CC** | | **R-456** | **[P3-LOW] A partly-dead stack is not a boot orphan, and that is written down nowhere.** MEASURED 2026-09-02 on demo-hp while validating v0.233.0: `docker rm -f bookstack` (leaving `bookstack-db` running) then a controller restart produced `Boot reconciliation: 1 boot-orphaned app(s) found: [bentopdf]` — **bookstack was NOT selected**, although the app container was gone and `desired_state: running` was recorded. Removing `bookstack-db` as well made the whole stack orphaned and the very next pass repaired it in 6.3 s. **So `bootrecon.isBootOrphan` requires the stack as a WHOLE to be down; one live member is enough to make it invisible to the reconciler.** **NOT called a defect, and the reason is part of the row:** `StateDegraded` IS in `IsDownState`, and the crash-loop/dead-app alarm path (`classifyRunStates`) does count a degraded stack as down — so the customer IS told; it is the automatic REPAIR that does not fire, and there may be a good reason (repairing half a stack while its DB is live is not obviously safe). **What is certain is that nobody has written the rule down**, so the next session re-derives it the same way this one did — by watching a reconciliation not happen, which is an absent observable and the weakest possible evidence. Either state the rule in `02-controller-module-map.md` with a test pinning it, or change it. Owner: **CC.** `tests/VALIDATION-update-slice12-2026-09-02.md` §2.2 | **READY — rank P3-LOW; owner: CC** | | **R-457** | **[P3-LOW] A test that hardcodes a date AND asserts an age derived from it is green on the day it is written and red the next morning — one instance PROVEN, six candidate files named.** MEASURED 2026-09-03: `TestGroupD_BadgeRendersOnBothSurfaces` (shipped the previous day in v0.233.0) pinned a fixture `catalog_since: "2026-07-18"` and asserted the rendered string `"Frissítés elérhető — 46 napja"`. **The pure badge tests inject a clock; the RENDER test does not and cannot** — it goes through the production templates, which call the funcmap entry `updateBadge`, which reads `time.Now()`. The suite was green on 2026-09-02 and **FAILED on 2026-09-03** with *"the behind badge is missing"* on both surfaces, because the true answer had become 47. **Fixed by DERIVING the fixture** — `catalog_since` is computed as *today minus 46 days*, so the test asserts the real number through the real clock and cannot rot. **THE CLASS, which is why this is a row and not just a fix:** a clock-reading test that also carries a date LITERAL is a bomb with a fuse of unknown length, and the suite being green is not evidence it is defused — it is evidence the fuse has not burned down yet. **NAMED AS UNCHECKED CANDIDATES, NOT ACCUSED** — six other test files contain both a `20xx-xx-xx` literal and `time.Now()`: `internal/backup/offbox_test.go`, `internal/web/handler_export_upload_test.go`, `internal/web/r103_tier2_action_test.go`, `internal/web/dashboard_backup_card_test.go`, `internal/web/async_restore_test.go`, `internal/stacks/installed_test.go`. Mixing the two is not itself a defect — it is one only where a literal feeds an assertion evaluated against the real clock — so each needs reading, which is a sweep and not this session. **The instrument that would end the class:** run the suite once under a faked future date in CI and see what turns red. Owner: **CC.** `felhom-controller` v0.234.0 CHANGELOG | **READY — rank P3-LOW; owner: CC** |