catalogue's compiled set is read from git.novox.be/novox/mesh-contracts/go/seats; the hand-written seat literal and its
verb builders go. The bar's block keeps its checks as code beside the data. A case of the seat set's test holds the
generated set equal, entry for entry, to the list compiled at 5a38a12 (testdata), but for the whole seats' additions
and the vault's two verbs; the three new registry seats are the only new entries. A holder of mesh-broker now serves its
eight verbs.
SDK-12: the vault's seat no longer promises accept and move-asks, which no holder served and nothing called; retire is
optional (design 32). The intake and move rows join one table; the move's own test goes with the two verbs.
Requires mesh-contracts (no version on the forge yet: a go.work, uncommitted, uses the sibling worktree until the
group pins it).
Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
165 lines
6.3 KiB
Go
165 lines
6.3 KiB
Go
package stdio
|
|
|
|
import (
|
|
"encoding/json"
|
|
"go/ast"
|
|
"go/parser"
|
|
"go/token"
|
|
"io/fs"
|
|
"path/filepath"
|
|
"reflect"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/buscalls"
|
|
)
|
|
|
|
// meshSDKPackages is every package mesh-sdk holds (novox/hq ADR 0593 §1): what every module shares, the value shapes
|
|
// and names more than one part reads, and the seat contracts it holds until they move into SDK modules (issue 167).
|
|
// Logic only some modules need is its owner module's package. A package added here is a decision to say in the pull
|
|
// request: does every module share it, or is it a shape more than one part reads?
|
|
var meshSDKPackages = []string{
|
|
".", "announce", "announcetest", "asker", "asks", "buildstep", "buscalls", "buscompose", "busretry", "calladdress", "catalogue",
|
|
"composer", "declaration", "delivery", "forgekey", "handacts", "health", "internal/beside", "kvlimit", "membership",
|
|
"objectlink", "outward", "overlay", "pernode", "process", "provider", "provider/harness", "provisions",
|
|
"sdkversion", "seal", "seatdef", "testbus",
|
|
}
|
|
|
|
// computesNot are the functions whose computation is one owner's alone (ADR 0593 §2, §7): the indexer computes a
|
|
// fingerprint, its named tree and a package's version; the node-engine composes.
|
|
var computesNot = []string{"SourceFingerprint", "SourceFingerprint2", "NamedTree", "MeshPackagesOf", "PackageVersionsOf",
|
|
"OtherPackages", "Compose", "CompositionsShadow", "JudgeMove", "Run"}
|
|
|
|
// composesNot are the methods of the composition, the node-engine's alone since its rules moved there (ADR 0593 §7):
|
|
// mesh-sdk renders no declaration and refuses no composition.
|
|
var composesNot = []string{"Resolution.Compose", "Resolution.Declaration", "Resolution.Rules", "Resolution.LeftOut"}
|
|
|
|
// ADR 0593 §1's check: mesh-sdk holds exactly the packages it keeps, and none of them computes what one owner alone
|
|
// computes.
|
|
func TestMeshSDKHoldsOnlyWhatEveryModuleShares(t *testing.T) {
|
|
var found []string
|
|
err := filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if d.IsDir() {
|
|
if n := d.Name(); n == "testdata" || (strings.HasPrefix(n, ".") && path != ".") {
|
|
return filepath.SkipDir
|
|
}
|
|
return nil
|
|
}
|
|
if !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
|
|
return nil
|
|
}
|
|
dir := filepath.ToSlash(filepath.Dir(path))
|
|
if !slices.Contains(found, dir) {
|
|
found = append(found, dir)
|
|
}
|
|
file, err := parser.ParseFile(token.NewFileSet(), path, nil, parser.SkipObjectResolution)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// 1966/AC5: the calls package imports nothing of mesh-sdk but the catalogue's types, so a change here rebuilds
|
|
// only its importers (ADR 0595 §9).
|
|
if dir == "buscalls" {
|
|
for _, imp := range file.Imports {
|
|
p, _ := strconv.Unquote(imp.Path.Value)
|
|
if strings.HasPrefix(p, sdkModule) && p != sdkModule+"/catalogue" {
|
|
t.Errorf("%s imports %s: the calls package imports nothing of mesh-sdk but catalogue (ADR 0595 §9)", path, p)
|
|
}
|
|
}
|
|
}
|
|
for _, decl := range file.Decls {
|
|
fn, ok := decl.(*ast.FuncDecl)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if fn.Recv == nil && slices.Contains(computesNot, fn.Name.Name) {
|
|
t.Errorf("%s: %s computes what its owner alone computes (ADR 0593 §2, §7)", path, fn.Name.Name)
|
|
}
|
|
if fn.Recv != nil {
|
|
recv := fn.Recv.List[0].Type
|
|
if star, isStar := recv.(*ast.StarExpr); isStar {
|
|
recv = star.X
|
|
}
|
|
if id, isID := recv.(*ast.Ident); isID && slices.Contains(composesNot, id.Name+"."+fn.Name.Name) {
|
|
t.Errorf("%s: %s.%s composes, which the node-engine alone does (ADR 0593 §7)", path, id.Name, fn.Name.Name)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
slices.Sort(found)
|
|
for _, dir := range found {
|
|
if !slices.Contains(meshSDKPackages, dir) {
|
|
t.Errorf("mesh-sdk holds the package %s, which it does not keep (ADR 0593 §1): it is its owner module's package", dir)
|
|
}
|
|
}
|
|
for _, want := range meshSDKPackages {
|
|
if !slices.Contains(found, want) {
|
|
t.Errorf("mesh-sdk's list keeps %s, which is gone: take it off the list", want)
|
|
}
|
|
}
|
|
|
|
// 1966/AC5: the call an apply names is the id the tool runner hands under `_meta`'s mesh/call.
|
|
if buscalls.MetaCall != "mesh/call" {
|
|
t.Errorf("the tool call's id is handed under %q, not mesh/call", buscalls.MetaCall)
|
|
}
|
|
// 1966/AC5: an index's key is its fields' values, then the instance's path, joined by "."; its entry names the
|
|
// instance and the sequence.
|
|
for _, c := range []struct {
|
|
fields []string
|
|
instance string
|
|
want string
|
|
}{
|
|
{[]string{"17950", "volvo"}, "listing/a17", "17950.volvo.listing.a17"},
|
|
// A value holding "." and one that does not meet in no key.
|
|
{[]string{"a.b"}, "c", "a=2Eb.c"},
|
|
{[]string{"a"}, "b/c", "a.b.c"},
|
|
// A title with spaces is a token the bus takes; "=" itself is encoded.
|
|
{[]string{"How it is checked", "x=y"}, "decision/0290", "How=20it=20is=20checked.x=3Dy.decision.0290"},
|
|
} {
|
|
k := buscalls.IndexKey(c.fields, c.instance)
|
|
if k != c.want {
|
|
t.Errorf("the index key of %q and %q is %q, want %q", c.fields, c.instance, k, c.want)
|
|
}
|
|
for _, token := range strings.Split(k, ".") {
|
|
if token == "" || strings.ContainsAny(token, " *>\t") {
|
|
t.Errorf("the index key %q holds a token the bus refuses: %q", k, token)
|
|
}
|
|
}
|
|
}
|
|
if buscalls.IndexKey([]string{"a.b"}, "c") == buscalls.IndexKey([]string{"a"}, "b/c") {
|
|
t.Error("two different index keys meet")
|
|
}
|
|
entry, _ := json.Marshal(buscalls.IndexEntry{Instance: "listing/a17", Sequence: 3})
|
|
if string(entry) != `{"instance":"listing/a17","sequence":3}` {
|
|
t.Errorf("an index entry is %s", entry)
|
|
}
|
|
// 1966/AC5: an apply naming the tool call it serves, and its answer with the state behind, round-trip.
|
|
for _, c := range []struct {
|
|
name string
|
|
in, back any
|
|
}{
|
|
{"apply", &buscalls.ApplyRequest{Ledger: "records", Instance: "decision/0290", Operation: "submit",
|
|
Body: json.RawMessage(`{"why":"x"}`), Call: "call-17"}, &buscalls.ApplyRequest{}},
|
|
{"applied", &buscalls.Applied{Taken: true, Sequence: 5, StateBehind: true}, &buscalls.Applied{}},
|
|
} {
|
|
raw, err := json.Marshal(c.in)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := json.Unmarshal(raw, c.back); err != nil || !reflect.DeepEqual(c.in, c.back) {
|
|
t.Errorf("%s read back as %+v from %s (%v)", c.name, c.back, raw, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// sdkModule is mesh-sdk's Go module path.
|
|
const sdkModule = "git.novox.be/novox/mesh-sdk/go"
|