Data-loss hunt: R-1180 proven against production code (the nightly rsync --delete mirror runs while a restore holds the backup flag and deletes the files being restored)
gates / gates (push) Successful in 13m20s

This commit is contained in:
admin committed 2026-10-20 13:02:50 +02:00
1 parent a57a8363df
commit 20a77d367b
4 files changed
+49 -2

No files matched your search

@@ -33,7 +33,7 @@ Register: none of the candidates below is in OPEN-ITEMS or CLOSED-ITEMS (searche
| D6 kept-data page can list a running app's folder | PLAUSIBLE | defect | needs an unreadable app.yaml |
| D7 unit restore can point the app at an old drive | PLAUSIBLE (low) | defect | |
| D8 local whole-guest keep=1 | design (07 §6.1 R-684 chose it) | design | **NOT-A-FINDING: the operator chose keep=1 for demo-hp; recorded in 07** |
| T1 the nightly mirror (`rsync --delete`) can run during a restore from that mirror | CONFIRMED-CODE | defect | `RunAllTier2` (`tier2.go:479-520`) takes no backup flag; the whole restore holds only `acquireRunning` (`tier2_whole.go:301`) |
| T1 the nightly mirror (`rsync --delete`) can run during a restore from that mirror | **CONFIRMED** (unit test, production rsync mirror: `C3-T1-mirror-during-restore/`) → R-1180 | defect | `RunAllTier2` (`tier2.go:479-520`) takes no backup flag; the whole restore holds only `acquireRunning` (`tier2_whole.go:301`) |
| T2 controller image without tzdata → UTC schedules | **NOT-A-FINDING:** measured on 9202 (controller 0.331.0, Debian 13 base): `/usr/share/zoneinfo/Europe/Budapest` present, scheduler logs „scheduled for … CEST" | — | |
| T3 whole-guest start creeps ~5 min/night | PLAUSIBLE | defect | needs the agent journal over weeks |
| T4 whole-guest quiesce stops apps mid-restore | PLAUSIBLE | defect | `quiesce.go:213-275` |
@@ -0,0 +1,8 @@
# go test at controller 7ca59e2 (v0.336.0), scratch clone; the PRODUCTION rsyncMirror (rsync -a --delete) on local temp dirs; 2026-10-20T11:02:49Z
=== RUN TestRehearsalT1_NightlyMirrorRunsDuringARestore
zz_rehearsal_t1_test.go:26: POSITIVE CONTROL: mirror after the night = [a.jpg b.jpg c.jpg d.jpg]
zz_rehearsal_t1_test.go:34: restore in progress: backup flag held = true
zz_rehearsal_t1_test.go:37: FAULT: mirror DURING the restore = [a.jpg b.jpg] (c.jpg, d.jpg — the files the restore is bringing back — are gone from the only copy)
--- PASS: TestRehearsalT1_NightlyMirrorRunsDuringARestore (0.22s)
PASS
ok gitea.dooplex.hu/admin/felhom-controller/internal/backup 0.230s
@@ -0,0 +1,39 @@
package backup
// THROWAWAY proof for the 2026-10-20 data-loss hunt (candidate T1 / R-1180). Never committed.
import (
"os"
"path/filepath"
"testing"
)
type t1Provider struct{ *t2v2Provider }
func (p t1Provider) ListDeployedStacks() []StackSummary { return []StackSummary{{Name: "app"}} }
func TestRehearsalT1_NightlyMirrorRunsDuringARestore(t *testing.T) {
m, src, target, prov := newTier2V2(t, "app")
prov.has["app"] = true
prov.binds["app"] = []ClassifiedBind{mHDD("appdata/app/photos")}
m.stackProvider = t1Provider{prov}
m.tier2Mirror = rsyncMirror // the PRODUCTION mirror (rsync -a --delete), local temp dirs only
for _, n := range []string{"a.jpg", "b.jpg", "c.jpg", "d.jpg"} {
mustWrite(t, filepath.Join(src, "appdata", "app", "photos", n), n)
}
m.RunAllTier2()
mir := filepath.Join(target, "backups", "secondary", "app", "hdd", "appdata", "app", "photos")
ls := func(d string) []string { e, _ := os.ReadDir(d); var o []string; for _, x := range e { o = append(o, x.Name()) }; return o }
t.Logf("POSITIVE CONTROL: mirror after the night = %v", ls(mir))
// The household deleted half the photos and starts the second drive's restore; the restore holds the backup flag.
os.Remove(filepath.Join(src, "appdata", "app", "photos", "c.jpg"))
os.Remove(filepath.Join(src, "appdata", "app", "photos", "d.jpg"))
if err := m.acquireRunning(); err != nil {
t.Fatal(err)
}
t.Logf("restore in progress: backup flag held = %v", m.acquireRunning() != nil)
// 03:30 — the nightly second-drive leg fires while the restore is still copying.
m.RunAllTier2()
t.Logf("FAULT: mirror DURING the restore = %v (c.jpg, d.jpg — the files the restore is bringing back — are gone from the only copy)", ls(mir))
m.releaseRunning()
}