Files
mesh-tools/node-tools/internal/runtime/ledgers_test.go
T
jschoubben 0fdec2bd60 A-6 the capability: the tool runner states approval-mark/1 in its announcement
Its gate sits on every handler it serves, so it says it honours the mark, and the
grants part, reading its node's readings, composes no deny of its modules' marked subjects.

Builds: design 46 7-approval-mark §6

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 05:15:43 +01:00

225 lines
10 KiB
Go

package runtime
import (
"context"
"encoding/json"
"errors"
"sort"
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/buscalls"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/membership"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
"github.com/novox/mesh-tools/node-tools/internal/bus"
"github.com/novox/mesh-tools/node-tools/internal/launch"
mt "github.com/novox/mesh-tools/node-tools/internal/meshtest"
)
// 1966/AC16: given a module with a ledger binding, when the runner lists its tools, then the four repair tools are
// among them, marked acts or reads; a module without one has none, and a binding written later brings them.
func TestTheLedgerRepairsAreToolsOfEveryLedgerOwner(t *testing.T) {
mesh := mt.New(t)
owner := mt.MembershipOf("owner", "anchor", true, nil)
owner.Bindings = []membership.BusBinding{{Local: "records", Storage: catalogue.StorageLedger, Template: catalogue.TemplateLedger,
History: "LEDGER_owner_records", Subject: "mesh.ledger.owner.records.history", State: "owner_records",
Snapshot: "mesh.ledger.owner.records.snapshot"}}
plain := mt.MembershipOf("plain", "anchor", true, nil)
mesh.Issue(t, owner)
mesh.Issue(t, plain)
nodeTools := connect(t, "node-tools", "anchor")
stop, err := Run(nodeTools, []Served{{"owner", []string{mt.Fixture("caller-echo.py")}},
{"plain", []string{mt.Fixture("caller-echo.py")}}}, nil, func(string, ...any) {})
if err != nil {
t.Fatal(err)
}
defer stop()
asker := connect(t, "console", "workstation")
listed := func(module string) (string, error) {
answered, err := asker.Ask(module+"."+ToolsVerb, map[string]any{}, "")
if err != nil {
return "", err
}
var got ToolsAnswer
if err := json.Unmarshal(answered.Result, &got); err != nil {
return "", err
}
var names []string
for _, tool := range got.Tools {
names = append(names, tool.Name+":"+tool.Effect)
}
sort.Strings(names)
return strings.Join(names, " "), nil
}
for _, c := range []struct{ name, module, want string }{
{"a ledger owner lists the four repairs", "owner", "ledger_lag:reads ledger_rebuild:acts ledger_reindex:acts ledger_snapshot:acts who:"},
{"a module with no ledger has none", "plain", "who:"},
} {
t.Run(c.name, func(t *testing.T) {
var got string
mt.Until(t, func() (err error) { got, err = listed(c.module); return })
if got != c.want {
t.Fatalf("%s lists %q, want %q", c.module, got, c.want)
}
})
}
t.Run("a ledger bound later brings them", func(t *testing.T) {
plain.Bindings = owner.Bindings
mesh.Issue(t, plain)
mt.Until(t, func() error {
got, err := listed("plain")
if err == nil && !strings.Contains(got, "ledger_rebuild:acts") {
err = errors.New("listed " + got)
}
return err
})
})
}
// aLedger raises one ledger of module m on the bus as the bus module would create it, binds it in m's membership, and
// answers the runtime's buses for m and for other; its types travel on its binding, and a second binding carries none.
func aLedger(t *testing.T) (m, other *moduleBus, mesh *mt.Mesh) {
t.Helper()
mesh = mt.New(t)
nc, err := nats.Connect(mt.URL(t))
if err != nil {
t.Fatal(err)
}
t.Cleanup(nc.Close)
js, _ := jetstream.New(nc)
ctx := context.Background()
for _, cfg := range []jetstream.StreamConfig{
{Name: "LEDGER_m_records", Subjects: []string{"mesh.ledger.m.records.history.>"}},
{Name: "SNAPSHOT_m_records", Subjects: []string{"mesh.ledger.m.records.snapshot.>"}, MaxMsgsPerSubject: 1},
} {
if _, err := js.CreateStream(ctx, cfg); err != nil {
t.Fatal(err)
}
}
if _, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: "m_records"}); err != nil {
t.Fatal(err)
}
mem := mt.MembershipOf("m", "anchor", false, nil)
mem.Bindings = []membership.BusBinding{{Local: "records", Storage: catalogue.StorageLedger, Template: catalogue.TemplateLedger,
History: "LEDGER_m_records", Subject: "mesh.ledger.m.records.history", State: "m_records", Snapshot: "mesh.ledger.m.records.snapshot",
Types: []catalogue.LedgerType{{Name: "decision", Schema: map[string]catalogue.LedgerField{"title": {Type: "string"}},
Operations: []catalogue.LedgerOperation{{Name: "title", Effect: catalogue.EffectSetFields, Fields: []string{"title"}, Author: true}}}}},
{Local: "untyped", Storage: catalogue.StorageLedger, Template: catalogue.TemplateLedger, History: "LEDGER_m_records",
State: "m_records"},
{Local: "elsewhere", Storage: catalogue.StorageLedger, Template: catalogue.TemplateLedger, History: "LEDGER_m_records",
Subject: "mesh.ledger.m.records.history", State: "m_records", Types: []catalogue.LedgerType{{Name: "decision"}}}}
mesh.Issue(t, mem)
mesh.Issue(t, mt.MembershipOf("other", "anchor", false, nil))
conn := connect(t, "node-tools", "anchor")
conn.Follow("m")
conn.Follow("other")
mt.Until(t, func() error {
if conn.Membership("m") == nil || conn.Membership("other") == nil {
return errors.New("not issued yet")
}
return nil
})
all := &consumers{conn: conn, logf: t.Logf, of: map[string]*moduleEvents{}, ledgers: ledgerSource{
writer: func(string) (*bus.Conn, error) { return conn, nil },
}}
return all.forModule("m").(*moduleBus), all.forModule("other").(*moduleBus), mesh
}
// 1966/AC17, 1966/AC18: the author an apply is stamped with is the caller the runtime proved for the call in flight it
// names, whatever author the bundle wrote; another module's call, or one not in flight, is refused naming it (ADR 0597 §7).
func TestAnApplyIsStampedWithTheCallerTheRuntimeProved(t *testing.T) {
m, other, _ := aLedger(t)
call, end := m.Begin("op@shanks")
defer end()
theirs, endTheirs := other.Begin("op@shanks")
defer endTheirs()
forged := `"body":{"title":"T","author":{"account":"x","caller":"forged"}}`
for _, c := range []struct{ name, params, caller, refusal string }{
{"1966/AC17 a call in flight stamps its proven caller", `{"ledger":"records","instance":"decision/1","operation":"title",` + forged + `,"call":"` + call + `","author":"forged"}`, "op@shanks", ""},
{"naming no call stamps the account alone", `{"ledger":"records","instance":"decision/2","operation":"title",` + forged + `}`, "", ""},
{"1966/AC18 another module's call is refused, naming it", `{"ledger":"records","instance":"decision/3","operation":"title",` + forged + `,"call":"` + theirs + `"}`, "", "the call " + theirs + ", which is another module's"},
{"a ledger bound with no types is refused, naming it", `{"ledger":"untyped","instance":"decision/3","operation":"title",` + forged + `}`, "", `m's ledger "untyped" is bound with no types`},
{"a binding naming subjects its account is not granted is refused, naming both", `{"ledger":"elsewhere","instance":"decision/3","operation":"title",` + forged + `}`, "", "bound with the history subject mesh.ledger.m.records.history, and its ledger account is granted mesh.ledger.m.elsewhere.history alone"},
{"a call not in flight is refused, naming it", `{"ledger":"records","instance":"decision/3","operation":"title",` + forged + `,"call":"c0ffee"}`, "", "the call c0ffee, which is not a tool call in flight"},
} {
t.Run(c.name, func(t *testing.T) {
var req struct {
Instance string `json:"instance"`
}
_ = json.Unmarshal([]byte(c.params), &req)
_, err := m.BusState(buscalls.LedgerApply, json.RawMessage(c.params))
if c.refusal != "" {
if err == nil || !strings.Contains(err.Error(), c.refusal) {
t.Fatalf("applied with %v, want refused with %q", err, c.refusal)
}
return
}
if err != nil {
t.Fatal(err)
}
raw, err := m.BusState(buscalls.LedgerHistory, json.RawMessage(`{"ledger":"records","instance":"`+req.Instance+`"}`))
if err != nil {
t.Fatal(err)
}
var page buscalls.HistoryPage
_ = json.Unmarshal(raw, &page)
if len(page.Operations) != 1 || page.Operations[0].Author != (buscalls.Author{Account: "m", Caller: c.caller}) {
t.Fatalf("the history holds %+v, want the author {m %s}", page.Operations, c.caller)
}
state, _ := m.BusState(buscalls.LedgerGet, json.RawMessage(`{"ledger":"records","instance":"`+req.Instance+`"}`))
if !strings.Contains(string(state), `"author":{"account":"m"`) || strings.Contains(string(state), "forged") {
t.Fatalf("the state took the bundle's author: %s", state)
}
})
}
end()
if _, err := m.BusState(buscalls.LedgerApply, json.RawMessage(`{"ledger":"records","instance":"decision/4","operation":"title","call":"`+call+`"}`)); err == nil {
t.Fatal("a call that ended was still taken as in flight")
}
}
// issue 1966 TLS-2: a bundle's tool named as one of the runner's ledger repairs is refused at the load, naming the tool and the
// repair; any other name is served.
func TestABundleToolNamedAsALedgerRepairIsRefused(t *testing.T) {
for _, c := range []struct{ name, tool, want string }{
{"rebuild", RepairRebuild, "m's bundle serves a tool named ledger_rebuild, which is also the ledger repair the runner serves for m"},
{"reindex", RepairReindex, "ledger_reindex"},
{"snapshot", RepairSnapshot, "ledger_snapshot"},
{"lag", RepairLag, "ledger_lag"},
{"another name", "ledger_status", ""},
} {
t.Run(c.name, func(t *testing.T) {
got := repairClash("m", []launch.Tool{{Name: "first"}, {Name: c.tool}})
if (c.want == "") != (got == "") || !strings.Contains(got, c.want) {
t.Errorf("clash %q, want one containing %q", got, c.want)
}
})
}
}
// issue 1966 TLS-3: every method of a capability the runner states is a call it answers, and the two states its calls under
// no other name; none answers "the runtime answers no ...".
func TestEveryCallOfAStatedCapabilityIsAnswered(t *testing.T) {
m, _, _ := aLedger(t)
if got := strings.Join(CallCapabilities(), ","); got != "approval-mark/1,bus-state-calls,ledger-calls,put-sessions,seat-settle-delay" {
t.Fatalf("the runner states %s", got)
}
for capability, methods := range callFamilies {
for _, method := range methods {
t.Run(capability+" "+method, func(t *testing.T) {
_, err := m.BusState(method, json.RawMessage(`"not a request"`))
if err != nil && strings.Contains(err.Error(), "the runtime answers no") {
if stop, werr := m.BusStateWatch(method, json.RawMessage(`"not a request"`), func(json.RawMessage) error { return nil }); werr != nil && strings.Contains(werr.Error(), "the runtime answers no") {
t.Errorf("%s is stated under %s and answered by neither BusState nor BusStateWatch", method, capability)
} else if stop != nil {
stop()
}
}
})
}
}
}