Files
mesh-catalog/modules/nats/cmd/nats-tools/requests_test.go
T
jschoubben f735392cb9 nats: the keeper wakes on decided, spends a warrant once, and says again what the bus did not take
Review round 1 of the registry milestone (design 25 § 12.2, § 12.3, § 12.7):
- the fill pass runs from the start, while the first view is asked;
- recreate and drop wake on the operator channel's decided on the keeper's durable consumer, never a minute's tick;
- a warrant is spent by compare-and-set (acted.<ask id> in requests) before anything is deleted; a second request on it
  is refused; a request whose ask ended unanswered is ended, said; the ask is raised before the request is handed;
- created is marked only once said; an event or a condition the bus did not take is kept, named by the health and
  said again at the next fill pass with its one id, on the subjects left (the double publish);
- the health judges the first view past its bound and never skips an unread consumer backlog;
- an unrecordable hand act is said on the request; nats_requests reads what waits, what ended and each warrant spent,
  and drops a waiting request by hand; a tool waits for its request's end on one watch of requests;
- the whole-module test reads the warrant from the router's record under a process connection.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 17:19:54 +01:00

146 lines
6.0 KiB
Go

package main
import (
"errors"
"runtime"
"strings"
"testing"
"time"
occ "git.novox.be/novox/mesh-contracts/go/clients/operatorchannelclient"
oc "git.novox.be/novox/mesh-contracts/go/seats/operatorchannel"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/handacts"
"nats-tools/internal/keeper"
)
// A person's acting tools hand their request to the keeper (design 25 § 12.3; CAT-N8): stamped with the caller the
// tool runner proved, whatever name the arguments give; a create's end read back; a drop raises the operator's ask
// first, binding exactly that act, and is not acted on by the tool.
func TestAPersonsRequestIsHandedToTheKeeperAsTheProvedCaller(t *testing.T) {
held := map[string]keeper.Request{}
var raised []oc.Ask
var acts []handacts.Request
rs := requestStore{
Put: func(key string, r keeper.Request) error {
if r.Verb == keeper.VerbCreate {
// The keeper's end, as its watch writes it.
r.Done, r.Outcome = time.Unix(1, 0), "created"
}
held[key] = r
return nil
},
Get: func(key string) (*keeper.Request, error) {
r, ok := held[key]
if !ok {
return nil, nil
}
return &r, nil
},
Keys: func() ([]string, error) {
var out []string
for k := range held {
out = append(out, k)
}
return append(out, keeper.ActedKey("bus-old")), nil
},
Delete: func(key string) error { delete(held, key); return nil },
Raw: func(string) ([]byte, error) { return []byte(`{"ask":"bus-old","request":"r0","verb":"drop"}`), nil },
Ask: func(a oc.Ask) error { raised = append(raised, a); return nil },
Now: func() time.Time { return time.Date(2026, 11, 4, 12, 0, 0, 0, time.UTC) },
Record: func(q handacts.Request) error { acts = append(acts, q); return nil },
}
tools := map[string]stdio.Tool{}
for _, tl := range requestTools(rs) {
tools[tl.Name] = tl
}
out, err := tools["nats_create"].Run(map[string]any{"module": "m", "local": "log", "why": "a test", "caller": "person.mallory"})
if err != nil {
t.Fatal(err)
}
if r := out.(*keeper.Request); r.Caller != stdio.Unproven || r.Outcome != "created" {
t.Errorf("answered %+v: acted for the caller the runtime proved (here none, %q), never person.mallory", r, stdio.Unproven)
}
out, err = tools["nats_drop"].Run(map[string]any{"module": "m", "local": "old", "why": "no manifest asks for it"})
if err != nil {
t.Fatal(err)
}
answer := out.(map[string]any)
r := held[answer["request"].(string)]
if len(raised) != 1 || r.Ask != raised[0].ID || !r.Done.IsZero() {
t.Fatalf("raised %+v, held %+v: the ask raised, the request waiting for it", raised, r)
}
binds, _ := occ.ActDigest(occ.Act{"verb": "drop", "module": "m", "local": "old"})
if o, _ := occ.OptionOf(raised[0], "go"); o.Level != occ.Destroy || o.Binds != binds || !strings.Contains(raised[0].Explanation, "snapshot") {
t.Errorf("the ask is %+v: its option destroys exactly this drop, after a snapshot", raised[0])
}
if _, err := tools["nats_drop"].Run(map[string]any{"module": "m", "local": "old"}); err == nil || !strings.Contains(err.Error(), "says why") {
t.Errorf("a drop without why: %v", err)
}
if got, _ := tools["nats_drop"].Run(map[string]any{"request": r.ID}); got.(*keeper.Request).ID != r.ID {
t.Errorf("reading the request back answered %+v", got)
}
// Review round 1 (tools F3): nats_requests reads what waits, what ended and each warrant spent, and drops a waiting
// request by hand, recorded; an ended one is not dropped.
out, err = tools["nats_requests"].Run(map[string]any{})
if err != nil {
t.Fatal(err)
}
list := out.(map[string]any)
if w, e, spent := list["waiting"].([]keeper.Request), list["ended"].([]keeper.Request), list["warrants-spent"].([]keeper.Acted); len(w) != 1 ||
w[0].ID != r.ID || len(e) != 1 || len(spent) != 1 || spent[0].Ask != "bus-old" {
t.Errorf("nats_requests answered %+v: the drop waiting, the create ended, one warrant spent", list)
}
if _, err := tools["nats_requests"].Run(map[string]any{"drop": r.ID}); err == nil || !strings.Contains(err.Error(), "says why") {
t.Errorf("a drop by hand without why: %v", err)
}
for id, r := range held {
if r.Verb == keeper.VerbCreate {
if _, err := tools["nats_requests"].Run(map[string]any{"drop": id, "why": "x"}); err == nil || !strings.Contains(err.Error(), "only a request that waits") {
t.Errorf("dropping an ended request: %v", err)
}
}
}
if _, err := tools["nats_requests"].Run(map[string]any{"drop": r.ID, "why": "nobody will answer"}); err != nil || len(acts) != 1 ||
!strings.Contains(acts[0].What, "dropped the waiting request "+r.ID) {
t.Errorf("dropped by hand (%v), recorded %+v", err, acts)
}
if _, still := held[r.ID]; still {
t.Error("the dropped request is still held")
}
// The ask first: an ask that cannot be raised hands no request to the keeper.
before := len(held)
rs.Ask = func(oc.Ask) error { return errors.New("no responders") }
tools = map[string]stdio.Tool{}
for _, tl := range requestTools(rs) {
tools[tl.Name] = tl
}
if _, err := tools["nats_recreate"].Run(map[string]any{"module": "m", "local": "log", "why": "drifted"}); err == nil || len(held) != before {
t.Errorf("an ask not raised: %v, %d requests held (was %d)", err, len(held), before)
}
// A request's end is heard on the one watch of the state, handed to the call that waits for it.
var handle func(string, []byte)
watches := 0
e := newEnds(func(h func(string, []byte)) error { watches++; handle = h; return nil })
got := make(chan *keeper.Request, 1)
go func() { got <- e.wait("r1", time.Minute) }()
for {
e.mu.Lock()
ready := len(e.waiting["r1"]) == 1 && handle != nil
e.mu.Unlock()
if ready {
break
}
runtime.Gosched()
}
handle("r1", []byte(`{"id":"r1"}`))
handle("r1", []byte(`{"id":"r1","done":"2026-11-04T12:00:01Z","outcome":"created"}`))
if r := <-got; r == nil || r.Outcome != "created" || watches != 1 {
t.Errorf("the end heard %+v on %d watch(es): created, on one", r, watches)
}
if r := e.wait("r2", time.Millisecond); r != nil || watches != 1 {
t.Errorf("an end not written: %+v on %d watch(es)", r, watches)
}
}