Merge pull request 'S15: a hand act a person decides by design is no repair, read from the verb that recorded it (hq to-be 45 §7)' (#94) from fix/s15-a-persons-decision-is-no-repair into main

This commit was merged in pull request #94.
This commit is contained in:
2026-10-06 18:14:47 +00:00
3 changed files with 202 additions and 19 deletions
+69 -1
View File
@@ -7,6 +7,7 @@ import (
"flag"
"fmt"
"os"
"slices"
"sort"
"strings"
"time"
@@ -27,6 +28,62 @@ import (
// step of a procedure is not a repair. `conditions silence` joins them when the condition store does
// (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.
type handActFlags struct {
why, cause, condition *string
@@ -144,7 +201,7 @@ func handActCommand(ctx context.Context, args []string) error {
if err != nil {
return err
}
repeated := link.RepeatedCauses(acts, now)
repeated := link.RepeatedCauses(repairs(acts), now)
if *asJSON {
body, err := json.MarshalIndent(map[string]any{"acts": acts, "repeated": repeated}, "", " ")
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
// the log could not be read, which status says rather than reading as none.
func handActsThisWeek(ctx context.Context) (int, string) {
+4 -11
View File
@@ -572,21 +572,14 @@ 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.
// 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 {
recent := make([]link.HandAct, 0, len(f.handActs))
for _, a := range f.handActs {
if personsDecision[a.Cause] {
continue
}
for _, a := range repairs(f.handActs) {
if f.now.Sub(a.At) <= handActsWithin {
recent = append(recent, a)
}
+129 -7
View File
@@ -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)
}
}