Commit Graph

389 Commits

Author SHA1 Message Date
admin a5a729aead wishlist 512M (R-612) and uptime-kuma skips its DB wizard (R-613)
gates / gates (push) Successful in 1s
Both measured and red-proofed on 9202 through the product. No image moved.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-23 21:16:44 +02:00
admin 6db08a5eb3 test record: an image move must carry its proof (09 decision 13, part 4)
gates / gates (push) Successful in 1s
update_ladder: in .felhom.yml, one JSON entry per line (spiked live on
controller v0.266.0 and v0.267.0 first). Two gates: check-test-record.py
(static, CI too) and check-test-record-move.py (history + registry for
moved refs only). 16 decoys, 3 red-proofs. The ONLY writer is
upgrade-test.py --write-ladder (bench AND box proven, digests resolved).
Harness v3: box fixtures on the bench, files_may_change.
Backfill: the 21 moves of 2026-09-22, 21 proven from their records.
No image: line moved.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-23 20:52:32 +02:00
admin cfcfe52784 upgrade-test: watch memory after the readback (harness v2, R-635/R-462)
gates / gates (push) Successful in 2s
After an edge reads back, --soak seconds (default 600) of light load while
the kernel's own oom_kill counter is read host-side from the container's
cgroup. A kill or restart turns proven into failed; a peak over 80% of the
limit adds the memory_tight mark. New Romm fixture; edges M1 / M1old.

Red-proof on scratch 9202: M1old (template as promoted, 512M, 4 workers)
OOM-killed at +76 s -> failed. M1 (current, 768M, 2 workers) proven, 0
kills in 608.5 s, peak 81% -> memory_tight.

Test code only; no template changed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-23 08:54:53 +02:00
admin 02844ae0a5 decoys: eight cases for the probe target rule (R-630)
gates / gates (push) Successful in 2s
Suite 56 -> 64. Both faults re-introduced one at a time and refused; an explicit container no
service declares refused; a unique prefix still resolves; the name moving in a comment convicts
nothing; and the no-PyYAML mode CI runs is covered both ways.

The unique-prefix case first used paperless-ngx and was WRONG - paperless-webserver does not begin
with paperless-ngx, so the gate was right to convict. Rewritten with immich.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-22 20:30:01 +02:00
admin 405a127e7c probe target: explicit container for paperless-ngx and immich; ambiguity refused (R-630)
gates / gates (push) Successful in 1s
No image: line moved.

paperless-ngx has no container named after its stack, so its probe had NEVER run on any box.
immich has four immich-* containers and no exact match, so the old first-prefix rule picked
whichever came first - possibly the database.

The gate now resolves the target by the same four rules as findProbeContainerMeta: exact name,
explicit container, a UNIQUE prefix, else refuse - and refusing is right, because verifying waits
on this probe and a successful update of such an app gets stopped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-22 20:28:40 +02:00
admin dcb6b1db68 CHANGELOG: the six proven moves (R-462)
gates / gates (push) Successful in 1s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-22 20:17:14 +02:00
admin 8898b1d322 termix: 2.5.0 -> 2.8.0 (proven on 9202, R-462)
Walked through the product's own guarded Update on scratch guest 9202 during the
twenty-eight drill, seeded and read back through the app's own front door.
Evidence: felhom.eu/documentation/audits/the-28-2026-09-22/apps/termix/verdict.json
catalog_since -> 2026-09-22 (R-452).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-22 20:16:44 +02:00
admin 0b283d2bee sonarr: 4.0.19 -> 4.0.20 (proven on 9202, R-462)
Walked through the product's own guarded Update on scratch guest 9202 during the
twenty-eight drill, seeded and read back through the app's own front door.
Evidence: felhom.eu/documentation/audits/the-28-2026-09-22/apps/sonarr/verdict.json
catalog_since -> 2026-09-22 (R-452).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-22 20:16:43 +02:00
admin b7b04799be radarr: 6.3.0 -> 6.4.4 (proven on 9202, R-462)
Walked through the product's own guarded Update on scratch guest 9202 during the
twenty-eight drill, seeded and read back through the app's own front door.
Evidence: felhom.eu/documentation/audits/the-28-2026-09-22/apps/radarr/verdict.json
catalog_since -> 2026-09-22 (R-452).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-22 20:16:43 +02:00
admin 12c12707f2 immich: v3.0.3 -> v3.2.2 (proven on 9202, R-462)
Walked through the product's own guarded Update on scratch guest 9202 during the
twenty-eight drill, seeded and read back through the app's own front door.
Evidence: felhom.eu/documentation/audits/the-28-2026-09-22/apps/immich/verdict.json
catalog_since -> 2026-09-22 (R-452).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-22 20:16:42 +02:00
admin acbfafaad1 ghost: 6.53.0 -> 6.64.0 (proven on 9202, R-462)
Walked through the product's own guarded Update on scratch guest 9202 during the
twenty-eight drill, seeded and read back through the app's own front door.
Evidence: felhom.eu/documentation/audits/the-28-2026-09-22/apps/ghost/verdict.json
catalog_since -> 2026-09-22 (R-452).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-22 20:16:41 +02:00
admin 7a6797ba38 emby: 4.10.0.20 -> 4.11.0.1 (proven on 9202, R-462)
Walked through the product's own guarded Update on scratch guest 9202 during the
twenty-eight drill, seeded and read back through the app's own front door.
Evidence: felhom.eu/documentation/audits/the-28-2026-09-22/apps/emby/verdict.json
catalog_since -> 2026-09-22 (R-452).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-22 20:16:40 +02:00
admin f4eb94f195 romm: two web workers, not four - the actual cause (R-635)
gates / gates (push) Successful in 1s
WEB_SERVER_CONCURRENCY=2, limit left at 768M. No image: line moved.

