catalog gates: one entry point, mandated in CLAUDE.md (R-161 ruling)
scripts/catalog_gates.py runs all three gates - image-pins, image-resolvable, volume-persistence - and exits non-zero if any fails. Mandated in CLAUDE.md the way felhom.eu/scripts/site_gates.py is: run it after any template change, naming the app(s) you touched. Operator ruling, recorded because both alternatives were rejected for measured reasons. Controller-side enforcement at template load was rejected because such a check can only read the file, and a static audit of all 53 templates reports the catalog clean INCLUDING papra - it would pass on the exact defect it exists to catch; the property is decidable only at runtime. CI was rejected for now: neither repo has any, and there are no users yet. What was chosen copies the shape that demonstrably works here - of this project's gates, the only ones that ever get run are the ones with a single entry point named in a CLAUDE.md; site_gates.py is run, and R-29's three orphans are named nowhere and have stopped nothing. Behaviour: 0 all clean / 1 convicted / 2 UNDETERMINED, never a pass; a conviction outranks an undetermined result so the reader knows which they have. Gate output is streamed, not captured. App names scope the two gates that accept scoping; with no names the runtime gate deploys every template and belongs on a scratch host. Adding a fourth gate means one line in GATES. R-161 stays OPEN at reduced scope: this is convention, run by a person. Real automatic enforcement is owed when a second person touches templates. Verified: image-pins passes standalone (53 templates, 0 unpinned), the unknown-option path exits 2, and the aggregation was unit-checked over five gate-code combinations. The runtime leg was deliberately NOT executed - it deploys templates via docker compose and DooPlex is the recovery chain - so the runner's end-to-end invocation of that third gate is inferred, not measured, and is flagged in REPORT.md to be closed on a scratch host at the next campaign. REPORT.md overwritten per convention; the persistence sweep's report is preserved at audits/persistence-sweep-2026-08-02/ and pointed to from the new one.
This commit is contained in:
@@ -27,6 +27,20 @@ deployed `app.yaml` (customer secrets) is never overwritten. Full deploy details
|
||||
- Update `CHANGELOG.md` (newest on top) and overwrite `REPORT.md` with every pushed change.
|
||||
- No secrets in any committed file; secrets are generated at deploy time via `deploy_fields`
|
||||
`generate:` specs.
|
||||
- **Run `python3 scripts/catalog_gates.py <app>` after ANY template change** — it is the ONE entry
|
||||
point and runs all three gates below, exiting non-zero if any fails. Name the app(s) you touched
|
||||
and it scopes the two gates that accept scoping, which is fast; with no names the runtime gate
|
||||
deploys **every** template, so that form belongs **on a scratch host, never a customer box**.
|
||||
Exit: 0 all clean · 1 convicted · 2 UNDETERMINED, which is never a pass.
|
||||
**Why a runner and not four separate invocations** (operator ruling 2026-08-02, R-161): of this
|
||||
project's gates, the only ones that ever get run are the ones with a single entry point named in a
|
||||
CLAUDE.md — `felhom.eu/scripts/site_gates.py` is run, and R-29's three orphans are named nowhere and
|
||||
have stopped nothing. Controller-side enforcement was rejected because a check at template load can
|
||||
only read the file, and a static audit of all 53 templates reports the catalog clean **including
|
||||
papra** — it would pass on the exact defect it exists to catch. CI was rejected for now: neither
|
||||
repo has any, and there are no users yet. **R-161 stays open at reduced scope** — this is
|
||||
convention, run by a person; real automatic enforcement is owed when a second person touches
|
||||
templates.
|
||||
- **Never `:latest` or untagged images in templates** — pin a concrete version tag; an app deployed
|
||||
anywhere in the fleet is pinned to the digest it is currently running (a pin must never cause a
|
||||
version jump). Digest pins (`@sha256:`) also count. Gate: `python scripts/check-image-pins.py`
|
||||
|
||||
Reference in New Issue
Block a user