R-452 CLOSED: the catalog-since gate (hook-enforced); 09 §8.2 limitation lifted; STATUS note updated
gates / gates (push) Successful in 18s

This commit is contained in:
2026-09-13 19:42:42 +02:00
parent 5e8a82c3c4
commit 321770d9d6
6 changed files with 19 additions and 9 deletions
+5 -4
View File
@@ -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).
+4 -3
View File
@@ -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
@@ -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
@@ -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
+1
View File
@@ -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` |
-1
View File
@@ -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** |