DRILL — R-102 / R-103: the second drive's copy becomes a way back
demo-hp (192.168.0.104), guest 9201 · controller v0.229.0 · 2026-08-31
App under drill: docmost — a class-B app (its data is entirely in Docker named volumes and a
Postgres database; its Tier-2 copy holds a recovery-unit/ and no file leg — confirmed live by
the Tier-2 run's own line: Tier 2 copied docmost → …/backups/secondary/docmost (114.5 MB, 0 leg(s))).
Venue is correct per runbooks/target-selection.md: demo-hp is Tier 0 — disposable. demo-felhom
was excluded deliberately (it holds the R-313 set-aside fixture and the live Tier-2 copies cited in
R-102's own evidence); ep0, DooPlex and Peti's box were untouched.
Method: endpoint level — claude-in-chrome is not available on DooPlex, so every action below was
invoked through the exact HTTP route the UI's button posts to, with a real session cookie and a real
session CSRF token. No server logic was skipped; only rendering was.
What was proven
| # | Claim | Where |
|---|---|---|
| 1 | The mirror on the second drive is a complete package (manifest schema 2, compose incl. app.yaml, 3 volume tars, 1 canonical .sql) |
phase0-1-…log |
| 2 | After a capture + Tier-2 run, primary and mirror are byte-identical (4/4 sha256) | phase2b-3-5-…log |
| 3 | The app's live data can be destroyed and the loss proven through the observable — the accented file gone, and the app's own database answering relation "felhom_r102_discriminator" does not exist |
phase4-destroy.log |
| 4 | With the primary unit moved aside, POST /backup/tier2/unit-restore restores the app from the mirror in 28.65 s — Restoring docmost from recovery unit /mnt/felhom-drives/hdd_1/backups/secondary/docmost/recovery-unit, 3 volumes of 3 listed, 1 database of 1 listed |
phase6-…log |
| 5 | The data came back byte-for-byte: accented filename Árvíztűrő tükörfúrógép.txt verified as hex c3817276c3ad7a74c5b172c5912074c3bc6bc3b67266c3ba72c3b367c3a9702e747874 (35 bytes) and content sha256 9228fdda…c444; the app read its own row over TCP with its own credential (docmost@172.20.0.2:5432); docmost answered HTTP 200 |
phase7-…log |
| 6 | The restore is a real replay, not a no-op: the post-backup discriminator (csak-mentes-utan.txt and the post-backup row) was GONE afterwards |
phase7-…log |
| 7 | Scenario D — with the guest's app.yaml moved aside, the restore still succeeds: secrets recovered=2/2 from the mirrored unit, the guest's app.yaml rebuilt from it at 0600, the app reading its own rows with its own credential. This closes 00-capability-map.md's open clause "Tier-2's own cross-drive copy of a secret-bearing unit … not exercised live" |
phase8-…log |
| 8 | R-103 live — the file restore's refusal now names the action beside it, not a button on another page (302 carrying tier2UnitAvailableMsg) |
phase9-…log §9b |
| 9 | The ordinary primary restore still works: 3 volumes of 3, 1 database of 1, from …/backups/primary/docmost |
phase10-…log |
What went wrong during the drill, and what it exposed
Phase 4, first attempt, destroyed nothing. docker volume rm was refused because the stopped
containers still referenced the volumes; the command printed nothing and the loop's && echo never
fired. Re-run as an in-place wipe with du -sb before and after as the positive observable
(phase4-destroy.log states this at the top). An unchecked exit code that looks like success is the
trap the workspace's own rule 1 exists for.
Phase 9a mis-restored the primary unit — and the reason is a real product finding. Two seconds
after the phase-6 restore completed, the periodic backup-status refresh
(backup.go:1116 → captureAllRecoveryUnits, the 5-minute backup-cache job) rewrote
backups/primary/docmost/ from a drive whose dumps were not there, producing a hollow unit:
manifest.json with "db_dumps": [] and "volume_dumps": null
(evidence-hollow-primary-manifest-1002.json, created_at 2026-08-31T10:02:59Z). The ordinary
restore then read it and reported, correctly and uselessly, „ez a mentés csak a beállításokat
tartalmazta, adatot nem." Repaired in phase10-…log; the app was left healthy with its data back.
This is filed as R-403 and is NOT fixed here. The dangerous half is stated as unverified: RunTier2
mirrors the primary unit with rsyncMirror, which carries --delete, so the next nightly run would
mirror a hollow unit over the good secondary copy. Nothing in f5_stale_primary_test.go or the R-181
capture floor guards that direction. It was not tested live and must not be reported as measured.
Teardown — all three layers
-
Machines provisioned: none. The drill used the existing guest 9201; no VM, no scratch guest.
-
Hub records created: none. No enrolment, no appliance, no escrow.
-
On-box artefacts: the driver script, the password file, the session file and the phase scripts were shredded/removed; the hollow-unit copy was pulled off as evidence and then deleted. The controller's
settings.json.r102bakwas removed. -
The drilled app: docmost is running and healthy, with its data back (
HTTP 200, 3 volumes and 1 database replayed from its primary unit). Primary and secondary are byte-identical again on all five artefacts. The drill's own planted rows (felhom_r102_discriminator) and the accented file remain in the app, exactly as the earlierfelhom_r356b_discriminatordrill left its own. -
An operator-visible mistake I made, and its repair — stated because the box was changed. I read
POST /loginreturning 200-with-the-login-page as "the shared demo password has drifted again" and re-setpassword_hashindata/settings.jsontobcrypt(PASSWORD). The password had not drifted. Values in~/.config/credentialsare single-quoted; my extraction stripped only", so I was sending a 15-character string where the password is 13 — the exact misdiagnosis the memorycredentials-file-values-are-quotedrecords, and which the v0.228.0 report had recorded on this same box on this same day. This is the third instance.Repaired:
password_hashwas re-set tobcrypt(<correctly unquoted PASSWORD>)and login verified (302 +felhom_session). The box's end state therefore matches the state the v0.228.0 session independently verified. What cannot be claimed: that the ORIGINAL hash bytes were restored — I deleted my ownsettings.json.r102bakbefore finding the error, so the original is gone. The end state is correct by verification, not by restoration. Filed as an Observation infelhom-controller/REPORT.md.