Record a module's act on the operator's warrant from the router's own record (hq ADR 0274)
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request

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.
This commit is contained in:
jochen
2026-10-10 03:40:49 +02:00
parent 9517f590ac
commit 0e5aed1253
8 changed files with 286 additions and 0 deletions
+3
View File
@@ -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 <what> --why <text> --cause <word> | hand-act drill <what> --why <text> " +
"| hand-acts [--days N] [--json]")
+7
View File
@@ -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":
+133
View File
@@ -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 <module> --ask <id> <what was done>
//
// 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 <module> --ask <id> <what was done on the warrant>")
}
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
})
}
+79
View File
@@ -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")
}
}