Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
15 KiB
REPORT — every app checked again with the fixed persistence check; the remove dialog tells the truth; licence rulings; v0.288.0 + golden (2026-10-02, afternoon)
Architecture read: 07-backup-architecture.md §6 (tiers) and §6.5 (kept data — now with decision 67), 09 §3 decisions
36, 65, 66, 67. Evidence root: documentation/audits/persistence-sweep-2026-10-02/.
| Part | State | One line |
|---|---|---|
| A — the re-sweep (R-801) | DONE | R-788 rule + the app's own fixture seed in the gate; all 58 on the bench; 0 BROKEN; papra's start-up defect found and fixed (R-803). |
| B — the remove dialog (R-800) | DONE | v0.288.0: userdata_kept in the dialog data and both results; the sentence in both languages; red-proofed; live on 9202. |
| C — release, floor, golden | DONE | Floor 0.288.0 (min_agent 0.131.0), both demo boxes in 20 s; golden 0.288.0 baked, round-tripped, vouched; currency gate OK. |
| Rulings | RECORDED | 09 §3 66 (licences) and 67 (userdata stays); 07 §6.5; STATUS "Before the first paying customer". |
Sweep headline (58 templates): before (2026-08-02, 53 apps, + the new apps' records) CLEAN 38 · UNDETERMINED 11 · BROKEN 4 — every one of those decided by start-time writes alone → now CLEAN 40 · UNDETERMINED 18 · BROKEN 0 · INCONCLUSIVE 0 (39 · 19 in the sweep; papra CLEAN after its fix). The August BROKEN four (gramps-web, papra, privatebin, wishlist) were fixed in Campaign 10; none is BROKEN now. No data-loss finding: no app writes outside a preserved folder.
Claims in the brief, checked
- "Every stored verdict came only from start-time writes" — RIGHT for the August sweep: 0 of 53 probes had sent a
request (
ports: [],exercise: []in everyprobe.json). Not quite for today's morning runs of MeTube and Grimmory, which ran after the R-801 fix. - "The upgrade fixtures can serve as exercisers" — PARTLY. 51 of 58 apps have one; the gate called it for 16 apps; it decided 2 (metube, privatebin). 13 seeded fine and stayed UNDETERMINED, because the seeds write only to the database — upload / media / cache / redis volumes stay empty (R-807). plex's seed cannot run (a plex.tv claim token).
- "Userdata is never touched by a remove" — RIGHT, and it was already true before this session: the remove reads
${HDD_PATH}binds only, pinned since R-442 (TestRemoveStack_R442_UserdataConventionFromPerAppPath). What was wrong was only that nothing SAID so. Measured again on 9202 (the video stayed). - The R-788 decoy as written ("an app that writes nothing → exit 2, red on the old rule") — the old rule already gave
exit 2 for an app that writes nothing. The real gap was an app that writes SOMEWHERE while a declared volume stays
empty (old: CLEAN). The decoy was built for that shape and seen red (
A/RP-R788-empty-volume.txt).
Part A — the sweep
Gate changes (catalog): an empty declared volume after the exercise is UNDETERMINED (R-788, red-proofed; three old tests
pinned the old rule and changed with it); when a volume stays empty the gate calls the app's own upgrade-fixture seed
(one call through upgrade_fixtures / upgrade_boxport, as upgrade-test.py does — it replaces the morning's
MeTube-only exerciser). Bench 9401 on demo-hp, 200G, swap 0, Docker Hub logged in, 8 batches; full table with reasons:
A/sweep/TABLE.md; verdict lines in the catalog: audits/persistence-sweep-2026-10-02/verdicts.txt.
| app | before | now | what made it write | notes |
|---|---|---|---|---|
| actualbudget | CLEAN (08-02, start writes) | CLEAN | GET exercise (or start) | - |
| adventurelog | CLEAN (08-02, start writes) | CLEAN | GET exercise (or start) | - |
| audiobookshelf | CLEAN (08-02, start writes) | CLEAN | GET exercise (or start) | - |
| bentopdf | UNDETERMINED (08-02, start writes) | UNDETERMINED | deep pass | nothing was written to any mount and nothing data-classified in any writable layer — the app produced no data |
| bookstack | CLEAN (08-02, start writes) | CLEAN | GET exercise (or start) | - |
| calcom | CLEAN (08-02, start writes) | CLEAN | GET exercise (or start) | - |
| calibre-web | CLEAN (08-02, start writes) | CLEAN | GET exercise (or start) | calibre-web: /app/calibre-web-automated/empty_library — database file(s) touched but byte-identical to the ima |
| claper | UNDETERMINED (08-02, start writes) | UNDETERMINED | fixture seed OK — `fixture Claper: seed OK — claper: see | claper: declared volume /app/priv/static/uploads is EMPTY after the exercise — the app was not shown to write |
| code-server | CLEAN (08-02, start writes) | CLEAN | GET exercise (or start) | - |
| crafty-controller | CLEAN (08-02, start writes) | UNDETERMINED | fixture seed OK — `fixture Crafty: seed OK — crafty: POS | crafty-controller: declared volume /crafty/servers is EMPTY after the exercise — the app was not shown to writ |
| dawarich | CLEAN (10-01, start writes) | UNDETERMINED | fixture seed OK — `fixture Dawarich: seed OK — dawarich: | dawarich-redis: declared volume /data is EMPTY after the exercise — the app was not shown to write where the t |
| docmost | UNDETERMINED (08-02, start writes) | UNDETERMINED | fixture seed OK — `fixture Docmost: seed OK — docmost: / | docmost: declared volume /app/data/storage is EMPTY after the exercise — the app was not shown to write where |
| emby | CLEAN (08-02, start writes) | CLEAN | GET exercise (or start) | - |
| ghost | CLEAN (08-02, start writes) | CLEAN | GET exercise (or start) | - |
| gitea | CLEAN (08-02, start writes) | CLEAN | GET exercise (or start) | - |
| glance | UNDETERMINED (08-02, start writes) | CLEAN | GET exercise (or start) | - |
| gokapi | CLEAN (08-02, start writes) | CLEAN | GET exercise (or start) | - |
| grafana | CLEAN (08-02, start writes) | CLEAN | GET exercise (or start) | - |
| gramps-web | BROKEN (08-02, start writes) | UNDETERMINED | fixture seed OK — `fixture GrampsWeb: seed OK — gramps-w | gramps-web: declared volume /app/secret is EMPTY after the exercise — the app was not shown to write where the |
| grimmory | CLEAN (10-02, after R-801) | CLEAN | GET exercise (or start) | grimmory: this container's mounts are all empty while it created entries in ['/', '/app', '/etc'] — benign whe |
| home-assistant | CLEAN (08-02, start writes) | CLEAN | GET exercise (or start) | - |
| homebox | CLEAN (08-02, start writes) | CLEAN | GET exercise (or start) | - |
| homepage | CLEAN (08-02, start writes) | CLEAN | GET exercise (or start) | - |
| immich | UNDETERMINED (08-02, start writes) | UNDETERMINED | fixture seed OK — `fixture Immich: seed OK — immich: see | immich-machine-learning: declared volume /cache is EMPTY after the exercise — the app was not shown to write w |
| jellyfin | CLEAN (08-02, start writes) | CLEAN | GET exercise (or start) | - |
| karakeep | CLEAN (10-01, start writes) | CLEAN | GET exercise (or start) | - |
| kimai | CLEAN (08-02, start writes) | CLEAN | GET exercise (or start) | - |
| komga | CLEAN (08-02, start writes) | CLEAN | GET exercise (or start) | - |
| mealie | CLEAN (08-02, start writes) | CLEAN | GET exercise (or start) | - |
| metube | CLEAN (10-02, after R-801) | CLEAN | fixture seed OK — `fixture MeTube: seed OK — metube: the | - |
| n8n | CLEAN (08-02, start writes) | CLEAN | GET exercise (or start) | - |
| navidrome | CLEAN (08-02, start writes) | CLEAN | GET exercise (or start) | - |
| nextcloud | CLEAN (08-02, start writes) | CLEAN | GET exercise (or start) | - |
| onlyoffice | CLEAN (08-02, start writes) | CLEAN | GET exercise (or start) | onlyoffice: writable-layer writes at /var/www/onlyoffice/documentserver/sdkjs-plugins/{07FD8DFA-DFE0-4089-AL24 |
| opengist | CLEAN (08-02, start writes) | CLEAN | GET exercise (or start) | - |
| outline | CLEAN (08-02, start writes) | UNDETERMINED | fixture seed OK — `fixture Outline: seed OK — outline: s | outline: declared volume /var/lib/outline/data is EMPTY after the exercise — the app was not shown to write wh |
| paperless-ngx | CLEAN (08-02, start writes) | CLEAN | GET exercise (or start) | - |
| papra | BROKEN (08-02, start writes) | CLEAN after the fix (768M); UNDETERMINED at 256M (OOM) | nothing answered HTTP | R-803: OOM-killed at 256M; fixed |
| plant-it | UNDETERMINED (08-02, start writes) | UNDETERMINED | start only (no routed port, no HTTP exercise) | no containers created (compose up rc=18: se/plant-it, repository does not exist or may require 'docker login': |
| plex | CLEAN (08-02, start writes) | UNDETERMINED | fixture seed FAILED — `fixture NoRoute: seed returned nothin | plex: declared volume /transcode is EMPTY after the exercise — the app was not shown to write where the templa |
| privatebin | BROKEN (08-02, start writes) | CLEAN | fixture seed OK — `fixture PrivateBin: seed OK — private | - |
| radarr | CLEAN (08-02, start writes) | CLEAN | GET exercise (or start) | radarr: writable-layer writes at /media (path suggests state, no database signature — judgement needed): ['mov |
| radicale | CLEAN (10-01, start writes) | CLEAN | GET exercise (or start) | - |
| rallly | CLEAN (08-02, start writes) | CLEAN | GET exercise (or start) | - |
| recipe-importer | UNDETERMINED (08-02, start writes) | UNDETERMINED | deep pass | NOTHING this app wrote landed in ANY folder the template preserves: all 1 mount(s) across 1 container(s) are e |
| romm | CLEAN (08-02, start writes) | CLEAN | GET exercise (or start) | - |
| seerr | CLEAN (08-02, start writes) | CLEAN | GET exercise (or start) | - |
| sonarr | CLEAN (08-02, start writes) | CLEAN | GET exercise (or start) | sonarr: writable-layer writes at /media (path suggests state, no database signature — judgement needed): ['tv' |
| sparkyfitness | UNDETERMINED (08-02, start writes) | UNDETERMINED | fixture seed OK — `fixture Sparkyfitness: seed OK — spar | sparkyfitness-server: declared volume /app/SparkyFitnessServer/backup is EMPTY after the exercise — the app wa |
| tandoor | CLEAN (08-02, start writes) | UNDETERMINED | fixture seed OK — `fixture Django: seed OK — tandoor: se | tandoor: declared volume /opt/recipes/mediafiles is EMPTY after the exercise — the app was not shown to write |
| termix | CLEAN (08-02, start writes) | CLEAN | GET exercise (or start) | - |
| uptime-kuma | UNDETERMINED (08-02, start writes) | CLEAN | GET exercise (or start) | - |
| vaultwarden | CLEAN (08-02, start writes) | CLEAN | GET exercise (or start) | - |
| vikunja | CLEAN (08-02, start writes) | UNDETERMINED | fixture seed OK — `fixture Vikunja: seed OK — vikunja: c | vikunja: declared volume /app/vikunja/files is EMPTY after the exercise — the app was not shown to write where |
| wanderer | UNDETERMINED (08-02, start writes) | UNDETERMINED | GET exercise (or start) | wanderer: unhealthy; wanderer: declared volume /app/uploads is EMPTY after the exercise — the app was not show |
| wger | CLEAN (08-02, start writes) | UNDETERMINED | fixture seed OK — `fixture Wger: seed OK — wger: POST /a | wger: declared volume /home/wger/media is EMPTY after the exercise — the app was not shown to write where the |
| wishlist | BROKEN (08-02, start writes) | UNDETERMINED | fixture seed OK — `fixture Wishlist: seed OK — wishlist: | wishlist: declared volume /usr/src/app/uploads is EMPTY after the exercise — the app was not shown to write wh |
| zipline | UNDETERMINED (08-02, start writes) | UNDETERMINED | fixture seed OK — `fixture Zipline: seed OK — zipline: / | zipline: declared volume /zipline/uploads is EMPTY after the exercise — the app was not shown to write where t |
REFUSED (BROKEN): none — so no P1 data-loss row, no stop, no box to name.
UNDETERMINED, why: 13 apps — a second volume (uploads / media / cache / redis / backup) stays empty after a successful seed that writes only the database (claper, crafty-controller, dawarich, docmost, gramps-web, immich, outline, sparkyfitness, tandoor, vikunja, wger, wishlist, zipline — R-807). plex — no seed route (a plex.tv claim token). bentopdf — stateless by design (no volume; a browser-side PDF tool). recipe-importer — stateless converter (its /data is never written). wanderer — unhealthy under the gate, no fixture. plant-it — its image is gone from Docker Hub (R-804).
Found and fixed on the way: papra (R-803, P1). At its 256M limit every fresh install was OOM-killed in its
migration and crash-looped (its 26.6.2 step was proven by harness v1, without a memory watch). Measured from birth: peak
anon 405 MiB, cgroup peak 483 MiB, ~255 MiB idle. Now 768M (mem_request 256M): healthy in 31 s, 0 kills, the seed
read back, persistence CLEAN (A/papra/). No box ran papra.
EXISTING-APPS-GAPS.md regenerated from the new verdicts (storage 38 → 36 of 53); records 2.1 updated (radicale,
karakeep, grimmory, metube CLEAN; dawarich done with the redis volume named; sparkyfitness and wger open).
Part B — the remove dialog (controller v0.288.0)
GET /api/stacks/{name}/hdd-data, POST …/remove, DELETE /api/stacks/{name} carry userdata_kept (always a
list). Both dialogs and both results: „A fájljaid ezekben a mappákban megmaradnak — a fájlböngészőben látod őket:" /
"Your files in these folders stay — you see them in the file browser:" + the folders. The false "no data on a drive" note
is gone when userdata exists. Tests + red-proof (two mutants, B/RP-R800-mutants.txt); parity fixtures regenerated —
additions only, 0 deletions, 109 pages. Live on 9202 (B/r800-9202.txt, endpoint-level, no browser): MeTube from the
live catalog, one download; the dialog's data names the folder; the page carries the line in hu and en (with a negative
control); remove with data → 409 on 9202 (no host agent, R-442 — the with-data result is pinned by the unit test);
remove keeping data → the result names the folder; the video still there.
Part C — release
Controller v0.288.0 (580b656), image built from the pushed tree. Floor 0.288.0 + min_agent 0.131.0 → demo-hp and
demo-felhom on 0.288.0 in 20 s (C/floor.txt). Golden 0.288.0: sha 436fdfa1…, round trip equal, vouched (golden 0.288.0
/ agent 0.138.0 / min_agent 0.131.0), R-120 refused 0.287.0 after; golden_currency_gate.py: "OK — the newest released
controller has a golden" (documentation/tests/golden-0.288.0-2026-10-02/).
Rows
Register 436 → 442. Closed: R-788, R-790, R-791, R-792, R-795, R-801, R-803. Updated: R-789 (Tandoor — trigger), R-800 (ruled A, built). Opened: R-802 (lawyer review), R-804 (plant-it image gone), R-805 (empty binds not judged), R-806 (https backends / gramps-web first GET), R-807 (seeds that write only the database).
Teardown
- Bench 9401: built and destroyed twice;
pct list9201 + 9202 only; nvme-scratch back to its baseline (+120 KiB). Docker Hub credentials shredded on DooPlex, demo-hp and the bench;docker logoutbefore the destroy. - 9202: MeTube removed (keeping data — with data refused by R-442), the harness's own test folder removed by hand; controller 0.288.0; live catalog.
- Hub: the floor and the artifact manifest changed on purpose; nothing else. Tester-2: not touched.