9.1 / 1.1 / 4.2 / 5.3 / 8.1 — the catalog gates for this app.

THE ONE ENTRY POINT is `python3 scripts/catalog_gates.py <app>`. It is run in TWO venues on purpose,
and both halves are below:

  * the WINDOWS checkout, with the repository's git history — every static gate, including the three
    that read the push range or a parent commit (engine-major, catalog-since, test-record-move) and
    copy-i18n, which needs the freeze;
  * BENCH 9401, which has Docker and a network — the two RUNTIME gates, `image-resolvable` and
    `volume-persistence`, which cannot run on this workstation at all. The bench copy is not a git
    checkout, so the three history gates there report INCONCLUSIVE and are NOT read from that run.

Together every gate in the runner's GATES table is green for this app.

===============================================================================================
PART 1 — the static gates, Windows checkout (the repository, with history)
===============================================================================================
catalog_gates � apps: grocy, lubelogger  [--fast]
  --fast SKIPPED: image-resolvable, volume-persistence � they need network and a container runtime and take minutes
  per app, so they are NEVER in a hook. They remain deliberate periodic runs: start
  of a catalog campaign, before a publish train, or when a template's volumes:/image
  changes. Run them with no --fast, on a scratch host.

==============================================================================
== gate: image-pins   (check-image-pins.py)
==============================================================================
image-pin gate OK � 60 templates, 0 unpinned images

==============================================================================
== gate: engine-major   (check-engine-major.py)
==============================================================================
engine-major gate � range origin/main..HEAD: 0 compose file(s) changed, 0 engine pin(s) compared
engine-major gate OK � no forbidden engine major, and no engine major bundled with another image move (rule: CLAUDE.md, amended by R-469 2026-09-21)

==============================================================================
== gate: catalog-since   (check-catalog-since.py)
==============================================================================
catalog-since gate � range origin/main..HEAD: 0 compose file(s) changed, 0 image move(s) dated
catalog-since gate OK � every image move in the range carries a catalog_since on or after its commit day

==============================================================================
== gate: copy-i18n   (check-copy-i18n.py grocy lubelogger)
==============================================================================
copy-i18n: matcher controls OK (positive �Jelentkezz be�" convicts, negative �Encrypted notes�" does not)
copy-i18n: retrieval-promise vocabulary matches the sibling clone
copy-i18n: 60 apps � 1199 Hungarian copy strings frozen � 1199 with English
copy-i18n: translated so far � actualbudget 15/15, adventurelog 18/18, audiobookshelf 21/21, bentopdf 14/14, bookstack 21/21, calcom 22/22, calibre-web 27/27, claper 20/20, code-server 20/20, crafty-controller 24/24, dawarich 21/21, docmost 19/19, emby 22/22, ghost 16/16, gitea 17/17, glance 15/15, gokapi 19/19, grafana 19/19, gramps-web 17/17, grimmory 24/24, grocy 20/20, home-assistant 18/18, homebox 17/17, homepage 15/15, immich 24/24, jellyfin 22/22, karakeep 20/20, kimai 20/20, komga 21/21, lubelogger 21/21, mealie 20/20, metube 19/19, n8n 17/17, navidrome 21/21, nextcloud 28/28, onlyoffice 24/24, opengist 16/16, outline 21/21, paperless-ngx 33/33, papra 16/16, plant-it 16/16, plex 25/25, privatebin 14/14, radarr 22/22, radicale 19/19, rallly 17/17, recipe-importer 19/19, romm 41/41, seerr 18/18, sonarr 22/22, sparkyfitness 21/21, tandoor 18/18, termix 12/12, uptime-kuma 16/16, vaultwarden 23/23, vikunja 18/18, wanderer 20/20, wger 20/20, wishlist 16/16, zipline 18/18
copy-i18n: OK

==============================================================================
== gate: probe-matches-compose   (check-probe-matches-compose.py grocy lubelogger)
==============================================================================
probe-matches-compose: 2 app(s), mode: full

1 WARNING(S) � not a conviction, and not a pass either:
  WARN  lubelogger: the compose healthcheck of 'lubelogger' dials no loopback URL or port (bash -c exec 3<>/dev/tcp/127.0.0.1/8080 && printf 'GET /Login/Index HTTP/1.0\r\n\r\n' >&3 ) � no oracle.

probe-matches-compose: OK � every probe dials the port (and, where it can fail, the path) that the app's own compose healthcheck dials

