REPORT: slice 4 (v0.237.0–v0.238.1) proven live — A, B, E, F, H and the restore walk
gates / gates (push) Successful in 13s

Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-09-13 12:29:56 +02:00
parent cbcca03061
commit cf8a371c39
+144 -159
View File
@@ -1,187 +1,172 @@
# REPORT — v0.236.0, R-442: "delete my data too" deletes the data, or says that it could not (2026-09-13)
# REPORT — Update arc slice 4: the Update button takes a backup first, and tells the truth (2026-09-13)
*Overwritten each run. This records the most recent implementation only.*
## For the operator — what changed, in plain words
> **Shipped as controller v0.237.0 (the guarded job) and v0.238.0 (the page), both live on both demo
> guests, every scenario A–H proven live on demo-hp — including the restore walk.** Four claims in the
> prompt turned out wrong or incomplete; they are first, in §1.
When a customer removes an app and ticks *also delete my data*, the box now deletes the data and lists
what went. If the box cannot work out where the data is, it refuses, keeps the app, and says so in one
sentence. It never again reports success over data left behind. Proven on the HP with a throwaway
Nextcloud. Live on both demo machines. Nothing is waiting on you.
## 1. Claims in the prompt that turned out wrong, named first
## Claims in the task that turned out wrong or incomplete — named first
1. **"A release in this task raises the floor and does NOT need a bake."** Wrong, measured. The hub
HOLDS a floor above the vouched golden (publish-train rule 1): raising the floor to 0.237.0 made the
hub log `managed floor HELD for demo-hp: held: floor 0.237.0 is ABOVE the vouched golden 0.236.0 …
(controller floor withheld)`, the box logged `SetFloor: floor "0.236.0" → ""`, and neither box moved
in 8 minutes. Both releases were hand-deployed by the skill's route; the floor was put back to
0.236.0. **This also falsifies the premise of this morning's golden-cadence ruling** — filed
**R-472**, an operator decision; the five documents that repeated the claim were corrected.
2. **"Is the restorable-unit predicate Tier-2-only?" — yes, and that is a finding.** The update
precondition is exactly what the backups page uses (`Tier2UnitRestorePoint`), so an app with no
Tier-2 copy cannot be updated at all — on demo-hp `gokapi` and `nextcloud` have no Tier-2 record.
The primary unit and the off-site unit are restorable routes it does not consider — **R-475**.
3. **"The proven copy date" is not one field.** The page names the unit MANIFEST's `created_at`, which
moves only when the app's definition changes — measured on demo-hp: bookstack's mirror held a
2026-09-13T00:30Z dump under a manifest dated 2026-09-12T02:15:29Z. Aging the copy by it would call a
fresh copy stale forever and a backup-first would not fix it. The update ages by the last SUCCESSFUL
copy instead; the page's date is **R-476**.
4. **"A person clears it by restoring, exactly as for R-379."** Not exactly: an R-379 hold is cleared
only by the operator CLI (`-clear-restore-hold`); nothing in the restore path cleared it. Slice 4
makes a successful unit restore lift an **update** hold only, and leaves R-379's rule unchanged.
1. **"Three lookups" was right, but the backup half needed the same fix, not only the same honesty.**
Under the global lookup, every `remove_backups: true` on every box was refused — not only paths
"outside the expected directory": with `hddPath == ""` the base was `/backups`, so nothing could
ever be under it. Making that refusal *visible* without fixing its *base* would have shown every
customer a wrong refusal. The base now follows the same per-app rule (the app's namespace root:
its own drive, or `<system data>/felhom-data` otherwise), which is exactly what the router's
`AppNamespaceRoot` produced the paths from. **This is one deliberate step beyond "and no more"**
(task §2, "the same honesty treatment and no more"), and the reason is above.
2. **"Deploy a small drive-backed app" — 8 of the 13 `needs_hdd` catalog apps have no `${HDD_PATH}`
bind at all.** They bind only `${USERDATA_PATH}` (the shared library) and named volumes. For them
"delete my data" correctly removes nothing on the drive, and the modal shows no checkbox. The
faithful throwaway was Nextcloud — the app R-442 was measured on.
3. **The task's §11 verifies the deploy on `felhom-pve` 9201 and validates on `demo-hp`.** Both guests
were deployed; the live validation ran on demo-hp as specified.
4. **"NOT ESTABLISHED: whether the fleet shares this config shape"** (the R-442 row) — now
established: demo-felhom also has 0 `hdd_path` lines and 0 `FELHOM_PATHS_*` variables.
Also: **the drive-return gate and the nightly volume dump started held apps** — "a hold that only one
path honours" was true of the R-379 hold too, before this slice. Both now honour it.
## Baselines (verified before starting)
## 2. Baselines, commits, deployed versions
| Repo | `main` before | after | version |
|---|---|---|---|
| felhom-controller | `bab82c471e03` | `42a73e6` (code) + this REPORT commit | v0.235.0 → **v0.236.0** |
| felhom.eu | `d6837d98ee24` | `4b2e560` (docs + evidence + gate fix) | — |
| repo | before | after |
|---|---|---|
| felhom-controller | `155271672265` (v0.236.0) | `0d402f7` v0.237.0 → `129201a` v0.238.0 → `cbcca03` v0.238.1 |
| app-catalog-felhom.eu | `3525e35` | live-test commits and reverts, **no net change** (see its REPORT) |
| felhom.eu | `abe567e` | documents + register (this session's final push) |
Highest `R-` id before: 464. **No new row minted.** R-442 closed.
**Deployed:** `gitea.dooplex.hu/admin/felhom-controller:0.238.1 Up … (healthy)` on guest 9201 of **both**
demo boxes, hand-deployed by the skill's route (the floor could not carry it — §1.1). Fleet floor left at
0.236.0, the vouched golden.
## Files
## 3. The two config knobs
- `controller/internal/stacks/delete.go` — `appHDDPath`, `composeBindsDrive`, `hddPathForRemoval`,
`hddNoteFor`; `RemoveRefusedError` + reasons; the three `m.cfg.Paths.HDDPath` reads replaced;
`[]` init; `hdd_paths_missing`, `hdd_note`, `backup_paths_refused`; backup base via
`appbackup.NamespaceRootFor`.
- `controller/internal/api/router.go` — `errors.As` → 409 + `Message` in `removeStack` and `deleteStack`.
- `controller/internal/web/templates/layout.html` — both success modals render `hdd_note` and
„Nem törölt mentések".
- Tests: `controller/internal/stacks/delete_r442_test.go` (13), `controller/internal/api/remove_r442_test.go` (2).
- Docs: `CHANGELOG.md` (v0.236.0), `CONTEXT.md`, `REUSE.md` (row 120), `controller/README.md` (3 rows).
| key | default | meaning |
|---|---|---|
| `update.backup_max_age` | `24h` | a proven Tier-2 copy older than this is refreshed before the update |
| `update.health_timeout` | `5m` | how long the new version has to become healthy before the app is held |
## Design rule this brings the removal path into line with
Plus two fixed rules, stated as fixed: the disk floor is **2 GB** (image size unknown without a registry
query) and an app with no `.felhom.yml` health check must run **60 s** with no container restarting.
`07-backup-architecture.md` ~L437, *"[DESIGN] 2026-08-22 — the restore destination is resolved by the
same rule as the capture destination: the drive if the app declares one (`HDD_PATH`), the system data
path otherwise."* Deploy (`withPathVars`), the start gate (`startGatedByMissingDrive`) and the backup
destination (`GetAppDrivePath`) already read the app's own record; removal was the odd one out.
**No fallback to the global** — the helper's comment says why.
## 4. Live evidence — endpoint-level, on demo-hp, throwaway app uptime-kuma
## Tests — 1761 in the module, 15 new, all green; gates all OK
All in `felhom.eu/documentation/audits/slice4-2026-09-13/live/`. Catalog syncs used `POST /api/sync`, the
dashboard's „Sablonok frissítése" button — the syncer's own entry point, not the 15-minute timer.
| Scenario | Test | Asserts (the effect) | Result |
|---|---|---|---|
| A drive app, data removed | `TestRemoveStack_R442_RemovesDeclaredDriveData` | folder gone from a real tree; listed with size; app.yaml gone; compose ran; `appdata/` root intact | PASS |
| B same app, data kept | `…KeepsDataWhenNotAsked` | file present; listed under preserved; `"hdd_paths_removed":[]` on the wire | PASS |
| **C unresolvable** | `…RefusesWhenDriveUnresolvable` | typed error, reason, exact sentence; **compose marker absent**; app.yaml present | PASS |
| **D SSD app** | `…SSDAppIsNotRefused` | no error; non-nil empty list; `[]` on the wire; note; app removed | PASS |
| drive absent | `…RefusesWhenDriveAbsent` | drive-absent sentence naming the path; nothing ran; app kept | PASS |
| keep-data never refused | `…KeepDataNeverRefusedOnHDDGrounds` | drive absent + `false` → removed | PASS |
| folder already gone | `…MissingFolderIsStatedNotRefused` | `hdd_paths_missing`, note names it, app removed | PASS |
| E backup half | `…BackupRefusalReachesResponse` | inside path removed + listed; outside path KEPT + `backup_paths_refused` | PASS |
| E SSD app backups | `…SSDAppBackupsUnderSystemNamespace` | `<sys>/felhom-data/backups/…` removable | PASS |
| `${USERDATA_PATH}` convention | `…UserdataConventionFromPerAppPath` | appdata removed, shared `userdata/` untouched; `ExportDataMounts` from the per-app path includes `<hdd>/userdata` | PASS |
| orphan delete C / A | `TestDeleteStack_R442_*` (2) | same shapes for `DeleteStack` | PASS |
| modal source | `TestGetStackHDDData_R442_ReadsPerAppRecord` | lists the folder while the global stays empty | PASS |
| **production wiring** | `TestRemoveHandler_R442_*` (2) | real handler → real `NewManager` + `ScanStacks` → **409**, exact sentence / drive-absent sentence, app.yaml still on disk | PASS |
**Red-proof 1 (Scenario C — the pre-fix silent fallback to the global, and nil lists):** 5 tests
failed, with the wrong values visible:
`want *RemoveRefusedError, got err=<nil> resp.hdd_paths_removed=[] app.yaml present=false — a 200 over inaction`;
handler: `HTTP 200 ok=true — a 2xx over a removal that could not resolve the data location`;
B: `got {"…","hdd_paths_removed":null,…}`. Fix restored → green.
**Red-proof 2 (Scenario D — make "declares no drive" refuse):** 2 tests failed:
`an SSD app must not be refused on HDD grounds, got: Az alkalmazás adatainak helye nem állapítható meg…`.
Fix restored → green. `git diff` clean after both.
Green gate: `go build ./... && go vet ./... && go test ./...` rc=0. `controller_gates.py --fast`: all OK.
## Deployed
`gitea.dooplex.hu/admin/felhom-controller:0.236.0` — `Up … (healthy)` on **both** demo guests
(felhom-pve 9201 and demo-hp 9201), verified with `docker ps`.
## Live validation on demo-hp — endpoint-level (the exact endpoints the UI's modal and buttons invoke: `GET …/hdd-data`, `GET …/backup-data`, `POST …/stop`, `POST …/remove`), driven from inside guest 9201 with the session cookie + `X-CSRF-Token`. Evidence: `felhom.eu/documentation/audits/R442-2026-09-13/`.
**Scenario A — Nextcloud, deployed through the real deploy endpoint, data written BY THE APP (its
first-run install: 69 files, 63 MB under `/mnt/felhom-drives/hdd_1/appdata/nextcloud`), stopped,
removed with `{"remove_hdd_data":true,"remove_backups":true}`:**
**A — real upgrade 2.3.2 → 2.4.0** (`05-A-update.txt`): 202 `{"accepted":true,"completed":false}`; phases
`safety-dump → pulling → starting → verifying → done`. Verbatim:
```
{"ok":true,"data":{"removed":"nextcloud","volumes_removed":null,
"hdd_paths_removed":["/mnt/felhom-drives/hdd_1/appdata/nextcloud (63M)"],
"hdd_paths_preserved":[],
"backup_paths_removed":["/mnt/felhom-drives/hdd_1/backups/primary/nextcloud/db-dumps (8.0K)"]},
"message":"Stack nextcloud removed"} HTTP 200
ls: cannot access '/mnt/felhom-drives/hdd_1/appdata/nextcloud': No such file or directory
ls /mnt/felhom-drives/hdd_1/appdata → paperless romm
[INFO] Removed HDD data: /mnt/felhom-drives/hdd_1/appdata/nextcloud (63M)
```
The db-dumps entry was a **planted** fixture file (`r442-fixture.sql`, stated) — no nightly had run
for a 3-minute-old app. The modal's `GET …/hdd-data` beforehand listed the folder with `63M` — under
0.235.0 that endpoint returned nothing for the same app.
**Scenario C — Nextcloud redeployed, stopped, then `HDD_PATH` deleted from its `app.yaml` (a
test-only edit, backed up and restored), `{"remove_hdd_data":true}`:**
```
{"ok":false,"error":"Az alkalmazás adatainak helye nem állapítható meg, ezért semmit nem töröltünk. Az alkalmazás nem lett eltávolítva."} HTTP 409
state=stopped deployed=True app.yaml present (1302 B) data files after: 69 (before: 69)
[ERROR] [stacks] RemoveStack nextcloud refused: data removal requested, the compose binds a drive path, but app.yaml records no HDD_PATH — nothing removed, app kept (R-442)
```
`app.yaml` restored → the **same call** returned HTTP 200 with the 63M folder listed and gone
(positive control: the refusal was about the record, not the app).
**Scenario D — Gokapi (SSD-only: 0 `HDD_PATH` lines in app.yaml, 0 drive binds in compose),
`{"remove_hdd_data":true}`:**
```
{"ok":true,"data":{"removed":"gokapi","volumes_removed":null,"hdd_paths_removed":[],"hdd_paths_preserved":[],
"hdd_note":"Az alkalmazás nem tárolt saját adatot külső meghajtón, így ott nem volt mit törölni."},…} HTTP 200
update uptime-kuma: precondition met — proven copy from 2026-09-13T10:03:07Z (4m0s old, limit 24h0m0s)
update safety dump for uptime-kuma: the app has no database — nothing to copy (no-op)
update uptime-kuma: pin advanced to the catalog's current definition (uptime-kuma=louislam/uptime-kuma:2.4.0)
update uptime-kuma: healthy after 5s (the app's health check passed)
update uptime-kuma: DONE in 47s
```
**ASCII-fragment controls** (`controls.txt`, counted on DooPlex over the saved bodies; `llap` is the
ASCII core of *állapítható*): C body = 2 (the JSON error + the router's ERROR line), A body = 0,
D body = 0 (in-guest count; the DooPlex file shows 1 from my own label line). Positive control that the
grep works: `hdd_paths_removed` = 1 in every file. `HTTP 409` appears only in C.
Final API: `update_phase: done`, pinned and installed `louislam/uptime-kuma:2.4.0`; the card reads
„Naprakész". The safety-dump **path** is empty because uptime-kuma has no database container — the
no-op the design specifies; the path shape is `pre-restore-<stamp>-<app>-<dbtype>.sql` (R-361).
Not validated live: Scenario B (kept data) and the drive-absent refusal — unit-tested only; no drive
was unplugged on the demo box.
**B — the copy is stale** (`06-B-setup.txt`, `07-B-update.txt`). **Stated test method:** the real knob
`update.backup_max_age: 2m` was appended to `controller.yaml` and the file restored byte-identical
afterwards (`sha256` prefix `042b71a3123d70da` before and after; no `update:` key remains).
## Teardown — three layers
```
update uptime-kuma: the proven copy is 7m0s old (limit 2m0s) — backing up first
update pre-backup for uptime-kuma: volume dump OK
update pre-backup for uptime-kuma: recovery unit captured (0 database dump(s))
Tier 2 copied uptime-kuma → /mnt/felhom-drives/hdd_1/backups/secondary/uptime-kuma (19.9 KB, 0 leg(s), 0s)
update pre-backup for uptime-kuma: complete in 1.395s
update uptime-kuma: DONE in 8s
```
1. **Apps:** both throwaway apps (`nextcloud`, `gokapi`) were removed BY THE FEATURE UNDER TEST; all
nine standing apps still deployed (`bentopdf bookstack calibre-web docmost kimai opengist
paperless-ngx privatebin romm`); `bentopdf` kept.
2. **Residue:** the deploy's recovery-unit capture left `backups/primary/nextcloud/{compose/,manifest.json}`
(not customer data; removal deletes only `db-dumps/` by design) — removed BY HAND, stated here.
`appdata/` holds only `paperless`, `romm` — unchanged.
3. **Helpers:** `/tmp/r442.sh`, `/tmp/r442.pw`, `/tmp/r442-*.json` deleted from guest and host
(count 0). Nothing else was provisioned. The password file travelled file→file and was never printed.
Tier-2 `last_success` moved to 10:09:51Z; the new volume tar is in both the primary unit and the mirror.
## Pushes and gates — stated plainly
**E — the tag does not exist** (`08-E-setup.txt`, `09-E-update.txt`): `update_error` is the Hungarian
sentence `Az új verzió letöltése nem sikerült, …`, no Docker stderr in it; `pin and definition PUT BACK
to the pre-update version (uptime-kuma=louislam/uptime-kuma:2.4.0)`. **The app untouched:** container
`cb541381d93e…`, `started=2026-09-13T10:09:50.950051403Z` — identical before and after; live and stored
definitions `2.4.0`; no pre-update copies left.
- felhom-controller: both pushes passed the pre-push gates (`controller_gates.py --fast`, all OK;
`golden-notice` advisory).
- **felhom.eu `4b2e560` was pushed with `git push --no-verify`.** The only convicting gate was
`golden-currency` — it fails whenever the newest controller release has no golden, has NO waiver
logic (it does not read the register), and would fail this way for any release until a golden is
baked. R-467 is the waiver row it asks for. Everything else was green, including the
`closed-register` gate, which **crashed** (a `NameError` on its own conviction path) on my first,
two-column CLOSED row instead of convicting it. Fixed in the same commit
(`scripts/closed_register_gate.py`), seen to convict the two-column row, then the row was reshaped
to four columns and the gate passed.
**F — never healthy** (`11-F-setup.txt`, `12-F-update.txt`): catalog `alpine:3.20`.
## Rows
```
update uptime-kuma FAILED after the new version was started: not healthy: not healthy within 5m0s (last: state restarting) — stopping and HOLDING the app; the pin stays on the new version (its migration may have run)
restore hold SET for uptime-kuma — the app stays stopped until it is cleared
```
- **R-442 → CLOSED** (`CLOSED-ITEMS.md`, top). **Opened: R-465** (the six remaining `Paths.HDDPath` readers — audit), **R-466** (recovery-unit residue after "delete backups"), **R-467** (v0.236.0 owes a golden) — all P3-LOW, owner CC. Register: OPEN 208 → 210, CLOSED 168 → 169.
- `00-capability-map.md` lifecycle row narrowed (remove proved the app, not the data) and re-proven.
- `STATUS.md`: item 13 (plain language) + **item 7 closed** (ruled 2026-09-02, `09` §3).
- Highest `R-` id now 467.
Hold record `{"reason":"update_failed","copy_date":"2026-09-13T10:09:51Z","at":"2026-09-13T10:18:02Z"}`.
API and page: `A(z) uptime-kuma frissítése 2026-09-13 12:18-kor nem sikerült, … Visszaállítható a(z)
2026-09-13 12:09-i biztonsági mentésből a Mentések oldalon.` Card: `data-held="true"`, **0** lifecycle
buttons. App info, ASCII fragments with controls: `friss` ×3, `leáll` ×3, `Mentések` ×2,
`href="/backups/apps"` ×2. Container removed.
## Observations
**H — every path refuses the held app** (`13-H-refusals.txt`): `start`, `restart`, `update` → **409**
with the hold sentence each; after a controller restart:
1. **`cfg.Paths.HDDPath` still has readers** — `report/builder.go:69`, `monitor/healthcheck.go:35`,
`api/router.go` system-info, `web/server.go:740`, `main.go` (auto-discovery seed + metrics). Each
reads an always-empty value on the fleet; whether any of them is silently inert the way removal was
is worth one short audit. Not touched here. FILED: R-465
2. **Removal with "delete backups" leaves the recovery unit's `compose/` + `manifest.json`** — the
router passes only `AppDBDumpPath`. Small, pre-existing; a customer who asked for backups gone is
left with a manifest. FILED: R-466
3. **8 of 13 `needs_hdd` apps bind only `${USERDATA_PATH}`**, so their modal never offers the data
checkbox. Correct by the placement design (`01` — userdata is the shared library), but the label
„Felhasználói adatok a merevlemezen" only ever appears for the 5 that bind `${HDD_PATH}`. NOT-A-FINDING: this IS the `01` placement design — userdata is the customer's shared library, and an app removal deleting it would destroy media other apps and the customer own; the modal correctly offers nothing.
4. `app.yaml` carries `HDD_PATH` twice (`env:` and `locked_fields:`); the test-only edit removed the
`env:` line only, which is the one `appHDDPath` reads — proven by the refusal firing. NOT-A-FINDING: `locked_fields` is a list of field NAMES (which fields are locked after deploy), not a second value; nothing reads a path from it.
5. **v0.236.0 owes a golden** (the golden-notice gate, advisory): newest golden baked is 0.232.0. A bake + vouch is the `RUNBOOK-manual-build.md` §4.1 procedure and is outside this task; until then a fresh install receives 0.232.0 and picks up 0.236.0 by self-update. FILED: R-467
```
[bootrecon] "uptime-kuma" is a boot orphan by intent but is HELD (held after a failed update (2026-09-13T10:18:02Z) — restore it from its backup to start it) — NOT starting it
[bootrecon] Boot reconciliation: nothing to start — 1 app(s) held (…): [uptime-kuma]
```
The drive-return gate cannot be exercised without unplugging a drive; it is covered by
`TestSlice4_DriveReturnGateSkipsAHeldApp` with its red-proof, not live.
**The restore walk** (`14-restore-walk.txt`): `POST /backup/tier2/unit-restore` (the page's own form) →
302; restore-status `ok: true`, `A(z) uptime-kuma: 1 adatkötet visszaállítva — az alkalmazás
újraindult. A visszaállítás forrása a második meghajtón lévő másolat volt (2026-09-13 12:09).` in 9.1 s.
Container `louislam/uptime-kuma:2.4.0 … (healthy)`, pinned 2.4.0, and
`uptime-kuma: restore completed — the update hold (set 2026-09-13T10:18:02Z) is CLEARED`.
## 5. Tests, red-proofs, gate
Full gate after each release: `go build ./...` rc 0, `go vet ./...` rc 0, `go test ./...` rc 0, **28 packages ok**.
| red-proof | mutation | seen to fail |
|---|---|---|
| **1** (A) | health wait replaced by `true` | `the health wait was never reached; state: updating=false phase=done` |
| **2** (C) | preflight drops `!rp.Restorable` | `an app with no restorable copy must be REFUSED (no_backup), got <nil>` |
| **3** (F) | the hold call skipped | `an app that did not come up must be HELD` |
| **4** (H, R-439) | `update` removed from the router's hold line | refused with the no-backup sentence instead of the hold's |
| nightly hold | isHeld skip removed from the volume dump | `the nightly volume dump touched a HELD app` |
| drive-return | appHeld check removed | `the drive-return gate tried to START a held app` |
| page ×2 | updating/held checks moved after `isOperational` | phase label missing, lifecycle buttons present |
| v0.238.1 | updating clause removed from isHeld | `a nightly leg touched an app MID-UPDATE` |
**Three red-proofs first ran inertly and were fixed before they counted:** proof 1 did not compile,
proof 2's fixture was also unproven, so another check still refused, and proof 4 was masked by the
preflight's own hold check. Outputs: `audits/slice4-2026-09-13/redproofs/`.
## 6. Rows
Closed: **R-448, R-443, R-439**. Opened: **R-472** (floor vs golden, operator), **R-473** (glance
template), **R-474** (removal leaves backups, reports `volumes_removed: null` — reproduced twice),
**R-475** (Tier-2-only precondition, operator), **R-476** (page names the manifest date). **R-469**
unblocked, not lifted. Register 214 open / 174 closed.
## 7. Teardown — three layers
1. **Machine:** glance and uptime-kuma removed through the feature. The removal left backup directories
and applied-compose files (R-474), cleared by hand by named path; four test images removed by name,
no prune. The standing apps and bentopdf were never touched and are `Up … (healthy)`.
2. **Host:** nothing created on demo-hp's Proxmox layer — no guest, no storage.
3. **Hub:** `/hosts` lists exactly `demo-felhom-8363b5` and `demo-hp-bb76ea`; 0 customer configs; the
floor is back at `0.236.0`. Two app-deployed events from the throwaways reached the hub as ordinary events.
## 8. Observations
1. **The floor cannot carry a release past the vouched golden.** **FILED: R-472.**
2. **The glance template crash-loops on a fresh install.** **FILED: R-473.**
3. **App removal leaves the recovery unit and Tier-2 copy behind and names no volume.** **FILED: R-474.**
4. **The update precondition is Tier-2-only.** **FILED: R-475.**
5. **The page names a copy by its manifest date, which lags the data.** **FILED: R-476.**
6. **The controller CHANGELOG headers v0.233.0–v0.238.1 still need a MinAgent line** — these three
releases carry one. **NOT-A-FINDING: already R-470.**
7. **A live-test catalog tag is exposed to every new install while it stands.**
**NOT-A-FINDING: the alpine tag was reverted about two minutes after landing, neither demo box installed uptime-kuma in that window, and the practice is recorded in the catalog report.**