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

This commit is contained in:
2026-10-25 17:17:00 +01:00
parent 3a1f09d29f
commit 935e624f10
6 changed files with 43 additions and 0 deletions
+3
View File
@@ -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
+8
View File
@@ -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)
}
}