controller v0.240.0: seven defects from the any-tier proof and the first nightly rotation
gates / gates (push) Successful in 13s

R-486 (P1): removing an app with its backups KEPT keeps its Tier-2 record,
so the second-drive restore is no longer refused over an intact mirror.
R-484: postgis/pgvector/timescaledb images are Postgres (logical dumps).
R-485: the backup card sizes the recovery unit and the mirror(s).
R-480: a held update's sentence leaves the card once the hold is lifted.
R-477: the update's off-site lookup is one snapshots call, no stats.
R-478: a copy older than this install's deploy does not count.
R-474: "delete backups" deletes the unit, the mirror(s) and the prefs.

Tests and red-proofs per row; evidence in felhom.eu
documentation/audits/v0240-2026-09-13/ and nightly-2026-09-13-adventurelog/.
This commit is contained in:
2026-09-13 19:26:50 +02:00
parent 0e3d831030
commit bdcbd50b42
20 changed files with 673 additions and 82 deletions
+13 -13
View File
@@ -43,13 +43,13 @@ func tier1At(at time.Time) func(string) ([]RestorePoint, bool) {
}
}
func noTier1(string) ([]RestorePoint, bool) { return []RestorePoint{}, true }
func offsiteWith(app string, at time.Time) func(context.Context) (OffsiteInventory, error) {
return func(context.Context) (OffsiteInventory, error) {
return OffsiteInventory{Apps: []OffsiteInventoryApp{{App: app, LatestAt: at}}}, nil
func offsiteWith(app string, at time.Time) func(context.Context) (map[string]time.Time, error) {
return func(context.Context) (map[string]time.Time, error) {
return map[string]time.Time{app: at}, nil
}
}
func noOffsiteTarget(context.Context) (OffsiteInventory, error) {
return OffsiteInventory{}, errNoOffsiteTarget
func noOffsiteTarget(context.Context) (map[string]time.Time, error) {
return nil, errNoOffsiteTarget
}
func tiersOf(ps []UpdateTierPoint) []int {
@@ -67,7 +67,7 @@ func TestR475_G_TierOrderIsSecondDriveThenOwnUnitThenOffsite(t *testing.T) {
offsiteCalls := 0
m.updateTier2PointFn = tier2At(r475T0.Add(-1 * time.Hour))
m.updateTier1PointsFn = tier1At(r475T0.Add(-2 * time.Hour))
m.updateOffsiteInvFn = func(ctx context.Context) (OffsiteInventory, error) {
m.updateOffsiteTimesFn = func(ctx context.Context) (map[string]time.Time, error) {
offsiteCalls++
return offsiteWith("gokapi", r475T0.Add(-3*time.Hour))(ctx)
}
@@ -94,7 +94,7 @@ func TestR475_H_OwnUnitOnly(t *testing.T) {
m, _ := r475Manager()
m.updateTier2PointFn = noTier2
m.updateTier1PointsFn = tier1At(r475T0.Add(-2 * time.Hour))
m.updateOffsiteInvFn = noOffsiteTarget
m.updateOffsiteTimesFn = noOffsiteTarget
p, ok, _ := m.UpdateRestorePoints(context.Background(), "gokapi", nil)
if !ok || p.Tier != UpdateTierLocal || !p.At.Equal(r475T0.Add(-2*time.Hour)) {
t.Fatalf("got %+v ok=%v", p, ok)
@@ -116,7 +116,7 @@ func TestR475_H_OwnUnitOnly(t *testing.T) {
func TestR475_I_OffsiteOnly(t *testing.T) {
m, _ := r475Manager()
m.updateTier2PointFn, m.updateTier1PointsFn = noTier2, noTier1
m.updateOffsiteInvFn = offsiteWith("gokapi", r475T0.Add(-5*time.Hour))
m.updateOffsiteTimesFn = offsiteWith("gokapi", r475T0.Add(-5*time.Hour))
if p, ok, _ := m.UpdateRestorePoints(context.Background(), "gokapi", nil); !ok || p.Tier != UpdateTierOffsite {
t.Fatalf("got %+v ok=%v", p, ok)
}
@@ -130,8 +130,8 @@ func TestR475_I_OffsiteOnly(t *testing.T) {
func TestR475_J_OffsiteUnreachableIsAbsentWithAWarn(t *testing.T) {
m, buf := r475Manager()
m.updateTier2PointFn, m.updateTier1PointsFn = noTier2, noTier1
m.updateOffsiteInvFn = func(context.Context) (OffsiteInventory, error) {
return OffsiteInventory{}, errors.New("ssh: connect to host: connection timed out")
m.updateOffsiteTimesFn = func(context.Context) (map[string]time.Time, error) {
return nil, errors.New("ssh: connect to host: connection timed out")
}
if _, ok, _ := m.UpdateRestorePoints(context.Background(), "gokapi", nil); ok {
t.Error("an unreachable off-site copy must count as absent")
@@ -142,7 +142,7 @@ func TestR475_J_OffsiteUnreachableIsAbsentWithAWarn(t *testing.T) {
// Control: a box with NO off-site target is plainly absent — that is not a fault, so no WARN.
buf.Reset()
m.updateOffsiteInvFn = noOffsiteTarget
m.updateOffsiteTimesFn = noOffsiteTarget
if _, ok, _ := m.UpdateRestorePoints(context.Background(), "gokapi", nil); ok || strings.Contains(buf.String(), "WARN") {
t.Errorf("no off-site target: want absent and silent; ok=%v log=%q", ok, buf.String())
}
@@ -152,9 +152,9 @@ func TestR475_J_OffsiteUnreachableIsAbsentWithAWarn(t *testing.T) {
updateOffsiteCheckTimeout = 50 * time.Millisecond
defer func() { updateOffsiteCheckTimeout = old }()
buf.Reset()
m.updateOffsiteInvFn = func(ctx context.Context) (OffsiteInventory, error) {
m.updateOffsiteTimesFn = func(ctx context.Context) (map[string]time.Time, error) {
<-ctx.Done()
return OffsiteInventory{}, ctx.Err()
return nil, ctx.Err()
}
start := time.Now()
_, ok, _ := m.UpdateRestorePoints(context.Background(), "gokapi", nil)