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
146 lines
6.0 KiB
Go
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)
|
|
}
|
|
}
|