Files
felhom.eu/REPORT.md
T
2026-09-23 18:14:38 +02:00

118 lines
8.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ADDENDUM (same session) — R-649, the operator's ruling: a failed install removes what it started (controller v0.266.0)
Ruling: "On failed install, the controller should clean up the containers." Built in `964ae75`:
`runComposeDeploy`'s failure branch runs `compose down` (no `-v` — volumes kept) before recording
not-deployed. Red-proof: the `down` removed → `TestR649_AFailedInstallRemovesWhatItStarted` fails with
`calls=["up -d"]`. Live on 9202 (`audits/r649-2026-09-23/`): outline with a never-healthy redis (drill
template only) failed after 102.7 s → 0 containers left, 3 volumes kept, log line
`the failed deploy's containers were removed (volumes kept) — R-649`. Floor **0.266.0** read back; both demo
boxes on it in 20 s. R-649 closed; **open rows 331 → 330.** Teardown: outline removed, images by name, config
identical to the saved copy, live catalog, drill reset. Nothing else touched.
---
# REPORT — clean-up evening: R-634's cause, held apps, the OOM storm (controller v0.265.0, hub v0.121.0)
2026-09-23 (late evening). Baselines verified live: controller `0a3026180ae8` (v0.264.0), agent
`d9864a94bf62`, felhom.eu `21f17ed32bdc` (hub v0.120.0), catalog `cfcfe5278428` — as the brief said.
Repos touched: **felhom-controller** (v0.265.0 `0054d4b`), **felhom.eu** (hub v0.121.0, manifest, docs,
register, evidence), **admin/app-catalog-drill** (drill commits, reset to live `main`).
**felhom-agent, app-catalog-felhom.eu: untouched.** Architecture read and named:
`09-update-architecture.md` §6.1/§6.1a/§6.4, `08-alarm-ladder.md` §4/§6.2, `02-controller-module-map.md`
(stacks ↔ backup seam). Evidence: `documentation/audits/cleanup-2026-09-23/README.md`. **Method:
endpoint-level** (the endpoints the UI invokes; no browser).
---
## 1. Not done, or changed
| item | state |
|---|---|
| Part 1 — R-634 | **cause found in ~25 min (cap 2 h), fixed, proven live.** One design question left → **R-649** (operator) |
| Part 2 — R-625 | **done.** The Update button was ALREADY hidden for a held app on the list (since v0.238.0); the defect was the badge only. Now pinned by a test with a positive control. The badge applies to **both** hold kinds (a failed restore holds the app stopped too; the way back is also a restore) — the brief said `update_failed` only |
| Part 3 — R-636 | **done, and the brief's rule changed shape:** "the same key re-fires ≥ 20 times" cannot work — `OOMKilled` is sticky, so the key re-fires every 30 s after ONE kill (a hiccup would storm after 10 min). The controller now counts real kills (kernel `oom_kill`); the 20-in-30-min threshold is applied to THAT |
| Part 3 — hub live `curl` | **not done:** posting a synthetic event with a real customer's key would put a fabricated alarm in the operator's record; the hub side is proven by unit tests |
| Part 4 — R-647 | **done** |
| Part 5 — R-648 | **done — no per-app endpoint exists**, so the drill presses nothing |
| Part 6 — floor | **raised to 0.265.0** — every live proof passed |
| a mistake of mine | my first draft of the R-634 backup test drove the real dump and **created an empty volume `outline_outline_data` on DooPlex**; my inspection of it pulled `alpine:3.20`. Both verified new and unused, removed by name. Tests rewritten on seams. **R-650** filed (the class is unguarded) |
**Claims in the brief, checked:**
1. *"sparkyfitness reproduces alone at today's pin"* — **wrong.** Deployed alone in 47.3 s on v0.264.0
(`10-*`). The 2026-09-22 "alone" walk pressed the whole-box backup itself — the same race.
2. *"a whole-box backup touches a DEPLOYING stack at all (never read)"* — **true, and it is THE cause.**
3. *"20 kills in 30 minutes separates a storm from a hiccup"* — **true only if kills are counted, and they
were not.** RomM's real rate: 4,530 kills in 6 h ≈ **375 per 30 min**; a hiccup is 1–3. 20 kept, applied
to the kernel's counter. Live: 21 kills 2.5 min after start → one storm.
4. *"a per-app backup endpoint exists"* — **wrong.** `router.go` has `/backup/run` and `/backup/tier2`
only; the per-app backup (`RunAppBackupNow`) is reachable only through the guarded update.
## 2. R-634 — the mechanism, at `file:line`
1. `stacks/deploy.go:395` — `DeployStack` sets the in-memory `Deployed=true` at ACCEPT (no-stale-button UX).
2. `cmd/controller/main.go:2556` — `ListDeployedStacks` filtered on that flag only → a deploying app is in
every backup leg's list.
3. `backup/backup.go:693` → `DumpAppVolumesSafe` — `StopStack` (`compose down`) in the middle of the deploy,
tar of half-made volumes, `StartStack` (a SECOND `compose up -d`, `backup.go:918`).
4. Both `up` calls fail together; `stacks/deploy.go:421-435` writes `Deployed=false` without asking whether
containers exist. Which `up` wins decides the end: running under „not deployed" (2026-09-22) or
`Created` (tonight, 14:39:48). **No deploy time limit exists** — shape (i) ruled out.
**Fix:** deploying apps leave the list; the volume leg re-asks right before the stop (SKIP, never FAIL);
`StopStack`/`StartStack` refuse a deploying stack (`ErrStackDeploying`) — the backstop for quiesce,
restore, export, storage and the Stop button, which all had the same exposure.
**Live (`32-*`, `33-*`):** outline's image removed so the deploy must pull, backup at +5 s: the backup ran
15:23:07–15:23:31, stopped gokapi, paperless-ngx and privatebin, **never outline**; `deployed
successfully (took 48.9s)`. The first fix run is kept and labelled NOT A RACE (images cached, 6.3 s).
## 3. Red-proofs (each seen failing, mutation restored, `=== RUN` checked)
| # | mutation | test → failure |
|---|---|---|
| 1 | backup skip removed | `TestR634_VolumeLegNeverStopsADeployingApp` — `dumped=[outline]` |
| 2 | StopStack guard removed | `TestR634_StopAndStartRefuseADeployingStack` — `exit code -1` instead of `ErrStackDeploying` |
| 3 | held badge removed (v0.264.0's shape) | `TestR625_AHeldAppSaysStoppedRestoreNeeded` — badge missing, „Frissítés elérhető" present |
| 4 | reader funcs off | `TestR647_AReaderInTheOtherLanguageReadsTheHoldInTheirs` — English hold for the Hungarian reader |
| 5 | Hungarian phrase wired back | `TestR647_HeldEventCarriesTheCopyHoldsKey` |
| 6 | health status passed as severity | `TestR647_DisabledHealthChangeNamesTheSeverity` — `severity warn` |
| 7 | escalation removed | `TestR636_TwentyKillsInThirtyMinutesIsOneStorm` — `kills=20 … storms=0, want 1` |
| 8 | counter never read | `TestR636_ScanReadsTheCounterOfFlaggedContainersOnly` — `Kills:-1` |
| 9 | hub: not operator-only | `TestAppOOMStormIsAllowlistedAndOperatorOnly` — "must be operator-only" |
| 10 | hub: not allow-listed | same test — "must be in allowedEventTypes" |
| 11 | hub: no per-app cooldown | `TestR389_TheAllowListHasExactlyOneMember` |
Also pinned without a mutation run: 19 kills → no storm, 200 → one, one hiccup over 6 h → none, 20 kills
over 3 h → none, an unreadable counter → never. Gates: controller `go test ./...` rc=0 +
`controller_gates.py` rc=0; hub `go test ./...` rc=0 + `repo_gates.py` rc=0. Parity: `app_info_deployed`
and `stacks_full` regenerated — measured diff **exactly one line each, the new held badge**.
## 4. Live proofs in both languages (9202)
- **R-625 + R-647 (1)** (`44-*`), every box/reader pair: hu reader „Megállítva — visszaállítás szükséges",
title „A frissítés nem sikerült, és az automatikus visszaállítás sem."; en reader "Stopped — restore
needed", title "The update did not succeed, and the automatic undo did not either."; with the box in
ENGLISH the Hungarian reader reads Hungarian (page and API). No vikunja Update button; other apps keep
theirs (control). `POST …/update` → **409 `held`**.
- **R-636** (`45-*`, `46-*`): romm at 320M — kills 8, 13, **21 → `OOM STORM — 21 kills in 30 min (limit
320M, peak 320M)`** + `DROPPED event app_oom_storm (severity error)`; one storm line at 49 kills.
- **R-648** (`43-*`): vikunja's update ran its own `backing-up` phase.
## 5. Rows
**Closed (5):** R-634, R-625, R-636, R-647, R-648. **Opened (2):** R-649 (operator question — a deploy that
fails on its own), R-650 (tests that reach real docker act on DooPlex). **Open rows 334 → 331.**
`09` §6.4 parts 8 and 9 marked SHIPPED; `08` §6.2 has the storm rung and the two per-app grains.
## 6. Floor
0.265.0 / MinAgent 0.131.0, read back from the hub (`min_controller_version=0.265.0`,
`min_agent=0.131.0`; `managed floor SERVED` for demo-felhom and demo-hp); both on 0.265.0 in 20 s.
## 7. Teardown — three layers
- **machine (9202):** all four test apps removed through the product; romm's drive folder by name (R-442
fence, as always); 0 volumes, 0 undo copies; test images removed by name where unused; config identical to
the saved copy; live catalog; language `hu`; recorders stopped. 9201 not touched.
- **host:** nothing provisioned. **hub:** v0.121.0 + floor (planned). **DooPlex:** the volume and image
above, removed. **drill repo:** reset to live `main`. Password files deleted from the scratchpad.