diff --git a/checks/controllerdeps/controllerdeps.go b/checks/controllerdeps/controllerdeps.go new file mode 100644 index 00000000..9c9177f3 --- /dev/null +++ b/checks/controllerdeps/controllerdeps.go @@ -0,0 +1,39 @@ +// Package controllerdeps is the catalogue's side of the lease write's manifest test (novox/hq ADR 0571 §13, ADR 0582 +// clause 2, issue 774): no module of this tree reads, invokes, consumes, waits on or uses the controller beyond what the +// named list of mesh-sdk's catalogue.KeepList keeps to the write itself, and the lease key, which names the lease's holder +// whoever that is. It decodes every manifest under root/modules and judges them with mesh-sdk's ManifestRefusals, the +// judge the grants part's grants_lease_check and the other two repositories' tests run, so the rule is said once. +package controllerdeps + +import ( + "fmt" + "os" + "path/filepath" + "sort" + + "git.novox.be/novox/mesh-sdk/go/catalogue" +) + +// Refusals is each reason the lease write's check gives over the manifests under root/modules in one phase +// (catalogue.PhaseBefore or catalogue.PhaseWrite), none when it passes. A module.json that does not decode is an error: +// the manifests check says which, and nothing here is judged on a tree it cannot read. +func Refusals(root, phase string) ([]string, error) { + files, err := filepath.Glob(filepath.Join(root, "modules", "*", "module.json")) + if err != nil { + return nil, err + } + sort.Strings(files) + manifests := map[string]catalogue.Manifest{} + for _, f := range files { + raw, err := os.ReadFile(f) + if err != nil { + return nil, err + } + m, err := catalogue.Decode(raw) + if err != nil { + return nil, fmt.Errorf("%s: %w", f, err) + } + manifests[m.Module] = m + } + return catalogue.ManifestRefusals(manifests, phase), nil +} diff --git a/checks/controllerdeps/controllerdeps_test.go b/checks/controllerdeps/controllerdeps_test.go new file mode 100644 index 00000000..5f5cd28e --- /dev/null +++ b/checks/controllerdeps/controllerdeps_test.go @@ -0,0 +1,56 @@ +package controllerdeps + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "git.novox.be/novox/mesh-sdk/go/catalogue" +) + +// 774/S13/AC1: every manifest of the catalogue at this head, at the phase of the lease write's own delivery, reads, +// invokes and consumes nothing of the controller beyond the lease and the entries kept to the write. +func TestNoManifestDependsOnTheControllerBeyondTheLeaseAndTheEntriesKeptToTheWrite(t *testing.T) { + got, err := Refusals("../..", catalogue.PhaseWrite) + if err != nil { + t.Fatal(err) + } + if len(got) > 0 { + t.Errorf("the catalogue still declares on the controller (novox/hq ADR 0571 §13):\n%s", strings.Join(got, "\n")) + } +} + +// The check refuses what it must and lets the lease and the kept entries through, on a tree of its own. +func TestRefusalsJudgeATreeByPhase(t *testing.T) { + for _, row := range []struct { + name, phase string + manifests map[string]string + want string // empty: passes + }{ + {"the lease is no dependency", catalogue.PhaseWrite, map[string]string{"a": `{"module":"a","reads":["mesh-controller.lease"]}`}, ""}, + {"a read of the controller's states", catalogue.PhaseWrite, map[string]string{"a": `{"module":"a","reads":["mesh-controller.conditions"]}`}, "a declares read mesh-controller.conditions"}, + {"a kept entry passes before the delivery", catalogue.PhaseBefore, map[string]string{"artifact-store-tools": `{"module":"artifact-store-tools","invokes":["seat:mesh-controller.collect"]}`}, ""}, + {"a kept-to-the-write entry passes at the write", catalogue.PhaseWrite, map[string]string{"artifact-store-tools": `{"module":"artifact-store-tools","invokes":["seat:mesh-controller.collect"]}`}, ""}, + } { + t.Run(row.name, func(t *testing.T) { + root := t.TempDir() + for module, text := range row.manifests { + dir := filepath.Join(root, "modules", module) + if err := os.MkdirAll(dir, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(dir, "module.json"), []byte(text), 0o644); err != nil { + t.Fatal(err) + } + } + got, err := Refusals(root, row.phase) + if err != nil { + t.Fatal(err) + } + if joined := strings.Join(got, "\n"); (row.want == "") != (joined == "") || !strings.Contains(joined, row.want) { + t.Errorf("refusals %q, want %q", joined, row.want) + } + }) + } +} diff --git a/checks/controllerdeps/go.mod b/checks/controllerdeps/go.mod new file mode 100644 index 00000000..5974999f --- /dev/null +++ b/checks/controllerdeps/go.mod @@ -0,0 +1,5 @@ +module checks/controllerdeps + +go 1.26.0 + +require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 diff --git a/checks/controllerdeps/go.sum b/checks/controllerdeps/go.sum new file mode 100644 index 00000000..2e60a9c1 --- /dev/null +++ b/checks/controllerdeps/go.sum @@ -0,0 +1,2 @@ +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915 h1:x8k7IeMirMTxUo8BH+xUO+MDnHYgU+636jl1NIw1wvs= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101022206-8af6e7266915/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= diff --git a/checks/controllerdeps/reads-outside b/checks/controllerdeps/reads-outside new file mode 100644 index 00000000..efc1a11c --- /dev/null +++ b/checks/controllerdeps/reads-outside @@ -0,0 +1,3 @@ +# What these tests read outside their package, as paths from the repository's root (issue 1936). +# every module's directory (its manifest), decoded and judged for what it still declares on the controller. +modules