The grants part keeps and recovers every binding of state asked in requires, and the validator reads both forms (issue 1966) #190

Open
mesh-tools wants to merge 7 commits from fix/1966-requires-grants into main
Showing only changes of commit 4d75646bf4 - Show all commits
+126
View File
@@ -9,6 +9,9 @@ import (
"fmt"
"git.novox.be/novox/mesh-contracts/go/seats/meshbroker"
stdio "git.novox.be/novox/mesh-sdk/go"
"go/ast"
"go/parser"
"go/token"
"io/fs"
"maps"
"os"
@@ -2870,6 +2873,68 @@ func TestGrants(t *testing.T) {
}
})
// Issue 1966 (ADR 0595 §1, §11): the state a manifest keeps and a membership issues is read through mesh-sdk's two rules,
// catalogue.KeptStates and Membership.StateIssued or StateNames, which answer the old `state` section and a key-value
// entry asked in `requires` alike. A reader of a manifest's or a membership's `State` beside them misses the new form,
// the kind of this issue's faults; only the rules that judge or compose the old section itself read it. Read by the
// syntax tree across the repository, since the field's name alone is shared by other types.
t.Run("the state a manifest keeps or a membership issues is read through mesh-sdk's two rules", func(t *testing.T) {
judgesTheOldSection := map[string]bool{
"modules/mesh-delivery-indexer/judge/busstate.go BusStateProblems": true,
"modules/mesh-delivery-indexer/judge/oldsections.go SectionsUsed": true,
"modules/mesh-delivery-indexer/judge/rules.go readOnlyStateProblems": true,
"modules/mesh-delivery-indexer/judge/rules.go parseManifest": true,
"modules/mesh-delivery-indexer/judge/rules.go StateProblems": true,
"modules/mesh-delivery-indexer/judge/rules.go ObjectsProblems": true,
"modules/mesh-grants/internal/grants/records.go bucketsOf": true,
}
planted := "package p\n\nimport \"git.novox.be/novox/mesh-sdk/go/catalogue\"\n\n" +
"func planted(m catalogue.Manifest) (n int) {\n\tfor range m.State {\n\t\tn++\n\t}\n\treturn n\n}\n"
if got, err := oldStateReaders("p.go", []byte(planted)); err != nil || !slices.Equal(got, []string{"p.go planted"}) {
t.Fatalf("a planted range m.State is read as %v (%v), want it found", got, err)
}
files, seen := 0, map[string]bool{}
err := filepath.WalkDir(filepath.Join("..", ".."), func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() && path != filepath.Join("..", "..") && (d.Name() == "testdata" || d.Name() == "node_modules" || (len(d.Name()) > 1 && d.Name()[0] == '.')) {
return filepath.SkipDir
}
if d.IsDir() || !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
return nil
}
src, err := os.ReadFile(path)
if err != nil {
return err
}
files++
rel, _ := filepath.Rel(filepath.Join("..", ".."), path)
readers, err := oldStateReaders(filepath.ToSlash(rel), src)
if err != nil {
return err
}
for _, r := range readers {
seen[r] = true
if !judgesTheOldSection[r] {
t.Errorf("%s reads a manifest's or a membership's State beside catalogue.KeptStates and Membership.StateIssued", r)
}
}
return nil
})
if err != nil {
t.Fatal(err)
}
if files < 100 {
t.Fatalf("read %d source files, want the repository's", files)
}
for r := range judgesTheOldSection {
if !seen[r] {
t.Errorf("%s no longer reads the old section: take it off the list", r)
}
}
})
// 774/S29/AC1 (ADR 0549 §5, ADR 0571 §10): the memberships composed from the assignments alone, with no read of the
// controller, are the ones the union of assignments and assignments gave on the same input: the fixture's (two nodes,
// the bus and mod-x on a, mod-y on b, as placed on the live mesh's own shape of nats beside its modules), whose
@@ -4476,3 +4541,64 @@ func channelTakes(t *testing.T, say func(oc.Condition) error) func(oc.Condition)
return say(c)
}
}
// oldStateReaders names, as "<path> <function>", every function of the source that reads the field State of a
// catalogue.Manifest or a membership.Membership: a receiver, parameter or declared variable of either type, or a field
// named Manifest or Membership (issue 1966's source guard).
func oldStateReaders(path string, src []byte) ([]string, error) {
f, err := parser.ParseFile(token.NewFileSet(), path, src, 0)
if err != nil {
return nil, err
}
typed := func(e ast.Expr) bool {
if s, ok := e.(*ast.StarExpr); ok {
e = s.X
}
sel, ok := e.(*ast.SelectorExpr)
if !ok {
return false
}
x, ok := sel.X.(*ast.Ident)
return ok && ((x.Name == "catalogue" && sel.Sel.Name == "Manifest") || (x.Name == "membership" && sel.Sel.Name == "Membership"))
}
var out []string
for _, decl := range f.Decls {
fn, ok := decl.(*ast.FuncDecl)
if !ok || fn.Body == nil {
continue
}
names := map[string]bool{}
var fields []*ast.Field
if fn.Recv != nil {
fields = append(fields, fn.Recv.List...)
}
for _, fl := range append(fields, fn.Type.Params.List...) {
if typed(fl.Type) {
for _, n := range fl.Names {
names[n.Name] = true
}
}
}
reads := false
ast.Inspect(fn.Body, func(n ast.Node) bool {
if vs, ok := n.(*ast.ValueSpec); ok && vs.Type != nil && typed(vs.Type) {
for _, n := range vs.Names {
names[n.Name] = true
}
}
if s, ok := n.(*ast.SelectorExpr); ok && s.Sel.Name == "State" {
switch x := s.X.(type) {
case *ast.Ident:
reads = reads || names[x.Name]
case *ast.SelectorExpr:
reads = reads || x.Sel.Name == "Manifest" || x.Sel.Name == "Membership"
}
}
return true
})
if reads {
out = append(out, path+" "+fn.Name.Name)
}
}
return out, nil
}