768M was still a guess and it was wrong: kills slowed from ~12/min to ~7/min and stopped nothing.
Measured instead - each warm uvicorn worker holds ~216 MiB, so four plus the master reach ~882 MiB,
and the cgroup's memory.peak read exactly 768 MiB.

/init:143 runs --workers "${WEB_SERVER_CONCURRENCY:-4}". Four is a server default; this is one
household on one small box. Two measure ~450 MiB, fit 768M with headroom, and halve the CPU churn.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-22 17:55:01 +02:00
admin 886956dcc7 romm: 768M, because 5.3.0 does not fit in 512M (R-635)
gates / gates (push) Successful in 1s
No image: line moved, so no catalog_since moved.

Measured on demo-hp after this morning's promotion: OOMKilled true, 4530 gunicorn worker SIGKILLs
in six hours, ~500% CPU in a permanent restart storm, host load 5.2 while otherwise idle. It ran
clean for two hours first, which is why the walk on the scratch guest did not catch it.

The update reported `done` and the app read `running` the whole time - nginx answers 200 while the
workers behind it die. Nothing alarmed; the operator heard the fans.

768M is a measured first step, not a final answer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-22 17:46:24 +02:00
admin 1ad1f34b6e gates: probe-matches-compose runs in CI's no-PyYAML mode (R-618)
gates / gates (push) Successful in 2s
CI job 877 on 15d7c2b failed: the runner has no PyYAML and the gate answered INCONCLUSIVE, which
the runner rightly refuses to call a pass. A gate red on every push is bypassed within a week.

Falls back to a line reader and announces mode: DEGRADED. Over all 53 apps it returns exactly what
the full reader returns. Five more decoy cases with PyYAML shadowed out prove it still convicts the
three real faults; suite now 56 cases.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-22 11:02:53 +02:00
admin 15d7c2b7ed CHANGELOG: the fifteen proven catalog moves (R-462)
gates / gates (push) Failing after 2s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-22 11:00:34 +02:00
admin 39374d5576 nextcloud: database engine mariadb 11.6 -> 12.3 (proven on 9202, R-462)
The ONE engine move in this series, and it is deliberate. Walked through the product's own
guarded Update on scratch guest 9202 during the update night: 217.4 s end to end, seed read back
through nextcloud's own front door before AND after, healthy after.

Permitted since R-469 (Slice 4 shipped): MARIADB_AUTO_UPGRADE=1 is already in this template and
converts the datadir. check-engine-major.py was run against this exact commit and ALLOWS it by
name, rather than being assumed to.

