From 2799e95035bfcc6d6504a4f7007b4fb2215f13d1 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 7 Oct 2026 13:55:31 +0200 Subject: [PATCH] Record a drill through its own verb, so S15 never counts a deliberate test as a repair Two ADR 0240 drills recorded with hand-act record --cause drill raised mesh.hand-acts.drill.healer-wanted. hand-act drill (seat verb drill) records them as a person's decision; hand-act record now refuses the cause, so the two recorded before it clear on the next tick and a repair cannot pass for a drill by the word it gives. --- cmd/mesh-controller/handacts.go | 57 ++++++++++++++++++++++++++-- cmd/mesh-controller/handacts_test.go | 29 ++++++++++++++ cmd/mesh-controller/seatverbs.go | 11 ++++++ cmd/mesh-controller/signals_test.go | 44 +++++++++++++++++++++ internal/catalogue/verbs.go | 8 ++++ module.json | 1 + 6 files changed, 147 insertions(+), 3 deletions(-) diff --git a/cmd/mesh-controller/handacts.go b/cmd/mesh-controller/handacts.go index f1b9109..7ab4ad5 100644 --- a/cmd/mesh-controller/handacts.go +++ b/cmd/mesh-controller/handacts.go @@ -41,6 +41,11 @@ type handActVerb struct { // causeLeakedInLogs is the cause a rotation after a value was printed into a log gives. const causeLeakedInLogs = "leaked-in-logs" +// causeDrill is the cause of every act `hand-act drill` records, and the one cause `hand-act record` +// refuses: a drill has its own verb, so whether an act was a drill is said by the verb a person chose, +// never by a word typed into a repair's cause (novox/hq issue 292). +const causeDrill = "drill" + // 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 @@ -57,8 +62,14 @@ var handActVerbs = []handActVerb{ {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"}, + // An act done outside the mesh: the mesh cannot tell a repair from a decision there, so it counts — + // except a drill recorded through it before `hand-act drill` existed (2026-10-07). It refuses the + // cause since, so no act recorded through it now carries it. + {Verb: "hand-act record", Decision: "a drill recorded before `hand-act drill` existed: a person's " + + "deliberate test, never a repair", DecidedFor: []string{causeDrill}}, + // A drill: something broken on purpose to see the mesh raise and clear it. A person's test, never a + // repair, however often it is run. + {Verb: "hand-act drill", Decision: "a drill is a person's deliberate test of the mesh, never a repair"}, // 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)"}, @@ -174,6 +185,10 @@ func handActCommand(ctx context.Context, args []string) error { return errors.New("hand-act record says the cause too: --cause , the word a second " + "act for the same reason will use — it is how a repair done twice is found") } + if strings.EqualFold(strings.TrimSpace(*f.cause), causeDrill) { + return errors.New("a drill is not recorded as a repair: hand-act drill --why " + + "records it as the person's deliberate test it is, which no healer is wanted for. Nothing was recorded") + } act := link.HandAct{Verb: "hand-act record", Args: []string{what}, Why: strings.TrimSpace(*f.why), Cause: strings.TrimSpace(*f.cause), Condition: strings.TrimSpace(*f.condition)} return onTheBus(func(conn *nats.Conn) error { @@ -186,8 +201,12 @@ func handActCommand(ctx context.Context, args []string) error { return nil }) } + if len(args) > 0 && args[0] == "drill" { + return handActDrill(ctx, args[1:]) + } if len(args) > 0 && args[0] != "list" && !strings.HasPrefix(args[0], "-") { - return errors.New("hand-act record --why --cause | hand-acts [--days N] [--json]") + return errors.New("hand-act record --why --cause | hand-act drill --why " + + "| hand-acts [--days N] [--json]") } if len(args) > 0 && args[0] == "list" { args = args[1:] @@ -239,6 +258,38 @@ func handActCommand(ctx context.Context, args []string) error { }) } +// handActDrill is `hand-act drill`: an act done on purpose to test the mesh — a module stopped, a +// process killed — recorded so the conditions it raises are read as the drill they are. Its cause is +// always causeDrill and S15 never counts it (handActVerbs). +func handActDrill(ctx context.Context, args []string) error { + set := flag.NewFlagSet("hand-act drill", flag.ContinueOnError) + why := set.String("why", "", "what the drill tests — recorded in the hand-act log (novox/hq to-be 45 §7)") + condition := set.String("condition", "", "the key of the condition the drill is meant to raise, if any") + positionals, err := parseAround(set, args) + if err != nil { + return err + } + what := strings.TrimSpace(strings.Join(positionals, " ")) + if what == "" { + return errors.New("hand-act drill --why [--condition ]") + } + if strings.TrimSpace(*why) == "" { + return errors.New("a drill says what it tests: --why (recorded in the hand-act log, novox/hq " + + "to-be 45 §7). Nothing was recorded") + } + act := link.HandAct{Verb: "hand-act drill", Args: []string{what}, Why: strings.TrimSpace(*why), + Cause: causeDrill, Condition: strings.TrimSpace(*condition)} + return onTheBus(func(conn *nats.Conn) error { + written, err := link.RecordHandAct(ctx, conn, act) + if err != nil { + return fmt.Errorf("the drill could not be recorded: %w", err) + } + fmt.Printf("recorded as %s: %s drilled %q, because %q — a drill, which no healer is wanted for\n", + written.ID, written.By, what, written.Why) + return nil + }) +} + // 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)) diff --git a/cmd/mesh-controller/handacts_test.go b/cmd/mesh-controller/handacts_test.go index 37756d7..b8d445b 100644 --- a/cmd/mesh-controller/handacts_test.go +++ b/cmd/mesh-controller/handacts_test.go @@ -92,3 +92,32 @@ func TestDurationsAreSummarisedPerSubject(t *testing.T) { t.Fatalf("a minute between words suggests %q", got[1].Suggests) } } + +// **A drill has its own verb** — `hand-act drill`, the seat's `drill` — and `hand-act record` refuses +// the cause, so a repair cannot pass for a drill by the word it gives. +func TestADrillIsRecordedThroughItsOwnVerb(t *testing.T) { + err := handActCommand(context.Background(), []string{"record", "stopped searxng", "--why", "a test", "--cause", "drill"}) + if err == nil || !strings.Contains(err.Error(), "hand-act drill") { + t.Errorf("hand-act record --cause drill was not sent to the drill verb: %v", err) + } + if err := handActCommand(context.Background(), []string{"drill", "stopped searxng"}); err == nil || + !strings.Contains(err.Error(), "--why") { + t.Errorf("a drill without what it tests: %v", err) + } + if err := handActCommand(context.Background(), []string{"drill", "--why", "a test"}); err == nil || + !strings.Contains(err.Error(), "hand-act drill 1 && argv[1] == "consumer-reset": return "broker consumer-reset" + case argv[0] == "hand-act" && len(argv) > 1 && argv[1] == "drill": + return "hand-act drill" case argv[0] == "hand-act": return "hand-act record" case argv[0] == "conditions" && len(argv) > 1 && argv[1] == "silence": diff --git a/cmd/mesh-controller/signals_test.go b/cmd/mesh-controller/signals_test.go index 068438d..9c7f4a1 100644 --- a/cmd/mesh-controller/signals_test.go +++ b/cmd/mesh-controller/signals_test.go @@ -550,3 +550,47 @@ func TestAWalkWaitingFourHoursIsUrgentAndNamesTheWayOn(t *testing.T) { t.Fatalf("it does not say how on: %q", got[0].Summary) } } + +// **A drill is a person's deliberate test, never a repair** — the log of 2026-10-07: two drills of ADR +// 0240 recorded through `hand-act record --cause drill` before `hand-act drill` existed raised +// `mesh.hand-acts.drill.healer-wanted`. A drill through its own verb never counts, the two recorded +// before it clear on the next tick, and the cause word alone on any other verb still counts — whether +// an act is a drill is said by the verb chosen, not by a word typed into a repair's cause. +func TestADrillIsNoRepairAndItsHealerWantedClears(t *testing.T) { + now := time.Date(2026, 10, 7, 12, 0, 0, 0, time.UTC) + f := calm(now) + f.handActs = []link.HandAct{actOf(now.Add(-26*time.Hour), "hand-act drill", causeDrill), + actOf(now.Add(-time.Hour), "hand-act drill", causeDrill), actOf(now.Add(-time.Minute), "hand-act drill", causeDrill)} + if got := watchHandActs(f); len(got) != 0 { + t.Errorf("drills repeated asked for a healer: %+v", got) + } + for _, verb := range []string{"push", "conditions silence", "plans close", "a verb nobody listed"} { + f := calm(now) + f.handActs = []link.HandAct{actOf(now.Add(-26*time.Hour), verb, causeDrill), actOf(now.Add(-time.Hour), verb, causeDrill)} + if got := watchHandActs(f); len(got) != 1 { + t.Errorf("%s with the cause %q passed for a drill: %+v", verb, causeDrill, got) + } + } + + 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()) + before := []conditions.Observation{{Scope: conditions.ScopeMesh, ID: "hand-acts." + causeDrill, + Token: "healer-wanted", Kind: "healer-wanted", Severity: conditions.Warning, + Summary: "\"drill\" was repaired by hand 2 times in 14 days"}} + if err := k.Reconcile(t.Context(), "S15", before); err != nil { + t.Fatal(err) + } + if open, _ := k.Open(t.Context()); len(open) != 1 { + t.Fatalf("the condition of the build before was not open: %+v", open) + } + f = calm(now) + f.handActs = []link.HandAct{actOf(now.Add(-11*time.Hour), "hand-act record", causeDrill), + actOf(now.Add(-30*time.Minute), "hand-act record", causeDrill)} + 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 two drills stayed open: %+v", open) + } +} diff --git a/internal/catalogue/verbs.go b/internal/catalogue/verbs.go index 9af432d..33e9df6 100644 --- a/internal/catalogue/verbs.go +++ b/internal/catalogue/verbs.go @@ -262,6 +262,14 @@ var ControllerVerbs = []Verb{ "cause": "the cause in a word, or a condition's kind — the word a second act for the same reason uses", "condition": "the key of the condition it addressed, if any (optional)", }, []string{"what", "why", "cause"})}, + {Name: "drill", Description: "Record a drill — something broken on purpose to see the mesh raise and clear it: " + + "a module stopped, a process killed — with what it tests, in the hand-act log. A drill is a person's " + + "deliberate test, never a repair: no healer is wanted for it however often it is run (S15).", + Input: schema(map[string]string{ + "what": "what was done on purpose, in a line", + "why": "what the drill tests", + "condition": "the key of the condition the drill is meant to raise, if any (optional)", + }, []string{"what", "why"})}, {Name: "hand-acts", Description: "What was done by hand lately — pushes, plans ended, consumers re-made, acts " + "recorded — who, why and the cause of each, and which causes repeat: each repeat is a healer the mesh lacks.", Input: schema(map[string]string{"days": "how many days back (default 14)"}, nil)}, diff --git a/module.json b/module.json index 115c786..4a94072 100644 --- a/module.json +++ b/module.json @@ -60,6 +60,7 @@ "pause", "resume", "hand-act", + "drill", "hand-acts", "durations", "conditions",