catalog: the lifecycle implementation itself (fixes the previous commit)

The previous commit landed only the new test/badge files: a 'git stash' used
to compare REUSE.md ref-check output silently dropped the staged index, so
every modification to an existing file was left behind and that commit does
not build. This adds the metadata field, the predicates, the fail-closed
deploy gate, the catalog filter, the funcmap entries, the template edits and
the docs that those tests exercise.
This commit is contained in:
2026-07-21 16:20:24 +02:00
parent ea0d3f1764
commit 5fdd2039fd
11 changed files with 213 additions and 5 deletions
+34
View File
@@ -248,6 +248,40 @@ Running apps on the Vezérlőpult now show a "Megnyitás ↗" button that opens
The optional `.felhom.yml` **`open_path`** field (`Metadata.OpenPath`) is appended to that URL for apps whose UI isn't at `/` — e.g. `gokapi``/admin`, `ghost``/ghost/`. Empty = bare root. Rendered via `.Meta.OpenPath` in `dashboard.html`, `deploy.html`, and `app_info.html` (the same field flows through all three open-link sites; no handler change needed). Must start with `/`; cosmetic only (does not affect routing).
#### App lifecycle — withdrawing an app without orphaning anyone (v0.158.0)
`.felhom.yml` carries an optional top-level **`lifecycle:`** (`Metadata.Lifecycle`), the catalog's
answer to "stop offering this app" that does not punish the customers already running it.
| value | offered for new installs? | shown to someone already running it |
|---|---|---|
| `available` (default; absent/empty ≡ this) | yes | nothing special |
| `hidden` | **no** | nothing — "we stopped offering this" is not their problem |
| `abandoned` | **no** | „Nem karbantartott" badge + a notice on the app page that updates and security fixes will no longer arrive |
**A deployed instance keeps full function in every state.** Lifecycle governs what is OFFERED, never
what runs — deleting a template instead would mark every deployed instance `Elavult` and offer a
Törlés button for working software.
Three predicates on `Metadata` are the single interpretation of the field — every surface goes
through them: `EffectiveLifecycle()`, `CanInstall()`, `IsAbandoned()`.
- **Listing** — `web.visibleCatalogStacks` drops a template that is not installable AND not deployed
here (`Deployed || Protected || CanInstall()`).
- **Deploy gate** — `api.deployStack` refuses server-side before any mutation with
„Ez az alkalmazás jelenleg nem telepíthető." (409). `stacks.DeployStack` repeats the check for any
caller that does not route through the API. Hiding the button is not a gate.
- **Unknown values fail OPEN** (→ `available` + one WARN), deliberately opposite to the gate's
fail-closed posture: a typo, or a state from a newer catalog than this controller, must never pull
a working app out of every customer's catalog. Both read the same `EffectiveLifecycle`, so they
cannot disagree.
- **Orphan detection must never see this field.** `getCatalogTemplateSlugs` keys on directory +
compose presence only; withdrawn templates stay in the catalog tree. Asserted by
`TestCatalogTemplateSlugs_IgnoresLifecycle` with a red-proof.
- **Badges are generic plumbing**: `web.MetaBadge` + the `meta_badge` template partial + the
`lifecycleBadge` funcmap entry. R-56's difficulty labels are intended as a sibling funcmap
function returning the same `*MetaBadge` — no new markup or CSS.
#### App Info Pages
Each app can define rich metadata in `.felhom.yml`: