From 0e5aed125320281af7655fbba19e04675b4a6d28 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 10 Oct 2026 03:40:49 +0200 Subject: [PATCH 1/3] Record a module's act on the operator's warrant from the router's own record (hq ADR 0274) A module that asks the operator acts with its own grants, and the hand-act log is where a person's decisions are read back. The new verb warranted records who chose, how and with which proofs from the router's record, never the caller's word, once per ask however many instances ask. --- cmd/mesh-controller/handacts.go | 3 + cmd/mesh-controller/seatverbs.go | 7 ++ cmd/mesh-controller/warranted.go | 133 ++++++++++++++++++++++++++ cmd/mesh-controller/warranted_test.go | 79 +++++++++++++++ internal/catalogue/verbs.go | 9 ++ internal/link/handacts.go | 27 ++++++ internal/link/handacts_test.go | 27 ++++++ module.json | 1 + 8 files changed, 286 insertions(+) create mode 100644 cmd/mesh-controller/warranted.go create mode 100644 cmd/mesh-controller/warranted_test.go diff --git a/cmd/mesh-controller/handacts.go b/cmd/mesh-controller/handacts.go index ae1916bf..fdfa987e 100644 --- a/cmd/mesh-controller/handacts.go +++ b/cmd/mesh-controller/handacts.go @@ -251,6 +251,9 @@ func handActCommand(ctx context.Context, args []string) error { 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]") diff --git a/cmd/mesh-controller/seatverbs.go b/cmd/mesh-controller/seatverbs.go index 48b17938..db546626 100644 --- a/cmd/mesh-controller/seatverbs.go +++ b/cmd/mesh-controller/seatverbs.go @@ -536,6 +536,11 @@ func (a *verbArguments) commandLine() ([]string, error) { argv = append(argv, "--condition", c) } return argv, nil + case "warranted": + if err := need("asker", "ask", "what"); err != nil { + return nil, err + } + return []string{"hand-act", "warrant", "--asker", str("asker"), "--ask", str("ask"), str("what")}, nil case "hand-acts": argv := []string{"hand-acts", "--json"} if d := str("days"); d != "" { @@ -935,6 +940,8 @@ func repairingCommand(argv []string) string { return "plans " + argv[1] case argv[0] == "broker" && len(argv) > 1 && argv[1] == "consumer-reset": return "broker consumer-reset" + case argv[0] == "hand-act" && len(argv) > 1 && argv[1] == "warrant": + return "" // the router's record of a person's answer, never a repair (novox/hq ADR 0274) case argv[0] == "hand-act" && len(argv) > 1 && argv[1] == "drill": return "hand-act drill" case argv[0] == "hand-act": diff --git a/cmd/mesh-controller/warranted.go b/cmd/mesh-controller/warranted.go new file mode 100644 index 00000000..90f1df9f --- /dev/null +++ b/cmd/mesh-controller/warranted.go @@ -0,0 +1,133 @@ +package main + +// A module's act on the operator's warrant, recorded in the hand-act log (novox/hq ADR 0274, ADR 0259 §6). +// +// mesh-controller hand-act warrant --asker --ask +// +// The verb `warranted` runs it. A module that asks the operator (an asker) acts on the warrant with its own grants; +// the controller's log is where a person's decisions are read back, so the module asks the controller to record +// it. **What is recorded is the router's word, never the caller's**: the controller reads the router's own record +// of that asker's ask — the bus lets only the router write it — and records who chose, through which channel, with +// which proofs, and which answer. The caller gives only what it did, said as its own words. Recorded once per +// ask, under an id the ask decides, however many of the module's instances ask; an ask still open, ended without +// a choice, or another asker's is refused and nothing is written. + +import ( + "context" + "encoding/json" + "errors" + "flag" + "fmt" + "regexp" + "strings" + + "github.com/nats-io/nats.go" + + "git.novox.be/novox/mesh-sdk/go/asks" + + "github.com/novox/mesh-controller/internal/conditions" + "github.com/novox/mesh-controller/internal/link" +) + +var askerModule = regexp.MustCompile(`^[a-z0-9][a-z0-9-]{0,62}$`) + +// warrantedID is the one entry an ask's warrant is recorded under. +func warrantedID(asker, ask string) string { return "warrant-" + asker + "-" + ask } + +// warrantedAct is the entry for an asker's act on the warrant the router recorded for its ask (state, w), or why +// none is written. +func warrantedAct(asker, ask, what, caller, state string, w *asks.Warrant) (link.HandAct, error) { + switch { + case !askerModule.MatchString(asker): + return link.HandAct{}, fmt.Errorf("%q is not a module's name", asker) + case asker == askerName: + return link.HandAct{}, errors.New("the controller records its own acts on a warrant as it performs them") + case !asks.UsableID(ask): + return link.HandAct{}, fmt.Errorf("%q is not an ask's id", ask) + case strings.TrimSpace(what) == "": + return link.HandAct{}, errors.New("say what was done on the warrant") + case state == "" || w == nil: + return link.HandAct{}, fmt.Errorf("the router holds no closed record of %s's ask %s", asker, ask) + case state == "open": + return link.HandAct{}, fmt.Errorf("%s's ask %s is still open: nobody has answered it", asker, ask) + case w.Asker != asker || w.Ask != ask: + return link.HandAct{}, fmt.Errorf("the router's record is for %s's ask %s", w.Asker, w.Ask) + case w.Outcome != asks.OutcomeChosen || w.By == nil: + return link.HandAct{}, fmt.Errorf("%s's ask %s ended %s: no person chose, so there is no warrant to record", asker, ask, w.Outcome) + case w.AskDigest == "": + return link.HandAct{}, fmt.Errorf("the router's warrant for %s's ask %s names no ask digest", asker, ask) + } + return link.HandAct{ID: warrantedID(asker, ask), Verb: handActWarrant, Args: []string{strings.TrimSpace(what)}, + Why: fmt.Sprintf("%s (ask %s of %s)", w.Says(), ask, asker), By: byWords(*w), Cause: conditions.CauseOperatorAnswer, + Via: viaWords(*w), Ask: ask, Proofs: w.Proofs, RequestedBy: asker + ", recorded at the word of " + caller, + Outcome: "done by " + asker, At: w.At.UTC()}, nil +} + +// readRouterRecord reads the router's record of one asker's ask: its state and warrant, or "" when there is none. +// The controller's grant reaches the JetStream API whole (`$JS.API.>`), so it reads any asker's record. +func readRouterRecord(ctx context.Context, conn *nats.Conn, bucket, asker, ask string) (string, *asks.Warrant, error) { + reply, err := conn.RequestWithContext(ctx, "$JS.API.DIRECT.GET.KV_"+bucket+".$KV."+bucket+"."+asker+"."+ask, nil) + if err != nil { + return "", nil, err + } + if status := reply.Header.Get("Status"); status != "" { + if status == "404" { + return "", nil, nil + } + return "", nil, fmt.Errorf("the router's record could not be read: %s %s", status, reply.Header.Get("Description")) + } + var rec struct { + State string `json:"state"` + Warrant *asks.Warrant `json:"warrant"` + } + if err := json.Unmarshal(reply.Data, &rec); err != nil { + return "", nil, fmt.Errorf("the router's record of %s's ask %s cannot be read: %w", asker, ask, err) + } + return rec.State, rec.Warrant, nil +} + +func handActWarrantCommand(ctx context.Context, args []string) error { + set := flag.NewFlagSet("hand-act warrant", flag.ContinueOnError) + asker := set.String("asker", "", "the module that asked") + ask := set.String("ask", "", "its ask's id") + positionals, err := parseAround(set, args) + if err != nil { + return err + } + what := strings.TrimSpace(strings.Join(positionals, " ")) + if *asker == "" || *ask == "" || what == "" { + return errors.New("hand-act warrant --asker --ask ") + } + open, err := openStores(ctx) + if err != nil { + return err + } + defer open.Close() + bucket, err := asksRecords(ctx, open.inventory) + if err != nil { + return err + } + if bucket == "" { + return errors.New("no module declares the operator channel's records, so no warrant can be read") + } + return onTheBus(func(conn *nats.Conn) error { + state, w, err := readRouterRecord(ctx, conn, bucket, *asker, *ask) + if err != nil { + return err + } + act, err := warrantedAct(*asker, *ask, what, link.Caller(), state, w) + if err != nil { + return fmt.Errorf("%w. Nothing was recorded", err) + } + written, err := link.RecordHandActOnce(ctx, conn, act) + if err != nil { + return fmt.Errorf("the warrant could not be recorded: %w", err) + } + if !written { + fmt.Printf("already recorded as %s: %s\n", act.ID, act.Why) + return nil + } + fmt.Printf("recorded as %s: %s, through %s; %s\n", act.ID, act.Why, act.Via, what) + return nil + }) +} diff --git a/cmd/mesh-controller/warranted_test.go b/cmd/mesh-controller/warranted_test.go new file mode 100644 index 00000000..7de5d4a2 --- /dev/null +++ b/cmd/mesh-controller/warranted_test.go @@ -0,0 +1,79 @@ +package main + +import ( + "slices" + "strings" + "testing" + "time" + + "git.novox.be/novox/mesh-sdk/go/asks" + + "github.com/novox/mesh-controller/internal/conditions" +) + +func chosenWarrant() *asks.Warrant { + return &asks.Warrant{Ask: "instr-1", Asker: "claude-code", Outcome: asks.OutcomeChosen, Option: "approve", + Label: "Approve", Level: asks.Approve, Channel: "telegram", Proofs: []string{"P1"}, + By: &asks.Person{Who: asks.Operator, Kind: "telegram", Identity: "42", Verified: "user id verified"}, + At: time.Date(2026, 10, 10, 4, 0, 0, 0, time.UTC), AskDigest: "sha256:ab"} +} + +// What is recorded of a module's act on a warrant is the router's word (novox/hq ADR 0274): who chose, how and +// with which proofs; the caller gives only what it did. Nothing is recorded without a person's choice. +func TestAWarrantIsRecordedFromTheRoutersRecordAlone(t *testing.T) { + act, err := warrantedAct("claude-code", "instr-1", "claude-code proposal instr-1 approved on shanks", + "node-tools.shanks", "chosen", chosenWarrant()) + if err != nil { + t.Fatal(err) + } + if act.ID != "warrant-claude-code-instr-1" || act.Verb != handActWarrant || act.Cause != conditions.CauseOperatorAnswer || + act.By != "the operator, as telegram identity 42" || act.Ask != "instr-1" || !slices.Equal(act.Proofs, []string{"P1"}) || + !strings.Contains(act.Why, "the operator, via telegram (user id verified), chose Approve") || + !strings.Contains(act.RequestedBy, "node-tools.shanks") { + t.Fatalf("recorded as %+v", act) + } + for name, c := range map[string]struct { + asker, ask, state string + w func() *asks.Warrant + }{ + "no record": {"claude-code", "instr-1", "", func() *asks.Warrant { return nil }}, + "still open": {"claude-code", "instr-1", "open", chosenWarrant}, + "another asker's": {"messenger", "instr-1", "chosen", chosenWarrant}, + "another ask's": {"claude-code", "instr-2", "chosen", chosenWarrant}, + "the controller's": {"mesh-controller", "instr-1", "chosen", chosenWarrant}, + "not a module": {"Claude Code", "instr-1", "chosen", chosenWarrant}, + "expired": {"claude-code", "instr-1", "expired", func() *asks.Warrant { + w := chosenWarrant() + w.Outcome, w.By = asks.OutcomeExpired, nil + return w + }}, + "no digest": {"claude-code", "instr-1", "chosen", func() *asks.Warrant { + w := chosenWarrant() + w.AskDigest = "" + return w + }}, + } { + if _, err := warrantedAct(c.asker, c.ask, "did it", "x", c.state, c.w()); err == nil { + t.Errorf("%s: recorded", name) + } + } +} + +func TestTheWarrantedVerbRunsTheWarrantLineWithoutAWhy(t *testing.T) { + argv, err := argvFor("warranted", map[string]any{"asker": "claude-code", "ask": "instr-1", "what": "approved on shanks"}) + if err != nil { + t.Fatal(err) + } + if !slices.Equal(argv, []string{"hand-act", "warrant", "--asker", "claude-code", "--ask", "instr-1", "approved on shanks"}) { + t.Fatalf("%v", argv) + } + if repairingCommand(argv) != "" { + t.Error("recording a person's answer is taken for a repair") + } + if terminalOnly(argv) != nil { + t.Error("the verb is kept for the terminal") + } + if _, err := argvFor("warranted", map[string]any{"asker": "claude-code"}); err == nil { + t.Error("a call naming no ask was taken") + } +} diff --git a/internal/catalogue/verbs.go b/internal/catalogue/verbs.go index a052ab98..adbccaea 100644 --- a/internal/catalogue/verbs.go +++ b/internal/catalogue/verbs.go @@ -338,6 +338,15 @@ var ControllerVerbs = []Verb{ "why": "what the drill tests", "condition": "the key of the condition the drill is meant to raise, if any (optional)", }, []string{"what", "why"})}, + {Name: "warranted", Description: "Record in the hand-act log what a module did on the operator's warrant (novox/hq " + + "ADR 0274, ADR 0259): who chose, through which channel, with which proofs and which answer are read from the " + + "router's own record of that module's ask, never from the caller; recorded once per ask however often it is " + + "asked. Refused for an ask still open, ended without a choice, or not that module's.", + Input: schema(map[string]string{ + "asker": "the module that asked, e.g. claude-code", + "ask": "its ask's id", + "what": "what it did on the warrant, in a line", + }, []string{"asker", "ask", "what"})}, {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/internal/link/handacts.go b/internal/link/handacts.go index dd42f6e0..92de55c7 100644 --- a/internal/link/handacts.go +++ b/internal/link/handacts.go @@ -122,6 +122,33 @@ func RecordHandAct(ctx context.Context, conn *nats.Conn, act HandAct) (HandAct, return act, err } +// RecordHandActOnce writes one entry under the id it carries, only where none is: an act recorded once however +// often it is asked, such as a module's act on a warrant, asked by each of its instances (novox/hq ADR 0274). It +// answers false, with no error, when the entry was already there. +func RecordHandActOnce(ctx context.Context, conn *nats.Conn, act HandAct) (bool, error) { + if act.ID == "" || strings.TrimSpace(act.Why) == "" { + return false, errors.New("an act recorded once carries its id and why") + } + if act.At.IsZero() { + act.At = time.Now().UTC() + } + kv, err := handActs(ctx, conn) + if err != nil { + return false, err + } + body, err := json.Marshal(act) + if err != nil { + return false, err + } + if _, err := kv.Create(ctx, act.ID, body); err != nil { + if errors.Is(err, jetstream.ErrKeyExists) { + return false, nil + } + return false, err + } + return true, nil +} + // HandActs is every entry since a moment, oldest first. func HandActs(ctx context.Context, conn *nats.Conn, since time.Time) ([]HandAct, error) { kv, err := handActs(ctx, conn) diff --git a/internal/link/handacts_test.go b/internal/link/handacts_test.go index 059e5527..e670fe7c 100644 --- a/internal/link/handacts_test.go +++ b/internal/link/handacts_test.go @@ -57,3 +57,30 @@ func TestNatsAnActByHandIsKeptWithWhyAndARepeatIsFound(t *testing.T) { t.Fatalf("%q", acts[2].ID) } } + +// An act on a warrant asked by each of a module's instances is recorded once (novox/hq ADR 0274). +func TestNatsAnActRecordedOnceIsWrittenOnce(t *testing.T) { + js := aBus(t) + api, err := jetstream.New(js.Conn()) + if err != nil { + t.Fatal(err) + } + _ = api.DeleteKeyValue(t.Context(), broker.HandActsBucket) + if err := js.EnsureControllerBuckets(); err != nil { + t.Fatal(err) + } + act := HandAct{ID: "warrant-claude-code-instr-1", Verb: "warrant", Why: "the operator chose Approve", By: "the operator"} + if _, err := RecordHandActOnce(t.Context(), js.Conn(), HandAct{Verb: "warrant", Why: "x"}); err == nil { + t.Fatal("an act without its id was written") + } + for i, want := range []bool{true, false, false} { + written, err := RecordHandActOnce(t.Context(), js.Conn(), act) + if err != nil || written != want { + t.Fatalf("ask %d: written %v, %v", i, written, err) + } + } + acts, err := HandActs(t.Context(), js.Conn(), time.Now().Add(-time.Hour)) + if err != nil || len(acts) != 1 || acts[0].ID != act.ID { + t.Fatalf("%v %+v", err, acts) + } +} diff --git a/module.json b/module.json index e0af4cbb..b3cef223 100644 --- a/module.json +++ b/module.json @@ -63,6 +63,7 @@ "resume", "hand-act", "drill", + "warranted", "hand-acts", "durations", "conditions", From abd3078491c526698a53078c5d0e84dfb75169a7 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 10 Oct 2026 03:52:04 +0200 Subject: [PATCH 2/3] warranted takes no word of the caller's: the record is the router's alone (hq ADR 0274 review) A caller's own line, recorded first under the one id the ask decides, would stand for every node's. --- cmd/mesh-controller/seatverbs.go | 4 ++-- cmd/mesh-controller/warranted.go | 21 +++++++++------------ cmd/mesh-controller/warranted_test.go | 15 +++++++++------ internal/catalogue/verbs.go | 6 +++--- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/cmd/mesh-controller/seatverbs.go b/cmd/mesh-controller/seatverbs.go index db546626..16241347 100644 --- a/cmd/mesh-controller/seatverbs.go +++ b/cmd/mesh-controller/seatverbs.go @@ -537,10 +537,10 @@ func (a *verbArguments) commandLine() ([]string, error) { } return argv, nil case "warranted": - if err := need("asker", "ask", "what"); err != nil { + if err := need("asker", "ask"); err != nil { return nil, err } - return []string{"hand-act", "warrant", "--asker", str("asker"), "--ask", str("ask"), str("what")}, nil + return []string{"hand-act", "warrant", "--asker", str("asker"), "--ask", str("ask")}, nil case "hand-acts": argv := []string{"hand-acts", "--json"} if d := str("days"); d != "" { diff --git a/cmd/mesh-controller/warranted.go b/cmd/mesh-controller/warranted.go index 90f1df9f..8f5caff8 100644 --- a/cmd/mesh-controller/warranted.go +++ b/cmd/mesh-controller/warranted.go @@ -2,13 +2,14 @@ package main // A module's act on the operator's warrant, recorded in the hand-act log (novox/hq ADR 0274, ADR 0259 §6). // -// mesh-controller hand-act warrant --asker --ask +// mesh-controller hand-act warrant --asker --ask // // The verb `warranted` runs it. A module that asks the operator (an asker) acts on the warrant with its own grants; // the controller's log is where a person's decisions are read back, so the module asks the controller to record // it. **What is recorded is the router's word, never the caller's**: the controller reads the router's own record // of that asker's ask — the bus lets only the router write it — and records who chose, through which channel, with -// which proofs, and which answer. The caller gives only what it did, said as its own words. Recorded once per +// which proofs, and which answer. The caller gives nothing but which ask: a word of its own, recorded first under +// the one id, would stand for every node's (the review of 2026-10-10). Recorded once per // ask, under an id the ask decides, however many of the module's instances ask; an ask still open, ended without // a choice, or another asker's is refused and nothing is written. @@ -19,7 +20,6 @@ import ( "flag" "fmt" "regexp" - "strings" "github.com/nats-io/nats.go" @@ -36,7 +36,7 @@ func warrantedID(asker, ask string) string { return "warrant-" + asker + "-" + a // warrantedAct is the entry for an asker's act on the warrant the router recorded for its ask (state, w), or why // none is written. -func warrantedAct(asker, ask, what, caller, state string, w *asks.Warrant) (link.HandAct, error) { +func warrantedAct(asker, ask, caller, state string, w *asks.Warrant) (link.HandAct, error) { switch { case !askerModule.MatchString(asker): return link.HandAct{}, fmt.Errorf("%q is not a module's name", asker) @@ -44,8 +44,6 @@ func warrantedAct(asker, ask, what, caller, state string, w *asks.Warrant) (link return link.HandAct{}, errors.New("the controller records its own acts on a warrant as it performs them") case !asks.UsableID(ask): return link.HandAct{}, fmt.Errorf("%q is not an ask's id", ask) - case strings.TrimSpace(what) == "": - return link.HandAct{}, errors.New("say what was done on the warrant") case state == "" || w == nil: return link.HandAct{}, fmt.Errorf("the router holds no closed record of %s's ask %s", asker, ask) case state == "open": @@ -57,7 +55,7 @@ func warrantedAct(asker, ask, what, caller, state string, w *asks.Warrant) (link case w.AskDigest == "": return link.HandAct{}, fmt.Errorf("the router's warrant for %s's ask %s names no ask digest", asker, ask) } - return link.HandAct{ID: warrantedID(asker, ask), Verb: handActWarrant, Args: []string{strings.TrimSpace(what)}, + return link.HandAct{ID: warrantedID(asker, ask), Verb: handActWarrant, Args: []string{fmt.Sprintf("%s acted on %s, chosen on its ask %s", asker, w.Label, ask)}, Why: fmt.Sprintf("%s (ask %s of %s)", w.Says(), ask, asker), By: byWords(*w), Cause: conditions.CauseOperatorAnswer, Via: viaWords(*w), Ask: ask, Proofs: w.Proofs, RequestedBy: asker + ", recorded at the word of " + caller, Outcome: "done by " + asker, At: w.At.UTC()}, nil @@ -94,9 +92,8 @@ func handActWarrantCommand(ctx context.Context, args []string) error { if err != nil { return err } - what := strings.TrimSpace(strings.Join(positionals, " ")) - if *asker == "" || *ask == "" || what == "" { - return errors.New("hand-act warrant --asker --ask ") + if *asker == "" || *ask == "" || len(positionals) > 0 { + return errors.New("hand-act warrant --asker --ask : what is recorded is the router's record, and nothing else") } open, err := openStores(ctx) if err != nil { @@ -115,7 +112,7 @@ func handActWarrantCommand(ctx context.Context, args []string) error { if err != nil { return err } - act, err := warrantedAct(*asker, *ask, what, link.Caller(), state, w) + act, err := warrantedAct(*asker, *ask, link.Caller(), state, w) if err != nil { return fmt.Errorf("%w. Nothing was recorded", err) } @@ -127,7 +124,7 @@ func handActWarrantCommand(ctx context.Context, args []string) error { fmt.Printf("already recorded as %s: %s\n", act.ID, act.Why) return nil } - fmt.Printf("recorded as %s: %s, through %s; %s\n", act.ID, act.Why, act.Via, what) + fmt.Printf("recorded as %s: %s, through %s\n", act.ID, act.Why, act.Via) return nil }) } diff --git a/cmd/mesh-controller/warranted_test.go b/cmd/mesh-controller/warranted_test.go index 7de5d4a2..1b90ec33 100644 --- a/cmd/mesh-controller/warranted_test.go +++ b/cmd/mesh-controller/warranted_test.go @@ -21,15 +21,15 @@ func chosenWarrant() *asks.Warrant { // What is recorded of a module's act on a warrant is the router's word (novox/hq ADR 0274): who chose, how and // with which proofs; the caller gives only what it did. Nothing is recorded without a person's choice. func TestAWarrantIsRecordedFromTheRoutersRecordAlone(t *testing.T) { - act, err := warrantedAct("claude-code", "instr-1", "claude-code proposal instr-1 approved on shanks", - "node-tools.shanks", "chosen", chosenWarrant()) + act, err := warrantedAct("claude-code", "instr-1", "node-tools.shanks", "chosen", chosenWarrant()) if err != nil { t.Fatal(err) } if act.ID != "warrant-claude-code-instr-1" || act.Verb != handActWarrant || act.Cause != conditions.CauseOperatorAnswer || act.By != "the operator, as telegram identity 42" || act.Ask != "instr-1" || !slices.Equal(act.Proofs, []string{"P1"}) || !strings.Contains(act.Why, "the operator, via telegram (user id verified), chose Approve") || - !strings.Contains(act.RequestedBy, "node-tools.shanks") { + !strings.Contains(act.RequestedBy, "node-tools.shanks") || + !slices.Equal(act.Args, []string{"claude-code acted on Approve, chosen on its ask instr-1"}) { t.Fatalf("recorded as %+v", act) } for name, c := range map[string]struct { @@ -53,18 +53,21 @@ func TestAWarrantIsRecordedFromTheRoutersRecordAlone(t *testing.T) { return w }}, } { - if _, err := warrantedAct(c.asker, c.ask, "did it", "x", c.state, c.w()); err == nil { + if _, err := warrantedAct(c.asker, c.ask, "x", c.state, c.w()); err == nil { t.Errorf("%s: recorded", name) } } } func TestTheWarrantedVerbRunsTheWarrantLineWithoutAWhy(t *testing.T) { - argv, err := argvFor("warranted", map[string]any{"asker": "claude-code", "ask": "instr-1", "what": "approved on shanks"}) + if _, err := argvFor("warranted", map[string]any{"asker": "claude-code", "ask": "instr-1", "what": "a word of the caller's"}); err == nil { + t.Error("the caller's own words were taken into the record") + } + argv, err := argvFor("warranted", map[string]any{"asker": "claude-code", "ask": "instr-1"}) if err != nil { t.Fatal(err) } - if !slices.Equal(argv, []string{"hand-act", "warrant", "--asker", "claude-code", "--ask", "instr-1", "approved on shanks"}) { + if !slices.Equal(argv, []string{"hand-act", "warrant", "--asker", "claude-code", "--ask", "instr-1"}) { t.Fatalf("%v", argv) } if repairingCommand(argv) != "" { diff --git a/internal/catalogue/verbs.go b/internal/catalogue/verbs.go index adbccaea..c85c952a 100644 --- a/internal/catalogue/verbs.go +++ b/internal/catalogue/verbs.go @@ -341,12 +341,12 @@ var ControllerVerbs = []Verb{ {Name: "warranted", Description: "Record in the hand-act log what a module did on the operator's warrant (novox/hq " + "ADR 0274, ADR 0259): who chose, through which channel, with which proofs and which answer are read from the " + "router's own record of that module's ask, never from the caller; recorded once per ask however often it is " + - "asked. Refused for an ask still open, ended without a choice, or not that module's.", + "asked; the caller names the ask and says nothing else. Refused for an ask still open, ended without a choice, " + + "or not that module's.", Input: schema(map[string]string{ "asker": "the module that asked, e.g. claude-code", "ask": "its ask's id", - "what": "what it did on the warrant, in a line", - }, []string{"asker", "ask", "what"})}, + }, []string{"asker", "ask"})}, {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)}, From 88e84a4dd78398912e00413e4b7e42cf4d2f910d Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 10 Oct 2026 03:55:44 +0200 Subject: [PATCH 3/3] warranted says what the operator chose, never that the module acted (hq ADR 0274 review) --- cmd/mesh-controller/warranted.go | 4 ++-- cmd/mesh-controller/warranted_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/mesh-controller/warranted.go b/cmd/mesh-controller/warranted.go index 8f5caff8..9f6f918e 100644 --- a/cmd/mesh-controller/warranted.go +++ b/cmd/mesh-controller/warranted.go @@ -55,10 +55,10 @@ func warrantedAct(asker, ask, caller, state string, w *asks.Warrant) (link.HandA case w.AskDigest == "": return link.HandAct{}, fmt.Errorf("the router's warrant for %s's ask %s names no ask digest", asker, ask) } - return link.HandAct{ID: warrantedID(asker, ask), Verb: handActWarrant, Args: []string{fmt.Sprintf("%s acted on %s, chosen on its ask %s", asker, w.Label, ask)}, + return link.HandAct{ID: warrantedID(asker, ask), Verb: handActWarrant, Args: []string{fmt.Sprintf("the operator chose %s on %s's ask %s", w.Label, asker, ask)}, Why: fmt.Sprintf("%s (ask %s of %s)", w.Says(), ask, asker), By: byWords(*w), Cause: conditions.CauseOperatorAnswer, Via: viaWords(*w), Ask: ask, Proofs: w.Proofs, RequestedBy: asker + ", recorded at the word of " + caller, - Outcome: "done by " + asker, At: w.At.UTC()}, nil + Outcome: "chosen; what " + asker + " did with it is in its own record", At: w.At.UTC()}, nil } // readRouterRecord reads the router's record of one asker's ask: its state and warrant, or "" when there is none. diff --git a/cmd/mesh-controller/warranted_test.go b/cmd/mesh-controller/warranted_test.go index 1b90ec33..b97ea128 100644 --- a/cmd/mesh-controller/warranted_test.go +++ b/cmd/mesh-controller/warranted_test.go @@ -29,7 +29,7 @@ func TestAWarrantIsRecordedFromTheRoutersRecordAlone(t *testing.T) { act.By != "the operator, as telegram identity 42" || act.Ask != "instr-1" || !slices.Equal(act.Proofs, []string{"P1"}) || !strings.Contains(act.Why, "the operator, via telegram (user id verified), chose Approve") || !strings.Contains(act.RequestedBy, "node-tools.shanks") || - !slices.Equal(act.Args, []string{"claude-code acted on Approve, chosen on its ask instr-1"}) { + !slices.Equal(act.Args, []string{"the operator chose Approve on claude-code's ask instr-1"}) { t.Fatalf("recorded as %+v", act) } for name, c := range map[string]struct {