Files
mesh-sdk/go/packages_list_test.go
T
jschoubben 0bb3a2c392 SDK-1: the runtime sends a file to a registry seat in parts and goes on from what the holder holds (ADR 0611, design 51)
publish.Publish opens the transfer naming every part's digest and sends only the parts the holder answers it lacks, in
chunks of at most 760 KiB (a message under the bus's 1 MiB once base64), in sequence with the last marked; a part
whose bytes are not its digest is refused before any of it travels. The seat's messages are mesh-contracts'; the call
is the caller's own. Its test takes the place of the vault's get test, merged into the vault's table.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 12:27:15 +01:00

216 lines
8.2 KiB
Go

package stdio
import (
"encoding/json"
"go/ast"
"go/parser"
"go/token"
"io/fs"
"os"
"path/filepath"
"reflect"
"slices"
"strconv"
"strings"
"testing"
contractseats "git.novox.be/novox/mesh-contracts/go/seats"
"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", "busstate", "busretry", "calladdress", "catalogue",
"composer", "declaration", "delivery", "forgekey", "handacts", "health", "internal/beside", "kvlimit", "membership",
"objectlink", "outward", "overlay", "pernode", "process", "provider", "provider/harness", "provisions", "publish",
"sdkversion", "seal", "seatdef", "testbus", "views",
}
// 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
// **mesh-sdk takes no new seat-specific name** (novox/hq ADR 0613 §5, design 26 *Contracts modules*, SDK-9): a seat's
// name is mesh-contracts' (go/seats), so no string literal here names a defined seat or its subjects, and no exported
// name is given to one, beyond what testdata/seat-names.txt keeps from before the move (each `<file>: <literal or
// name>`), which only shrinks as areas move. A literal naming a module of the same name as a seat counts too.
defined := contractseats.Names()
namesSeat := func(s string) bool {
for _, seat := range defined {
if s == seat || strings.HasPrefix(s, "mesh.seat."+seat+".") {
return true
}
}
return false
}
var seatNames []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
}
where := filepath.ToSlash(path) + ": "
ast.Inspect(file, func(n ast.Node) bool {
switch n := n.(type) {
case *ast.BasicLit:
if v, err := strconv.Unquote(n.Value); err == nil && n.Kind == token.STRING && namesSeat(v) {
seatNames = append(seatNames, where+v)
}
case *ast.ValueSpec:
for i, name := range n.Names {
if i < len(n.Values) && name.IsExported() {
if sel, ok := n.Values[i].(*ast.SelectorExpr); ok {
if x, ok := sel.X.(*ast.Ident); ok && x.Name == "contractseats" {
seatNames = append(seatNames, where+name.Name)
}
}
}
}
}
return true
})
// 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)
}
kept, err := os.ReadFile("testdata/seat-names.txt")
if err != nil {
t.Fatal(err)
}
keep := strings.Split(strings.TrimSpace(string(kept)), "\n")
for _, n := range seatNames {
if !slices.Contains(keep, n) {
t.Errorf("%s names a seat the definitions define: a seat's name and its contract are mesh-contracts' (ADR 0613 §5)", n)
}
}
for _, k := range keep {
if !slices.Contains(seatNames, k) {
t.Errorf("testdata/seat-names.txt keeps %q, which is gone: take it off the list", k)
}
}
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"