diff --git a/cmd/mesh-controller/signals.go b/cmd/mesh-controller/signals.go index 9b449e5..5d22d2a 100644 --- a/cmd/mesh-controller/signals.go +++ b/cmd/mesh-controller/signals.go @@ -571,12 +571,21 @@ func watchLease(f *signalFacts) []conditions.Observation { // handActsWithin is how far back a repeated cause counts (S15). const handActsWithin = 14 * 24 * time.Hour +// personsDecision are the causes of hand acts that are a person's decision by design, never a repair a +// healer could take over: approving or rejecting a retirement, and deleting what was retired (novox/hq +// ADR 0230 — nothing is retired past the bound or deleted without a person). Repeated, they are the +// mesh working as decided, not a healer wanted. +var personsDecision = map[string]bool{kindRetireWaiting: true, kindCleanupWaiting: true} + // watchHandActs is S15: a cause recorded by hand twice within a fortnight is a healer wanted, named by // the cause. **A heal is never a hand act** (healers.go), so a cause a healer exists for and a person // still repaired twice says the healer is not enough — its reach or its budget — and is said so. func watchHandActs(f *signalFacts) []conditions.Observation { recent := make([]link.HandAct, 0, len(f.handActs)) for _, a := range f.handActs { + if personsDecision[a.Cause] { + continue + } if f.now.Sub(a.At) <= handActsWithin { recent = append(recent, a) } diff --git a/cmd/mesh-controller/signals_test.go b/cmd/mesh-controller/signals_test.go index 98c67b7..5978577 100644 --- a/cmd/mesh-controller/signals_test.go +++ b/cmd/mesh-controller/signals_test.go @@ -161,6 +161,19 @@ func TestARepeatedHandActNamesItsCauseAndItsHealer(t *testing.T) { } } +// **Approving a retirement and deleting what was retired are a person's decision by design** (ADR +// 0230): repeated, they are not a healer wanted. +func TestARetirementDecisionRepeatedWantsNoHealer(t *testing.T) { + now := time.Date(2026, 10, 6, 12, 0, 0, 0, time.UTC) + f := calm(now) + f.handActs = []link.HandAct{actByHand(now.Add(-2*time.Hour), kindRetireWaiting), + actByHand(now.Add(-time.Hour), kindRetireWaiting), actByHand(now.Add(-time.Hour), kindCleanupWaiting), + actByHand(now.Add(-time.Minute), kindCleanupWaiting)} + if got := watchHandActs(f); len(got) != 0 { + t.Fatalf("a person's decision asked for a healer: %+v", got) + } +} + // standingSaid is a provider's failing word last said at a moment. func standingSaid(at time.Time) conditions.Condition { return conditions.Condition{Key: "provider.idp.anchor.app.failing", Kind: kindProviderFailing, LastObserved: at}