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")
}
}
+9
View File
@@ -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)},
+27
View File
@@ -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)
+27
View File
@@ -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)
}
}
+1
View File
@@ -63,6 +63,7 @@
"resume",
"hand-act",
"drill",
"warranted",
"hand-acts",
"durations",
"conditions",