v0.233.0: record what each compose service actually installed, and badge whether it is current
gates / gates (push) Successful in 12s
gates / gates (push) Successful in 12s
Update arc slices 1 and 2. NEITHER CHANGES ANY BEHAVIOUR — no new endpoint, no auto-update, the three lifecycle buttons byte-identical. Slice 1 — app.yaml gains installed_images, keyed by compose SERVICE name, each entry carrying ref + repo digest + first-seen timestamp. Written by Manager.recordInstalledImages after a successful compose up from StartStack, RestartStack, UpdateStack and runComposeDeploy. Read from the CONTAINER, never from docker-compose.yml: the syncer overwrites a deployed app's compose on a 15-minute cycle and the two disagreed for 25 minutes in the spike's own measurement. A failed write NEVER refuses the action - the deliberate opposite of SetDesiredState, because this is an observation and that is an intent. Not called from StartStackServices (the R-47 DB-only window). Its own docker seam with a context and a 30s timeout, which neither existing exec helper has. Slice 2 — .felhom.yml gains optional catalog_since; web.updateBadge compares the recorded ref per service against what the current template pins and returns a *MetaBadge through the EXISTING meta_badge partial. No new markup, no new CSS. NO RECORD RENDERS NOTHING: absent means unknown and never means current. No version number reaches the customer and no registry is queried. Known limitation, filed not hidden: 23 catalog pins float, so those apps can read Naprakesz when the image behind the tag has moved. +17 tests (1707 -> 1724), 28 packages green. Wiring proven through a real RestartStack plus an AST walk of the four call sites. Three companion red-proofs run and reverted.
This commit is contained in:
@@ -467,6 +467,64 @@ through them: `EffectiveLifecycle()`, `CanInstall()`, `IsAbandoned()`.
|
||||
`lifecycleBadge` funcmap entry. R-56's difficulty labels are intended as a sibling funcmap
|
||||
function returning the same `*MetaBadge` — no new markup or CSS.
|
||||
|
||||
#### What is installed, and is it current? (v0.233.0 — update arc slices 1 & 2)
|
||||
|
||||
Two additions, and **neither changes how an update behaves**. Slice 1 is a record; slice 2 is a label.
|
||||
|
||||
**Slice 1 — `app.yaml` gains `installed_images`.** After every successful `compose up` from
|
||||
`StartStack`, `RestartStack`, `UpdateStack` and the deploy path, `Manager.recordInstalledImages`
|
||||
(`internal/stacks/installed.go`) reads what each container is ACTUALLY running and writes it down,
|
||||
**keyed by compose SERVICE name**:
|
||||
|
||||
```yaml
|
||||
installed_images:
|
||||
web:
|
||||
ref: lscr.io/linuxserver/bookstack:26.05.2
|
||||
digest: sha256:aaaa… # the only identifier that cannot move; "" if never pulled
|
||||
at: "2026-09-02T18:41:03Z" # when this ref+digest was FIRST seen for this service
|
||||
```
|
||||
|
||||
- **Read from the CONTAINER, never from `docker-compose.yml`.** That file is the value that has
|
||||
already moved: the catalog syncer overwrites a deployed app's compose file on a 15-minute cycle
|
||||
with no deployed check, and file and container can disagree indefinitely (measured live,
|
||||
`SPIKE-app-update-2026-09-01` §3). `Manager.checkLocalImages` is a line scan of that file and is
|
||||
deliberately NOT reused.
|
||||
- **A failed write NEVER refuses the action** — the deliberate opposite of `SetDesiredState`.
|
||||
`desired_state` is the customer's INTENT, so an act whose intent could not be recorded is refused;
|
||||
`installed_images` is an OBSERVATION, and refusing to start an app because a note could not be
|
||||
written would trade a real outage for a bookkeeping gap. Logged at ERROR and the app stays up.
|
||||
- **NOT called from `StartStackServices`** — that path starts only the database service for the R-47
|
||||
restore window, and a partial record would overwrite a complete one.
|
||||
- **Absent means UNKNOWN and never means current.** Every `app.yaml` predating v0.233.0 has no entry.
|
||||
- Its own docker seam (`Manager.installedExecFn`) carries a **context and a 30 s timeout**, which
|
||||
`composeExecCustomEnv`/`execCommand` do not — a bookkeeping read must not be able to wedge a
|
||||
lifecycle action.
|
||||
|
||||
**Slice 2 — one badge, and no version number.** `.felhom.yml` gains optional
|
||||
`catalog_since: "YYYY-MM-DD"` (`Metadata.CatalogSince` + `CatalogSinceAge`), the date the catalog last
|
||||
moved that app's pins. `web.updateBadge` compares the recorded reference for each service against what
|
||||
the current template pins and returns a `*MetaBadge` rendered by the existing `meta_badge` partial —
|
||||
**no new markup, no new CSS**, which is exactly what `metabadge.go`'s comment asks of its second user.
|
||||
|
||||
| state | badge |
|
||||
|---|---|
|
||||
| every service matches the template | „Naprakész" (`tag-ok`) |
|
||||
| any service differs, `catalog_since` usable | „Frissítés elérhető — N napja" (`tag-warn`) |
|
||||
| any service differs, `catalog_since` absent/malformed/future | „Frissítés elérhető" |
|
||||
| **no record, or the template cannot be read** | **nothing is rendered** |
|
||||
|
||||
- **No version string is shown to the customer anywhere** — operator ruling, 2026-09-02: a household
|
||||
cannot act on `26.05.2`, only on "you are behind, and by this long". Versions stay in the logs, the
|
||||
API and the hub.
|
||||
- **No registry is queried.** A customer's box must not need eight upstream registries to render a
|
||||
page. **Known limitation:** for the 23 floating pins (`postgres:16-alpine`, `mariadb:11.6`, …) the
|
||||
reference can be identical while the image behind it has moved, so those apps can read „Naprakész"
|
||||
when they may not be. Digest-level comparison needs a registry query and is deferred.
|
||||
- **Information only.** The badge is wired to no action; `Frissítés`/`Újraindítás`/`Leállítás` are
|
||||
byte-identical to before (`TestScenarioE_TheUpdateButtonIsUntouched`).
|
||||
|
||||
Reasoning and the seven-slice plan: `felhom.eu/documentation/architecture/09-update-architecture.md`.
|
||||
|
||||
#### App Info Pages
|
||||
|
||||
Each app can define rich metadata in `.felhom.yml`:
|
||||
|
||||
Reference in New Issue
Block a user