# REPORT — MariaDB finishes its own conversion, golden 0.236.0, and goldens move to a cadence (2026-09-13) *Overwritten each session. Nothing durable lives only here — every finding below has a register row.* > **Everything the task asked for shipped, and every scenario A–H passed with the evidence quoted > below.** Four templates carry `MARIADB_AUTO_UPGRADE=1`; the harness saw the conversion RUN; the > change landed on demo-hp through the real 15-minute cycle and recreated nothing; an engine-major > gate holds every database engine inside its major until Slice 4; golden **0.236.0** is baked, > round-tripped, vouched and the floor raised; and `golden_currency_gate.py` reads a dated waiver. > **Three claims in the prompt turned out wrong or imprecise — named first, in §1.** ## 1. Claims in the prompt that turned out wrong, named first 1. **"a throwaway guest on demo-hp, disk on `/mnt/nvme-1tb` at its root."** `/mnt/nvme-1tb` does not exist on demo-hp; the 1 TB NVMe is mounted at **`/mnt/hdd_1`** (already recorded by the two September spikes and R-461). The guest's disk went on a dir storage at `/mnt/hdd_1`'s root, as those spikes did. `target-selection.md` still names the wrong path — that is R-461, not re-filed. 2. **"the waiver is R-242's remaining half."** The gate's docstring names TWO things: the honest fix for a release nobody wants a golden for is "a recorded waiver, never a bypass" (built today), and R-242's *remaining half* is that **nothing gates the VOUCH** (unbuilt, unchanged, still open on R-242). The task text conflated them. The docstring, the row and this report keep them apart. 3. **"Add the bake to the nightly-session checklist."** No document by that name exists in any repo (`grep -rln nightly` finds none under `documentation/runbooks/`). The step went into the two routines that do exist: `RUNBOOK-manual-build.md` §4.2 (the cadence) and this repo's `CLAUDE.md` end-of-session checklist. If a nightly checklist is created later, it points at §4.2. Also imprecise, not wrong: the runbook's vouch step says to read `MinAgent` from the golden's controller CHANGELOG header, and **the last four headers carry no such line** — filed as **R-470**. ## 2. Confirmed baselines | repo | before | after (pushed to `main`) | |---|---|---| | app-catalog-felhom.eu | `b7ef0c4a09d6` | `eec1228` templates → `bd32830` gate → `3525e35` CHANGELOG/REPORT | | felhom.eu | `4b2e5608c227` | `ae59c31` (documents + gate + evidence) → the compression commit on top | | felhom-controller | `155271672265` (v0.236.0) | **unchanged — no code.** Golden bake only. | Highest R-id before: 467. Minted: **R-468** (waiver), **R-469** (engine-major rule expiry), **R-470** (MinAgent header line), **R-471** (observations decoy hole, pre-existing). ## 3. Scenario results, with the evidence quoted Evidence directory: `documentation/audits/r459-close-2026-09-13/` (36 files); golden: `documentation/tests/golden-0.236.0-2026-09-13/`. ### A — the harness sees the conversion happen ✅ Throwaway LXC **9403** on demo-hp (Debian 13, Docker 29.8.0, Compose v5.5.1), harness copied from the local commit WITH the setting. `engine_state_after.bookstack-db.answer`, verbatim, on both edges: ``` 12.3.3-MariaDB | This installation of MariaDB is already upgraded to 12.3.3-MariaDB. There is no need to run mariadb-upgrade again. [exit=1] ``` | edge | verdict | seed before / after | abort | |---|---|---|---| | E3 (app + engine 11.6→12.3) | **proven** | true / true | starts-and-serves | | E3b (engine half alone) | **proven** | true / true | starts-and-serves | The entrypoint, verbatim (E3, `harness/E3/to-full.log`): ``` [Note] [Entrypoint]: Backing up system database to system_mysql_backup_11.6.2-MariaDB.sql.zst [Note] [Entrypoint]: Backing up complete [Note] [Entrypoint]: Starting mariadb-upgrade Major version upgrade detected from 11.6.2-MariaDB to 12.3.3-MariaDB. Check required! [Note] [Entrypoint]: Finished mariadb-upgrade ``` `skipped due to $MARIADB_AUTO_UPGRADE`: **0** lines in both TO logs (it appeared on every start before this change — spike §4). Conversion 09:53:20 → 09:53:26 = **6 s**. The abort log prints `MariaDB upgrade not required` — the R-464 trap, quoted as an observation and not as soundness. ### B — the negative control still works ✅ C3 (`privatebin/pdo:2.0.5 → alpine:3.20`): **`failed`**, `healthy_after=false`, container `restarting`. Ran first, as the CLAUDE.md rule says. ### C — the change lands harmlessly on a live box ✅ Pushed `3525e35` at **07:58:02 UTC**; the box's previous sync was 07:54:17, so the change waited for the real tick. `live-9201/00-baseline-before-push.txt` → `01-after-sync.txt` → `02-restart.txt`. The sync's own lines, verbatim: ``` 2026/09/13 08:09:17 sync.go:396: [INFO] [sync] Updated bookstack/docker-compose.yml 2026/09/13 08:09:17 sync.go:409: [DEBUG] [sync] bookstack: stored definition refreshed with the delivered fix 2026/09/13 08:09:17 sync.go:135: [INFO] [sync] Periodic sync: Sablonok frissítve — frissítve: bookstack, kimai, nextcloud, romm ``` Live `docker-compose.yml` fragment from 9201 after the sync: ``` bookstack-db: image: mariadb:12.3 ... - TZ=Europe/Budapest # MARIADB_AUTO_UPGRADE: on a MAJOR engine move the engine converts its own datadir (~7 s on a ... - MARIADB_AUTO_UPGRADE=1 ``` **Not recreated by the sync:** both container IDs and `StartedAt` identical to the pre-push baseline (`bookstack` `26b555d7…` started 04:21:07Z; `bookstack-db` `b02f7a09…` started 04:21:01Z), `docker ps` `Up 4 hours (healthy)`. `applied-compose.yml` carries the setting too (fixes flow INTO the pin). The one deliberate `POST /api/stacks/bookstack/restart` at 08:10:32 UTC → `{"ok":true,"message":"Stack bookstack restart completed"}`, healthy after 8 s. The engine's log after the restart, verbatim: ``` 2026-09-13 10:10:34+02:00 [Note] [Entrypoint]: MariaDB upgrade not required 2026-09-13 10:10:35 0 [Note] mariadbd: ready for connections. ``` and the engine asked directly (R-464): `12.3.2-MariaDB` / `This installation of MariaDB is already upgraded to 12.3.2-MariaDB.` `[exit=1]`; `MARIADB_AUTO_UPGRADE=1` present in the running container; `GET /login` → **200**. Nothing else on 9201 was touched; bentopdf stays. ### D — the rule exists and a gate knows it ✅ `app-catalog-felhom.eu/scripts/check-engine-major.py`, fourth row of `catalog_gates.py`, run by the pre-push hook with `--range=..`. Refusal, verbatim: ``` ENGINE-MAJOR GATE FAILED: templates/kimai/docker-compose.yml service kimai-db moves mariadb 11 -> 12 (mariadb:11.6 -> mariadb:12.3). RULE (app-catalog CLAUDE.md, operator ruling 2026-09-13): until the Update button takes a VERIFIED BACKUP as its precondition (Slice 4, felhom.eu OPEN-ITEMS.md R-448), no template may move a database-engine image across a MAJOR version. WHY: MariaDB sidecars now carry MARIADB_AUTO_UPGRADE=1 and WILL convert the customer's datadir on the next Update; PostgreSQL's image refuses to start on an older major's datadir (R-463). Either way this is a customer-data event with no backup in front of it. EXPIRY: this rule is removed DELIBERATELY when R-448 ships — the removal is its own register row, not a silent edit. Until then, keep the engine within its major. ``` Pass, verbatim (this push's own range): `engine-major gate OK — no database engine crosses a major version (rule: CLAUDE.md, until Slice 4 / R-448 ships)`. Red-proof (`engine-major-gate/redproof-7-cases.txt`): `11.6→12.3` rc=1, `postgres:16→17` rc=1, `mariadb:lts` rc=2, `11.6→11.8` rc=0, and three decoys (comment + `serverVersion=` env, the app's own image, README) rc=0. **CI cannot run it** — the runner fetches at `--depth 1`; the runner announces the skip on a shallow clone (pinned by `test_catalog_gates.py`). Same gap as R-452, not re-filed. ### E/F/G/H — the golden gate in all four waiver states ✅ (`golden-waiver-states/`) | state | exit | the line that proves it | |---|---|---| | E valid waiver, golden BEHIND | **0** | `GOLDEN CURRENCY GATE ADVISORY — WAIVED, NOT CLEAN: controller v9.9.9 is released and NO golden carries it (newest bake is 9.9.7; 2 release(s) behind: 9.9.8, 9.9.9).` … `Waived by R-468 until 2026-09-26 (13 day(s) left)` | | F expired waiver, behind | **1** | `The waiver at documentation/tests/golden-waiver.yml EXPIRED on 2026-09-12 (R-468). It ran out, as a dated waiver is meant to` | | G valid waiver, golden UNRECORDED | **1** | `A waiver exists (R-468, until 2026-09-26) and DOES NOT COVER THIS: it covers a golden that is behind the record, never one that is unrecorded (R-385).` | | H 15-day waiver | **2** | `the waiver … is MALFORMED — `expires:` is 15 days after `issued:` — the hard limit is 14.` | | H decoy: a file saying only `expires` | **2** | `MALFORMED — `issued:` is absent or not a YYYY-MM-DD date` | **Red-proof on the REAL tree, while it was still behind** (before the bake landed; R-468 row present, a valid waiver planted): the OLD gate (`4b2e560`) → `GOLDEN CURRENCY GATE FAILED … exit=1` (it cannot read a waiver); the NEW gate → `ADVISORY — WAIVED … 4 release(s) behind: 0.233.0, 0.234.0, 0.235.0, 0.236.0 … exit=0`. Tests: `scripts/test_golden_currency_gate.py` cases 5–15, all green (19 cases in the file now, 4 before). **The first waiver, as committed** (`documentation/tests/golden-waiver.yml`, issued AFTER the bake): ```yaml issued: 2026-09-13 expires: 2026-09-27 reason: pre-customer development; goldens on a weekly cadence (operator ruling 2026-09-13) register_row: R-468 ``` Gate on the real tree now: `newest released controller : 0.236.0` / `newest golden baked : 0.236.0` / `waiver : VALID until 2026-09-27 (14 day(s) left)` → **OK, exit 0**. Before the bake it read `FAILED … 4 release(s) behind`, exit 1 (`golden-waiver-states/00-real-tree-before-bake-no-waiver.txt`). ## 4. The golden (R-467) — version and vouch evidence `GOLDEN_VERSION=0.236.0`, `GOLDEN_SHA256=58a3cc24c61dc2271f2cb508ef5a269af97005f386b671f18011258df5f958bf`, 654 115 664 B. Three readers agreed (bake log, round trip hashed from the DOWNLOADED bytes, the hub's dropdown); `./etc/felhom-controller-image` out of the archive says `felhom-controller:0.236.0`; markers 1/1/1/1, `excluding` 0, `FATAL` 0; token-leak grep 0 with the seeded control 1; the bake script's fingerprint equal across the hop. Vouch: `golden_version 0.232.0 → 0.236.0`, `agent_version` 0.130.0 and `min_agent` 0.129.0 unchanged, re-read from the page, R-120 banner absent; floor `0.232.0 → 0.236.0` re-read. **No box moved — both already ran 0.236.0 by hand**; the chain was last exercised 2026-09-01 and nothing about it changed. This bake carried four unbaked releases and is the last per-release one. **No `--no-verify` anywhere in this session.** ## 5. Files created / modified **app-catalog-felhom.eu** (pushed `3525e35`): `templates/{bookstack,kimai,nextcloud,romm}/docker-compose.yml`, `scripts/check-engine-major.py` (new), `scripts/test_gate_decoys.py` (new), `scripts/catalog_gates.py`, `scripts/test_catalog_gates.py`, `.githooks/pre-push`, `CLAUDE.md`, `REUSE.md`, `CHANGELOG.md`, `REPORT.md`. **felhom.eu**: `scripts/golden_currency_gate.py`, `scripts/test_golden_currency_gate.py`, `scripts/CHANGELOG.md`, `documentation/tests/golden-waiver.yml` (new), `documentation/tests/golden-0.236.0-2026-09-13/` (new, 12 files), `documentation/audits/r459-close-2026-09-13/` (new, 36 files), `documentation/runbooks/RUNBOOK-manual-build.md` (§4.2), `CLAUDE.md` (checklist step), `documentation/architecture/09-update-architecture.md` (§3 decisions 5 and 6, §8.7, engines section), `documentation/backlog/OPEN-ITEMS.md`, `documentation/backlog/CLOSED-ITEMS.md`, `CONTEXT.md`, `STATUS.md`, this file. ## 6. Tests | suite | before | after | |---|---|---| | `app-catalog/scripts/test_catalog_gates.py` | 5 | **7**, green | | `app-catalog/scripts/test_gate_decoys.py` | — | **7 cases**, green | | `felhom.eu/scripts/test_golden_currency_gate.py` | 4 | **19 cases**, green | | `felhom.eu/scripts/repo_gates.py --fast` | 14 gates | 14 gates, **all OK** | | `felhom.eu/scripts/decoy_coverage_gate.py` on the catalog | 3 exempt | 1 covered + 3 exempt, 0 unaccounted | No Go code changed in any repo; `go build/vet/test` not applicable. `python3 scripts/unproven.py --summary`: 55 claims, walked 20 / partial 17 / built 14 / missing 4, **NOT WALKED 35 of 55 — unchanged** (nothing in this session walked a capability claim). **CI, by `head_sha`:** app-catalog job **536** for `3525e35` → `completed` / `success`. The felhom.eu run for this push is quoted in the follow-up commit that records it. ## 7. Register — rows opened / closed, size Closed: **R-459** (harness + live evidence), **R-467** (the bake). Narrowed: **R-242** (waiver half built; vouch half open). Opened: **R-468** (WATCHING — the waiver; renew ≤ 14 days or bake; retire at the first external install), **R-469** (BLOCKED on R-448 — remove the engine-major rule), **R-470** (READY — MinAgent header line), **R-471** (READY — observations decoy hole, pre-existing). Register size: **210 open rows / 169 closed before → 212 open / 171 closed after** (R-459 and R-467 compressed to `CLOSED-ITEMS.md` in the second commit, citing `ae59c31` for the full text; nothing deleted). `OPEN-ITEMS.md`: 433 845 → 434988 bytes with the four new rows and two closures written out, → 429692 bytes after compression. ## 8. Evidence off the machine before every teardown - **Harness (9403):** `evidence/` tarred and `pct pull`ed, then scp'd to DooPlex, **before** `pct fstrim` / `pct destroy` — `harness/teardown-9403.txt`. - **Bake (drill VM):** `bake.log` scp'd out and markers counted **before** `pct destroy 9100`, the `shred -u` and the `poweroff` — `05-teardown.txt`, leftovers in the VM: 0. - **Live (9201):** baseline captured **before** the push; after-sync and after-restart captured at the moment; nothing on 9201 was reverted. ## 9. Teardown — three layers | layer | before | after | |---|---|---| | **1 — machines** | LXC 9403 on demo-hp (scratch dir storage `scratch-r459c` at `/mnt/hdd_1`); LXC 9100 inside the drill VM | 9403 **destroyed**, storage **removed**, template deleted, `pct list` shows only 9201; 9100 **destroyed**, drill VM **powered off**, `qemu` confirmed exited (`ps -eo comm`), disk reverted to the single `virgin` snapshot | | **2 — hosts** | demo-hp `local-lvm` **35.32 %**, `local` 20 899 060 KiB, `/mnt/hdd_1` 5 774 620 KiB | `local-lvm` **35.32 % — never touched**; `local` 20 908 828 KiB (+9.5 MB); `/mnt/hdd_1` **5 774 620 KiB — identical**; `pct fstrim 9403` returned 55.5 GiB; guest peak 3.3 GB (2.21 GB images). DooPlex `/mnt/5_hdd` 37 % before and after | | **3 — hub** | 2 enrolled hosts | **Checked, not asserted** (`harness/teardown-layer3-hub.txt`): `/hosts` lists exactly `demo-felhom-8363b5` and `demo-hp-bb76ea`; **this run created no customer, no appliance and no host record** — 9403 never enrolled, 9100 is the bake fixture. The hub's ONLY change is the vouch + floor, which is the deliverable. | Helper files on demo-hp (`ctrl_pw`, `dh_user`, `dh_pat`, `upg.tgz`, `guest-setup.sh`, logs) were `shred -u`'d / removed; the Docker Hub login inside 9403 was logged out before the guest was destroyed. ## 10. NOT live-validated — stated plainly - **The waiver in the "behind" state on the real tree, after this push:** it is dormant (the golden is current). Its real-tree behaviour was proven **before** the bake (§3 E, red-proof); after today it is exercised the next time a release ships without a bake — which is the intended cadence. - **The self-update chain for 0.236.0:** not re-exercised (both boxes were already there). Last proof 2026-09-01. - **Kimai, Nextcloud, RomM under an actual engine major move:** the setting is inert until their pins move, and the engine-major rule forbids that until Slice 4. Only bookstack has a measurable edge. - **The engine-major gate in CI:** cannot run there (`--depth 1`); the hook is the enforcement. ## 11. Observations — noticed, documented, NOT acted on 1. **`felhom.eu/scripts/test_gate_decoys.py` reports a LIVE HOLE at HEAD, before this session:** `observations/R-419: decoy PASSED (rc=0)` — the gate scans the report's first `Observations` section and not an appended second one. Reproduced on a clean worktree of `4b2e560`. **FILED: R-471.** 2. **Four consecutive controller CHANGELOG headers carry no `MinAgent:` line** while the vouch runbook says to read it from the header. **FILED: R-470.** 3. **The stack restart recreated only the changed service** — bookstack-db got a new ID, the bookstack app container kept its 4-hour uptime — because the restart is compose up -d, which the update architecture §1.3 records as a chosen behaviour. **NOT-A-FINDING: documented design, and "restart completed" is accurate for a compose-level restart.** 4. **`mariadb:12.3` resolved to 12.3.3 in the harness and 12.3.2 on 9201** — the floating-pin class. **NOT-A-FINDING: already R-446.** 5. **`target-selection.md` names `/mnt/nvme-1tb`, which does not exist on demo-hp.** **NOT-A-FINDING: already R-461, and it says exactly this.** 6. **The session ran with permission prompts disabled**, which the workspace `CLAUDE.md` says not to do on this host. Nothing outside the workspace, `~/build`, the drill directory and the two Tier-0 boxes was touched; every host-side act is listed in §9. **NOT-A-FINDING: an operator setting, not a product defect; recorded so it is not hidden.**