Files
felhom.eu/documentation/tests/VALIDATION-update-slice3-2026-09-06.md
T
admin 417df06f35
gates / gates (push) Successful in 17s
slice 3 docs: the ruling, the shipped mechanism, and four rows closed
09-update-architecture.md gains the fourth dated operator ruling (2026-09-06,
Option 1) and its section 5 is rewritten from a proposed shape into the shipped
one: the pin, the stored definition, the render table, the four writers, the
startup ordering, and the trap this slice set for slice 2 - the live compose file
is now the frozen one, so a badge comparing against it would answer Naprakesz on
exactly the apps that are behind.

02-controller-module-map.md said 'copy compose + .felhom.yml'. That stopped being
true today, so it is corrected, and the two sections describing the old seam now
carry a banner saying they describe v0.234.0 and below - kept because every box
under v0.235.0 still behaves that way and because they are the measured account
of why it changed.

R-447, R-441, R-438 and R-455 closed and compressed into CLOSED-ITEMS; R-458
opened for the .felhom.yml asymmetry, with what would settle it by measurement.

Live evidence: two real catalog pushes travelling the real 15-minute cycle, both
reverted, the tree byte-identical afterwards. The restart that used to take 18.3
seconds and pull a new image now takes 0.1 seconds and pulls nothing.
2026-09-06 10:37:33 +02:00

180 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
<span class="tag tag-warn" title="Újabb változat érhető el ehhez az alkalmazáshoz. A frissítés indításához nyomd meg a Frissítés gombot.">Frissítés elérhető — 56 napja</span>
```
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.