# VALIDATION — update arc slice 3, live on demo-hp (2026-09-06) **Controller `0.235.0`, guest 9201 on `demo-hp` (Tier 0 — disposable).** Evidence copied off the box at the end of the phase that produced it. **Method: endpoint level plus a REAL CATALOG PUSH travelling the REAL 15-minute cycle.** There is no browser on DooPlex. Because v0.235.0 changes what the catalog SYNCER does, a hand-edited file on the box would have proved nothing — the same reasoning `SPIKE-app-update-2026-09-01` §3 used, and the same app: **`bentopdf`**, deployed on demo-hp only, file-based, no database and no volume, so no data anywhere could be touched. **Two catalog commits, both reverted in the same session**, and the catalog tree is byte-identical to `8220f8d` afterwards: `dc7e548` (non-image), `09b4ff5` (image), `1798ce6` + `17cc784` (the reverts). --- ## 0. Baseline, read before anything ``` ref = ghcr.io/alam00000/bentopdf:v2.8.6 digest = sha256:eaeea1e447205a79cb61d7efdc6966f37311dc1bc9c36a3a5c897bf79107c2c3 compose = image: ghcr.io/alam00000/bentopdf:v2.8.6 / interval: 30s controller before = 0.234.0 ``` ## 1. Adoption — all nine apps pinned, none guessed at ``` 07:46:50 [INFO] [stacks] pin bentopdf: bentopdf=ghcr.io/alam00000/bentopdf:v2.8.6 07:46:50 [INFO] [stacks] pin bookstack: bookstack=lscr.io/linuxserver/bookstack:26.05.2, bookstack-db=mariadb:12.3 07:46:50 [INFO] [stacks] pin docmost: docmost=docmost/docmost:0.95.0, docmost-postgres=postgres:16-alpine, docmost-redis=redis:7-alpine … 07:46:50 [INFO] [stacks] pin adoption: 9 pinned, 0 already pinned, 0 left unpinned (0 not completely observed, 0 running something the template no longer offers) ``` `applied-compose.yml` appeared in every stack dir (bentopdf: 1109 bytes). **Multi-service apps pinned per service** — `docmost` and `paperless-ngx` carry three each. ## 2. Scenario A — the catalog still offers the pinned version: **FIXES FLOW** Catalog commit `dc7e548`: healthcheck `interval: 30s → 45s`. **No image touched.** Left to travel the real cycle. ``` 08:01:51 [INFO] [sync] Updated bentopdf/docker-compose.yml 08:01:51 [INFO] [sync] Periodic sync: Sablonok frissítve — frissítve: bentopdf live compose : image: …bentopdf:v2.8.6 interval: 45s ← the fix arrived container : ghcr.io/alam00000/bentopdf:v2.8.6 started=2026-09-06T03:30:20Z ← untouched ``` **This is the half the operator explicitly chose to keep, and it is intact.** ## 3. Scenario B — the catalog moves: **THE APP FREEZES** Catalog commit `09b4ff5`: `v2.8.6 → v2.8.5`. Real cycle again. ``` 08:20:29 [INFO] [sync] Updated bentopdf/docker-compose.yml catalog cache: image: …bentopdf:v2.8.5 ← the catalog has moved live compose : image: …bentopdf:v2.8.6 ← the app has NOT pin : bentopdf: ghcr.io/alam00000/bentopdf:v2.8.6 ``` ### 3b. And a restart afterwards leaves the version alone — **the quantitative discriminator** `POST /api/stacks/bentopdf/restart` → `{"ok":true,…}` | | before v0.235.0 (spike §2, variant 1a) | **now** | |---|---|---| | elapsed | **18.3 s** | **0.1 s** | | image pulled? | **yes**, v2.8.5 appeared in the local store | **no** — `docker images` lists only `v2.8.6` | | container | recreated on the new image | **not even recreated**, `started=2026-09-06T03:30:20Z` unchanged | | digest | changed | `sha256:eaeea1e4…` — **identical to the baseline** | ``` 08:21:44 [INFO] [api] restart requested for stack: bentopdf 08:21:44 [INFO] [stacks] Stack bentopdf restarted successfully (took 0.1s) ``` **The 18.3 s → 0.1 s split is worth more than the tags:** the old path spent eighteen seconds because it was pulling a new image. There is nothing to pull now. ## 4. Scenario G — the badge still tells the truth **The trap this slice set for slice 2:** the live compose file now names the OLD version, so a badge comparing against it would answer „Naprakész" on exactly the apps that are behind. Quoted from the live page while frozen: ```html Frissítés elérhető — 56 napja ``` 56 is arithmetic on a real value: bentopdf's `catalog_since` is `2026-07-12`. ASCII fragments, `grep -oF`, with positive **and** negative controls: | fragment | `/apps/bentopdf` | `/stacks` | |---|---|---| | `Naprak` | **0** | **8** (the eight apps that are current) | | `napja` | **1** | **1** | | `BentoPDF` (positive control) | 4 | 1 | | `zzz-never-present` (**negative**) | **0** | **0** | | `Nem-karbantartott-XYZ` (**negative**) | **0** | **0** | The `update` button renders exactly once, unchanged. ## 5. Scenario D — the Update button still moves the version `POST /api/stacks/bentopdf/update` → `{"ok":true,…}`, 16.7 s wall clock. ``` 08:22:21 [INFO] [stacks] update bentopdf: pin advanced to the catalog's current definition (bentopdf=…:v2.8.5) 08:22:38 [INFO] [stacks] Stack bentopdf updated successfully (took 16.6s) container : ghcr.io/alam00000/bentopdf:v2.8.5 started=2026-09-06T08:22:38Z digest : sha256:2d867aacb8ab5b196d00ee86944b1899d09d72df355384c5e15cf974737963a0 live : image: …:v2.8.5 pin: …:v2.8.5 applied-compose: …:v2.8.5 ``` **The pin advanced at 08:22:21, seventeen seconds before the update completed** — i.e. BEFORE the pull, which is the ordering the design requires. The digest matches the one the spike recorded for v2.8.5 (`sha256:2d867aac…`), independently. ## 6. A REAL GAP THIS RUN FOUND, fixed before Scenario B was run **Scenario A passing is what exposed it.** The stored definition is written when the PIN is written, so the healthcheck fix delivered at 08:01:51 landed in the live compose file and **not** in `applied-compose.yml`. The first time the catalog then moved, the freeze rendered the *pre-fix* definition — **reverting every fix delivered since, silently undoing the half of the ruling that says fixes keep flowing.** Observed directly at 08:20:29: the frozen live file came back with `interval: 30s`, not the `45s` delivered nineteen minutes earlier. **Fixed the same session:** the equal-images branch now refreshes the stored definition as it delivers a fix. The images cannot move in that branch by construction, so no version moves and no intent is rewritten. `TestFixRefreshesTheStoredDefinition` pins both halves. **The 08:20:29 observation above was taken on the binary before that fix and is left in this document deliberately** — it is the evidence for why the fix exists. ## 6b. The freeze holds in BOTH directions — and the refreshed store proves itself The catalog was reverted to `v2.8.6` (commits `1798ce6`, `17cc784`) and left to travel the cycle. The app was by then pinned to **v2.8.5**, so the *older* catalog is now the one that has "moved" relative to the pin: ``` 08:35:29 [DEBUG] [sync] bentopdf/docker-compose.yml: hash match, skipped catalog : image: …:v2.8.6 interval: 30s live : image: …:v2.8.5 interval: 45s ← frozen, and BOTH fields come from the stored definition container: ghcr.io/alam00000/bentopdf:v2.8.5 ``` **The pin is what the customer HAS, not what is newest** — a downgrade in the catalog is refused exactly as an upgrade is. And the `interval: 45s` here is the §6 fix working: the applied definition written by the 08:22 update carried both the v2.8.5 image *and* the 45 s healthcheck, so the freeze reproduced the whole thing. `hash match, skipped` shows the content-compare still suppresses a pointless rewrite. ## 7. Teardown — back to the baseline, byte for byte `POST /api/stacks/bentopdf/update` once more, against the reverted catalog: ``` container : ghcr.io/alam00000/bentopdf:v2.8.6 digest : sha256:eaeea1e447205a79cb61d7efdc6966f37311dc1bc9c36a3a5c897bf79107c2c3 ← IDENTICAL to §0 live : image: …:v2.8.6 interval: 30s ← identical to §0 pin : …:v2.8.6 status : healthy ``` Fleet afterwards: **21 containers up, none unhealthy**; `/stacks` renders **„Naprakész" ×9** with `napja` at 0 and the negative control at 0. Controller `0.235.0`, healthy. Both catalog commits reverted; the catalog tree is byte-identical to `8220f8d`. **This run provisioned nothing** — no guest, no storage, no hub record — so there is no teardown to report on any of the three layers.