catalog_since -> 2026-09-22 (R-452).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-22 11:00:20 +02:00
admin 22f598bb95 vikunja: 2.3.0 -> 2.6.0 (proven on 9202, R-462)
Walked through the product's own guarded Update on scratch guest 9202 during the
update night, seeded and read back through the app's own front door.
Evidence: felhom.eu/documentation/audits/update-night-2026-09-21/apps/vikunja/verdict.json
catalog_since -> 2026-09-22 (R-452).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-22 10:57:24 +02:00
admin c3807c7e7e tandoor: 2.6.13 -> 2.6.15 (proven on 9202, R-462)
Walked through the product's own guarded Update on scratch guest 9202 during the
update night, seeded and read back through the app's own front door.
Evidence: felhom.eu/documentation/audits/update-night-2026-09-21/apps/tandoor/verdict.json
catalog_since -> 2026-09-22 (R-452).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-22 10:57:23 +02:00
admin 15f9ebfb13 romm: 5.0.0 -> 5.3.0 (proven on 9202, R-462)
Walked through the product's own guarded Update on scratch guest 9202 during the
update night, seeded and read back through the app's own front door.
Evidence: felhom.eu/documentation/audits/update-night-2026-09-21/apps/romm/verdict.json
catalog_since -> 2026-09-22 (R-452).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-22 10:57:22 +02:00
admin f547f1646c privatebin: 2.0.5 -> 2.0.6 (proven on 9202, R-462)
Walked through the product's own guarded Update on scratch guest 9202 during the
update night, seeded and read back through the app's own front door.
Evidence: felhom.eu/documentation/audits/update-night-2026-09-21/apps/privatebin/verdict.json
catalog_since -> 2026-09-22 (R-452).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-22 10:57:21 +02:00
admin e96887eb5f papra: 26.6.1 -> 26.6.2 (proven on 9202, R-462)
Walked through the product's own guarded Update on scratch guest 9202 during the
update night, seeded and read back through the app's own front door.
Evidence: felhom.eu/documentation/audits/update-night-2026-09-21/apps/papra/verdict.json
catalog_since -> 2026-09-22 (R-452).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-22 10:57:21 +02:00
admin 7708a04dc9 navidrome: 0.63.2 -> 0.64.0 (proven on 9202, R-462)
Walked through the product's own guarded Update on scratch guest 9202 during the
update night, seeded and read back through the app's own front door.
Evidence: felhom.eu/documentation/audits/update-night-2026-09-21/apps/navidrome/verdict.json
catalog_since -> 2026-09-22 (R-452).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-22 10:57:20 +02:00
admin 4132e35cf1 n8n: 2.31.3 -> 2.40.5 (proven on 9202, R-462)
Walked through the product's own guarded Update on scratch guest 9202 during the
update night, seeded and read back through the app's own front door.
Evidence: felhom.eu/documentation/audits/update-night-2026-09-21/apps/n8n/verdict.json
catalog_since -> 2026-09-22 (R-452).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-22 10:57:19 +02:00
admin 008348bdec mealie: v3.20.1 -> v3.27.0 (proven on 9202, R-462)
Walked through the product's own guarded Update on scratch guest 9202 during the
update night, seeded and read back through the app's own front door.
Evidence: felhom.eu/documentation/audits/update-night-2026-09-21/apps/mealie/verdict.json
catalog_since -> 2026-09-22 (R-452).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-22 10:57:18 +02:00
admin 4405f120ee home-assistant: 2026.7.2 -> 2026.9.3 (proven on 9202, R-462)
Walked through the product's own guarded Update on scratch guest 9202 during the
update night, seeded and read back through the app's own front door.
Evidence: felhom.eu/documentation/audits/update-night-2026-09-21/apps/home-assistant/verdict.json
catalog_since -> 2026-09-22 (R-452).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-22 10:57:17 +02:00
admin 068f445587 grafana: 13.1.0 -> 13.2.2 (proven on 9202, R-462)
Walked through the product's own guarded Update on scratch guest 9202 during the
update night, seeded and read back through the app's own front door.
Evidence: felhom.eu/documentation/audits/update-night-2026-09-21/apps/grafana/verdict.json
catalog_since -> 2026-09-22 (R-452).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-22 10:57:16 +02:00
admin 6d8cd877cb docmost: 0.95.0 -> 0.96.0 (proven on 9202, R-462)
Walked through the product's own guarded Update on scratch guest 9202 during the
update night, seeded and read back through the app's own front door.
Evidence: felhom.eu/documentation/audits/update-night-2026-09-21/apps/docmost/verdict.json
catalog_since -> 2026-09-22 (R-452).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-22 10:57:15 +02:00
admin ac4828fe77 bookstack: 26.05.2 -> 26.05.5 (proven on 9202, R-462)
Walked through the product's own guarded Update on scratch guest 9202 during the
update night, seeded and read back through the app's own front door.
Evidence: felhom.eu/documentation/audits/update-night-2026-09-21/apps/bookstack/verdict.json
catalog_since -> 2026-09-22 (R-452).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-22 10:57:15 +02:00
admin 6525b8e78e audiobookshelf: 2.35.1 -> 2.36.1 (proven on 9202, R-462)
Walked through the product's own guarded Update on scratch guest 9202 during the
update night, seeded and read back through the app's own front door.
Evidence: felhom.eu/documentation/audits/update-night-2026-09-21/apps/audiobookshelf/verdict.json
catalog_since -> 2026-09-22 (R-452).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-22 10:57:14 +02:00
admin 20600326f6 actualbudget: 26.7.0 -> 26.9.0 (proven on 9202, R-462)
Walked through the product's own guarded Update on scratch guest 9202 during the
update night, seeded and read back through the app's own front door.
Evidence: felhom.eu/documentation/audits/update-night-2026-09-21/apps/actualbudget/verdict.json
catalog_since -> 2026-09-22 (R-452).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-22 10:57:13 +02:00
admin 6c690a1947 gates: refuse a health probe the app does not answer (R-618)
gates / gates (push) Failing after 2s
check-probe-matches-compose.py, a --fast gate so it bites in the hook and in CI.

