package localapi import "time" // normalizeBackupTiers resolves the tier list the Server serves. // // Contract (R-82), and the reason this is a named function rather than inline setup: the UNTARGETED // local-API endpoints must keep behaving exactly as they did before multi-tier existed, forever. // That property lives here. // // - tiers == nil → synthesize ONE tier from the legacy (Backups, BackupCadence) pair and mark it // primary. This is the pre-R-82 shape; every existing caller and test hits this path. // - tiers supplied → keep order but hoist the primary to the front; if none is marked primary, // the FIRST becomes primary (a tier list with no primary would leave untargeted requests with // nothing to act on, which would silently stop backups). // - tiers with a nil Service are dropped: a tier with no runner cannot back anything up, and // advertising it would be an "applied and empty" tier — the exact fault R-82 exists to fix. func normalizeBackupTiers(tiers []BackupTier, legacy BackupService, cadence time.Duration) []BackupTier { usable := make([]BackupTier, 0, len(tiers)) for _, t := range tiers { if t.Service == nil || t.TargetID == "" { continue } if t.Cadence <= 0 { t.Cadence = cadence } if t.WaitTimeout <= 0 { t.WaitTimeout = 2 * time.Hour } usable = append(usable, t) } if len(usable) == 0 { if legacy == nil { return nil } return []BackupTier{{TargetID: "", Cadence: cadence, WaitTimeout: 2 * time.Hour, Primary: true, Service: legacy}} } primary := -1 for i, t := range usable { if t.Primary { primary = i break } } if primary < 0 { primary = 0 } out := make([]BackupTier, 0, len(usable)) usable[primary].Primary = true out = append(out, usable[primary]) for i, t := range usable { if i == primary { continue } t.Primary = false out = append(out, t) } return out }