Design build done: R-469, R-638 closed; R-518/R-717/R-762 narrowed, R-528 stopped by its measurement; delivery + teardown evidence; STATUS, CONTEXT, REPORT (137 -> 137; 2 opened, 2 closed); section counts recomputed
gates / gates (push) Successful in 2m39s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-10-06 16:11:28 +02:00
parent 8db4425d98
commit 2441c86a89
8 changed files with 171 additions and 130 deletions
+91 -116
View File
@@ -1,147 +1,122 @@
# REPORT — instruction files kept true; vaultwarden on the ladder; the burn-down continued (2026-10-06 afternoon)
# REPORT — the design build: restore order, out-of-memory alarm, one stop per tier, the family window, wger's files (2026-10-06 evening)
| Part | Result |
|---|---|
| **A** — the instruction-file rule (R-891, R-469, sweep) | **done** — rule in all four copies of `unprompted-work.md` §5 and `PROMPT-TEMPLATE.md` §9 rule 9; R-891 fixed and closed; R-469 re-read: NOT removable, narrowed (an operator question); sweep: 26 factual edits in four repos (list below). No permission prompt or refusal came up. |
| **B** — vaultwarden on the update ladder (R-890) | **done** — the test-box admin seed built (catalog `6b4877d`, red-proved); 1.36.0-alpine → 1.37.4-alpine proven on bench 9401 and on 9202; written by `--write-ladder` (catalog `ecb8552`); R-890 closed |
| **C** — the burn-down | **3 closed** (R-644, R-763, R-764), **2 stopped with the reason written** (R-762 medium, R-717 needs a design) |
| **A** — housekeeping | **done** — decisions 151–156 recorded first; R-469 closed by ruling; the agent repo has the shared rule file (identical: `diff` against the controller's copy empty, one md5 across five copies); the Tester 1 walk route is NOT a 30-minute fix → R-892 |
| **B** — restore order (R-638) | **done** — measured first on 9202: both main paths SAFE (docmost, romm); two side paths fixed by order, red-proved; exposure 3 not fixable by order → known limit in `07` §6.3 + R-893; R-638 closed |
| **C** — out-of-memory alarm (R-528) | **STOPPED by its measurement** — Docker 29.8.2 set `OOMKilled` correctly in all four shapes; nothing built; a decision for you |
| **D** — one stop per backup tier (R-518) | **built and delivered, not shown live** — measured first (read-only): the off-site part of a stop is ~2 s; red-proved; the live button test was not possible on 9202 (no agent connection) and the demo boxes take deliveries only → R-518 open for the first night read-back |
| **E** — reopen a sign-up for the family window (R-717) | **done for wishlist, proven live; opengist stopped** (no tool in its container) → R-717 narrowed |
| **F** — wger's file server (R-762) | **done and proven on both venues; wger stays hidden** — R-762 narrowed to the `runserver` half it carries from R-755 |
| Rows before | Rows after | Opened | Closed |
|---|---|---|---|
| **142** | **137** | **0** | **5** (R-890, R-891, R-644, R-763, R-764) |
| **137** | **137** | **2** (R-892, R-893) | **2** (R-469, R-638) |
## Baselines and rulings
Verified at the start: felhom.eu `7d0dffcf34`, controller `36088fd82e` (v0.300.0), agent `cefdc731a4` (v0.149.0),
catalog `65130c6c03`; register 142. Read: every repo's `CLAUDE.md` and `.claude/rules/`, `REPORT.md`, R-890, R-891,
R-469. The two rulings were recorded first as `09` §3 decisions 149 and 150, with the reviewer's error recorded in 150.
Architecture read: `09-update-architecture.md` §3 (decisions 16, 35, 42, 146), §6.4 part 4 (the ladder writer), §6.5
(the drill catalog).
Verified at the start: felhom.eu `01d5dbd3e0`, controller `31242786af` (v0.300.0), agent `3e8ebeb96c` (v0.149.0), catalog
`ecb8552ee9`, register 137. Read: the three designs in `audits/night-burndown-2026-10-05/`, rows R-638, R-528, R-518, R-717,
R-762, R-469, `07` §6, `08` (the OOM rung), `09` §3 decisions 47/49. Rulings recorded first as `09` §3 151–156 (ruling 6 is
carried as A by the brief; the operator's chat answer was not visible to this session).
## Part A
## Part B — R-638
**The rule** — `.claude/rules/unprompted-work.md` §5 „Instruction files", the operator's text verbatim plus the
permission-check sentence; identical in felhom.eu, felhom-controller, app-catalog-felhom.eu and the workspace root's
unversioned copy (md5 `c1e6c881…` for all four). `documentation/PROMPT-TEMPLATE.md` §9, rule 9. felhom-agent has no copy
of the shared rule file (it never had one); adding one is a rule change, left for the operator.
**Slice 0, measurement only** (scratch 9202, controller 0.299.0, drill catalog; `audits/design-build-2026-10-06/B/`).
Install the OLD definition → seed → the guarded Update migrates (its backing-up phase makes the copy at the old version) →
the household's restore of that copy (`POST /backup/restore`, the only copy offered: "helyi").
**R-469 — not met.** R-463 closed (8 of 11 PostgreSQL apps converted by the box; zipline, adventurelog, immich stay on 16
by decision 42), but the engine gate now enforces decision 35: a PostgreSQL major passes only with a two-venue ladder
entry carrying `engine_conversion`. Removing it would let an unproven major ship, and the image refuses to start on the
old datadir. That is a loosened fence. Nothing is left for CC to build; the row asks the operator to close it (CC's pick)
or keep it.
| app | migration | after restore | `Imported DB dump` | seed | the copy's own dump (control) |
|---|---|---|---|---|---|
| docmost 0.95.0 → 0.96.0 (pg16) | 42 → 48 tables (+6 `oauth_*`, `public_spaces`, `siem_destinations`) | 42, none of the 6 | yes | read back | 42, equal |
| romm 5.0.0 → 5.3.0 (mariadb 11.4) | 27 → 39 (+12) | 27, none of the 12 | yes | read back | 27, equal (views counted) |
**Every instruction-file edit** (before → after, why). All factual; none loosens a rule.
Both went back to the old pinned version and ran. **Said plainly:** romm needed four runs — run 1 refused the install
(its data path must be a drive), run 2 hit an empty drill commit, run 3 measured correctly but my control read the wrong
file (a `pre-restore-*` dump) and missed three VIEWS; run 4 fixed both. docmost's run 1 installed the live version
because the box had not read the drill yet (the script then waited for the old reference).
felhom.eu
1. `CLAUDE.md` „Gates — ONE entry point": a list of ten gates + „`--fast` … today that is all of them" → the `GATES`
table is the list (nineteen gates); `--fast` skips the full-run-only gates and names them (today `iso-bootstrap`). Why:
`repo_gates.py` imported: 19 gates, `iso-bootstrap` not fast. **R-891.**
2. `CLAUDE.md` design pointer `architecture/01..05-*.md` → `01..11-*.md` (00 = the capability map). Why: 06–11 exist.
3. `.claude/rules/docs.md` „the locked design" `01..05` → `01..11`. Same reason.
4. `.claude/rules/website.md`: the list of what `site_gates.py` asserts gains „no embedded `<style>` blocks". Why: its
gate 7 (`site_gates.py` docstring).
5. `.claude/rules/unprompted-work.md`: §5 added; „Same wording lives in all three repos'" → names the three repos and the
workspace root. Why: four copies exist.
6. `documentation/runbooks/workspace-CLAUDE.md` (the workspace root `CLAUDE.md` links to it): standing rule 3 carried the
R-286 sentence group twice in a row; the second copy removed.
**Slices 1 and 2** (controller `9c94568`): `RestoreApp` starts only the database services, replays, then the app; a dump
with no identifiable database service is refused before anything is touched; a failed volume step skips the replay
(fallback and unit restore). Red: `B/red-slice1-fallback-order.txt` ("the FULL stack was already up when the replay fired
(calls before the replay: stop,start)"), `B/red-slice2-no-replay-after-volume-failure.txt`. **Exposure 3** (the off-site
rollback loads the newer pre-restore dump over the older volume; the older definition is not put back) cannot be fixed by
order — known limit in `07` §6.3, row R-893.
felhom-agent
7. `CLAUDE.md` gates: „`reuse_refs_check` and `instructions_gate` … today that is all of them" → every gate in `GATES`
(five: three shared, `published`, `release-complete`); `--fast` skips `published`. Why: `agent_gates.py:52-60`.
8. `CLAUDE.md` decoy paragraph: `scripts/decoy_coverage_gate.py`, `documentation/audits/AUDIT-gate-decoys-…` → with
`felhom.eu/`. Why: neither exists in this repo.
9. `.claude/rules/health-checks.md` (comment): „The single source is now felhom.eu/CLAUDE.md 'Code quality rules'" → the
copies are felhom.eu `hub.md` and the controller's `gates.md`. Why: that section holds no health-check rule.
## Part C — R-528, stopped
felhom-controller
10. `.claude/rules/gates.md`: `python3 controller/scripts/controller_gates.py` (from `controller/`) →
`python3 scripts/controller_gates.py` from `controller/` (or the long path from the repo root). Why: the old path does
not exist from `controller/`; the runner's usage line and CI use the new one.
11. `.claude/rules/gates.md`: the list of ten local gates → a pointer to `GATES` (fourteen local + three shared; the
advisory `golden-notice`). Why: `controller_gates.py:71-101`.
12. `.claude/rules/gates.md`: the shared list gains `observations_gate.py`. Why: `controller_gates.py:87`.
13. `.claude/rules/gates.md` (two lines): decoy gate and audit paths with `felhom.eu/`.
14. `CLAUDE.md` Commands table: the same command fix as 10.
15. `CLAUDE.md`: „Protected stacks (traefik, cloudflared, felhom-controller)" → „…, and always samba". Why:
`internal/config/config.go` `alwaysProtectedStacks`.
16. `CLAUDE.md` design pointer `architecture/01/02/03-*.md` → `architecture/NN-*.md` (01–03; 07, 09, 10).
17. `.claude/rules/unprompted-work.md`: as 5.
`C/C0-oom-signal-measure-9202.txt`: four runs in a throwaway alpine container under a cap. Runs 1–3: the kernel killed the
hog AND the main process (the box cannot protect it: `--oom-score-adj -1000` left pid 1 at 0) — exit 137,
`OOMKilled=true`. Run 4 (256 MB, `sleep infinity`, one 400 MB block): the container kept running, `oom_kill` 0 → 3,
`OOMKilled=true`, three `oom` events. The design's premise — the flag stays false while the counter rises — did not
reproduce on Docker 29.8.2 (the false flags of 2026-09-15 were on 29.8.0). By the brief's measure-first rule, nothing was
built. Cost read for the record: one exec read of demo-hp's 21 containers 1.6 s wall (`C/C1-…`). `08` records it.
app-catalog-felhom.eu
18. `CLAUDE.md`: „each holding exactly `docker-compose.yml` + `.felhom.yml`" → without „exactly", plus `steps/<StepKey>.yml`
for a superseded ladder step. Why: 20 templates have `steps/`.
19. „Only the two template files sync" → „Only the template files sync".
20. „60 checks in 10 groups" → 61. Why: `NEW-APP-CHECKLIST.md` row-count table.
21. „runs all four gates … scopes the two gates that accept scoping" → every gate in `GATES` (fourteen); scopes every gate
that accepts an app scope (eight). Why: `catalog_gates.py:88-131`.
22. „CI was rejected for now … R-161 stays open at reduced scope" → CI exists and re-runs `--fast`; R-161 closed 2026-10-05.
23. „`--fast` … is gate 1 and the engine-major gate … the other two" → every gate except image-resolvable and
volume-persistence.
24. The paragraph „There is no gate for this yet and that is a known gap" removed. Why: `check-catalog-since.py` is a
registered gate (R-452, closed); the same section already names it.
25. „The four MariaDB services (…)" → the five (grimmory added), noting the gate judges by glob; „The eleven PostgreSQL
services" → twelve, postgis and immich's image counted. Why: `templates/*/docker-compose.yml`.
26. Decoy gate and audit paths with `felhom.eu/`; `.claude/rules/unprompted-work.md` as 5.
## Part D — R-518
Sweep notes not acted on: word-for-word repeated paragraphs (R-421 in three CLAUDE.md files; „Presence is not success" in
the controller's `backup-paths.md`) — repetition, not a wrong fact.
**Measured first, read-only:** demo-felhom's night off-site job (2026-10-06): started 06:21:08, `snapshotted` 06:21:10,
its one app running at 06:21:18. demo-hp had no successful off-site run in 10 days. A new off-site run from 9202 was not
possible (it would write to ep0). **Built** (helper, then corrected by me): one window per tier, resume at that tier's
`snapshotted`, the next tier in a later cycle; the button makes the LOCAL copy only. **My correction:** the helper let the
button back up the off-site tier when the local storage is absent; the ruling says local only, so it now backs up
nothing and stops nothing (red: `D/red-press-no-local-backs-up-nothing.txt`); an agent that flags no tier primary keeps
the old first tier (the breaker test's fake agent showed that case). Red: `D/red-first-tier-resume.txt` (restarts
sampled at each poll `[0 0 0 0]`), `D/red-manual-local-only.txt`. Page text, hu + en: about 1–1.5 minutes — an estimate
from measured parts, not a measured press. **Not shown live** (see the Part table).
## Part B — R-890
## Part E — R-717
**Built** (catalog `6b4877d`): `box_admin_seed_allowed(w, app)` allows the admin seed on a box walk only when all five
hold — not the bench venue; `FELHOM_BOX_ADMIN_SEED=1`; the walk targets demo-hp guest 9202 (never 9201, a demo box's
household-shaped guest); THIS run installed the app (`box_walk.DEPLOYED_THIS_RUN`, set on the deploy's 202); the box's
`controller.yaml` names the drill catalog. The invite runs inside the box: the token is read from the container's
environment into a shell variable and handed to curl on stdin; the admin cookie is in a 0600 file that is shredded
(vaultwarden marks it Secure, so it is sent by hand over the container's plain-http address); only HTTP codes come back.
**The Tester 1 box is allowed by the ruling, but the walk has no route to it** (`box_walk.py` drives demo-hp guests
only) — written in the code, not built. Tests `BoxAdminSeedGuard` (6); **red-proof:** a guard returning `(True, "")`
fails three tests (`test_each_condition_alone_refuses`, `test_a_household_shaped_box_is_never_seeded`, and the bench's
`test_the_box_walk_never_signs_in_as_admin`).
Controller (`5b86569`): `after_setup` `open_command` / `open_success`; the window marks `opening` on disk first; a failed
open closes again and the page says so; the close runs at the window's end, at every start and after a successful
update; a failed close retries every 2 min. Six red-proofs in `E/`. Catalog (`a5a5b51`, pushed AFTER the controller
reached the three boxes): wishlist closes/opens `system_config.enableSignup` (group `global`) with Node's own sqlite
module, `min_controller: "0.301.0"`. **Live on 9202** (the 0.301.0 test image, `E/live.txt`): after setup `false`, a
stranger straight at the app 401 "invite only", users 1; window `true`, a family member 200, users 2; window end 13:47:34Z,
close 13:47:44Z, `false`, a stranger 401, users 2. **Opengist:** no sqlite tool, no script runtime, and its CLI has only
`create-user`, `reset-password`, `toggle-admin` — not doable with this mechanism.
**Proven:**
- Bench LXC 9401 (harness v5, 600 s memory watch): verdict `proven`; seed read back before and after; healthy in 30.9 s;
anon peak 16.3 % (cgroup 22.3 %); 0 kills, 0 restarts; no file changed.
- Box 9202 (drill catalog): self-registration 400; the in-box admin sign-in and invite → (200, session yes, 200);
invited registration 200; seed read back; guarded Update backing-up → pulling → copying → verifying → done in 12.3 s;
seed read back; badge „Frissítés elérhető — 80 napja" → „Naprakész"; removed through the product.
- `upgrade-test.py --write-ladder` from both verdicts → `vaultwarden/server:1.37.4-alpine`, `catalog_since` 2026-10-06,
the first ladder entry (catalog `ecb8552`); `check-image-resolvable.py vaultwarden` OK.
- **Who gets it:** no box reports vaultwarden today (hub DB copy with its WAL: last vaultwarden telemetry 2026-09-18,
demo-hp; control: the latest telemetry row of any app 13:51 today; the copy shredded). Read directly: demo-hp 9201 and
demo-felhom 9201 run no vaultwarden container.
## Part F — R-762
## Part C
Upstream's production compose read first (its `nginx` serves `/static/` and `/media/` from the shared volumes;
`prod.env` sets `DJANGO_DEBUG=False`). Here traefik sends only those two paths to a `wger-files` nginx
(`nginx:1.30.5-alpine`, 32 MB, stock config, volumes read-only) — every box gate wraps every router, so it is gated
like wger. The bench tool could not express a step that adds a service, so I added `--move-to` and
`--write-ladder --to-definition` (catalog `c48a0db`, red-proved). **Bench:** proven, wger anon peak 50.3 %, wger-files
15.9 %, the page's hashed CSS 200 from wger-files and 404 straight at wger. **9202:** the guarded Update done in 59.4 s,
the seed read back, a photo posted before the step 404 → 200 / 178 B / image/png after, CSS 404 → 200, wger-files
7.6 MiB. **Said plainly:** my box script crashed after the step when it decoded the PNG as text; the after-checks were
re-read binary-safe and the box verdict was written by hand from them (it says so). **Ready to show?** Files and sign-up
yes; it still runs Django's `runserver` (upstream's gunicorn runs 3 workers, which do not fit 384 MB) — not ready. Cost:
283 MB of collected static files in a named volume, in every wger backup (the checks refuse an unnamed volume).
- **R-644 closed:** no gokapi on 9202 (no container, no volume; control: the six running containers are listed).
- **R-763 closed** (fix from 2026-10-05, proven now): wger installed fresh on 9202; its own process reads
`ALLOW_REGISTRATION False`, `ALLOW_GUEST_USERS False`; straight at the app, a stranger's sign-up GET and a CSRF-valid
POST both redirect to the features page (control: the same CSRF token passes on the login form, 200); three anonymous
dashboard visits → login; users 1 before and after (2 → 4 before the fix).
- **R-764 closed** (proven now): mail off → healthy, 0 restarts, console backend; mail on (the toggle's injection) →
settings load, smtp backend, port 2526, TLS off. Not measured: a real reset mail (9202 has no app-mail).
- **R-762 stopped:** still 404 on wger 2.7; the image has no whitenoise and runs Django's `runserver`, so it needs a
second container for `/static` and `/media` — medium. Note added to the row.
- **R-717 stopped — needs a design:** the controller's `after_setup` command form runs only when the lock is set; the
household's 15-minute window cannot reopen a database switch. Note added to the row.
## Release and delivery
## Said plainly
Controller **v0.301.0** (`0b1b8d3`, image `sha256:0e80f6af…`), MinAgent 0.131.0. Golden **0.301.0**
(`GOLDEN_SHA256=96e94fed…`, Docker pinned to the approved set, registry round trip equal, token grep 0 with a working
control). Hub: vouched agent 0.149.0 / golden 0.301.0 / min_agent 0.131.0; floors 0.301.0 for demo-hp, demo-felhom,
tester-1 — first saved without a declared MinAgent (allowed: floor = golden), then re-saved with 0.131.0. demo-hp and
demo-felhom run 0.301.0 (healthy); tester-1's hub page "Controller elindult (0.301.0)". Global floor and Tester 2 not
touched. No agent release.
- **A merge conflict was committed into the DRILL catalog** (vaultwarden's `.felhom.yml`, by a `commit -am` after a
failed merge). Found at once by its conflict markers and fixed in the next drill commit; the drill was then reset to
live. The live catalog was never touched by it.
- My first wger user-count command had a quoting bug (it printed a Python NameError); the reads were repeated through a
file. The stranger checks were not affected.
## Instruction-file edits (decision 150)
- `.claude/rules/unprompted-work.md`, all five copies, line 8–9: before „…in `felhom.eu`, `felhom-controller` and
`app-catalog-felhom.eu` `.claude/rules/`, and in the workspace root's unversioned `.claude/rules/`; change all four or
none." → after: names `felhom-agent` too, „change all five or none". Why: decision 152 added the agent's copy.
- `felhom-agent/.claude/rules/unprompted-work.md`: new file (decision 152), identical to the other copies.
## Fixed without a row
- `OPEN-ITEMS.md` section headings: every count was stale (e.g. „Install & onboarding — 11 rows" over 5); recomputed.
## CI, last commit of every repo
catalog `ecb8552` → 1433 success (and `6b4877d` → 1430 success); agent `3e8ebeb` → 1431 success; controller `3124278`
→ 1432 success; felhom.eu `a29ee9dd` → 1434 (checked before the next push), and this commit (checked after the push).
controller `0b1b8d3` → 1439 success; catalog `cf1ed43` → 1438 success (`a5a5b51` checked after its push); agent
`de812bc` (checked after its push); felhom.eu `8db4425` → 1440 (checked), and this commit (checked after the push).
## Teardown
Machine: 9202 back on the live catalog (`controller.yaml` restored byte-identical), vaultwarden and wger removed through
the product (no container, no volume), the same six containers as at the start. Bench 9401: 0 containers, `.env` gone,
stopped. Drill: reset to live (`ecb8552` = `ecb8552`). Host: nothing else. Hub: nothing changed (one read-only DB copy,
shredded). Scratch secrets shredded at the end.
Machine: 9202 back on the live catalog (`controller.yaml` byte-identical), on the released 0.301.0 (was 0.299.0), every
test app removed through the product (no volume left), the test images removed by name, the same six containers as at
the start. Bench 9401 stopped. Drill VM: build guest destroyed, files shredded, off, `virgin`. Drill catalog = live.
Host: nothing else. Hub: the vouch and three floors; two read-only DB copies, deleted.