package approval import ( "encoding/json" "sort" "strings" "sync" "testing" "time" "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient" seat "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel" catalogseat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" stdio "git.novox.be/novox/mesh-sdk/go" "git.novox.be/novox/mesh-sdk/go/seatdef" ) // mem is the runner's state approvals as a fixture; fail makes every read and write fail, as a bus refusing them. type mem struct { mu sync.Mutex vals map[string]json.RawMessage revs map[string]uint64 n uint64 fail error } func newMem() *mem { return &mem{vals: map[string]json.RawMessage{}, revs: map[string]uint64{}} } func (m *mem) Get(k string) (*stdio.StateEntry, error) { m.mu.Lock() defer m.mu.Unlock() if m.fail != nil { return nil, m.fail } v, ok := m.vals[k] if !ok { return nil, nil } return &stdio.StateEntry{Key: k, Value: v, Revision: m.revs[k]}, nil } func (m *mem) Create(k string, v any) (uint64, error) { m.mu.Lock() defer m.mu.Unlock() if m.fail != nil { return 0, m.fail } if _, ok := m.vals[k]; ok { return 0, stdio.ErrChanged } return m.put(k, v) } func (m *mem) Update(k string, v any, rev uint64) (uint64, error) { m.mu.Lock() defer m.mu.Unlock() if m.fail != nil { return 0, m.fail } if rev == 0 || m.revs[k] != rev { return 0, stdio.ErrChanged } return m.put(k, v) } func (m *mem) Put(k string, v any) (uint64, error) { m.mu.Lock() defer m.mu.Unlock() if m.fail != nil { return 0, m.fail } return m.put(k, v) } func (m *mem) Keys() ([]string, error) { m.mu.Lock() defer m.mu.Unlock() if m.fail != nil { return nil, m.fail } var out []string for k := range m.vals { out = append(out, k) } sort.Strings(out) return out, nil } func (m *mem) put(k string, v any) (uint64, error) { raw, ok := v.(json.RawMessage) if !ok { raw, _ = json.Marshal(v) } m.n++ m.vals[k], m.revs[k] = raw, m.n return m.n, nil } // world is a gate on a fixture mesh: the catalog half, the router's records as the router writes them (mesh-catalog's // messenger, asks.go), the asks published, the clock. type world struct { g *Gate state *mem cat *fakeCatalog records map[string]seat.AsksByAskerIDValue // by `.` takes bool // the router takes an ask as it is published asked []seat.Ask now time.Time runs int findings *findings } func newWorld() *world { shell := entry("src2:1", []string{"node-login-shell.execute"}, map[string]any{"needs-approval": []any{"wipe"}}) w := &world{state: newMem(), records: map[string]seat.AsksByAskerIDValue{}, takes: true, findings: &findings{}, now: time.Date(2026, 11, 12, 10, 0, 0, 0, time.UTC), cat: &fakeCatalog{revision: "7", byFp: map[string]catalogseat.CatalogEntry{"src2:1": shell}, newest: map[string]catalogseat.CatalogEntry{"shell": shell}, defs: map[string]seatdef.Definition{"node-login-shell": {Seat: "node-login-shell", Verbs: []seatdef.Verb{{Name: "execute", NeedsApproval: true}}}}}} w.g = New(Deps{Node: "n1", Runner: "node-tools", Catalog: w.cat, State: w.state, Findings: w.findings, Now: func() time.Time { return w.now }, After: func(d time.Duration) <-chan time.Time { w.now = w.now.Add(d) c := make(chan time.Time, 1) c <- w.now return c }, Publish: func(module, subject string, body any, id string) error { if subject != seat.AcceptSubjectAsk(module) { return nil } a := body.(seat.Ask) w.asked = append(w.asked, a) if w.takes { w.records[module+"."+a.ID] = seat.AsksByAskerIDValue{Asker: module, Key: module + "." + a.ID, State: "open", Headline: a.Headline, Expires: a.Expires, Opened: operatorchannelclient.FormatTime(w.now), OfferedOn: []string{"telegram"}} } return nil }, Record: func(module, id string) (*stdio.StateEntry, error) { rec, ok := w.records[module+"."+id] if !ok { return nil, nil } raw, _ := json.Marshal(rec) return &stdio.StateEntry{Key: module + "." + id, Value: raw}, nil }}) _ = w.g.Unserved("shell", "src2:1", []string{"wipe", "tidy"}) return w } // decide writes the router's record of the newest ask as the router writes a person's choice: its state and warrant. func (w *world) decide(option string, at time.Time) seat.Ask { a := w.asked[len(w.asked)-1] key := "shell." + a.ID rec := w.records[key] rec.State = "chosen" rec.Warrant = &seat.Warrant{Ask: a.ID, Asker: "shell", Outcome: operatorchannelclient.OutcomeChosen, Option: option, Level: operatorchannelclient.Approve, At: operatorchannelclient.FormatTime(at), AskDigest: operatorchannelclient.Digest(a), By: &seat.Person{Who: operatorchannelclient.Operator, Kind: "telegram", Identity: "42", Verified: "user id verified"}} w.records[key] = rec return a } // do makes a call through the gate, counting the runs. func (w *world) do(c Call) (any, error) { return w.g.Run(c, func() (any, error) { w.runs++; return "done", nil }) } // call is a fixture call of the marked tool wipe, or of another tool of the module. func call(name, args string) Call { return Call{Address: "shell." + name, Module: "shell", Name: name, Fingerprint: "src2:1", Caller: "agent", Args: json.RawMessage(args), Description: "Wipes a scratch directory.\nMore words."} } // theActAndItsAsk is §8's and §11's rows: the act's fields, and how an ask shows a call's arguments. func theActAndItsAsk(t *testing.T) { base := call("wipe", `{"dir":"scratch","n":1}`) digest := func(c Call) string { act, err := ActOf(c, "n1") if err != nil { t.Fatal(err) } d, _ := operatorchannelclient.ActDigest(act) return d } act, _ := ActOf(base, "n1") want := operatorchannelclient.Act{"address": "shell.wipe", "node": "n1", "module": "shell", "tool": "wipe", "build": "src2:1", "arg.dir": "scratch", "arg.n": "1"} if len(act) != len(want) { t.Fatalf("act %v", act) } for k, v := range want { if act[k] != v { t.Errorf("act[%s] = %q, want %q", k, act[k], v) } } seen := map[string]string{digest(base): "the call"} for name, c := range map[string]Call{ "an omitted argument": call("wipe", `{"dir":"scratch"}`), "another number": call("wipe", `{"dir":"scratch","n":2}`), "an object": call("wipe", `{"dir":{"name":"scratch"},"n":1}`), "a changed build": func() Call { c := base; c.Fingerprint = "src2:2"; return c }(), "another node's seat": {Address: "node-login-shell.execute", Module: "shell", Seat: "node-login-shell", Name: "execute", Fingerprint: "src2:1"}, } { d := digest(c) if other, dup := seen[d]; dup { t.Errorf("%s gives the digest of %s", name, other) } seen[d] = name } if got, _ := Canonical(json.RawMessage(`{"b":[1.0,2.5e-7,1e21],"a":"é<"}`)); got != `{"a":"é<","b":[1,2.5e-7,1e+21]}` { t.Errorf("canonical form %s", got) } // §11: a path, an address and a long argument are shown in their shape and cut. text, whole := shown(json.RawMessage(`{"dir":"/home/someone/scratch","host":"10.0.0.7","ok":"yes"}`)) if whole || text != "dir: ‹path›\nhost: ‹address›\nok: yes" { t.Errorf("shown %q whole %v", text, whole) } long, _ := json.Marshal(map[string]string{"text": strings.Repeat("word ", 500)}) if text, whole := shown(long); whole || !strings.HasSuffix(text, "NOT SHOWN IN FULL") || len(text) > PartBytes+len("\nNOT SHOWN IN FULL") { t.Errorf("a long argument shown whole %v, %d bytes", whole, len(text)) } // §10: the headline, cut with … to 60 characters, ending Part shown. for _, tc := range []struct{ name, node, want string }{ {"wipe", "n1", "Run wipe on n1?"}, {strings.Repeat("x", 50), "n1", "Run " + strings.Repeat("x", 50) + "?"}, {strings.Repeat("y", 80), "n1", "Run " + strings.Repeat("y", 54) + "…?"}, } { if got := headlineOf(tc.name, tc.node, true); got != tc.want || len([]rune(got)) > 60 { t.Errorf("headline %q, want %q", got, tc.want) } } if got := headlineOf("wipe", "n1", false); got != "Run wipe on n1? Part shown" { t.Errorf("headline %q", got) } } // Design 46 7-approval-mark §22: the refusals and answers of a marked call, on the fixture mesh, the router's record and // the warrant written as the router writes them. func TestTheRefusalsAndAnswers(t *testing.T) { t.Run("§8, §10, §11: the act's fields give each its digest, and the ask shows the arguments", theActAndItsAsk) for _, tc := range []struct { name string run func(t *testing.T, w *world) }{ {"§9: a secret in the arguments is refused asking nothing", func(t *testing.T, w *world) { _, err := w.g.firstCall(call("wipe", `{"token":"ghp_0123456789abcdefghijABCDEFGHIJ012345"}`)) if err == nil || !strings.Contains(err.Error(), "which no ask may carry") || len(w.asked) != 0 { t.Fatalf("%v, asked %d", err, len(w.asked)) } }}, {"§9: more than 16 KiB of arguments is refused asking nothing", func(t *testing.T, w *world) { big, _ := json.Marshal(map[string]string{"text": strings.Repeat("a b ", 5000)}) _, err := w.g.firstCall(call("wipe", string(big))) if err == nil || !strings.Contains(err.Error(), "more than the 16384") || len(w.asked) != 0 { t.Fatalf("%v, asked %d", err, len(w.asked)) } }}, {"§10, §12: the published ask's fields, and Not run with the router's record", func(t *testing.T, w *world) { _, err := w.g.firstCall(call("wipe", `{"dir":"scratch"}`)) if err == nil || !strings.HasPrefix(err.Error(), "Not run: this needs the operator's approval. Asked on telegram at ") || !strings.Contains(err.Error(), "make the same call again within 30 minutes of the approval; the ask expires at ") { t.Fatalf("answer %v", err) } if len(w.asked) != 1 { t.Fatalf("asked %d", len(w.asked)) } a := w.asked[0] act, _ := ActOf(call("wipe", `{"dir":"scratch"}`), "n1") d, _ := operatorchannelclient.ActDigest(act) if a.About != d || a.Who != "operator" || a.OnExpiry != OnExpiry || a.Headline != "Run wipe on n1?" || a.Expires != operatorchannelclient.FormatTime(time.Date(2026, 11, 12, 11, 0, 0, 0, time.UTC)) || len(a.Options) != 2 || a.Options[0].ID != "approve" || a.Options[1].ID != "decline" || a.Options[0].Binds != d || a.Options[1].Binds != d || a.Options[1].Level != "approve" { t.Errorf("ask %+v", a) } if !strings.HasPrefix(a.Explanation, "Wipes a scratch directory.\nOn n1, called through the mesh's tools, which cannot tell who called.\ndir: scratch\nFingerprint ") { t.Errorf("explanation %q", a.Explanation) } var k operatorchannelclient.Kept _ = json.Unmarshal(w.state.vals["n1.ask."+a.ID], &k) if k.Act["arg.dir"] != "scratch" || string(k.Arguments) != `{"dir":"scratch"}` || string(w.state.vals["n1.act."+d]) != `"`+a.ID+`"` { t.Errorf("kept %+v, act key %s", k, w.state.vals["n1.act."+d]) } }}, {"§12: with no record within 5 seconds, Not run, asked; not yet taken by the router", func(t *testing.T, w *world) { w.takes = false start := w.now _, err := w.g.firstCall(call("wipe", `{}`)) if err == nil || !strings.HasPrefix(err.Error(), "Not run, asked; not yet taken by the router (ask ") || w.now.Sub(start) > RecordWithin { t.Fatalf("answer %v after %s", err, w.now.Sub(start)) } }}, {"§10, §17: a second identical call publishes nothing and is told it still waits", func(t *testing.T, w *world) { _, _ = w.do(call("wipe", `{"dir":"scratch"}`)) _, err := w.do(call("wipe", `{"dir":"scratch"}`)) if len(w.asked) != 1 || err == nil || !strings.HasPrefix(err.Error(), "Not run: still waiting for the operator's answer") || w.runs != 0 { t.Fatalf("asked %d, %v, runs %d", len(w.asked), err, w.runs) } }}, {"§17: an ask the router holds back as the fourth open one still waits", func(t *testing.T, w *world) { _, _ = w.do(call("wipe", `{}`)) a := w.asked[0] rec := w.records["shell."+a.ID] rec.State, rec.HeldSince = "held-back", operatorchannelclient.FormatTime(w.now) w.records["shell."+a.ID] = rec if _, err := w.do(call("wipe", `{}`)); err == nil || !strings.Contains(err.Error(), "still waiting") || len(w.asked) != 1 { t.Fatalf("%v, asked %d", err, len(w.asked)) } }}, {"§14, §15: approved, the same call runs once; made again it is already run and asks anew", func(t *testing.T, w *world) { _, _ = w.do(call("wipe", `{"dir":"scratch"}`)) w.decide(OptionApprove, w.now.Add(time.Minute)) w.now = w.now.Add(2 * time.Minute) if got, err := w.do(call("wipe", `{"dir":"scratch"}`)); err != nil || got != "done" || w.runs != 1 { t.Fatalf("%v %v, runs %d", got, err, w.runs) } _, err := w.do(call("wipe", `{"dir":"scratch"}`)) if err == nil || !strings.Contains(err.Error(), "was already run; asked again") || w.runs != 1 || len(w.asked) != 2 { t.Fatalf("%v, runs %d, asked %d", err, w.runs, len(w.asked)) } }}, {"§15: two calls at the same moment run it once", func(t *testing.T, w *world) { _, _ = w.do(call("wipe", `{}`)) w.decide(OptionApprove, w.now) var wg sync.WaitGroup var mu sync.Mutex runs := 0 for i := 0; i < 2; i++ { wg.Add(1) go func() { defer wg.Done() _, _ = w.g.Run(call("wipe", `{}`), func() (any, error) { mu.Lock(); runs++; mu.Unlock(); return "done", nil }) }() } wg.Wait() if runs != 1 { t.Fatalf("ran %d times", runs) } }}, {"§14: approved for other arguments runs nothing", func(t *testing.T, w *world) { _, _ = w.do(call("wipe", `{"dir":"scratch"}`)) a := w.decide(OptionApprove, w.now) rec := w.records["shell."+a.ID] other, _ := ActOf(call("wipe", `{"dir":"home"}`), "n1") d, _ := operatorchannelclient.ActDigest(other) a.Options[0].Binds = d // the ask the person was shown bound another act rec.Warrant.AskDigest = operatorchannelclient.Digest(a) w.records["shell."+a.ID] = rec var k operatorchannelclient.Kept _ = json.Unmarshal(w.state.vals["n1.ask."+a.ID], &k) k.Ask = a raw, _ := json.Marshal(k) w.state.vals["n1.ask."+a.ID] = raw if _, err := w.do(call("wipe", `{"dir":"scratch"}`)); err == nil || w.runs != 0 || !strings.Contains(err.Error(), "nothing is done") { t.Fatalf("%v, runs %d", err, w.runs) } }}, {"§14: approved 31 minutes ago asks anew", func(t *testing.T, w *world) { _, _ = w.do(call("wipe", `{}`)) w.decide(OptionApprove, w.now) w.now = w.now.Add(31 * time.Minute) if _, err := w.do(call("wipe", `{}`)); err == nil || !strings.Contains(err.Error(), "approved too long ago") || w.runs != 0 || len(w.asked) != 2 { t.Fatalf("%v, runs %d, asked %d", err, w.runs, len(w.asked)) } }}, {"§14, §17: declined, the same call is not asked again for 10 minutes", func(t *testing.T, w *world) { _, _ = w.do(call("wipe", `{}`)) w.decide(OptionDecline, w.now) w.now = w.now.Add(9 * time.Minute) if _, err := w.do(call("wipe", `{}`)); err == nil || !strings.Contains(err.Error(), "declined") || len(w.asked) != 1 || w.runs != 0 { t.Fatalf("%v, asked %d", err, len(w.asked)) } w.now = w.now.Add(2 * time.Minute) if _, err := w.do(call("wipe", `{}`)); err == nil || !strings.Contains(err.Error(), "asked again") || len(w.asked) != 2 { t.Fatalf("%v, asked %d", err, len(w.asked)) } }}, {"§14, §17: expired asks anew", func(t *testing.T, w *world) { _, _ = w.do(call("wipe", `{}`)) a := w.asked[0] rec := w.records["shell."+a.ID] rec.State = "expired" w.records["shell."+a.ID] = rec if _, err := w.do(call("wipe", `{}`)); err == nil || !strings.Contains(err.Error(), "ended expired; asked again") || len(w.asked) != 2 { t.Fatalf("%v, asked %d", err, len(w.asked)) } }}, {"§14: another asker's warrant runs nothing", func(t *testing.T, w *world) { _, _ = w.do(call("wipe", `{}`)) a := w.decide(OptionApprove, w.now) rec := w.records["shell."+a.ID] rec.Warrant.Asker = "mail" w.records["shell."+a.ID] = rec if _, err := w.do(call("wipe", `{}`)); err == nil || w.runs != 0 || !strings.Contains(err.Error(), "its warrant does not hold") { t.Fatalf("%v, runs %d", err, w.runs) } }}, {"§17: the router's refusal is said with its words", func(t *testing.T, w *world) { _, _ = w.do(call("wipe", `{}`)) a := w.asked[0] rec := w.records["shell."+a.ID] rec.State, rec.Answer = "refused", "the asker has too many open asks" w.records["shell."+a.ID] = rec if _, err := w.do(call("wipe", `{}`)); err == nil || !strings.Contains(err.Error(), "the asker has too many open asks") { t.Fatalf("%v", err) } }}, {"§20: a runner's own tool marked is refused unasked, never run", func(t *testing.T, w *world) { c := call("wipe", `{}`) c.Own = true if _, err := w.do(c); err == nil || w.runs != 0 || len(w.asked) != 0 { t.Fatalf("%v, runs %d, asked %d", err, w.runs, len(w.asked)) } }}, {"§13: with no binding, an unmarked call runs and a marked one is refused", func(t *testing.T, w *world) { w.g.d.State = nil if got, err := w.do(call("tidy", `{}`)); err != nil || got != "done" { t.Fatalf("unmarked: %v %v", got, err) } if _, err := w.do(call("wipe", `{}`)); err == nil || !strings.Contains(err.Error(), "binds no state approvals") || w.runs != 1 { t.Fatalf("marked: %v, runs %d", err, w.runs) } }}, {"§13: an ask over 256 KiB is refused naming the cause", func(t *testing.T, w *world) { c := call("wipe", `{}`) c.Description = strings.Repeat("w", 300*1024) if _, err := w.do(c); err == nil || !strings.Contains(err.Error(), "more than the 262144 a value of the state approvals holds") || len(w.asked) != 0 { t.Fatalf("%v, asked %d", err, len(w.asked)) } }}, {"§3, §2: a seat verb its seat marks is asked for its holder", func(t *testing.T, w *world) { c := Call{Address: "node-login-shell.execute", Module: "shell", Seat: "node-login-shell", Name: "execute", Fingerprint: "src2:1", Args: json.RawMessage(`{"command":"ls"}`)} if _, err := w.do(c); err == nil || !strings.HasPrefix(err.Error(), "Not run") || len(w.asked) != 1 || w.asked[0].Headline != "Run node-login-shell.execute on n1?" { t.Fatalf("%v, asked %d", err, len(w.asked)) } }}, } { t.Run(tc.name, func(t *testing.T) { tc.run(t, newWorld()) }) } }