The oracle was already in every template: the probed service's own compose healthcheck dials the
app on 127.0.0.1. The gate compares the .felhom.yml probe against it, statically.

Port mismatch REFUSES for every check type. Path mismatch REFUSES only where the probe can fail on
it (type api WITH expect) and WARNS otherwise, because probeHTTP calls any response healthy
otherwise - measured, not assumed. Six WARNs on the current catalog, each named in the CHANGELOG;
paperless-ngx is the loud one: no container matches the stack name, so no probe ever runs.

Four red-proofs and five decoys, suite now 51 cases. --root lets the suite judge its own clone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-22 10:51:34 +02:00
admin 793c4fba00 templates: three health probes dial where the app listens (R-618)
gates / gates (push) Successful in 1s
tandoor 8080->80, wger 80->8000, zipline /api/health->/api/healthcheck.

The probe dials <container-name>:<port><path> from inside the compose network, so the port is the
one the process LISTENS on. Each fix matches the port/path that the SAME service's own compose
healthcheck already dials on 127.0.0.1 — the oracle that was sitting in the file all along.

This is P1 and not cosmetic: the guarded update's `verifying` phase waits on this probe, so
`failAndHold` stopped a working app at the end of a SUCCESSFUL update. Measured on 9202 2026-09-21.

No image: line moved, so no catalog_since moves.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-22 10:50:01 +02:00
admin d4392e2a10 Vikunja fixture: the create verb is PUT, not POST (R-462)
gates / gates (push) Successful in 1s
Test code only. Vikunja creates a project with PUT /api/v1/projects; the fixture sent POST, which
answers 405 Method Not Allowed and reads like a broken app rather than a wrong verb. Corrected
box-side first, where the edge then walked clean (vikunja 2.3.0 -> 2.6.0, proven, 24.6 s); this is
the same correction in the ported copy.

Gates: catalog_gates.py --fast — all four OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-21 23:14:17 +02:00
admin 4463243f2e Upgrade harness: four fixtures and seven real upstream edges from the update night (R-462)
gates / gates (push) Successful in 1s
Test code only — no template changed and no image: line moved.

The update night walked real within-a-major upstream edges on scratch guest 9202 through the
product's own guarded Update, against a PRIVATE DRILL CATALOG; the live catalog was never
touched. This brings the expensive half of that work — the seed routes — back into the harness
so the same edges can be run here WITH their ABORT step, which the box deliberately does not
offer (09 6.1: whether the old image starts on migrated data is per-app and unpredictable).

- upgrade_fixtures.py: ActualBudget, Navidrome, AudiobookShelf, Vikunja. Each seeds through the
  app's OWN interface (R-156); each carries a negative control run on every verify(), so a
  readback that has broken into always succeeding fails instead of passing everything.
- upgrade-test.py: edges U1..U7, all real upstream moves existing 2026-09-21 that this catalog
  has NOT made, each holding its database engine constant.
