controller v0.239.0: any backup tier lets an app update (R-475)
gates / gates (push) Successful in 14s
gates / gates (push) Successful in 14s
Operator ruling 2026-09-13. The update precondition walks Tier 2, Tier 1 (own recovery unit, "helyi") and Tier 3 (off-site, 15 s bound; unreachable counts as absent with a WARN) and leans on the first FRESH copy; the backup_max_age rule applies to whichever tier is chosen. No copy anywhere: back up first. Refused only when nothing exists and no backup can be taken. RunAppBackupNow tolerates a Tier-2 failure (WARN) and marks the captured unit proven current. The hold names the tier (második meghajtó / saját meghajtó / távoli mentés) and the date; pre-v0.239.0 holds keep their text. A successful off-site restore now lifts an update hold. The backups page still uses Tier2UnitRestorePoint unchanged. Scenarios G-M tested; red-proofs M, L, the tail and the off-site clear in felhom.eu documentation/audits/rulings-r472-r475-2026-09-13/.
This commit is contained in:
@@ -3378,16 +3378,32 @@ func (a *updateGuardsAdapter) Busy(name string) (bool, string) {
|
||||
return a.b.UpdateBusy(name)
|
||||
}
|
||||
|
||||
func (a *updateGuardsAdapter) RestorePoint(name string) (stacks.UpdateRestorePoint, error) {
|
||||
// RestorePoints (R-475) reads EVERY tier through backup.UpdateRestorePoints. It must never go back to
|
||||
// Tier2UnitRestorePoint alone — pinned by TestR475_AdapterReadsEveryTier.
|
||||
func (a *updateGuardsAdapter) RestorePoints(ctx context.Context, name string, accept func(stacks.UpdateRestorePoint) bool) (stacks.UpdateRestorePoint, bool, []stacks.UpdateRestorePoint) {
|
||||
if a.b == nil {
|
||||
return stacks.UpdateRestorePoint{}, fmt.Errorf("backup is not enabled on this box")
|
||||
return stacks.UpdateRestorePoint{}, false, nil
|
||||
}
|
||||
rp, err := a.b.Tier2UnitRestorePoint(name)
|
||||
if err != nil {
|
||||
return stacks.UpdateRestorePoint{}, err
|
||||
conv := func(p backup.UpdateTierPoint) stacks.UpdateRestorePoint {
|
||||
return stacks.UpdateRestorePoint{Tier: p.Tier, ProvenAt: p.At}
|
||||
}
|
||||
at, proven := rp.ProvenCopyTime()
|
||||
return stacks.UpdateRestorePoint{Restorable: rp.Restorable, Proven: proven, ProvenAt: at}, nil
|
||||
var acc func(backup.UpdateTierPoint) bool
|
||||
if accept != nil {
|
||||
acc = func(p backup.UpdateTierPoint) bool { return accept(conv(p)) }
|
||||
}
|
||||
chosen, ok, seen := a.b.UpdateRestorePoints(ctx, name, acc)
|
||||
out := make([]stacks.UpdateRestorePoint, 0, len(seen))
|
||||
for _, p := range seen {
|
||||
out = append(out, conv(p))
|
||||
}
|
||||
return conv(chosen), ok, out
|
||||
}
|
||||
|
||||
func (a *updateGuardsAdapter) CanBackUp(name string) (bool, string) {
|
||||
if a.b == nil {
|
||||
return false, "backup is not enabled on this box"
|
||||
}
|
||||
return a.b.CanBackUpApp(name)
|
||||
}
|
||||
|
||||
func (a *updateGuardsAdapter) BackupNow(ctx context.Context, name string) error {
|
||||
@@ -3404,9 +3420,9 @@ func (a *updateGuardsAdapter) SafetyDump(ctx context.Context, name string) ([]st
|
||||
return a.b.WriteUpdateSafetyDump(ctx, name)
|
||||
}
|
||||
|
||||
func (a *updateGuardsAdapter) HoldAfterFailedUpdate(name string, at, provenCopyAt time.Time) error {
|
||||
func (a *updateGuardsAdapter) HoldAfterFailedUpdate(name string, at time.Time, rp stacks.UpdateRestorePoint) error {
|
||||
if a.b == nil {
|
||||
return fmt.Errorf("backup is not enabled on this box — the hold cannot be recorded")
|
||||
}
|
||||
return a.b.HoldAfterFailedUpdate(name, at, provenCopyAt)
|
||||
return a.b.HoldAfterFailedUpdate(name, at, rp.ProvenAt, rp.Tier)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user