package backup import ( "context" "sort" "sync" "time" "gitea.dooplex.hu/admin/felhom-agent/internal/hub" ) // ForeignKeyLedger (R-366 slice 2, `09` §3 decision 168) holds, per backup tier, the whole-guest archives the // restore-test pick skipped because another key wrote them (R-727 — an earlier install of this box). Since R-727 the // skip was one INFO log line per archive and nothing else, so after a reinstall the operator was never told that the // box's older whole-guest copies are unreadable to it. The host report carries this ledger; the hub raises ONE // operator event when it changes. // // It reports nil until a tier has been evaluated since the agent started, so a restart does not read as "the set // changed to empty" (the hub keeps its last state for an absent field). type ForeignKeyLedger struct { mu sync.Mutex byTarget map[string]hub.ForeignKeyArchives } // NewForeignKeyLedger builds an empty ledger. func NewForeignKeyLedger() *ForeignKeyLedger { return &ForeignKeyLedger{} } func (l *ForeignKeyLedger) set(target string, n int, oldest, newest int64) { l.mu.Lock() defer l.mu.Unlock() if l.byTarget == nil { l.byTarget = map[string]hub.ForeignKeyArchives{} } e := hub.ForeignKeyArchives{Target: target, Count: n} if n > 0 { e.Oldest = time.Unix(oldest, 0).UTC().Format(time.RFC3339) e.Newest = time.Unix(newest, 0).UTC().Format(time.RFC3339) } l.byTarget[target] = e } // ForeignKeyArchives implements hub.ForeignKeyArchiveReporter: nil before any evaluation; otherwise the tiers that // hold such archives (`Tiers` empty, never nil, when none do), sorted by tier. func (l *ForeignKeyLedger) ForeignKeyArchives(context.Context) *hub.ForeignKeyArchivesStanza { l.mu.Lock() defer l.mu.Unlock() if l.byTarget == nil { return nil } out := []hub.ForeignKeyArchives{} for _, e := range l.byTarget { if e.Count > 0 { out = append(out, e) } } sort.Slice(out, func(i, j int) bool { return out[i].Target < out[j].Target }) return &hub.ForeignKeyArchivesStanza{Tiers: out} }