v0.235.0: freeze the version, keep the fixes flowing (operator ruling 2026-09-06)
gates / gates (push) Successful in 12s

Slice 3. R-447 was BLOCKED because R-438 established that RestartStack's use of
up -d to pick up template changes was CHOSEN and written down in its own comment.
The operator ruled Option 1, and this implements it.

The rule: while the catalog offers the same version you run, its fixes flow to
you; the moment it moves to a newer version you are frozen until you update.

NOTHING was added to any of the thirteen compose up -d call sites. Most of them
are repairs - the boot reconciler, the drive-return gate, the app-stop guard -
and a repair path that refuses to repair leaves a customer's app down, which is
worse than the problem. They are made safe by removing the reason.

app.yaml gains pinned_images: what the app is SUPPOSED to run. It is NOT
installed_images, which is an observation; letting a reading become a deployment
is the R-166 category error one field over. Four writers, each also storing the
exact definition as applied-compose.yml. UpdateStack advances the pin and
re-renders BEFORE the pull, because pull and up -d act on the file on disk, and a
pin set afterwards would pull the frozen version and report success.

The syncer renders instead of copying, through one nil-safe seam. Catalog images
equal the pin -> verbatim, so fixes and self-healing both survive; they differ ->
the WHOLE stored definition, never a substitution of refs into a newer template
(wger 2.6 needs a DB config the older template cannot supply). This is
deliberately not 'skip deployed apps', which was option B and was rejected.

AdoptPins runs once at boot after the backfill, files only, and skips loudly
rather than inventing a pin. syncer.Start() moved to after it: the initial sync
would otherwise run while every app was unpinned and overwrite a deployed app's
version once per boot.

THE BADGE HAD TO CHANGE OR SLICE 2 WOULD HAVE INVERTED SILENTLY. TemplateImages
reads the LIVE compose file, which is now the frozen one, so the comparison would
have answered Naprakesz on exactly the apps that are behind - with every test
green, because the new field has the same type. It now reads CatalogImages.

+16 tests (1729 -> 1745), 28 packages green. Three red-proofs run and reverted.
A test also caught the syncer writing an empty compose file over a live app.
This commit is contained in:
2026-09-06 09:45:34 +02:00
parent 998aa31958
commit 8a0e0a59ad
13 changed files with 1437 additions and 17 deletions
+23
View File
@@ -532,6 +532,29 @@ the current template pins and returns a `*MetaBadge` rendered by the existing `m
Reasoning and the seven-slice plan: `felhom.eu/documentation/architecture/09-update-architecture.md`.
#### Freeze the version, keep the fixes flowing (v0.235.0 — update arc slice 3)
**Operator ruling, 2026-09-06.** An app's version is frozen to what the customer has; only a
deliberate Update moves it. Everything else in a template — health checks, memory limits, new deploy
fields — still arrives on the 15-minute cycle, and a broken definition still repairs itself.
- **`app.yaml` gains `pinned_images`** (service → ref): what the app is SUPPOSED to run. **Not**
`installed_images`, which is an observation. Written only by the deploy path, `UpdateStack`, a
restore, and the one-time `AdoptPins`. **Absent = unpinned = pre-v0.235.0 behaviour.**
- **`applied-compose.yml`** in the stack dir stores the exact definition the pin came from. The
syncer copies only `docker-compose.yml` and `.felhom.yml`, so that name is safe.
- **`Syncer` renders instead of copying**, via the nil-safe `SetRenderPlanFn` seam. Catalog images
equal the pin → copy verbatim (fixes flow, self-healing works). They differ → write the stored
definition, **whole** — never a substitution of refs into a newer template (`wger 2.6`).
- **`.felhom.yml` always flows**, even to a frozen app: it holds no image and carries `catalog_since`.
Known limitation, R-458.
- **Nothing was added to the thirteen `compose up -d` call sites.** Most are repairs; a repair that
refuses to repair leaves an app down.
- **The badge reads `Stack.CatalogImages`, never `TemplateImages`.** After the freeze the live compose
file is the frozen one, so comparing against it would answer „Naprakész" on apps that are behind.
Reasoning: `felhom.eu/documentation/architecture/09-update-architecture.md` §3, §5.
#### App Info Pages
Each app can define rich metadata in `.felhom.yml`: