Issue 1585, S12: grants, builder and checker read with catalogue.Decode and grants holds a module the indexer refused; the checker asks the Go build seat to run mesh-judge, built at mesh-delivery's trunk or at the group's mesh-delivery head when it changes the judge, over the catalogue as the change leaves it, and fails on a refusal. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
159 lines
9.3 KiB
Go
159 lines
9.3 KiB
Go
package manifest
|
|
|
|
import (
|
|
"encoding/json"
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
|
|
"mesh-grants/internal/grants"
|
|
)
|
|
|
|
// Issue 774, design 47 L3 (ADR 0547 §11, ADR 0561 §3, ADR 0571 §13): grants_lease_check answers from bus state who holds
|
|
// the controller's lease, every manifest's dependency on the controller with what keeps it, the controller's streams in
|
|
// use, and the lease write's check run dry, at the write (the three kept to it alone) and before its delivery.
|
|
func TestTheLeaseCheckJudgesEveryDependencyOnTheController(t *testing.T) {
|
|
const mManifest = `{"module":"mod-m","version":"1","reads":["mesh-controller.declared-packages"]}`
|
|
controllerHolds := map[string]any{"instance": "controller@novox-be pid 1 since 2026-10-30T14:52:10Z", "epoch": 60,
|
|
"taken": "2026-10-30T14:52:10Z", "renewed": "2026-10-30T22:00:00Z"}
|
|
handedAway := map[string]any{"handed-to": "the operator's hand-over", "handed-by": "operator"}
|
|
type want struct {
|
|
deps []string // "<module> <kind> <what> <kept?>" with kept as "kept" or "unkept"
|
|
writePasses bool
|
|
beforePasses bool
|
|
reason string // a reason the check at the write must name, or ""
|
|
bothPhases bool // the check before the delivery must name the reason too
|
|
handedAway bool
|
|
durable []string // durable consumers of KV_mesh-controller_assignments
|
|
}
|
|
for _, row := range []struct {
|
|
name string
|
|
manifests map[string]string
|
|
lease map[string]any
|
|
want want
|
|
setup func(b *fakeBus)
|
|
}{
|
|
{"a read of the lease alone is no dependency, and the controller's own manifest is not judged",
|
|
map[string]string{"mod-l": `{"module":"mod-l","version":"1","reads":["mesh-controller.lease"]}`,
|
|
"mesh-controller": `{"module":"mesh-controller","version":"1","invokes":["seat:mesh-controller.status"]}`},
|
|
controllerHolds, want{writePasses: true, beforePasses: true}, nil},
|
|
{"the builder's consume of artifacts-gone is kept to the write itself",
|
|
map[string]string{"mesh-delivery-builder": `{"module":"mesh-delivery-builder","version":"1","consumes":["mesh-controller.artifacts-gone"]}`},
|
|
controllerHolds, want{deps: []string{"mesh-delivery-builder consume mesh-controller.artifacts-gone kept"}, writePasses: true, beforePasses: true}, nil},
|
|
{"the assigner's read of composed is kept until the write's delivery, which drops it",
|
|
map[string]string{"mesh-delivery-assigner": `{"module":"mesh-delivery-assigner","version":"1","reads":["mesh-controller.composed","mesh-controller.lease"]}`},
|
|
controllerHolds, want{deps: []string{"mesh-delivery-assigner read mesh-controller.composed kept"}, beforePasses: true,
|
|
reason: "mesh-delivery-assigner declares read mesh-controller.composed, kept by ADR 0571 §8 only until the lease write's own delivery"}, nil},
|
|
{"a read no record keeps fails both phases, named",
|
|
map[string]string{"pacman": `{"module":"pacman","version":"1","reads":["mesh-controller.declared-packages"]}`},
|
|
controllerHolds, want{deps: []string{"pacman read mesh-controller.declared-packages unkept"},
|
|
reason: "pacman declares read mesh-controller.declared-packages, which no record keeps until the lease write"}, nil},
|
|
{"a seat verb and a module tool of the controller's, a consume and a wait are each one; every tool (*) is none",
|
|
map[string]string{"mod-i": `{"module":"mod-i","version":"1","invokes":["seat:mesh-controller.root-free","mesh-controller.status","*","nats.nats_users"],` +
|
|
`"consumes":["mesh-controller.checked","mod-x.thing"],"uses":["mesh-controller"]}`,
|
|
"node-tools": `{"module":"node-tools","version":"1","waits":["mesh-controller.checked"]}`},
|
|
controllerHolds, want{deps: []string{"mod-i consume mesh-controller.checked unkept", "mod-i invoke mesh-controller.status unkept",
|
|
"mod-i invoke seat:mesh-controller.root-free unkept", "mod-i uses mesh-controller unkept", "node-tools waits mesh-controller.checked unkept"},
|
|
reason: "mod-i declares invoke seat:mesh-controller.root-free, which no record keeps"}, nil},
|
|
{"a process's own invoke of the controller is its module's dependency, named by the process",
|
|
map[string]string{"mod-p": `{"module":"mod-p","version":"1","resources":[{"id":"account","type":"user","name":"mod-p"},{"id":"worker","type":"process","run":["./w"],"user":"mod-p",` +
|
|
`"bus":{"invokes":["seat:mesh-controller.held"]}}]}`},
|
|
controllerHolds, want{deps: []string{"mod-p invoke seat:mesh-controller.held unkept"},
|
|
reason: "mod-p (process worker) declares invoke seat:mesh-controller.held"}, nil},
|
|
{"no manifest read from the indexer fails both phases, though nothing read depends on the controller",
|
|
map[string]string{},
|
|
controllerHolds, want{reason: "no manifest was read from the indexer (it holds 0 modules)", bothPhases: true}, nil},
|
|
{"a module whose manifest digest is missing fails both phases, named",
|
|
map[string]string{"mod-l": `{"module":"mod-l","version":"1"}`, "mod-m": mManifest},
|
|
controllerHolds, want{reason: "manifest of mod-m could not be read", bothPhases: true}, func(b *fakeBus) {
|
|
delete(b.states[grants.ReadManifests], delivery.ManifestDigest([]byte(mManifest)))
|
|
}},
|
|
{"a manifest the indexer's judge refuses fails both phases, named",
|
|
map[string]string{"mod-l": `{"module":"mod-l","version":"1"}`, "mod-r": `{"module":"mod-r","version":"1","waits":["mesh-controller.checked"]}`},
|
|
controllerHolds, want{reason: "manifest of mod-r could not be read", bothPhases: true}, func(b *fakeBus) {
|
|
b.refuse("mod-r", `{"module":"mod-r","version":"1","waits":["mesh-controller.checked"]}`, "waits on what no record keeps")
|
|
}},
|
|
{"a lease already handed away leaves nothing to write",
|
|
map[string]string{},
|
|
handedAway, want{handedAway: true, reason: "there is nothing to write: the lease is handed to the operator's hand-over"}, nil},
|
|
} {
|
|
t.Run(row.name, func(t *testing.T) {
|
|
b := newFakeBus()
|
|
for m, raw := range row.manifests {
|
|
b.manifestOf(m, raw)
|
|
}
|
|
b.put(grants.ReadLease, grants.LeaseKey, row.lease)
|
|
if row.setup != nil {
|
|
row.setup(b)
|
|
}
|
|
b.streams = []map[string]any{
|
|
{"name": "KV_mesh-controller_assignments", "consumers": 3, "messages": 2991, "subjects_held": 40, "last": "2026-10-30T12:55:13Z"},
|
|
{"name": "KV_mesh-controller_rollouts", "consumers": 0, "messages": 6652},
|
|
{"name": "KV_mesh-grants_memberships", "consumers": 243, "messages": 1573},
|
|
}
|
|
b.consumers = map[string][]map[string]any{"KV_mesh-controller_assignments": {{"name": "0AYfjxta"}, {"name": "1g9XJFUX"}, {"name": "grants-assignments"}}}
|
|
raw, err := partOn(b).LeaseCheck()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var got grants.LeaseAnswer
|
|
data, _ := json.Marshal(raw)
|
|
if err := json.Unmarshal(data, &got); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var deps []string
|
|
for _, d := range got.Dependencies {
|
|
kept := "unkept"
|
|
if d.Kept != "" {
|
|
kept = "kept"
|
|
}
|
|
deps = append(deps, d.Module+" "+d.Kind+" "+d.What+" "+kept)
|
|
}
|
|
if !slices.Equal(deps, row.want.deps) {
|
|
t.Errorf("dependencies %q, want %q", deps, row.want.deps)
|
|
}
|
|
if got.Check.Phase != grants.PhaseWrite || got.Check.Passes != row.want.writePasses {
|
|
t.Errorf("check at the write passes=%v (%v), want %v", got.Check.Passes, got.Check.Reasons, row.want.writePasses)
|
|
}
|
|
if got.Before.Phase != grants.PhaseBefore || got.Before.Passes != row.want.beforePasses {
|
|
t.Errorf("check before the delivery passes=%v (%v), want %v", got.Before.Passes, got.Before.Reasons, row.want.beforePasses)
|
|
}
|
|
if row.want.reason != "" && !slices.ContainsFunc(got.Check.Reasons, func(r string) bool { return strings.Contains(r, row.want.reason) }) {
|
|
t.Errorf("reasons %q name nothing like %q", got.Check.Reasons, row.want.reason)
|
|
}
|
|
if row.want.bothPhases && !slices.ContainsFunc(got.Before.Reasons, func(r string) bool { return strings.Contains(r, row.want.reason) }) {
|
|
t.Errorf("reasons before the delivery %q name nothing like %q", got.Before.Reasons, row.want.reason)
|
|
}
|
|
if got.Lease.HandedAway != row.want.handedAway {
|
|
t.Errorf("lease handed away %v (%s), want %v", got.Lease.HandedAway, got.Lease.Says, row.want.handedAway)
|
|
}
|
|
if !row.want.handedAway && (got.Lease.Since == nil || got.Lease.Since.Format("15:04") != "14:52" ||
|
|
!strings.Contains(got.Lease.Says, "the controller holds the lease") || !slices.Equal(got.Lease.Does, grants.DoesWhileController)) {
|
|
t.Errorf("lease %+v: the controller's instance since 14:52, and what it does while it holds it", got.Lease)
|
|
}
|
|
if len(got.Streams) != 1 || got.Streams[0].Stream != "KV_mesh-controller_assignments" || got.Streams[0].Consumers != 3 ||
|
|
!slices.Equal(got.Streams[0].Durable, []string{"grants-assignments"}) || !slices.Equal(got.Idle, []string{"KV_mesh-controller_rollouts"}) {
|
|
t.Errorf("streams %+v idle %v: the controller's assignments bucket with its one durable consumer, the rollouts idle, no other module's", got.Streams, got.Idle)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Every entry of the keep list names the record that keeps it, and exactly three are kept to the write itself (ADR 0571 §13).
|
|
func TestTheKeepListNamesItsRecordsAndThreeToTheWrite(t *testing.T) {
|
|
toTheWrite := 0
|
|
for _, k := range grants.KeepList {
|
|
if !strings.HasPrefix(k.Record, "ADR ") || k.Module == "" || k.What == "" {
|
|
t.Errorf("entry %+v names no record, module or declaration", k)
|
|
}
|
|
if k.ToTheWrite {
|
|
toTheWrite++
|
|
}
|
|
}
|
|
if toTheWrite != 3 {
|
|
t.Errorf("%d entries kept to the write itself, ADR 0571 §13 names three", toTheWrite)
|
|
}
|
|
}
|