42bf40bd2c
gates / gates (push) Successful in 29s
- Twelve steps published on both venues (catalog): first ladders for calibre-web, gitea, wger, crafty-controller, uptime-kuma, zipline (two steps); within-major emby, ghost, home-assistant, outline, rallly. immich's step v3.0.3 -> v3.2.2 re-proven at 768M (Part D). - Part C: the night leg skips a digest-only change AND the catalog never records a same-tag re-test, so a same-name upstream fix reaches no box. Row R-740; the decision in STATUS; `09` decision 30 carries a dated note (the decision itself unchanged). - Rows: 369 -> 377. Opened R-735..R-742; closed R-735, R-738, R-742; narrowed R-462, R-624, R-446, R-440, R-734, R-732. The currency audit gains §1b (and corrects its 31+1 to 30+2). Evidence: documentation/audits/more-night-apps-2026-09-30/. Report: REPORT-more-night-apps-2026-09-30.md. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
414 lines
32 KiB
Markdown
414 lines
32 KiB
Markdown
# Catalog currency — measured 2026-09-30
|
||
|
||
Catalog `app-catalog-felhom.eu` main = `64461979250b` (clean tree). Controller source read at
|
||
`felhom-controller` main = `d48da6c`. Read-only. Measured from DooPlex against the public registries
|
||
with anonymous tokens. No box was touched. Nothing was pulled. Nothing was committed.
|
||
|
||
Method: the 2026-09-21 method (`UPDATE-ARC-STATE-2026-09-21.md` §3, `update-arc-2026-09-21/00-drift.py`),
|
||
adapted in `catalog-currency-2026-09-30/00-currency.py`. What changed is written at the top of that script.
|
||
|
||
## 1. Headline numbers
|
||
|
||
**53 apps · 79 `image:` lines · 67 distinct pins.** 53 lines are exact releases, 24 are floating lines
|
||
(12 distinct), 1 is Felhom's own image, 1 could not be measured (plant-it: the repository is gone).
|
||
|
||
| question | apps |
|
||
|---|---|
|
||
| **behind upstream inside the same major** (any image) | **25 of 53** |
|
||
| **behind upstream across a major** (any image) | **19 of 53** |
|
||
| behind in either way | 37 of 53 |
|
||
| — inside the major, exact pins only | 24 |
|
||
| — across a major, exact pins only | 8 (gitea, gokapi, gramps-web, homepage, jellyfin, nextcloud, paperless-ngx, sparkyfitness) |
|
||
| — across a major, only because a floating engine line has a newer major | 14 (postgres → 18, redis → 8, mariadb → 13.0, postgis → 18, claper 2.5 → 3.0) |
|
||
|
||
**Can update itself at night today:**
|
||
|
||
| number | apps | what it counts |
|
||
|---|---|---|
|
||
| **(a)** a box at the pin just before the catalog's head gets a step tonight | **28**, plus **1** conditional (nextcloud) | the controller's own rule, catalog side (§4) |
|
||
| **(b)** the catalog's ladder has ≥1 `proven` entry | **29** | a looser count |
|
||
| never, today | **24** | no `update_ladder:` at all → skip reason `no_test_record` |
|
||
|
||
(a) and (b) differ by one app only. Every entry in all 29 ladders is `proven`, and no entry has
|
||
`needs_person`. The one difference is nextcloud: its newest step has `files_may_change`, so the leg
|
||
takes it only when a fresh whole copy of the app is on the box.
|
||
|
||
**What (a) does NOT say.** It says the catalog offers a step. It does not say how many boxes are at a
|
||
pin that matches. Nobody measured the fleet's pins today. A box installed from today's catalog is at
|
||
the head and has nothing to do. And a night step only goes to the catalog's head, which is itself
|
||
behind upstream for 37 apps.
|
||
|
||
### 1a. After the same day's moves (added by the session, 2026-09-30 afternoon)
|
||
|
||
The tables below are the MORNING measurement (catalog `6446197`). The session then moved five apps on both venues:
|
||
rallly, outline and sparkyfitness to PostgreSQL 18 (their first ladder steps), bookstack 26.05.5 → 26.09.1 and kimai
|
||
apache-2.57.0 → 2.67.0 (+ the Part F apps named in `pg-last-six-2026-09-30/F/`). Re-running `04-analyse.py` on the
|
||
afternoon catalog with the same registry data: **night (a) 31 + nextcloud conditional; (b) 32 carry a proven step; 21
|
||
have no ladder.** The "behind upstream" counts are not re-measured; bookstack and kimai are now at their newest release
|
||
inside the major.
|
||
|
||
### 1b. After the evening's moves (added 2026-09-30 evening, `more-night-apps-2026-09-30/`)
|
||
|
||
**Correction first:** the numbers above were counted before the immich step of the late afternoon; that step carries
|
||
`files_may_change` (R-734), so at `4c552cf` — the evening session's start — `04-analyse.py` reads **night (a) 30 + 2
|
||
conditional (nextcloud, immich); (b) 32**, not 31 + 1. The evening published first ladders for calibre-web, gitea, wger,
|
||
crafty-controller, uptime-kuma and zipline, and within-major steps for emby, ghost, home-assistant, outline, rallly
|
||
(and zipline's second). Re-run of `04-analyse.py` on the evening catalog, same registry data (`more-night-apps-2026-09-30/count/`):
|
||
**night (a) 35 + 3 conditional (nextcloud, immich, calibre-web); (b) 38 carry a proven step; 15 have no ladder.**
|
||
The "behind upstream" counts are not re-measured; each of the eleven moved apps is now at the newest release inside its
|
||
major (re-checked the same evening, `more-night-apps-2026-09-30/00-upstream-recheck.txt`).
|
||
|
||
## 2. What could not be measured — stated, not guessed
|
||
|
||
1. **plant-it** (`msdeluise/plant-it:0.10.0`): Docker Hub gives 401 on the tag list, and its catalog
|
||
API gives **404** for the repository. Same finding as 2026-09-21. The app is `lifecycle: abandoned`.
|
||
2. **recipe-importer**: Felhom's own image (`gitea.dooplex.hu`), not upstream. Not compared.
|
||
3. **Upload dates**: not measured for any image. ghcr's anonymous API has no timestamp. Not needed
|
||
for this question, so not attempted.
|
||
4. **Whether the newest tag is a GA release.** The filter is by tag name only (rc, beta, alpha, dev,
|
||
nightly, …). Two results need a person to check: `mariadb:13.0` (a two-part line tag exists; GA not
|
||
checked) and `gitea/gitea:28.0.0` (pushed to Docker Hub 2026-09-30 00:15 UTC, the same day; a
|
||
version renumbering, not checked).
|
||
5. **Tag shape switches.** The rule compares only tags of the same shape. A control pass
|
||
(`09-shape-switch-check.txt`) looked for higher versions under ANY shape for every pin called
|
||
"up to date". It found three real switches, applied by hand with their tag lists
|
||
(`10-shape-switch-detail.txt`):
|
||
- **gramps-web** `v25.6.0`: upstream dropped the `v`. It is at `26.9.1`. Calendar versions, so 25→26
|
||
is counted as across a major.
|
||
- **jellyfin** `10.11.11`: upstream 12 uses two-part tags. It is at `12.1` (12.0 had rc1..rc7).
|
||
- **kimai** `apache-2.57.0`: upstream stopped the `apache-` tags. Plain `2.67.0` exists. Whether it
|
||
is the same Apache variant is NOT checked.
|
||
Two more hits were noise: sonarr (`5.14-2.0.0…-ls5`, a develop-style tag) and tandoor (a
|
||
`dependabot-…` branch tag). The control ran only on pins called up to date, and not on floating
|
||
lines. A switch hiding behind a pin already called "behind" would change only its "newest" value.
|
||
6. **Floating pins — did the tag move?** 18 of 24 floating lines resolve to **exactly the digest
|
||
the ladder recorded when the step was tested**. None has moved since its test. 6 cannot be compared,
|
||
because the app has no ladder: outline (postgres 16, redis 7), rallly (postgres 16), sparkyfitness
|
||
(postgres 15), zipline (postgres 16), wger (`2.6`).
|
||
7. **Majors for 0.x and calendar versions.** "Major" is the first number of the tag, as written. For
|
||
`v0.x` apps a minor step can be breaking. For calendar versions (bookstack 26.05, home-assistant
|
||
2026.9, actualbudget 26.9, papra 26.6) the first number is a year. This table does not interpret it.
|
||
8. **ghcr rate limit.** immich-server and immich-machine-learning got HTTP 429 on the first run. They
|
||
were measured on a slower retry (`02-retry-429-run.txt`). Nothing is left at 429.
|
||
|
||
## 3. Per image
|
||
|
||
"Floating (line)" = the tag names a version LINE, not a release: fewer than three numbers in its
|
||
first version run (`12.3`, `16-alpine`, `16-3.5-alpine`, immich's `16-vectorchord…`). For a floating
|
||
line, "newest same major" is the newest line of the same shape in that major. "Newest at all" is the
|
||
newest line of that shape.
|
||
|
||
| app | service | pinned | newest same major | newest at all | behind in major | behind across major | note |
|
||
|---|---|---|---|---|---|---|---|
|
||
| actualbudget | actualbudget | `26.9.0` | `26.9.0` | `26.9.0` | no | no | |
|
||
| adventurelog | adventurelog | `v0.13.0` | `v0.13.0` | `v0.13.0` | no | no | |
|
||
| adventurelog | adventurelog-postgres | `16-3.5-alpine` | `16-3.5-alpine` | `18-3.6-alpine` | no | yes | floating (line); digest = ladder's tested digest |
|
||
| adventurelog | adventurelog-frontend | `v0.13.0` | `v0.13.0` | `v0.13.0` | no | no | |
|
||
| audiobookshelf | audiobookshelf | `2.36.1` | `2.37.1` | `2.37.1` | yes | no | |
|
||
| bentopdf | bentopdf | `v2.8.6` | `v2.8.8` | `v2.8.8` | yes | no | |
|
||
| bookstack | bookstack | `26.05.5` | `26.09.1` | `26.09.1` | yes | no | |
|
||
| bookstack | bookstack-db | `12.3` | `12.3` | `13.0` | no | yes | floating (line); digest = ladder's tested digest |
|
||
| calcom | calcom | `v6.2.0` | `v6.2.0` | `v6.2.0` | no | no | |
|
||
| calcom | calcom-postgres | `18-alpine` | `18-alpine` | `18-alpine` | no | no | floating (line); digest = ladder's tested digest |
|
||
| calibre-web | calibre-web | `v4.0.6` | `v4.0.8` | `v4.0.8` | yes | no | |
|
||
| claper | claper | `2.5` | `2.5` | `3.0` | no | yes | floating (line); digest = ladder's tested digest |
|
||
| claper | claper-postgres | `17-alpine` | `17-alpine` | `18-alpine` | no | yes | floating (line); digest = ladder's tested digest |
|
||
| code-server | code-server | `4.129.0` | `4.139.1` | `4.139.1` | yes | no | |
|
||
| crafty-controller | crafty-controller | `4.10.7` | `4.11.0` | `4.11.0` | yes | no | |
|
||
| docmost | docmost | `0.96.0` | `0.96.0` | `0.96.0` | no | no | |
|
||
| docmost | docmost-postgres | `18-alpine` | `18-alpine` | `18-alpine` | no | no | floating (line); digest = ladder's tested digest |
|
||
| docmost | docmost-redis | `7-alpine` | `7-alpine` | `8-alpine` | no | yes | floating (line); digest = ladder's tested digest |
|
||
| emby | emby | `4.11.0.3` | `4.11.0.4` | `4.11.0.4` | yes | no | |
|
||
| ghost | ghost | `6.65.0-alpine` | `6.67.0-alpine` | `6.67.0-alpine` | yes | no | |
|
||
| gitea | gitea | `1.27.0` | `1.27.3` | `28.0.0` | yes | yes | |
|
||
| glance | glance | `v0.8.5` | `v0.8.6` | `v0.8.6` | yes | no | |
|
||
| gokapi | gokapi | `v1.9.6` | `v1.9.6` | `v2.2.4` | no | yes | |
|
||
| grafana | grafana | `13.2.2` | `13.2.3` | `13.2.3` | yes | no | |
|
||
| gramps-web | gramps-web | `v25.6.0` | `v25.6.0` | `26.9.1` | no | yes | SHAPE SWITCH: upstream dropped the `v` prefix in 2026 (26.x.y); calendar versions, so the 25→26 step is a new year, counted as across |
|
||
| home-assistant | home-assistant | `2026.9.3` | `2026.9.4` | `2026.9.4` | yes | no | |
|
||
| homebox | homebox | `0.26.2` | `0.26.2` | `0.26.2` | no | no | |
|
||
| homepage | homepage | `v1.13.2` | `v1.13.2` | `v2.4.0` | no | yes | |
|
||
| immich | immich-server | `v3.2.2` | `v3.2.4` | `v3.2.4` | yes | no | measured on retry after ghcr 429 |
|
||
| immich | immich-machine-learning | `v3.2.2` | `v3.2.4` | `v3.2.4` | yes | no | measured on retry after ghcr 429 |
|
||
| immich | immich-postgres | `16-vectorchord0.4.3-pgvectors0.2.0` | `16-vectorchord0.4.3-pgvectors0.3.0` | `17-vectorchord0.4.3-pgvectors0.3.0` | yes | yes | floating (line); digest = ladder's tested digest |
|
||
| immich | immich-redis | `7-alpine` | `7-alpine` | `8-alpine` | no | yes | floating (line); digest = ladder's tested digest |
|
||
| jellyfin | jellyfin | `10.11.11` | `10.11.11` | `12.1` | no | yes | SHAPE SWITCH: upstream 12.x publishes two-part tags (`12.1`); 12.0 went through rc1..rc7 |
|
||
| kimai | kimai | `apache-2.57.0` | `2.67.0` | `2.67.0` | yes | no | SHAPE SWITCH: upstream stopped publishing `apache-` tags after 2.57.0; plain `2.67.0` exists (whether it is the same apache variant is NOT checked) |
|
||
| kimai | kimai-db | `11.8` | `11.8` | `13.0` | no | yes | floating (line); digest = ladder's tested digest |
|
||
| komga | komga | `1.27.1` | `1.28.0` | `1.28.0` | yes | no | |
|
||
| mealie | mealie | `v3.28.0` | `v3.28.0` | `v3.28.0` | no | no | |
|
||
| n8n | n8n | `2.41.2` | `2.42.1` | `2.42.1` | yes | no | |
|
||
| navidrome | navidrome | `0.64.1` | `0.64.2` | `0.64.2` | yes | no | |
|
||
| nextcloud | nextcloud | `34.0.4-apache` | `34.0.4-apache` | `35.0.1-apache` | no | yes | |
|
||
| nextcloud | nextcloud-db | `12.3` | `12.3` | `13.0` | no | yes | floating (line); digest = ladder's tested digest |
|
||
| nextcloud | nextcloud-redis | `7-alpine` | `7-alpine` | `8-alpine` | no | yes | floating (line); digest = ladder's tested digest |
|
||
| onlyoffice | onlyoffice | `9.4.0` | `9.4.0` | `9.4.0` | no | no | |
|
||
| opengist | opengist | `1.15` | `1.15` | `1.15` | no | no | floating (line); digest = ladder's tested digest |
|
||
| outline | outline | `1.9.1` | `1.10.1` | `1.10.1` | yes | no | |
|
||
| outline | outline-postgres | `16-alpine` | `16-alpine` | `18-alpine` | no | yes | floating (line); no ladder digest to compare |
|
||
| outline | outline-redis | `7-alpine` | `7-alpine` | `8-alpine` | no | yes | floating (line); no ladder digest to compare |
|
||
| paperless-ngx | paperless-webserver | `2.20.15` | `2.20.15` | `3.2.1` | no | yes | |
|
||
| paperless-ngx | paperless-postgres | `18-alpine` | `18-alpine` | `18-alpine` | no | no | floating (line); digest = ladder's tested digest |
|
||
| paperless-ngx | paperless-redis | `7-alpine` | `7-alpine` | `8-alpine` | no | yes | floating (line); digest = ladder's tested digest |
|
||
| papra | papra | `26.6.2-rootless` | `26.6.2-rootless` | `26.6.2-rootless` | no | no | |
|
||
| plant-it | plant-it | `0.10.0` | ? | ? | ? | ? | NOT MEASURED: HTTPError: 401 Client Error: Unauthorized for url: https://r |
|
||
| plex | plex | `1.41.4.9463-630c9f557` | `1.43.4.10903-e5521bd8c` | `1.43.4.10903-e5521bd8c` | yes | no | |
|
||
| privatebin | privatebin | `2.0.6` | `2.0.6` | `2.0.6` | no | no | |
|
||
| radarr | radarr | `6.4.4` | `6.4.4` | `6.4.4` | no | no | |
|
||
| rallly | rallly | `4.11.1` | `4.15.3` | `4.15.3` | yes | no | |
|
||
| rallly | rallly-postgres | `16-alpine` | `16-alpine` | `18-alpine` | no | yes | floating (line); no ladder digest to compare |
|
||
| recipe-importer | recipe-importer | `v0.9.11` | — | — | n/a | n/a | Felhom's own image, not upstream |
|
||
| romm | romm | `5.3.1` | `5.3.1` | `5.3.1` | no | no | |
|
||
| romm | romm-db | `11.8` | `11.8` | `13.0` | no | yes | floating (line); digest = ladder's tested digest |
|
||
| romm | romm-redis | `7-alpine` | `7-alpine` | `8-alpine` | no | yes | floating (line); digest = ladder's tested digest |
|
||
| seerr | seerr | `2.7.3` | `2.7.3` | `2.7.3` | no | no | |
|
||
| sonarr | sonarr | `4.0.20` | `4.0.20` | `4.0.20` | no | no | |
|
||
| sparkyfitness | sparkyfitness-db | `15-alpine` | `15-alpine` | `18-alpine` | no | yes | floating (line); no ladder digest to compare |
|
||
| sparkyfitness | sparkyfitness-server | `v0.17.3` | `v0.17.3` | `v1.7.3` | no | yes | |
|
||
| sparkyfitness | sparkyfitness-frontend | `v0.17.3` | `v0.17.3` | `v1.7.3` | no | yes | |
|
||
| tandoor | tandoor | `2.6.15` | `2.6.15` | `2.6.15` | no | no | |
|
||
| tandoor | tandoor-postgres | `17-alpine` | `17-alpine` | `18-alpine` | no | yes | floating (line); digest = ladder's tested digest |
|
||
| termix | termix | `2.8.0` | `2.8.0` | `2.8.0` | no | no | |
|
||
| uptime-kuma | uptime-kuma | `2.4.0` | `2.5.5` | `2.5.5` | yes | no | |
|
||
| vaultwarden | vaultwarden | `1.36.0-alpine` | `1.37.3-alpine` | `1.37.3-alpine` | yes | no | |
|
||
| vikunja | vikunja | `2.6.0` | `2.6.0` | `2.6.0` | no | no | |
|
||
| wanderer | wanderer | `v0.20.0` | `v0.21.0` | `v0.21.0` | yes | no | |
|
||
| wanderer | wanderer-db | `v0.20.0` | `v0.21.0` | `v0.21.0` | yes | no | |
|
||
| wanderer | wanderer-search | `v1.36.0` | `v1.54.2` | `v1.54.2` | yes | no | |
|
||
| wger | wger | `2.6` | `2.7` | `2.7` | yes | no | floating (line); no ladder digest to compare |
|
||
| wishlist | wishlist | `v0.67.1` | `v0.67.1` | `v0.67.1` | no | no | |
|
||
| zipline | zipline | `4.6.1` | `4.8.0` | `4.8.0` | yes | no | |
|
||
| zipline | zipline-postgres | `16-alpine` | `16-alpine` | `18-alpine` | no | yes | floating (line); no ladder digest to compare |
|
||
|
||
## 4. The night rule, from the code
|
||
|
||
Source: `felhom-controller/controller/internal/stacks/unattended.go` at `d48da6c`, function
|
||
`legCandidate` (L392–463). `RunUpdateLeg` (L221) → `runUpdateLeg` (L232–347) calls it for every
|
||
deployed, non-protected app (L276–281) and presses `StartGuardedUpdate` only when it returns no reason
|
||
(L311–321). The ladder reader is `ladder.go` (`LoadLadder` L83, `sameRefs` L118, `nextLadderStep`
|
||
L152, which makes the same choice, L163).
|
||
|
||
In order, an app is skipped when:
|
||
|
||
| code line | skip | condition |
|
||
|---|---|---|
|
||
| L397–407 | `held` | a hold on the app |
|
||
| L400 | `stopped_by_household` | the household stopped it |
|
||
| L408–416 | current / `ahead` / `order_unknown` | `CatalogOrder` is not Behind |
|
||
| L417–419 | `unpinned` | app.yaml has no `pinned_images` |
|
||
| L422–424 | `no_test_record` | the template has no `update_ladder:` |
|
||
| L426–432 | — | picks the NEWEST entry whose `from` equals the box's pin, every service, exactly |
|
||
| L433–435 | `no_test_record` | no match, and the pin is the head's `to` (behind only on a digest no step records) |
|
||
| L436–438 | `older_than_ladder` | no match at all: the box is older than the ladder |
|
||
| L441 | `not_proven` | the entry's verdict is not `proven` |
|
||
| L444 | `needs_person` | `marks.needs_person` is set and not blank |
|
||
| L447 | `failed_before` | the same step was undone or held on this ladder (R-680) |
|
||
| L450–458 | `files_may_change_no_whole_copy` | `marks.files_may_change` and no fresh whole copy on the box |
|
||
|
||
After that, `StartGuardedUpdate` can still refuse for box reasons: `no_backup`, `engine_no_test`,
|
||
`memory`, `disk`, `busy`… (`UpdatePreflight`, `update.go` L368 on; the checks at L431–470).
|
||
|
||
**Definition used for (a).** For each app, take a box whose pin is the `from` of the ladder's
|
||
newest entry (one step behind the head). Apply the catalog-side rows above (L422–458) to the entry
|
||
the code would pick. Count "yes" when nothing skips it, and "conditional" when only
|
||
`files_may_change` stands in the way. The box-side rows (hold, stop, failed_before, whole copy,
|
||
refusals) are not in this count, because they depend on the box.
|
||
|
||
Three things this rule means, read from the code:
|
||
|
||
- **A box older than the ladder is never pressed** (L436–438). For 21 apps the ladder's first entry is
|
||
a backfill of the 2026-09-21/22 move (`backfilled`). A box installed before that move matches it and
|
||
can climb. A box older than that `from` cannot.
|
||
- **A digest-only change is never pressed at night.** A box at the head tag with an older digest is
|
||
"behind" by `digestBehind` (updateorder.go L95), but no entry has that `from`, so L433–435 skips it.
|
||
- **The 11 backfilled head entries are harness v1** (no memory watch). The box does not read
|
||
`harness_version`, only `verdict`, so it presses them like any other: actualbudget, audiobookshelf,
|
||
bookstack, grafana, home-assistant, papra, privatebin, radarr, sonarr, termix, vikunja.
|
||
|
||
## 5. Per app — the ladder
|
||
|
||
"Head = compose" is the gate's rule 3 (`check-test-record.py`, run today: 53 read, 29 with a ladder,
|
||
0 convicted, `03-check-test-record.txt`). "Box one step behind, tonight" is number (a).
|
||
|
||
| app | entries | proven | of which backfilled | needs_person | files_may_change | engine conversion | head = compose | box one step behind, tonight | catalog_since |
|
||
|---|---|---|---|---|---|---|---|---|---|
|
||
| actualbudget | 1 | 1 | 1 | 0 | 0 | 0 | yes | yes | 2026-09-22 |
|
||
| adventurelog | 1 | 1 | 0 | 0 | 0 | 0 | yes | yes | 2026-09-27 |
|
||
| audiobookshelf | 1 | 1 | 1 | 0 | 0 | 0 | yes | yes | 2026-09-22 |
|
||
| bentopdf | 0 | 0 | 0 | 0 | 0 | 0 | — | no ladder: never (no_test_record) | 2026-07-12 |
|
||
| bookstack | 1 | 1 | 1 | 0 | 0 | 0 | yes | yes | 2026-09-22 |
|
||
| calcom | 1 | 1 | 0 | 0 | 0 | 1 | yes | yes | 2026-09-28 |
|
||
| calibre-web | 0 | 0 | 0 | 0 | 0 | 0 | — | no ladder: never (no_test_record) | 2026-07-12 |
|
||
| claper | 1 | 1 | 0 | 0 | 0 | 1 | yes | yes | 2026-09-28 |
|
||
| code-server | 0 | 0 | 0 | 0 | 0 | 0 | — | no ladder: never (no_test_record) | 2026-07-18 |
|
||
| crafty-controller | 0 | 0 | 0 | 0 | 0 | 0 | — | no ladder: never (no_test_record) | 2026-06-26 |
|
||
| docmost | 2 | 2 | 1 | 0 | 0 | 1 | yes | yes | 2026-09-25 |
|
||
| emby | 2 | 2 | 1 | 0 | 0 | 0 | yes | yes | 2026-09-23 |
|
||
| ghost | 2 | 2 | 1 | 0 | 0 | 0 | yes | yes | 2026-09-23 |
|
||
| gitea | 0 | 0 | 0 | 0 | 0 | 0 | — | no ladder: never (no_test_record) | 2026-07-18 |
|
||
| glance | 0 | 0 | 0 | 0 | 0 | 0 | — | no ladder: never (no_test_record) | 2026-09-21 |
|
||
| gokapi | 0 | 0 | 0 | 0 | 0 | 0 | — | no ladder: never (no_test_record) | 2026-07-18 |
|
||
| grafana | 1 | 1 | 1 | 0 | 0 | 0 | yes | yes | 2026-09-22 |
|
||
| gramps-web | 0 | 0 | 0 | 0 | 0 | 0 | — | no ladder: never (no_test_record) | 2026-07-18 |
|
||
| home-assistant | 1 | 1 | 1 | 0 | 0 | 0 | yes | yes | 2026-09-22 |
|
||
| homebox | 0 | 0 | 0 | 0 | 0 | 0 | — | no ladder: never (no_test_record) | 2026-07-19 |
|
||
| homepage | 0 | 0 | 0 | 0 | 0 | 0 | — | no ladder: never (no_test_record) | 2026-07-18 |
|
||
| immich | 2 | 2 | 1 | 0 | 0 | 0 | yes | yes | 2026-09-23 |
|
||
| jellyfin | 0 | 0 | 0 | 0 | 0 | 0 | — | no ladder: never (no_test_record) | 2026-07-18 |
|
||
| kimai | 1 | 1 | 0 | 0 | 0 | 0 | yes | yes | 2026-09-23 |
|
||
| komga | 1 | 1 | 0 | 0 | 0 | 0 | yes | yes | 2026-09-23 |
|
||
| mealie | 2 | 2 | 1 | 0 | 0 | 0 | yes | yes | 2026-09-25 |
|
||
| n8n | 3 | 3 | 1 | 0 | 0 | 0 | yes | yes | 2026-09-25 |
|
||
| navidrome | 2 | 2 | 1 | 0 | 0 | 0 | yes | yes | 2026-09-23 |
|
||
| nextcloud | 2 | 2 | 1 | 0 | 1 | 0 | yes | conditional: files_may_change (needs a fresh whole copy) | 2026-09-23 |
|
||
| onlyoffice | 0 | 0 | 0 | 0 | 0 | 0 | — | no ladder: never (no_test_record) | 2026-07-18 |
|
||
| opengist | 1 | 1 | 0 | 0 | 0 | 0 | yes | yes | 2026-09-23 |
|
||
| outline | 0 | 0 | 0 | 0 | 0 | 0 | — | no ladder: never (no_test_record) | 2026-07-18 |
|
||
| paperless-ngx | 1 | 1 | 0 | 0 | 0 | 1 | yes | yes | 2026-09-27 |
|
||
| papra | 1 | 1 | 1 | 0 | 0 | 0 | yes | yes | 2026-09-22 |
|
||
| plant-it | 0 | 0 | 0 | 0 | 0 | 0 | — | no ladder: never (no_test_record) | 2026-07-21 |
|
||
| plex | 0 | 0 | 0 | 0 | 0 | 0 | — | no ladder: never (no_test_record) | 2026-02-15 |
|
||
| privatebin | 1 | 1 | 1 | 0 | 0 | 0 | yes | yes | 2026-09-22 |
|
||
| radarr | 1 | 1 | 1 | 0 | 0 | 0 | yes | yes | 2026-09-22 |
|
||
| rallly | 0 | 0 | 0 | 0 | 0 | 0 | — | no ladder: never (no_test_record) | 2026-07-18 |
|
||
| recipe-importer | 0 | 0 | 0 | 0 | 0 | 0 | — | no ladder: never (no_test_record) | 2026-07-12 |
|
||
| romm | 3 | 3 | 1 | 0 | 0 | 0 | yes | yes | 2026-09-23 |
|
||
| seerr | 0 | 0 | 0 | 0 | 0 | 0 | — | no ladder: never (no_test_record) | 2026-07-18 |
|
||
| sonarr | 1 | 1 | 1 | 0 | 0 | 0 | yes | yes | 2026-09-22 |
|
||
| sparkyfitness | 0 | 0 | 0 | 0 | 0 | 0 | — | no ladder: never (no_test_record) | 2026-07-18 |
|
||
| tandoor | 2 | 2 | 1 | 0 | 0 | 1 | yes | yes | 2026-09-27 |
|
||
| termix | 1 | 1 | 1 | 0 | 0 | 0 | yes | yes | 2026-09-22 |
|
||
| uptime-kuma | 0 | 0 | 0 | 0 | 0 | 0 | — | no ladder: never (no_test_record) | 2026-09-21 |
|
||
| vaultwarden | 0 | 0 | 0 | 0 | 0 | 0 | — | no ladder: never (no_test_record) | 2026-07-18 |
|
||
| vikunja | 1 | 1 | 1 | 0 | 0 | 0 | yes | yes | 2026-09-22 |
|
||
| wanderer | 0 | 0 | 0 | 0 | 0 | 0 | — | no ladder: never (no_test_record) | 2026-07-21 |
|
||
| wger | 0 | 0 | 0 | 0 | 0 | 0 | — | no ladder: never (no_test_record) | 2026-07-19 |
|
||
| wishlist | 1 | 1 | 0 | 0 | 0 | 0 | yes | yes | 2026-09-23 |
|
||
| zipline | 0 | 0 | 0 | 0 | 0 | 0 | — | no ladder: never (no_test_record) | 2026-07-18 |
|
||
|
||
## 6. Plan for R-462 — widen the upgrade harness
|
||
|
||
**Order.** First: what a household loses if an update goes wrong. Then: how far behind.
|
||
**T1** = a database engine service, or household files the backup marks `mandatory`.
|
||
**T2** = household data in the app's own volume (SQLite or files). **T3** = little or no household data.
|
||
|
||
**Cost, per app.** The only measured numbers are in R-462: a proven edge takes 6.4–305 s (median 72 s)
|
||
plus the 600 s soak, a failing edge ~556 s, and a new fixture dominates everything else (the database
|
||
legs were costed at 21–34 CC-hours for 15 services, `09` §6.4). So: **fixture exists** = one bench run
|
||
plus one box run. **Fixture needed** = write a seed and a read-back through the app's own interface,
|
||
with a negative control, first. **No route** = the harness can only answer `inconclusive`.
|
||
|
||
### 6.1 Do next — in this order
|
||
|
||
1. **immich** (T1, fixture exists). Server and ML `v3.2.2 → v3.2.4`, and a newer pgvectors line inside
|
||
PostgreSQL 16. Cheapest high-value run: the fixture exists and the step is inside the major.
|
||
2. **bookstack** (T1, fixture exists). `26.05.5 → 26.09.1`. The fixture proves the database half only
|
||
(R-460).
|
||
3. **kimai** (T1, fixture exists). `2.57.0 → 2.67.0`, but the tag shape changed (`apache-` is gone).
|
||
A person must first confirm which plain tag is the Apache image. Then one run.
|
||
4. **calibre-web** (T1, **fixture needed**). `v4.0.6 → v4.0.8`, household books marked `mandatory`,
|
||
and no ladder at all. The first new fixture to write.
|
||
5. **gitea** (T2, fixture exists but fails at the web installer, R-624). `1.27.0 → 1.27.3`. Cheap:
|
||
the installer-form POST was never written. It unblocks gitea's first ladder entry.
|
||
6. **The T2 apps with a fixture and a step inside the major** — one run each: audiobookshelf, emby,
|
||
ghost, grafana, home-assistant, komga, n8n, navidrome. This is the bulk and costs machine time only.
|
||
7. **T2 apps that need a new fixture and are behind inside the major:** wanderer (3 images, incl.
|
||
meilisearch `v1.36 → v1.54`), uptime-kuma, crafty-controller, wger.
|
||
|
||
### 6.2 Majors — human work by the 2026-09-02 ruling, not harness bulk
|
||
|
||
nextcloud 34 → 35, paperless-ngx 2 → 3, gokapi 1 → 2, homepage 1 → 2, sparkyfitness 0.17 → 1.7,
|
||
gitea 1 → 28 (check first), jellyfin 10 → 12, gramps-web v25 → 26. Engine lines: redis 7 → 8 (6 apps),
|
||
mariadb → 13.0 (bookstack, kimai, nextcloud, romm; GA not checked), PostgreSQL for the R-463 remainder
|
||
(adventurelog/postgis, immich, outline, rallly, sparkyfitness on 15, zipline).
|
||
|
||
### 6.3 The honest ceiling — CORRECTED the same day
|
||
|
||
The morning version of this section named 8 apps that can only ever be `inconclusive`. **Three of them were wrong**,
|
||
measured on the bench the same afternoon: **outline** has a front-door first-run route (`POST /api/installation.create`,
|
||
refused once a workspace exists), **rallly** signs up through its own API with the six-digit e-mail code read from its own
|
||
table in place of a mailbox, and **zipline** 4's first-run route is `POST /api/setup` (the old fixture called two other
|
||
paths). outline and rallly moved on both venues the same day; zipline's fixture now tries `/api/setup` first.
|
||
|
||
**What remains in the class:** vaultwarden (closed sign-up by design, R-624) and code-server (a browser IDE); plex (a
|
||
plex.tv claim token), homepage and onlyoffice (no household data) are no-route by nature. Plus plant-it (image gone) and
|
||
recipe-importer (our own image; a fixture is needed). So the most the harness can prove is **47 of 53**.
|
||
|
||
### 6.4 The whole ranking
|
||
|
||
| rank | app | data at risk | ladder entries | images behind in major | across major | fixture |
|
||
|---|---|---|---|---|---|---|
|
||
| 1 | outline | T1: DB engine: outline-postgres | 0 | 1 | 2 | ~~no route~~ **fixture written 2026-09-30** (`installation.create`); moved to PG 18 the same day |
|
||
| 2 | rallly | T1: DB engine: rallly-postgres | 0 | 1 | 1 | ~~no route~~ **fixture written 2026-09-30** (sign-up + its own e-mail code); moved to PG 18 the same day |
|
||
| 3 | zipline | T1: DB engine: zipline-postgres | 0 | 1 | 1 | ~~no route~~ **first-run route `POST /api/setup` measured 2026-09-30**; fixture fixed; PG stays 16 |
|
||
| 4 | calibre-web | T1: household files (backup class mandatory) | 0 | 1 | 0 | fixture needed |
|
||
| 5 | sparkyfitness | T1: DB engine: sparkyfitness-db | 0 | 0 | 3 | fixture needed |
|
||
| 6 | immich | T1: DB engine: immich-postgres; household files (backup class mandatory) | 2 | 3 | 2 | fixture exists |
|
||
| 7 | bookstack | T1: DB engine: bookstack-db | 1 | 1 | 1 | fixture exists |
|
||
| 8 | kimai | T1: DB engine: kimai-db | 1 | 1 | 1 | fixture exists |
|
||
| 9 | nextcloud | T1: DB engine: nextcloud-db; household files (backup class mandatory) | 2 | 0 | 3 | fixture exists |
|
||
| 10 | claper | T1: DB engine: claper-postgres | 1 | 0 | 2 | fixture exists |
|
||
| 11 | paperless-ngx | T1: DB engine: paperless-postgres; household files (backup class mandatory) | 1 | 0 | 2 | fixture exists |
|
||
| 12 | romm | T1: DB engine: romm-db | 3 | 0 | 2 | fixture exists |
|
||
| 13 | adventurelog | T1: DB engine: adventurelog-postgres | 1 | 0 | 1 | fixture exists |
|
||
| 14 | docmost | T1: DB engine: docmost-postgres | 2 | 0 | 1 | fixture exists |
|
||
| 15 | tandoor | T1: DB engine: tandoor-postgres | 2 | 0 | 1 | fixture exists |
|
||
| 16 | calcom | T1: DB engine: calcom-postgres | 1 | 0 | 0 | fixture exists |
|
||
| 17 | wanderer | T2: household data in the app's own volume (SQLite / files) | 0 | 3 | 0 | fixture needed |
|
||
| 18 | gitea | T2: household data in the app's own volume (SQLite / files) | 0 | 1 | 1 | fixture exists, fails at the web installer (R-624, fixable) |
|
||
| 19 | code-server | T2: household data in the app's own volume (SQLite / files) | 0 | 1 | 0 | no route: its front door is a browser IDE behind one password |
|
||
| 20 | crafty-controller | T2: household data in the app's own volume (SQLite / files) | 0 | 1 | 0 | fixture needed |
|
||
| 21 | plex | T2: household data in the app's own volume (SQLite / files) | 0 | 1 | 0 | no route: the first-run claim needs a token minted at plex.tv by a real Plex acc |
|
||
| 22 | uptime-kuma | T2: household data in the app's own volume (SQLite / files) | 0 | 1 | 0 | fixture needed |
|
||
| 23 | vaultwarden | T2: household data in the app's own volume (SQLite / files) | 0 | 1 | 0 | no route by design: closed sign-up (R-624) |
|
||
| 24 | wger | T2: household data in the app's own volume (SQLite / files) | 0 | 1 | 0 | fixture needed |
|
||
| 25 | gokapi | T2: household data in the app's own volume (SQLite / files) | 0 | 0 | 1 | fixture needed |
|
||
| 26 | gramps-web | T2: household data in the app's own volume (SQLite / files) | 0 | 0 | 1 | fixture exists |
|
||
| 27 | jellyfin | T2: household data in the app's own volume (SQLite / files) | 0 | 0 | 1 | fixture exists |
|
||
| 28 | homebox | T2: household data in the app's own volume (SQLite / files) | 0 | 0 | 0 | fixture exists |
|
||
| 29 | plant-it | T2: household data in the app's own volume (SQLite / files) | 0 | 0 | 0 | fixture needed |
|
||
| 30 | recipe-importer | T2: household data in the app's own volume (SQLite / files) | 0 | 0 | 0 | fixture needed |
|
||
| 31 | seerr | T2: household data in the app's own volume (SQLite / files) | 0 | 0 | 0 | fixture needed |
|
||
| 32 | audiobookshelf | T2: household data in the app's own volume (SQLite / files) | 1 | 1 | 0 | fixture exists |
|
||
| 33 | emby | T2: household data in the app's own volume (SQLite / files) | 2 | 1 | 0 | fixture exists |
|
||
| 34 | ghost | T2: household data in the app's own volume (SQLite / files) | 2 | 1 | 0 | fixture exists |
|
||
| 35 | grafana | T2: household data in the app's own volume (SQLite / files) | 1 | 1 | 0 | fixture exists |
|
||
| 36 | home-assistant | T2: household data in the app's own volume (SQLite / files) | 1 | 1 | 0 | fixture exists |
|
||
| 37 | komga | T2: household data in the app's own volume (SQLite / files) | 1 | 1 | 0 | fixture exists |
|
||
| 38 | n8n | T2: household data in the app's own volume (SQLite / files) | 3 | 1 | 0 | fixture exists |
|
||
| 39 | navidrome | T2: household data in the app's own volume (SQLite / files) | 2 | 1 | 0 | fixture exists |
|
||
| 40 | actualbudget | T2: household data in the app's own volume (SQLite / files) | 1 | 0 | 0 | fixture exists |
|
||
| 41 | mealie | T2: household data in the app's own volume (SQLite / files) | 2 | 0 | 0 | fixture exists |
|
||
| 42 | opengist | T2: household data in the app's own volume (SQLite / files) | 1 | 0 | 0 | fixture exists |
|
||
| 43 | papra | T2: household data in the app's own volume (SQLite / files) | 1 | 0 | 0 | fixture exists |
|
||
| 44 | privatebin | T2: household data in the app's own volume (SQLite / files) | 1 | 0 | 0 | fixture exists |
|
||
| 45 | radarr | T2: household data in the app's own volume (SQLite / files) | 1 | 0 | 0 | fixture exists |
|
||
| 46 | sonarr | T2: household data in the app's own volume (SQLite / files) | 1 | 0 | 0 | fixture exists |
|
||
| 47 | termix | T2: household data in the app's own volume (SQLite / files) | 1 | 0 | 0 | fixture exists |
|
||
| 48 | vikunja | T2: household data in the app's own volume (SQLite / files) | 1 | 0 | 0 | fixture exists |
|
||
| 49 | wishlist | T2: household data in the app's own volume (SQLite / files) | 1 | 0 | 0 | fixture exists |
|
||
| 50 | bentopdf | T3: little or no household data (stateless or config only) | 0 | 1 | 0 | fixture needed |
|
||
| 51 | glance | T3: little or no household data (stateless or config only) | 0 | 1 | 0 | fixture needed |
|
||
| 52 | homepage | T3: little or no household data (stateless or config only) | 0 | 0 | 1 | no route: a dashboard rendered from config files in the template |
|
||
| 53 | onlyoffice | T3: little or no household data (stateless or config only) | 0 | 0 | 0 | no route: a stateless document server: it holds no household data of its own, so |
|
||
|
||
## 7. Files
|
||
|
||
In `catalog-currency-2026-09-30/`:
|
||
|
||
| file | what |
|
||
|---|---|
|
||
| `00-currency.py` | the measurement (registries, read-only) |
|
||
| `01-currency-raw.json`, `01-currency-run.txt` | its raw output |
|
||
| `02-retry-429.py`, `02-retry-429-run.txt` | the slower retry for the two ghcr 429 rows |
|
||
| `03-check-test-record.txt` | the catalog's own ladder gate, run today |
|
||
| `04-analyse.py`, `04-analyse-run.txt` | tables and counts (offline; the night rule is in its header) |
|
||
| `05-summary.json` | the counts and app lists |
|
||
| `06-`, `07-`, `08-*.md` | the three tables above |
|
||
| `09-shape-switch-check.txt`, `10-shape-switch-detail.txt` | the shape-switch control |
|
||
|
||
**Findings that should become register rows** (this session did not edit the register — read-only
|
||
brief): the three tag-shape switches (gramps-web, jellyfin, kimai), which the badge and any shape-based
|
||
tool read as "up to date"; and the `gitea 28.0.0` / `mariadb 13.0` tags, which need a GA check before
|
||
anyone plans on them.
|