- Limitations kept: Navidrome and AudiobookShelf seed the DATABASE half only, and say so.

OWED, stated so it is not mistaken for done: the U1..U7 harness RUNS, and with them the per-app
ABORT answers. The code is in; the runs are not.

Gates: catalog_gates.py --fast — image-pins, engine-major, catalog-since, copy-i18n all OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-21 21:00:50 +02:00
admin f5f6a152b5 REVERT both drill bumps: all four app pins back to their pre-drill images
gates / gates (push) Successful in 1s
Restores templates/{vikunja,uptime-kuma,wishlist,glance}/docker-compose.yml to exactly
their content at ff9717d379 — verified byte-identical for every image line.

catalog_since is 2026-09-21 on all four rather than the older pre-drill dates: the
catalog-since gate requires an image move to carry the day's date in EITHER direction,
and a revert is a move. The bump and its revert net to zero.

This clears the vikunja alpine:3.20 negative-control edge, which a background security
review correctly flagged as a supply-chain change. It was deliberate, it is the
documented C3-class control, no customer or demo box runs vikunja, and a deployed app
is frozen at its own pin since v0.235.0 — but the window is now closed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-21 14:54:58 +02:00
admin ae08a037fd DRILL 2: one real edge and one deliberately failing edge for the unattended-night spike
gates / gates (push) Successful in 1s
uptime-kuma 2.5.0 -> 2.5.1 : a real one-step edge (scenario F, must succeed)
vikunja 2.6.0 -> alpine:3.20 : a C3-class negative control (scenario G, must HOLD)

Both reverted in this same session. No customer box runs either app.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-21 14:41:39 +02:00
admin 573e41f59b DRILL: move four app pins for the power-cut update-arc measurement
gates / gates (push) Successful in 1s
vikunja 2.3.0 -> 2.6.0, uptime-kuma 2.4.0 -> 2.5.0,
wishlist v0.66.0 -> v0.67.0, glance v0.8.5 -> v0.8.6.
catalog_since set to 2026-09-21 on all four.

This is a measurement drill on the scratch guest 9202 (demo-hp) only.
REVERTED in the same session by the following REVERT commit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-21 14:25:24 +02:00
admin ff9717d379 REVERT the drill bump: uptime-kuma back to 2.4.0 (update-arc measurement finished)
gates / gates (push) Successful in 1s
Puts the catalog pin back where 5ff36d0 had it. The box is deliberately left running 2.5.0 for the
R-524 half of the measurement -- a box AHEAD of the catalog must read "Naprakesz"/"Up to date" and
its Update must be refused 409, not offered as a downgrade.

catalog_since stays 2026-09-21, NOT restored to 2026-07-18: the catalog-since gate requires
since >= the commit day of any commit that moves an image: line, and a revert is an image move.
Restoring the old date would fail the gate. So this file does not return byte-for-byte to 5ff36d0 --
the image: line does, the date does not, and that is the gate's rule, not a leftover.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-21 13:09:51 +02:00
admin 89304ab896 DRILL: uptime-kuma 2.4.0 -> 2.5.0 for the update-arc measurement (reverted in this session)
gates / gates (push) Successful in 1s
A temporary image move so a live box can be walked through the update arc on demo-hp LXC 9202
(audit update-arc-2026-09-21): the badge going to "Frissites elerheto - ma", a power cut mid-pull
(R-520), then the revert that leaves the box AHEAD of the catalog (R-524).

