D47 follow-up: a set-aside tells the operator once (backup_run_failures, leg unit-set-aside, both identities, own run id). Red-proved. Commit only.
gates / gates (push) Successful in 1m33s
gates / gates (push) Successful in 1m33s
This commit is contained in:
@@ -1543,6 +1543,14 @@ func main() {
|
||||
})
|
||||
// R-893 option A (D20 = A): a failed off-site restore that was PUT BACK — the live volumes and definition poured
|
||||
// back and the app started. The same operator-tier event as the hold above (no new event type), its own leg.
|
||||
// D47 follow-up: a unit of an earlier install was set aside whole — the operator sees it (its own leg).
|
||||
backupMgr.SetUnitSetAsideNotify(func(stack, unitID, liveID, dest string) {
|
||||
d := notify.BackupRunFailuresDetails{RunID: "unit-set-aside-" + stack + "-" + filepath.Base(dest), RunKind: "backup", Failed: 1, Attempted: 1}
|
||||
d.Apps = append(d.Apps, notify.RunFailureDetail{App: stack, Leg: "unit-set-aside", Reason: "the backup of an earlier install was set aside whole (D47)"})
|
||||
msg := fmt.Sprintf("App %q: its backup belonged to an earlier install (%s; this install %s), so it was SET ASIDE whole at %s (D47) "+
|
||||
"and listed on the restore page. Expected after a reinstall; not expected on an upgrade — look if no one reinstalled the app.", stack, unitID, liveID, dest)
|
||||
notifier.NotifyBackupRunFailures(msg, d)
|
||||
})
|
||||
// D48: a restore refused BEFORE anything was touched because an image of the backup's version could not be
|
||||
// downloaded — the operator line that tells a deleted version (R-698, R-933) from a busy registry.
|
||||
backupMgr.SetRestorePullRefusedNotify(func(stack, ref string, cause error) {
|
||||
|
||||
@@ -16,6 +16,9 @@ func TestR1279_MainWiresTheImportsDownloadFirst(t *testing.T) {
|
||||
if !strings.Contains(string(b), "appExporter.SetImagePreparer(backupMgr.PrepareRestoreImages)") {
|
||||
t.Fatal("main.go must wire the import's download-first helper (SetImagePreparer)")
|
||||
}
|
||||
if !strings.Contains(string(b), "backupMgr.SetUnitSetAsideNotify(") {
|
||||
t.Fatal("main.go must wire the D47 set-aside operator line")
|
||||
}
|
||||
if !strings.Contains(string(b), "func (a *stackAdapter) ContainerSamples(") {
|
||||
t.Fatal("main.go's stack adapter must read container samples for the stable-start rule (fix A)")
|
||||
}
|
||||
|
||||
@@ -248,6 +248,9 @@ type Manager struct {
|
||||
imagePresentFn func(ref string) bool
|
||||
imagePullFn func(ctx context.Context, ref string) (string, error)
|
||||
restorePullRefusedNotify func(stack, ref string, cause error)
|
||||
// unitSetAsideNotify (D47 follow-up, brief 2026-10-26 Part 5.2): one operator line when a box sets an earlier
|
||||
// install's unit aside — so a household's set-aside after the floor is visible to the operator.
|
||||
unitSetAsideNotify func(stack, unitID, liveID, dest string)
|
||||
|
||||
// R-403 unit-REHYDRATE seam — the copy that refills the primary unit from the Tier-2 mirror after
|
||||
// a restore, overridable so the "only when hollow" condition and the byte-identical non-effect are
|
||||
|
||||
@@ -181,9 +181,17 @@ func (m *Manager) guardUnitDir(stack, unitDir, setAsideNs, what string) error {
|
||||
}
|
||||
m.logger.Printf("[INFO] [backup] %s: the recovery unit of an earlier install (%s; %s) is SET ASIDE whole → %s; this install (%s) gets its own unit from its first backup (D47)",
|
||||
stack, orUnknown(unitID), why, dest, orUnknown(liveID))
|
||||
if m.unitSetAsideNotify != nil {
|
||||
m.unitSetAsideNotify(stack, orUnknown(unitID), orUnknown(liveID), dest)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetUnitSetAsideNotify wires the operator line for a set-aside unit (main.go).
|
||||
func (m *Manager) SetUnitSetAsideNotify(fn func(stack, unitID, liveID, dest string)) {
|
||||
m.unitSetAsideNotify = fn
|
||||
}
|
||||
|
||||
func orUnknown(id string) string {
|
||||
if id == "" {
|
||||
return "unrecorded"
|
||||
|
||||
@@ -302,3 +302,21 @@ func TestD47_OwnerVerdict(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// D47 follow-up (brief 2026-10-26 Part 5.2): a set-aside tells the operator once — the box's own households must not
|
||||
// set units aside silently after the floor. COMPANION RED-PROOF: drop the notify call → this fails.
|
||||
func TestD47_ASetAsideTellsTheOperatorOnce(t *testing.T) {
|
||||
r := d47Setup(t, "aaaa000000000001")
|
||||
var got []string
|
||||
r.m.SetUnitSetAsideNotify(func(stack, unitID, liveID, dest string) { got = append(got, stack+" "+unitID+" "+liveID) })
|
||||
r.reinstall("bbbb000000000002")
|
||||
if err := r.m.captureRecoveryUnit("tandoor", false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := r.m.captureRecoveryUnit("tandoor", false); err != nil { // the next refresh: nothing more to set aside
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(got) != 1 || got[0] != "tandoor aaaa000000000001 bbbb000000000002" {
|
||||
t.Fatalf("the operator hears once, with both identities: %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user