==============================================================================
== gate: probe-measured   (check-probe-measured.py grocy lubelogger)
==============================================================================
probe-measured: OK � 0 probes, each with a measured before/after

==============================================================================
== gate: test-record   (check-test-record.py grocy lubelogger)
==============================================================================
test-record gate � 2 template(s) read, 2 carry a ladder, 0 convicted

==============================================================================
== gate: test-record-move   (check-test-record-move.py)
==============================================================================
test-record-move gate � range origin/main..HEAD: 0 compose file(s) changed

==============================================================================
== gate: onboarding   (check-onboarding.py)
==============================================================================
onboarding gate — 61 checklist ids; 60 template dirs: 53 exempt (published before 2026-10-01), 7 new; 2 exempt app(s) with a record (shape-checked): sparkyfitness, wger
  NOT CHECKED here (sibling repository absent — the pre-push hook on DooPlex checks them): 3 path(s)
    gunicorn with 2 workers since 2026-10-08 (`WGER_USE_GUNICORN=True`, `WEB_CONCURRENCY=2`, R-762): 2 × „Booting worker" in the log; login page, CSS and a photo 200; 10-min watch 0 oom_kill, 0 restarts
    DJANGO_DEBUG=False` (R-762, 2026-10-06): `collectstatic` runs at start; the static files are served by `wger-files` (nginx)
    a progress photo posted to the gallery reads back through the front door: 200, 178 B, image/png (404 before the step)
onboarding gate OK

==============================================================================
== gate: family-gate   (check-family-gate.py)
==============================================================================
family-gate: newest baked golden 0.301.0 >= 0.287.0
family-gate gate OK: 2 family-gated template(s) ['grimmory', 'metube']

==============================================================================
== gate: mem-limit-sum   (check-mem-limit-sum.py grocy lubelogger)
==============================================================================
mem-limit-sum gate OK: 2 template(s), every mem_limit is the sum of its compose limits

==============================================================================
== gate: data-key   (check-data-key.py grocy lubelogger)
==============================================================================
data-key gate OK: 2 template(s), 0 data key(s), every flag agrees with the name rule and REGISTRY

==============================================================================
== summary
==============================================================================
  image-pins           OK            (exit 0)
  engine-major         OK            (exit 0)
  catalog-since        OK            (exit 0)
  copy-i18n            OK            (exit 0)
  probe-matches-compose OK            (exit 0)
  probe-measured       OK            (exit 0)
  test-record          OK            (exit 0)
  test-record-move     OK            (exit 0)
  onboarding           OK            (exit 0)
  family-gate          OK            (exit 0)
  mem-limit-sum        OK            (exit 0)
  data-key             OK            (exit 0)

all catalog gates OK

===============================================================================================
PART 2 — the two RUNTIME gates, bench LXC 9401 (Docker + network). `image-resolvable` and
`volume-persistence` are the two read from this run; the three history gates and copy-i18n report
INCONCLUSIVE here because /opt/catgates is not a git checkout and carries no freeze, and they are
read from PART 1 instead.
===============================================================================================
== gate: mem-limit-sum   (check-mem-limit-sum.py grocy lubelogger)
==============================================================================
mem-limit-sum gate OK: 2 template(s), every mem_limit is the sum of its compose limits

==============================================================================
== gate: data-key   (check-data-key.py grocy lubelogger)
==============================================================================
data-key gate OK: 2 template(s), 0 data key(s), every flag agrees with the name rule and REGISTRY

==============================================================================
== summary
==============================================================================
  image-pins           OK            (exit 0)
  image-resolvable     OK            (exit 0)
  volume-persistence   OK            (exit 0)
  engine-major         INCONCLUSIVE  (exit 2)
  catalog-since        INCONCLUSIVE  (exit 2)
  copy-i18n            INCONCLUSIVE  (exit 2)
  probe-matches-compose OK            (exit 0)
  probe-measured       OK            (exit 0)
  test-record          OK            (exit 0)
  test-record-move     INCONCLUSIVE  (exit 2)
  onboarding           FAILED        (exit 1)
  family-gate          OK            (exit 0)
  mem-limit-sum        OK            (exit 0)
  data-key             OK            (exit 0)

CONVICTED: onboarding
UNDETERMINED (it ran; some results could not be decided — never a pass): engine-major, catalog-since, copy-i18n, test-record-move
RUNNER EXIT=0

[exited with code 0]
