F-S2 + F-S3: compose-derived appdata dir resolution (v0.131.0)

The controller assumed an app's HDD appdata dir is always appdata/<stackName>.
paperless-ngx writes appdata/paperless (stack paperless-ngx), so every consumer
keying by stack name silently missed it via a stat-and-skip. One canonical
resolver appbackup.AppDataDirNames derives the real dir name(s) from the app's
compose ${HDD_PATH} binds; all consumers use it.

- F-S2 (tier-2): RunTier2 mirrors the resolved appdata/<name> (paperless docs
  got NO tier-2 copy before). Tier2Info size + RestoreTier2Files live dir use it.
  WARN when a declared appdata dir is absent. New tier2Mirror seam.
- F-S3 (migrate, NEW): all six per-app appdata legs (collision/size/copy/verify/
  cleanup/skip-set) now loop resolved names. scope="app" migration of paperless
  previously copied nothing and left an empty media dir (scope="all" was saved by
  the merge walk). WARN on missing declared dir in the copy leg.
- Multi-dir (N>1) refusal: tier-2 backup/info/restore refuse loudly (Hungarian);
  migrate supports N. No catalog app hits it today; lifted by Task 3.
- Display: storage page sums resolved dirs.
- Truth repair: the v0.130.0 "tier-2 copies the namespace wholesale" claim is
  false; corrected in CHANGELOG + main.go export-adapter comment.

+9 tests; red-proofs RP-1..RP-5 all confirmed. Controller-only, no agent/hub
coupling. Task 1 of the backup-classification-redesign arc.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A45Qop8YY8tS94bz63LFne
This commit is contained in:
2026-07-14 17:45:53 +02:00
parent b42904bbab
commit 68f0e0cf5c
16 changed files with 765 additions and 44 deletions
+43
View File
@@ -1,5 +1,48 @@
## Changelog
### v0.131.0 — F-S2 + F-S3: compose-derived appdata dir resolution (paperless-ngx → appdata/paperless) (2026-07-14)
The controller assumed an app's HDD appdata dir is always `appdata/<stackName>`. paperless-ngx binds
`${HDD_PATH}/appdata/paperless/...` — stack `paperless-ngx`, dir `paperless` — so every consumer that
keyed by stack name silently missed it via a stat-and-skip. One canonical resolver
(`appbackup.AppDataDirNames`) now derives the real dir name(s) from the app's compose `${HDD_PATH}`
binds, and all consumers use it. Task 1 of the backup-classification-redesign arc
(`felhom.eu/documentation/audits/SPIKE-backup-classification-2026-07-14.md`), deliberately independent
of the classification schema.
- **F-S2 (spike-proven live) — tier-2 backup/info/restore.** `RunTier2` now mirrors the resolved
`appdata/<name>` dir, so paperless documents get their off-drive copy (previously: the appdata leg's
`os.Stat` gate skipped `appdata/paperless-ngx`, which never existed — **silent, no copy**).
`Tier2Info`'s size + the SSD-headroom guard use the resolved dir. `RestoreTier2Files` targets the
resolved live dir (was restoring into a wrong/empty `appdata/paperless-ngx`). A `[WARN]` now fires
when the compose DECLARES an appdata dir but it is absent on disk (the silence that hid F-S2). Every
rsync leg goes through a new `tier2Mirror` seam (prod behavior unchanged).
- **F-S3 (NEW, found this session) — scope="app" migration.** `migrate.go` keyed all six per-app
appdata legs (collision check, source-size, copy, verify, cleanup, skip-set) by stack name. For
**scope="app"** there is no merge walk, so migrating paperless-ngx copied nothing, "verified"
vacuously, flipped `HDD_PATH`, and the app came up with an **empty media dir**. (scope="all" was
saved by the merge walk — data safe, accounting off.) All six legs now loop the resolved name(s);
the copy leg WARNs on a missing declared dir.
- **Multi-dir refusal (defensive; no catalog app hits it today).** An app resolving to N>1 distinct
appdata dirs is refused loudly by tier-2 backup/info (honest `no_target` status +
`"az alkalmazáshoz több adatkönyvtár tartozik — a 2. mentés jelenleg alkalmazásonként egy könyvtárat
támogat"`) and tier-2 file-restore (refused BEFORE the app is stopped). Migrate supports N naturally.
This limitation is lifted by the tier-policy engine (Task 3).
- **Display.** The storage-detail page sums the resolved appdata dir(s), so paperless-ngx shows a
non-empty size.
- **Truth repair.** The v0.130.0 entry below states "The scheduled/tier-2 backup path was NOT affected
(it copies the felhom-data namespace wholesale)" — **that sentence is false** and is left in place
only as the historical record it corrects here: tier-2 copies the recovery unit + the resolved
`appdata/<name>` dir(s) ONLY (never the userdata tree — F-S1, unaddressed here — and never the
namespace wholesale). The `main.go` export-adapter comment that repeated the claim is fixed in code.
Scope guards: destination layout unchanged (`<destBase>/appdata` stays flat); no userdata copying at
any tier (F-S1 is the classification redesign's, not this task's); `ExportDataMounts` / `.fab` /
offbox untouched. Tests: +9 (resolver table incl. dedupe/foreign-drive/whole-root; RunTier2
paperless/legacy/multi-dir; Tier2Info + restore refusals + resolved live dir; scope="app" paperless
migration). Red-proofs RP-1..RP-5 all confirmed (resolver, RunTier2 leg, restore dst, migrate copy
leg, N>1 guard). Controller-only; no agent/hub coupling; MinAgent unchanged.
### v0.130.0 — CRITICAL C6B-F1: hollow .fab export (three compounding defects) + C6B-F2 share-removal guard (2026-07-14)
CAMPAIGN-6B surfaced that `.fab` export produced a **config-only, data-free bundle** for 12/13