2.5.0 is the next REAL released upstream tag: 2.4.1 and 2.4.2 do not exist on Docker Hub
(docker manifest inspect, all three checked). catalog_since moves to today as the catalog-since
gate requires of any commit that moves an image: line.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-21 13:03:42 +02:00
admin bd22749e50 REPORT for the R-469 rule lift
gates / gates (push) Successful in 1s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-21 12:59:35 +02:00
admin 5ff36d098c R-469: lift the MariaDB half of the engine-major rule; add R-450's own-edge clause
gates / gates (push) Successful in 1s
Slice 4 shipped 2026-09-13, so the rule's own expiry condition is met — for MariaDB.
PostgreSQL and MySQL stay refused (R-463: no pg_upgrade, refuses to start on an
older major's datadir across eleven templates).

A MariaDB major is now allowed ONLY as its own edge: never in the same commit as
another image move in that template (R-450, the bookstack 0b73e5e shape).

Two new decoy cases; two red-proofs, each seen to fail. 40 cases green.
No template moved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-21 12:49:18 +02:00
admin 18a6d2d824 GATE FIX: the copy gate could not run in CI at all (R-595)
gates / gates (push) Successful in 1s
Six pushes in a row turned CI red while the local pre-push hook was green. The alarm
mail's own text says what that means and that it outranks the push it interrupted.

Cause, found by contrast rather than by reading a log: check-copy-i18n.py imports
PyYAML and is the ONLY gate in this repo importing anything outside the standard
library. The workflow's own header says the runner is "a host-mode container with
python3 and git and nothing else". The gate raised ImportError before checking
anything, so catalog_gates.py exited non-zero on every push, clean ones included.

The fix is a DEGRADED MODE, not a skip: without PyYAML the gate runs the check that
needs no parser and matters most — every frozen Hungarian string must still occur
verbatim in its app's bytes — and then prints in full what it did NOT check. Same
division catalog_gates.py already uses for engine-major on a shallow clone.

Measured before claimed: 1 030 of 1 032 frozen strings appear byte-for-byte in the raw
files; the two that do not are romm help_texts whose YAML escapes an inner double
quote, so the escaped spelling is accepted too. 1 032 of 1 032 found, so the degraded
check convicts nothing honest.

Five new decoy cases run with PyYAML shadowed by a module that refuses to import —
what CI actually executes. 38 cases in total.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-20 18:28:27 +02:00
admin de392cd7b8 REPORT: the catalog speaks English — all 53 apps, 1 031 of 1 032 strings
gates / gates (push) Failing after 1s
The per-app table, the unverified UI labels for the slice-6 walk, the judgement
calls recorded rather than hidden, the one string deliberately left in Hungarian and
why the ceiling's floor is 1 rather than 0, and the live proof: the English Apps list
shows zero Hungarian app descriptions across all 53.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-20 16:39:42 +02:00
admin 1f8065076b English copy — BATCH 3 of 3: the last 16 apps. THE CATALOG IS TRANSLATED.
gates / gates (push) Failing after 2s
actualbudget, claper, docmost, emby, gitea, immich, kimai, komga, onlyoffice,
opengist, plant-it, rallly, recipe-importer, seerr, vaultwarden, zipline — 306
strings. EN_MISSING_CEILING 307 -> 1.

1 031 of 1 032 strings now carry an English twin. The one that does not is papra's
AUTH_SECRET description, a Hungarian defect (R-593) left to fall back rather than
translated wrongly.

The gate convicted two of my own sentences and was half right: vaultwarden's invite
step and sign-up setting ended "can open an account", and the retrieval-promise
pattern reads "can ... open" as the claim that sealed backups can be opened. Opening
an ACCOUNT is not that claim, so the conviction was a false positive — but the
wording was also the weaker wording, so both now read "can sign up". The gate has no
way to REGISTER a legitimate occurrence, which the shared vocabulary's own design
calls for; filed as R-594.

No Hungarian byte moved in any of the three batches; the freeze gate proves it on all
53 apps on every push.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-20 16:34:47 +02:00
admin 5fe70d1f8a English copy — BATCH 2 of 3: 17 apps, 317 strings (R-560 slice 5)
gates / gates (push) Failing after 2s
audiobookshelf, code-server, crafty-controller, glance, gokapi, gramps-web,
jellyfin, mealie, navidrome, plex, sonarr, tandoor, termix, uptime-kuma, vikunja,
wger, wishlist. EN_MISSING_CEILING 624 -> 307.

Two lines needed judgement rather than translation, and both are written up in the
CHANGELOG so a later reader does not take them for slips. Jellyfin's setup step tells
the reader to pick Hungarian in the wizard — wrong advice for an English household,
so the English says "choose your language". Mealie's "Hungarian is available too"
becomes "English and Hungarian among them". Neither adds a promise the Hungarian does
not make; the Hungarian is untouched in both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-20 16:32:18 +02:00
admin 0695d8eeda English copy — BATCH 1 of 3: 17 apps, 319 strings (R-560 slice 5)
gates / gates (push) Failing after 2s
adventurelog, bentopdf, bookstack, calcom, calibre-web, ghost, grafana,
home-assistant, homebox, homepage, n8n, nextcloud, outline, papra, radarr,
sparkyfitness, wanderer. EN_MISSING_CEILING 943 -> 624.

No Hungarian byte moved; no image, pin, catalog_since or compose line changed.

The blocks are GENERATED from a flat {path: english} map rather than hand-written:
fifty nested blocks whose keys must match the Hungarian exactly is fifty chances to
mistype an env_var, and a mistyped key is INERT on the box rather than an error, so
nobody would learn. The generator builds from the same flat paths the freeze uses,
derived from the Hungarian file itself, so an invented key cannot be written.

One string is deliberately untranslated: papra's AUTH_SECRET description is a
Hungarian DEFECT (it describes a session-signing key as "the app's subdomain").
Translating it faithfully would ship the error in a second language; changing the
Hungarian is forbidden in a localisation release. It falls back, papra stands at
13/14, and the ceiling's floor is 1 until R-593 is fixed — stated in the ceiling's
own comment so a later batch does not "fix" it by editing Hungarian.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-20 16:29:37 +02:00
admin d9aa02a93f REPORT: the pilot's English quoted in full for the operator's read (R-560)
gates / gates (push) Failing after 2s
The three apps' English text, the per-app table, the unverified UI labels for the
slice-6 walk, the gate's five checks and the three defects its own decoys found in
it, and the live proof on both demo boxes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-20 14:44:02 +02:00
admin e81d41e527 English copy — the PILOT: privatebin, paperless-ngx, romm (R-560 slice 5)
gates / gates (push) Failing after 2s
89 of 1 032 strings. No Hungarian byte moved; no image, pin, catalog_since or
compose line changed. EN_MISSING_CEILING 1032 -> 943 in this commit.

Chosen for SHAPE: privatebin exercises the plain case (description, tagline, lists);
paperless-ngx adds select options, a placeholder and a customer-facing folder label;
romm carries the catalog's only optional_config block, whose group has no id of its
own and is matched by `match_group` — the Hungarian group name it translates. All
three run on the demo box, so the English pages can be fetched rather than reasoned
about.

Two gate defects fixed while translating, each found by its own decoy rather than by
reading: coverage was counted only for the apps NAMED on the command line, so
`check-copy-i18n.py privatebin` reported 47 more missing strings than the same tree
unscoped and either number could have been made to "pass"; and the ASCII-Hungarian
stems matched as bare substrings, so „ird be" convicted "the third best" and „angol"
convicted "Angola". Both now have their own case in the decoy suite.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-20 14:34:22 +02:00
admin 84e058463b GATE: copy-i18n — Hungarian frozen, English sound (R-560, slice 5)
`scripts/check-copy-i18n.py`, fifth row of `catalog_gates.py`, static and in the
pre-push hook. Five checks:

  1. FREEZE — every Hungarian copy string equals `copy_freeze/hu.json`. Runs on all
     53 apps whatever scope is named: a scoped push that quietly edits a neighbour is
     what a freeze is for. A NEW app must be admitted with `--add-app NAME --reason`.
  2. STRUCTURE — the `i18n.en` block may carry copy fields and nothing else; every
     key-matched entry (`env_var`, option `value`, `match_group`, `target`, `path`)
     must have a Hungarian twin, or it would be INERT on the box and the translator
     would never know. Lists must have the Hungarian's length — they are replaced
     whole, never merged by index.
  3. LANGUAGE — no accented Hungarian letter, no ASCII-ONLY Hungarian, no
     "please"/"kindly", no English retrieval promise the Hungarian does not make, the
     app name and „Felhom" preserved.
  4. CREDENTIALS — the login tokens inside `default_creds` and the initial-credentials
     note survive translation verbatim.
  5. RATCHET — `EN_MISSING_CEILING` (1032 today) convicts above AND below.

MEASURED, against the numbers the task carried: 1 032 copy strings, 832 of them with
a Hungarian letter (that half matches). The ASCII-only Hungarian is NOT three strings
(„Igen"/„Nem"/„Nincs" do not occur in this catalog at all) but roughly 120 — „Aldomain"
and „A szerver domain neve" alone are 53 each. An accent-only gate would have passed
every one of them inside an English block, which is why check 3 folds and stems.

18 decoy cases in `test_gate_decoys.py`, each seen to convict or to pass as intended
(R-421). One of them found a real hole while being written: the credential check
searched for the token as a substring, so „admin" matched "administrator" and a
rewritten login passed. It now requires word boundaries.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-20 14:26:07 +02:00