# 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 every `probe.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 list` 9201 + 9202 only; nvme-scratch back to its baseline (+120 KiB). Docker Hub credentials shredded on DooPlex, demo-hp and the bench; `docker logout` before 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.