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:
@@ -7,6 +7,7 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -27,6 +28,62 @@ import (
|
|||||||
// step of a procedure is not a repair. `conditions silence` joins them when the condition store does
|
// step of a procedure is not a repair. `conditions silence` joins them when the condition store does
|
||||||
// (Phase 1).
|
// (Phase 1).
|
||||||
|
|
||||||
|
// handActVerb is one verb that writes the hand-act log, and whether what it records is a repair.
|
||||||
|
type handActVerb struct {
|
||||||
|
Verb string
|
||||||
|
// Decision says why an act of this verb is a person's decision by design rather than a repair a
|
||||||
|
// healer could take over; empty for a repair.
|
||||||
|
Decision string
|
||||||
|
// DecidedFor limits Decision to these causes; empty, it holds for every act of the verb.
|
||||||
|
DecidedFor []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// causeLeakedInLogs is the cause a rotation after a value was printed into a log gives.
|
||||||
|
const causeLeakedInLogs = "leaked-in-logs"
|
||||||
|
|
||||||
|
// handActVerbs is every verb that writes the hand-act log (novox/hq to-be 45 §7). **S15 reads it**:
|
||||||
|
// an act recorded by a verb whose entry names a decision is the mesh working as decided, never a
|
||||||
|
// repair, and does not count toward `healer-wanted` — whatever cause it gives. A verb not listed, or
|
||||||
|
// listed without a decision, counts, so a new verb is a repair until its entry says otherwise.
|
||||||
|
var handActVerbs = []handActVerb{
|
||||||
|
// Repairs: each repeated is a healer the mesh lacks. A push by hand is exactly what roll-out by
|
||||||
|
// default (ADR 0236) exists to end.
|
||||||
|
{Verb: "push"},
|
||||||
|
{Verb: "plans stop"},
|
||||||
|
{Verb: "plans close"},
|
||||||
|
{Verb: "broker consumer-reset"},
|
||||||
|
// Silencing the same condition twice says the condition, or what it watches, wants mending.
|
||||||
|
{Verb: "conditions silence"},
|
||||||
|
// An act done outside the mesh: the mesh cannot tell a repair from a decision there, so it counts.
|
||||||
|
{Verb: "hand-act record"},
|
||||||
|
// A person's decisions by design.
|
||||||
|
{Verb: "retire approve", Decision: "nothing is retired past its bound without a person (ADR 0230)"},
|
||||||
|
{Verb: "retire reject", Decision: "keeping a consumer active is a person's word (ADR 0230)"},
|
||||||
|
{Verb: "cleanup delete", Decision: "nothing retired is deleted without a person (ADR 0230)"},
|
||||||
|
{Verb: "bus upgrade", Decision: "the bus is never rolled by the mesh: replacing it is a planned step a " +
|
||||||
|
"person starts (ADR 0236)"},
|
||||||
|
{Verb: "upgrade release-backlog", Decision: "after a release plan failed, the next opens only when a " +
|
||||||
|
"person releases it (ADR 0236)"},
|
||||||
|
// A leak is judged by a person — which value was exposed, to whom — and its rotation is the answer
|
||||||
|
// to that judgement. Several values rotate for one leak, and a leak that recurs is a defect of the
|
||||||
|
// module that prints them, an issue against it, not a healer that rotates. A rotation for any other
|
||||||
|
// cause — a credential that stopped working — counts: a schedule or a healer could take it over.
|
||||||
|
{Verb: "secret rotate", Decision: "a value a person judged disclosed is replaced on their word",
|
||||||
|
DecidedFor: []string{causeLeakedInLogs}},
|
||||||
|
}
|
||||||
|
|
||||||
|
// personsDecision is whether an act in the log is a person's decision by design, by the verb that
|
||||||
|
// recorded it (handActVerbs).
|
||||||
|
func personsDecision(a link.HandAct) bool {
|
||||||
|
for _, v := range handActVerbs {
|
||||||
|
if v.Verb != a.Verb {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return v.Decision != "" && (len(v.DecidedFor) == 0 || slices.Contains(v.DecidedFor, a.Cause))
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// handActFlags are the flags every repairing verb takes.
|
// handActFlags are the flags every repairing verb takes.
|
||||||
type handActFlags struct {
|
type handActFlags struct {
|
||||||
why, cause, condition *string
|
why, cause, condition *string
|
||||||
@@ -144,7 +201,7 @@ func handActCommand(ctx context.Context, args []string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
repeated := link.RepeatedCauses(acts, now)
|
repeated := link.RepeatedCauses(repairs(acts), now)
|
||||||
if *asJSON {
|
if *asJSON {
|
||||||
body, err := json.MarshalIndent(map[string]any{"acts": acts, "repeated": repeated}, "", " ")
|
body, err := json.MarshalIndent(map[string]any{"acts": acts, "repeated": repeated}, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -179,6 +236,17 @@ func handActCommand(ctx context.Context, args []string) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// repairs are the acts that are not a person's decision by design: what S15 counts.
|
||||||
|
func repairs(acts []link.HandAct) []link.HandAct {
|
||||||
|
out := make([]link.HandAct, 0, len(acts))
|
||||||
|
for _, a := range acts {
|
||||||
|
if !personsDecision(a) {
|
||||||
|
out = append(out, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// handActsThisWeek is how many acts were done by hand in the last seven days, for `status`; -1 when
|
// handActsThisWeek is how many acts were done by hand in the last seven days, for `status`; -1 when
|
||||||
// the log could not be read, which status says rather than reading as none.
|
// the log could not be read, which status says rather than reading as none.
|
||||||
func handActsThisWeek(ctx context.Context) (int, string) {
|
func handActsThisWeek(ctx context.Context) (int, string) {
|
||||||
|
|||||||
@@ -572,21 +572,14 @@ func watchLease(f *signalFacts) []conditions.Observation {
|
|||||||
// handActsWithin is how far back a repeated cause counts (S15).
|
// handActsWithin is how far back a repeated cause counts (S15).
|
||||||
const handActsWithin = 14 * 24 * time.Hour
|
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
|
// 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
|
// 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.
|
// still repaired twice says the healer is not enough — its reach or its budget — and is said so. **An act
|
||||||
|
// that is a person's decision by design is no repair** (handActVerbs), so it never counts; one counted
|
||||||
|
// before this was so clears on the next tick, as any condition the row no longer sees.
|
||||||
func watchHandActs(f *signalFacts) []conditions.Observation {
|
func watchHandActs(f *signalFacts) []conditions.Observation {
|
||||||
recent := make([]link.HandAct, 0, len(f.handActs))
|
recent := make([]link.HandAct, 0, len(f.handActs))
|
||||||
for _, a := range f.handActs {
|
for _, a := range repairs(f.handActs) {
|
||||||
if personsDecision[a.Cause] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if f.now.Sub(a.At) <= handActsWithin {
|
if f.now.Sub(a.At) <= handActsWithin {
|
||||||
recent = append(recent, a)
|
recent = append(recent, a)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"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
|
// actOf is an act in the log recorded by a verb, with its cause.
|
||||||
// 0230): repeated, they are not a healer wanted.
|
func actOf(at time.Time, verb, cause string) link.HandAct {
|
||||||
func TestARetirementDecisionRepeatedWantsNoHealer(t *testing.T) {
|
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)
|
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 := calm(now)
|
||||||
f.handActs = []link.HandAct{actByHand(now.Add(-2*time.Hour), kindRetireWaiting),
|
f.handActs = []link.HandAct{actOf(now.Add(-26*time.Hour), "hand-act record", "bus-upgrade"),
|
||||||
actByHand(now.Add(-time.Hour), kindRetireWaiting), actByHand(now.Add(-time.Hour), kindCleanupWaiting),
|
actOf(now.Add(-time.Hour), "bus upgrade", "bus-upgrade")}
|
||||||
actByHand(now.Add(-time.Minute), kindCleanupWaiting)}
|
|
||||||
if got := watchHandActs(f); len(got) != 0 {
|
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