S15: a hand act a person decides by design is no repair, read from the verb that recorded it
Two planned bus upgrades raised healer-wanted, though ADR 0236 never lets the mesh roll the bus. Instead of naming one more cause, the hand-act verbs are one table saying which record a person's decision (retire approve/reject, cleanup delete, bus upgrade, upgrade release-backlog, and secret rotate after a leak); S15 and `hand-acts` skip those, push and the other repairs keep counting. Conditions already open for them clear on the next tick.
This commit is contained in:
@@ -3,6 +3,9 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -161,16 +164,135 @@ 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) {
|
||||
// actOf is an act in the log recorded by a verb, with its cause.
|
||||
func actOf(at time.Time, verb, cause string) link.HandAct {
|
||||
a := actByHand(at, cause)
|
||||
a.Verb, a.Args = verb, nil
|
||||
return a
|
||||
}
|
||||
|
||||
// **An act a person decides by design is no repair** (handActVerbs): approving or rejecting a
|
||||
// retirement and deleting what was retired (ADR 0230), a bus upgrade and a backlog released (ADR 0236),
|
||||
// and a rotation after a leak — repeated, none is a healer wanted, whatever cause it gives.
|
||||
func TestAPersonsDecisionRepeatedWantsNoHealer(t *testing.T) {
|
||||
now := time.Date(2026, 10, 6, 12, 0, 0, 0, time.UTC)
|
||||
for _, c := range []struct{ verb, cause string }{
|
||||
{"retire approve", kindRetireWaiting}, {"retire reject", kindRetireWaiting},
|
||||
{"cleanup delete", kindCleanupWaiting}, {"bus upgrade", "bus-upgrade"},
|
||||
{"bus upgrade", "a word the person chose"}, {"upgrade release-backlog", "upgrade release-backlog"},
|
||||
{"secret rotate", causeLeakedInLogs},
|
||||
} {
|
||||
f := calm(now)
|
||||
f.handActs = []link.HandAct{actOf(now.Add(-26*time.Hour), c.verb, c.cause),
|
||||
actOf(now.Add(-time.Hour), c.verb, c.cause)}
|
||||
if got := watchHandActs(f); len(got) != 0 {
|
||||
t.Errorf("%s (cause %s) repeated asked for a healer: %+v", c.verb, c.cause, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// **What repairs still counts**: a push by hand above all (ADR 0236 exists to end it), a rotation for
|
||||
// any cause but a leak, an act recorded outside the mesh whatever cause it names, and a verb the table
|
||||
// does not know.
|
||||
func TestARepairRepeatedStillWantsAHealer(t *testing.T) {
|
||||
now := time.Date(2026, 10, 6, 12, 0, 0, 0, time.UTC)
|
||||
for _, c := range []struct{ verb, cause string }{
|
||||
{"push", "push"}, {"plans close", "plans close"}, {"conditions silence", "consumer-behind"},
|
||||
{"secret rotate", "stopped-working"}, {"hand-act record", "bus-upgrade"},
|
||||
{"hand-act record", kindCleanupWaiting}, {"a verb nobody listed", "x"},
|
||||
} {
|
||||
f := calm(now)
|
||||
f.handActs = []link.HandAct{actOf(now.Add(-26*time.Hour), c.verb, c.cause),
|
||||
actOf(now.Add(-time.Hour), c.verb, c.cause)}
|
||||
if got := watchHandActs(f); len(got) != 1 || got[0].Kind != "healer-wanted" {
|
||||
t.Errorf("%s (cause %s) repeated wanted no healer: %+v", c.verb, c.cause, got)
|
||||
}
|
||||
}
|
||||
// A decision does not make up the second of a cause a repair recorded once.
|
||||
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)}
|
||||
f.handActs = []link.HandAct{actOf(now.Add(-26*time.Hour), "hand-act record", "bus-upgrade"),
|
||||
actOf(now.Add(-time.Hour), "bus upgrade", "bus-upgrade")}
|
||||
if got := watchHandActs(f); len(got) != 0 {
|
||||
t.Fatalf("a person's decision asked for a healer: %+v", got)
|
||||
t.Errorf("one repair and one decision asked for a healer: %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
// **A healer-wanted already open for a decision clears on the next tick** — the log of 2026-10-06:
|
||||
// a bus upgrade recorded after the fact and one through its verb, and two rotations after a leak.
|
||||
func TestAHealerWantedOpenForADecisionClearsOnTheNextTick(t *testing.T) {
|
||||
now := time.Date(2026, 10, 6, 18, 0, 0, 0, time.UTC)
|
||||
store := conditions.NewInMemory()
|
||||
k := conditions.NewKeeper(t.Context(), conditions.Options{Store: store, History: store,
|
||||
Teller: &conditions.Told{}, Now: func() time.Time { return now }})
|
||||
defer k.Close(context.Background())
|
||||
// What the build before this one raised, as it raised it.
|
||||
var before []conditions.Observation
|
||||
for _, cause := range []string{"bus-upgrade", causeLeakedInLogs} {
|
||||
before = append(before, conditions.Observation{Scope: conditions.ScopeMesh, ID: "hand-acts." + cause,
|
||||
Token: "healer-wanted", Kind: "healer-wanted", Severity: conditions.Warning, Summary: "repaired twice"})
|
||||
}
|
||||
if err := k.Reconcile(t.Context(), "S15", before); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if open, _ := k.Open(t.Context()); len(open) != 2 {
|
||||
t.Fatalf("the conditions of the build before were not open: %+v", open)
|
||||
}
|
||||
f := calm(now)
|
||||
f.handActs = []link.HandAct{actOf(now.Add(-11*time.Hour), "hand-act record", "bus-upgrade"),
|
||||
actOf(now.Add(-8*time.Hour), "secret rotate", causeLeakedInLogs),
|
||||
actOf(now.Add(-8*time.Hour), "secret rotate", causeLeakedInLogs),
|
||||
actOf(now.Add(-time.Hour), "bus upgrade", "bus-upgrade")}
|
||||
w := &watchdogs{keeper: k, started: now.Add(-time.Hour)}
|
||||
w.see(t.Context(), f)
|
||||
if open, _ := k.Open(t.Context()); len(open) != 0 {
|
||||
t.Fatalf("a healer-wanted for a person's decision stayed open: %+v", open)
|
||||
}
|
||||
}
|
||||
|
||||
// **Every verb that writes the hand-act log is in handActVerbs**, so whether it is a repair is said
|
||||
// where S15 reads it, not left to a default nobody chose.
|
||||
func TestEveryVerbThatRecordsAHandActIsInTheTable(t *testing.T) {
|
||||
listed := map[string]bool{}
|
||||
for _, v := range handActVerbs {
|
||||
if listed[v.Verb] {
|
||||
t.Errorf("%s is in the table twice", v.Verb)
|
||||
}
|
||||
listed[v.Verb] = true
|
||||
if len(v.DecidedFor) > 0 && v.Decision == "" {
|
||||
t.Errorf("%s limits a decision it does not state", v.Verb)
|
||||
}
|
||||
}
|
||||
files, err := filepath.Glob("*.go")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
literal := regexp.MustCompile(`(?:\.record\(ctx, |HandAct\{Verb: |RetireApproved: |RetireRejected: |RetireDeleted: )"([^"]+)"\s*[,})]`)
|
||||
composed := regexp.MustCompile(`\.record\(ctx, "([^"]+ )"\s*\+`)
|
||||
found := 0
|
||||
for _, name := range files {
|
||||
if strings.HasSuffix(name, "_test.go") {
|
||||
continue
|
||||
}
|
||||
body, err := os.ReadFile(name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, m := range literal.FindAllStringSubmatch(string(body), -1) {
|
||||
found++
|
||||
if !listed[m[1]] {
|
||||
t.Errorf("%s records %q, which handActVerbs does not list", name, m[1])
|
||||
}
|
||||
}
|
||||
// "plans " + stop|close, "retire " + approve|reject: some listed verb begins with it.
|
||||
for _, m := range composed.FindAllStringSubmatch(string(body), -1) {
|
||||
found++
|
||||
if !slices.ContainsFunc(handActVerbs, func(v handActVerb) bool { return strings.HasPrefix(v.Verb, m[1]) }) {
|
||||
t.Errorf("%s records %q…, which no verb of handActVerbs begins with", name, m[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
if found < 12 {
|
||||
t.Fatalf("found %d recording verbs, fewer than the table's own: the search no longer sees them", found)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user