R-203 Part 2: a run that missed a MANDATORY directory is not a successful run (v0.197.0)
gates / gates (push) Successful in 8s
gates / gates (push) Successful in 8s
The gap was already detected and warned about, in Hungarian, naming the app and the folders -- that warning is what stopped the R-201 drill. The defect was that the run still reported `ok` beside it, and a warning standing beside a success is read as a success. last_status gains "incomplete": minted, because "ok" | "error" | "running" had nothing meaning "it ran, and this app is not fully protected". NOT "error" -- the rest of the run worked and what was captured is real, so SnapshotCount and the LastSuccess anchor still record it. Half a backup is not no backup. The gaps are now recorded STRUCTURALLY (offboxRunResult.mandatoryGaps), not only as prose, so the verdict has something to act on. It reaches the operator through the EXISTING per-run digest (backup_run_failures) rather than a new event type -- a new type is a two-repo change and the hub drops anything outside allowedEventTypes. The stat-filter gains the ClassMandatory check Tier 2 already had. It is a NO-OP today (TierOffsite admits mandatory only), so no customer-visible warning disappears -- demonstrated by widening the tier filter alone and watching the check hold the line. ANTICIPATED: calibre-web on demo-hp has exactly this gap, so its off-site status becomes incomplete the moment this ships. That is correct and is the point. Red-proofs: my first Scenario-C proof PASSED because the test only reached offboxCaptureSet while the mutation lives in runOffboxInternal -- a mutation the test cannot observe is not a red-proof, and the fix was the test. The run-level test now fails under both mutations (unreachable gap recording; unconditional ok).
This commit is contained in:
@@ -1,3 +1,65 @@
|
||||
## v0.197.0 — the app and its backup look in the same place, and "ok" means it (2026-08-04, R-203)
|
||||
|
||||
Found when the R-201 drill halted at its pre-wipe backup rather than wiping a machine: the run
|
||||
reported `ok` with three snapshots and the sentinel file was in none of them.
|
||||
|
||||
### Part 1 — one resolver, five callers
|
||||
|
||||
`appbackup`'s path helpers take a **namespace root**; five call sites passed a bare **drive** path.
|
||||
On an enrolled drive the two coincide — which is why this survived. On the system-data fallback (a
|
||||
**supported, named** arrangement: *"the SSD-only system-data fallback"*, `paths.go:26`) they differ by
|
||||
exactly the `felhom-data` segment, so the app bound `/mnt/sys_drive/userdata/media/books` while the
|
||||
off-site capture set looked for `/mnt/sys_drive/felhom-data/userdata/media/books`.
|
||||
|
||||
**The rule now has ONE expression** — `appbackup.NamespaceRootFor` / `IsEnrolledDrive`. There were
|
||||
already **two** copies and they differed: `backup.Manager.namespaceRoot` compared without
|
||||
`filepath.Clean`, `stacks.Manager.inGuest` with it, so a trailing slash from config would have flipped
|
||||
the mode in one package and not the other. Both now delegate.
|
||||
|
||||
Routed through it: `stacks/deploy.go` `withPathVars` → `${USERDATA_PATH}` (the live defect);
|
||||
`appexport/fabplan.go` + `export.go` (via a new `GetStackNamespaceRoot` provider method);
|
||||
`web/handlers.go` FileBrowser mounts (**latent** — the system drive is deliberately never a
|
||||
registered `StoragePath`, so it is the identity today); and `stacks/delete.go`'s `ExportDataMounts`.
|
||||
|
||||
`ComputeFabBuckets` now receives the namespace root, which is what `ComputeCaptureSet` has always
|
||||
received — so the export's classified paths and the backup's capture set describe the same
|
||||
directories by construction rather than by coincidence.
|
||||
|
||||
**The census found FIVE sites, not the four the spec named.** The fifth is the FileBrowser mount
|
||||
builder — the customer's own file browser would have shown the wrong directory on a non-enrolled path.
|
||||
|
||||
**`ExportDataMounts` lives in `delete.go` and is NOT a delete path.** Its only production caller is
|
||||
the `.fab` export adapter; nothing deletes on its result. The delete path's own guard,
|
||||
`ProtectedHDDPaths`, is layout-agnostic by construction — it protects **both** `<hdd>/…` and
|
||||
`<hdd>/felhom-data/…` — so deletion was never affected. That note is now in the function's doc comment,
|
||||
and the change shipped as its own commit anyway.
|
||||
|
||||
### Part 2 — a run that missed a mandatory directory is not a successful run
|
||||
|
||||
The gap was already **detected**, and warned about, in Hungarian, naming the app and the folders —
|
||||
that warning is what stopped the drill. The defect was that the run still reported **`ok`** beside it,
|
||||
and a warning standing beside a success is read as a success.
|
||||
|
||||
`last_status` gains **`incomplete`**: minted, because the existing vocabulary (`ok` | `error` |
|
||||
`running`) had nothing meaning *"it ran, and this app is not fully protected"*. **Not `error`** — the
|
||||
rest of the run worked and the data captured is real, so `SnapshotCount` and the `LastSuccess` anchor
|
||||
still record it. Half a backup is not no backup, and reporting it as none would be its own lie.
|
||||
|
||||
It reaches the **operator** via the existing per-run digest (`backup_run_failures`), not only the
|
||||
page: a new event type would be a two-repo change and the hub drops anything outside
|
||||
`allowedEventTypes`. The Hungarian customer warning is unchanged.
|
||||
|
||||
The stat-filter gains the `ClassMandatory` check Tier 2 already had (*"optional-missing is silent"*).
|
||||
**It is a no-op today** — `TierOffsite`'s `tierKeeps()` admits mandatory only — so **no customer-visible
|
||||
warning disappears**. Demonstrated: widening the tier filter alone keeps the tests green *because of
|
||||
this check*; widening it and removing the check makes an optional gap start reporting.
|
||||
|
||||
### Anticipated live effect
|
||||
|
||||
**`calibre-web` on demo-hp has exactly this gap.** Its off-site status becomes `incomplete` and the
|
||||
operator digest fires. That is correct — it genuinely is not fully backed up — and it is the fix
|
||||
telling the truth for the first time, not a regression.
|
||||
|
||||
## v0.196.0 — the recovered key installs itself (2026-08-04, R-200 plumbing half) — MinAgent 0.125.0
|
||||
|
||||
`--recover-offsite-install` is the sibling of `--recover-offsite-check`: same fetch → unseal → extract
|
||||
|
||||
Reference in New Issue
Block a user