diff --git a/documentation/audits/CAMPAIGN-10-closeout-2026-08-02.md b/documentation/audits/CAMPAIGN-10-closeout-2026-08-02.md new file mode 100644 index 0000000..b8525e5 --- /dev/null +++ b/documentation/audits/CAMPAIGN-10-closeout-2026-08-02.md @@ -0,0 +1,135 @@ +# Campaign 10 — closeout: two last measurements, then teardown (2026-08-02) + +**Companion doc** (my call, per the task) to `CAMPAIGN-10-two-storage-soak-2026-07-31.md` and +`SPIKE-recovery-unit-space-2026-08-02.md`, rather than appending — those two are already long and this +session answers two specific questions and then dismantles the rig. + +**Part 1 headline:** Q1 **lowers** R-158's rank — the failure *is* customer-visible, on one page, and it +persists and clears correctly. Q2 **clears** `ValidateDump` of shipping bad dumps, and simultaneously +**kills C2's proposed gate** on its current predicate. + +--- + +## Q1 — when a backup refuses for lack of space, what does the customer see? + +Method: ballast `/mnt/sys_drive` to 699 MB free (homebox's unit needs 2.3 GB), run the real backup +through `POST /api/backup/run`, and read every customer-facing surface through the real UI endpoints. + +### The five points + +| # | Surface | Result | +|---|---|---| +| 1 | **`/backups` overview** | **SHOWS IT** — `✗ Adatmentés sikertelen` | +| 2 | **`/backups/apps`** (per-app) | **shows nothing** — Homebox tier-1 reads `Utolsó: 3 perce` | +| 3 | `/stacks/homebox` (app detail) | **shows nothing** | +| 4 | `/` dashboard · `/launcher` | **show nothing** — no banner | +| 5 | `GET /api/backup/status` | `db_dump.success: **false**` | +| — | hub event / email | **none** (R-158) | + +Exact copy, as rendered on `/backups`: + + ✗ Adatmentés sikertelen + +### The point-5 question — retry, stay failed, or quietly go green? + +**It retries, stays failed, and does not lie.** Measured across three runs: + +| run | condition | `status.success` | `/backups` shows failure | unit mtime | +|---|---|---|---|---| +| 1 | constrained | false | **true** | 07:30:50 | +| 2 | still constrained | false | **true (persists)** | 07:30:50 (unchanged) | +| 3 | space freed | **true** | **false (cleared)** | **07:37:38 (fresh)** | + +**`/backups/apps` is honest, not deceptive.** Its `Utolsó: 3 perce` tracks the *unit's real mtime* +(07:30:50), not the failed run's timestamp — so it correctly reports the age of the last good unit. It +simply never says the latest attempt failed. **This is NOT the R-156 family**: no surface claims a +fresh backup over a stale unit. + +### R-158's rank — LOWERED, with the reason + +> **R-158 is a notification gap, not a silent-failure defect. It ranks BELOW R-157.** + +Because the customer *can* find out by looking, and the indicator behaves correctly — it persists while +failing and clears on recovery. What is missing is that **nothing pushes it**: no hub event, no email, +no dashboard banner. And the discovery path is poor in a specific way worth fixing: **the per-app +backup page — the natural place to ask "is homebox backed up?" — shows no failure at all**, while the +answer lives one page away on the overview. + +R-157 outranks it because R-157's mechanism B leaves a `deployed: true` app **not running**, with the +deadapp check reporting `0 currently down` — silent on *every* channel, including the one the customer +would look at. + +--- + +## Q2 — was `ValidateDump`'s warning correct? + +The warning, seen twice: + + [WARN] [backup] ValidateDump: …/rallly-postgres.sql is structurally valid (32 tables) but its + accounts table has NO rows — the dump may predate the customer's data + +### It was factually correct, and the dump was NOT deficient + +The live database genuinely had no accounts. Every non-empty table, from `pg_stat_user_tables`: + + _prisma_migrations | 129 + cc_proof | 82 <-- the campaign's own canary + instance_settings | 1 + accounts | 0 + +**An empty table proves nothing either way**, so — as the task required — an account was **seeded** and +the backup re-run: + + INSERT users (id='c10user', …) -> 1 row + INSERT accounts (id='c10acct', …) -> 1 row + +Result: + + [INFO] [backup] DB dump: rallly-postgres → rallly-postgres.sql (100.6 KB, 396ms, 32 tables) + (no ValidateDump line at all — the warning STOPPED) + + grep -c c10acct = 1 grep -c c10user = 2 + dump size 102 766 B -> 103 029 B + +**Verdict: the validator is sound and no bad dumps are shipping.** It correctly detected an empty +`accounts` table, and the moment real data existed the dump captured it and the warning cleared. + +*(Seeded data disclosed: one `users` row and one `accounts` row, both `c10*`-prefixed, on a scratch +customer that this session then deletes.)* + +### What it does to C2's ordering — the proposed gate is DEAD on this predicate + +C2 proposed dropping the DB volume tar (halving DB-app units, and closing the D5/R-127(b) initdb-skip +password trap) **once `ValidateDump` is promoted from a warning to a gate**. That ordering does not +survive Q2: + +> **A fresh appliance legitimately has zero accounts.** Gating on "the `accounts` table has rows" would +> **block the backups of every new customer** until someone registers a user — refusing good backups +> for the entire period a box is most fragile. + +The validator's *fact* is right and its *inference* ("may predate the customer's data") is wrong: there +was no data to predate. So the dependency chain is longer than C2 assumed: + +1. a **sound gate predicate** first — a comparison against the **live** DB (per-table row counts in the + dump vs `pg_stat_user_tables`) rather than an absolute expectation about one table; +2. **then** the warn→gate promotion; +3. **only then** the tar-drop. + +**Until (1) exists, the DB volume tar stays load-bearing** — not because dumps are bad, but because +nothing can yet *prove* a given dump is good. + +--- + +## Findings + +**No new `R-n`.** Register grepped (`documentation/backlog/*.md`) for `ValidateDump`, `accounts table`, +`Adatmentés sikertelen`, and the per-app backup page: no existing rows, and neither Q1 nor Q2 produced +a defect that is not already covered. + +- **Q1 does not mint an R** — it *re-ranks* R-158 (filed in the spike) and adds the specific + improvement worth making: surface the per-app failure on `/backups/apps`, where a customer looks for + a specific app. +- **Q2 does not mint an R** — it *removes* a proposed change (the gate) and lengthens C2's dependency + chain. Recording that a design is wrong is the spike rule working. + +Nothing fixed, per the fences.