package main import ( "context" "encoding/json" "errors" "flag" "fmt" "io" "os" "slices" "sort" "strings" "time" "github.com/nats-io/nats.go" "github.com/novox/mesh-controller/internal/conditions" "github.com/novox/mesh-controller/internal/link" ) // Acts done by hand, and why (novox/hq to-be 45 §7). // // **Which verbs ask why.** `plans close` and `plans stop`, `broker consumer-reset` and `hand-act // record` refuse without it everywhere: nothing automated runs them, so a call without a reason is a // person who has not given one. A named `push` asks for it through the mesh-controller seat, which is // how a person or an agent acts by hand on the mesh; at a shell `--why` is recorded when given and not // required, because the installer and the lab push by command line as a step of what they do, and a // 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 // DecidedWhen limits Decision to the acts it answers true for: what the controller read the act to // be from what it did (a push's kind), never a word the person gave. DecidedWhen func(link.HandAct) bool } // 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 // 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 — except a push that only moved builds a `record` policy held for // a person's word (ADR 0242), which the push itself reads from what it carried (recorded_push.go). {Verb: "push", Decision: "a recorded build moves only by a person's push: that push is the word its " + "upgrade policy asks for (ADR 0242)", DecidedWhen: pushedRecorded}, // Stopping or starting a walk the operator chose on a warrant (novox/hq ADR 0259) is their decision. {Verb: "plans stop", Decision: "the operator's answer to an ask is their decision, not a repair (ADR 0259)", DecidedFor: []string{conditions.CauseOperatorAnswer}}, {Verb: "plans close"}, // A walk started by a person instead of its delivery's owner (novox/hq ADR 0239): the owner down, or // not trusted with it — either is a repair the owner should have made. Unless the operator chose it on // a warrant (ADR 0259). {Verb: "plans go", Decision: "the operator's answer to an ask is their decision, not a repair (ADR 0259)", DecidedFor: []string{conditions.CauseOperatorAnswer}}, // An act the operator chose on a warrant (novox/hq ADR 0259): asked by the controller, answered on a // channel that proved who answered, performed by the controller as itself. {Verb: handActWarrant, Decision: "the operator chose it, answering what the controller asked (ADR 0259)"}, {Verb: "broker consumer-reset"}, // Silencing the same condition twice says the condition, or what it watches, wants mending — unless // it is the operator's answer on a notification: a decision to live with it (novox/hq ADR 0258). {Verb: "conditions silence", Decision: "the operator's answer on a notification is their decision, " + "not a repair (ADR 0258)", DecidedFor: []string{conditions.CauseOperatorAnswer}}, // 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)"}, {Verb: "cleanup delete", Decision: "nothing retired is deleted without a person (ADR 0230)"}, // What becomes of a message a consumer gave up on (novox/hq issue 330): kept until a person says. {Verb: "dead-letters deliver", Decision: "a message a consumer gave up on is delivered again only on a " + "person's word (issue 330)"}, {Verb: "dead-letters drop", Decision: "a message a consumer gave up on is let go only on a person's word " + "(issue 330)"}, // The sweep run on a person's word rather than after a build: the same decision the records make, at // a moment the person chose (ADR 0251) — never a repair. {Verb: "collect", Decision: "letting the store go of what the records keep for no reason, now rather " + "than at the next build, is a person's word (ADR 0251)"}, // Recording a copy no record names as the mesh's: a person's reading of the store, never a repair // (ADR 0257). {Verb: "mirrors", Decision: "which copies in the store no record names are the mesh's is a person's " + "word (ADR 0257)"}, {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. // A value given at the desk (novox/hq ADR 0259 §10): an outside party's key, such as a bot token, which // only a person can give. Their word, never a repair. {Verb: "secret accept", Decision: "a value an outside party issued is given by a person, at their desk"}, {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)) && (v.DecidedWhen == nil || v.DecidedWhen(a)) } return false } // handActFlags are the flags every repairing verb takes. type handActFlags struct { why, cause, condition *string } func addHandActFlags(set *flag.FlagSet) handActFlags { return handActFlags{ why: set.String("why", "", "why this is done by hand — recorded in the hand-act log (novox/hq to-be 45 §7)"), cause: set.String("cause", "", "the cause, in a word or a condition's kind; the verb's own name when not given"), condition: set.String("condition", "", "the key of the condition this act addresses, if any"), } } // given is whether a reason was given. func (f handActFlags) given() bool { return strings.TrimSpace(*f.why) != "" } // require refuses an act without a reason, before anything is done. func (f handActFlags) require(verb string) error { if f.given() { return nil } return fmt.Errorf("%s is a repair done by hand, and says why: --why (recorded in the hand-act "+ "log, novox/hq to-be 45 §7). Nothing was done", verb) } // handActConn is the serving controller's connection, for what it reads of the log itself; a // command dials its own. var handActConn *nats.Conn // onTheBus runs f with a connection to the bus: the serving controller's, or one of its own. func onTheBus(f func(*nats.Conn) error) error { if handActConn != nil { return f(handActConn) } js, err := aBus() if err != nil { return err } defer js.Close() return f(js.Conn()) } // record writes the entry for an act about to be done. **Before the act, and never instead of it**: // a log that cannot be written is said loudly, and the repair it was about still happens — a mesh // whose bus is down is exactly the mesh somebody is repairing by hand. func (f handActFlags) record(ctx context.Context, verb string, args []string) { if !f.given() { return } act := link.HandAct{Verb: verb, Args: args, Why: strings.TrimSpace(*f.why), Cause: strings.TrimSpace(*f.cause), Condition: strings.TrimSpace(*f.condition)} // A push naming one machine says whether it only moves recorded builds (novox/hq issue 301): // read from what it carries, before it is sent. if verb == "push" && len(args) == 1 && !strings.HasPrefix(args[0], "-") { switch carried, why, err := recordedPushOf(ctx, args[0]); { case err != nil: fmt.Fprintf(os.Stderr, "whether this push only moves recorded builds could not be read, so it is "+ "recorded as a push by hand: %v\n", err) case len(carried) > 0: act.Kind, act.Carried = link.KindRecordedBuilds, carried default: fmt.Printf("a push by hand, not of recorded builds only: %s\n", why) } } err := onTheBus(func(conn *nats.Conn) error { written, err := link.RecordHandAct(ctx, conn, act) act = written return err }) if err != nil { fmt.Fprintf(os.Stderr, "this act by hand could NOT be recorded in the hand-act log, and is done anyway: %v\n", err) return } if act.Kind == link.KindRecordedBuilds { fmt.Printf("recorded as %s in the hand-act log: a push of recorded builds (%s) by %s, because %q "+ "— the person's word their upgrade policy asks for, which no healer is wanted for\n", act.ID, strings.Join(act.Carried, "; "), act.By, act.Why) return } fmt.Printf("recorded as %s in the hand-act log: %s, because %q (cause: %s)\n", act.ID, act.By, act.Why, act.Cause) } // handActCommand is `hand-act record` and `hand-acts`. func handActCommand(ctx context.Context, args []string) error { if len(args) > 0 && args[0] == "record" { set := flag.NewFlagSet("hand-act record", flag.ContinueOnError) f := addHandActFlags(set) positionals, err := parseAround(set, args[1:]) if err != nil { return err } what := strings.TrimSpace(strings.Join(positionals, " ")) if what == "" { return errors.New("hand-act record --why [--cause ] [--condition ]") } if err := f.require("hand-act record"); err != nil { return err } if strings.TrimSpace(*f.cause) == "" { 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 { written, err := link.RecordHandAct(ctx, conn, act) if err != nil { return fmt.Errorf("the act could not be recorded: %w", err) } fmt.Printf("recorded as %s: %s did %q, because %q (cause: %s)\n", written.ID, written.By, what, written.Why, written.Cause) return nil }) } if len(args) > 0 && args[0] == "drill" { return handActDrill(ctx, args[1:]) } if len(args) > 0 && args[0] == "warrant" { return handActWarrantCommand(ctx, args[1:]) } if len(args) > 0 && args[0] != "list" && !strings.HasPrefix(args[0], "-") { 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:] } return listHandActs(ctx, args, os.Stdout) } // listHandActs is `hand-acts`: what was done by hand lately, and the causes done more than once. func listHandActs(ctx context.Context, args []string, w io.Writer) error { set := flag.NewFlagSet("hand-acts", flag.ContinueOnError) usageTo(set, w) days := set.Int("days", 14, "how many days back") asJSON := set.Bool("json", false, "as data") if _, err := parseAround(set, args); err != nil { return err } return onTheBus(func(conn *nats.Conn) error { now := time.Now() acts, err := link.HandActs(ctx, conn, now.Add(-time.Duration(*days)*24*time.Hour)) if err != nil { return err } repeated := link.RepeatedCauses(repairs(acts), now) if *asJSON { body, err := json.MarshalIndent(map[string]any{"acts": acts, "repeated": repeated}, "", " ") if err != nil { return err } fmt.Fprintln(w, string(body)) return nil } if len(acts) == 0 { fmt.Fprintf(w, "nothing was done by hand in the last %d day(s)\n", *days) return nil } for i := len(acts) - 1; i >= 0; i-- { a := acts[i] fmt.Fprintf(w, "%s %s %s %s\n by %s — %s (cause: %s", a.At.Local().Format("2006-01-02 15:04"), a.ID, a.Verb, strings.Join(a.Args, " "), a.By, a.Why, a.Cause) if a.Condition != "" { fmt.Fprintf(w, ", condition %s", a.Condition) } fmt.Fprintln(w, ")") if pushedRecorded(a) { carried := strings.Join(a.Carried, "; ") if carried == "" { carried = recordedBefore[a.ID] } fmt.Fprintf(w, " a push of recorded builds, no repair: %s\n", carried) } } if len(repeated) > 0 { causes := make([]string, 0, len(repeated)) for c, n := range repeated { causes = append(causes, fmt.Sprintf("%s ×%d", c, n)) } sort.Strings(causes) fmt.Fprintf(w, "\ndone by hand more than once in a fortnight — a healer is wanted (to-be 45 S15): %s\n", strings.Join(causes, ", ")) } return nil }) } // 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)) 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) { n := -1 err := onTheBus(func(conn *nats.Conn) error { reading, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() acts, err := link.HandActs(reading, conn, time.Now().Add(-7*24*time.Hour)) n = len(acts) return err }) if err != nil { return -1, err.Error() } return n, "" }