acb44672fd
Layer 1: guest 9301 destroyed, vm-9301-disk-0 removed, pct list clean. Layer 2: local-lvm available returned to 258 702 410 KiB — EXACTLY the pre-run baseline (29.27%). Layer 3: no hub record was ever created (the scratch guest ran no controller and was never enrolled, by design); confirmed absent from /configs and /hosts after teardown. papra referral updated: c10-soak was torn down by the other session mid-run and papra disappeared from hub telemetry with grafana, homebox and rallly — Campaign 10's four discriminator apps (§A4). So papra's one deployment was on c10-soak. The fix is STILL not applied: that is absence evidence, demo-hp is fenced, and applying it wrongly is irreversible while leaving it is a one-line push. The single command that settles it is recorded.
45 lines
3.6 KiB
Markdown
45 lines
3.6 KiB
Markdown
# REPORT — catalog persistence sweep (2026-08-02)
|
|
|
|
**Question:** does every app's data actually land in a folder the template preserves?
|
|
**Answer, over all 53 templates:** **43 CLEAN · 3 BROKEN · 7 UNDETERMINED.**
|
|
|
|
Full report, per-app evidence, proofs and proposed register entries:
|
|
**`audits/persistence-sweep-2026-08-02/README.md`**.
|
|
|
|
## What was found
|
|
|
|
| app | defect | disposition |
|
|
|---|---|---|
|
|
| **gramps-web** | Mounted `/app/data` (a path the app never writes), `/app/media`, `/tmp`. Its accounts database AND **its family tree** landed in the container's writable layer — destroyed by any redeploy, absent from every backup, while `gramps_data` was tarred nightly as an empty directory. | **FIXED** — persists the 8 paths the image's own environment names. Survives-a-redeploy proven: `users.sqlite` and the family-tree files byte-identical, same inode. |
|
|
| **wishlist** | Mounted `wishlist_data:/data`, another path the app never writes. `prod.db` landed in the **anonymous** volume from the image's `VOLUME` directive — never in `ResolveDockerVolumeNames`, so never backed up, and orphaned by a redeploy. | **FIXED** — mounts `/usr/src/app/data` + `/usr/src/app/uploads`. `prod.db` byte-identical with the same inode across a redeploy. |
|
|
| **papra** (R-156) | Mounts `papra_data:/app/data`, which is root-owned, unwritable by uid 999, and **does not exist in the papra image at all**; the database is written to `/app/app-data/db/`. | **NOT FIXED — referred to the operator.** Fix prepared and proven in the scratch guest (current: `db.sqlite` differs after a redeploy, so a real account created via the API is lost; fixed: byte-identical, it survives). It was deployed on one box; that box (`c10-soak`) was torn down by the other session mid-run and papra now appears nowhere in hub telemetry — but that is *absence* evidence, and `demo-hp` is fenced. One command settles it: §6.1a. |
|
|
|
|
7 UNDETERMINED are listed with reasons and **never folded into CLEAN** (§4.2): 4 apps write nothing
|
|
until a user completes setup, 3 could not be started.
|
|
|
|
## What was built
|
|
|
|
**`scripts/check-volume-persistence.py`** — the third catalog gate and the only RUNTIME one. This
|
|
defect class is **invisible to static analysis**, measured rather than assumed: a static audit of
|
|
all 53 composes reports the catalog clean *and reports papra clean*. So the gate deploys each
|
|
template, exercises it into writing data, and compares where the data landed with what is mounted.
|
|
Exit 0 clean / **1 REFUSED** / 2 undecided; UNDETERMINED is never a pass.
|
|
|
|
It **refuses to report at all** unless it has just re-proven itself in both directions against two
|
|
canary templates that differ only in which path the volume mounts at — so every run carries a live
|
|
demonstration of R-156 and of its fix. 41 fixture tests driving `check()` (the function `__main__`
|
|
calls); every rule red-proofed.
|
|
|
|
Registered in `CLAUDE.md` and `REUSE.md`. **Enforcement is convention, not CI** — the catalog repo
|
|
has no CI of any kind. Raising that is proposed as R-160.
|
|
|
|
## Method notes worth carrying forward
|
|
|
|
- **Seven detector defects were found by adjudicating flagged apps rather than trusting the label**,
|
|
and six of them made a *correct* app look broken. Every one came from a rule identifying data by
|
|
its **name** rather than by evidence of what it is. Detail: §3.5.
|
|
- The sweep was **restarted from the first app twice** rather than merge a matrix built by more than
|
|
one version of the rules.
|
|
- No `docker exec` anywhere — Campaign 7 §1.1's OCI-error-to-stdout trap.
|
|
- No hub record was created; the hub was read-only throughout. Teardown, all three layers: §9.
|