Move the controller-dependence judge into mesh-sdk, so three repositories run the same lease-write check
ADR 0571 §13 names a test in mesh-catalog, mesh-delivery and mesh-tools that refuses a manifest depending on the controller beyond the lease and the entries kept. The judge lived in mesh-grants (leasecheck.go); it moves here whole (the kinds, the phases, KeepList, EndedBy, Dependencies, Judge) with ManifestRefusals(manifests, phase) for those tests. Kept is renamed KeptDependency (catalogue already has a Kept), and a gap is Unread. mesh-grants re-exports it in its own commit; the keep list is the one at mesh-delivery 4a84370. Test: TestTheLeaseWritesCheckJudgesEveryDependencyOnTheController, moved from mesh-grants' leasecheck_test.go (its manifest rows and the keep-list test, which mesh-grants deletes), with the conditions row of 774/S4/AC1 added; not a new test. (issue 774, 774/S4) Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
package catalogue
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// **What still depends on the controller, and the lease write's check** (novox/hq ADR 0571 §13, ADR 0582 clause 2,
|
||||
// issue 774): every declaration of a manifest other than the controller's on the controller (a read of one of its
|
||||
// states, an invoke of one of its verbs or tools, a consume or a wait on one of its events, a use of its seat), judged
|
||||
// against the named, temporary list of what another accepted record keeps until the lease write. The grants part's
|
||||
// grants_lease_check runs it over every manifest the indexer holds; ManifestRefusals runs it over a repository's
|
||||
// manifests, in the test ADR 0571 §13 names in mesh-catalog, mesh-delivery and mesh-tools. A read of the lease key is no
|
||||
// dependency: it names the lease's holder, whoever that is, and parts read it to learn that the write happened.
|
||||
|
||||
// ControllerLease is the controller's lease as a reader names it (`<module>.<state>`): no dependency.
|
||||
const ControllerLease = ControllerSeatName + ".lease"
|
||||
|
||||
// The kinds of a dependency on the controller, as a manifest declares them.
|
||||
const (
|
||||
DependsRead = "read"
|
||||
DependsInvoke = "invoke"
|
||||
DependsConsume = "consume"
|
||||
DependsUses = "uses"
|
||||
DependsWaits = "waits"
|
||||
)
|
||||
|
||||
// The phases of the lease write's check (ADR 0571 §13): before the lease write's own delivery the whole named list is
|
||||
// allowed; that delivery drops every entry but the three kept to the write itself, and its check runs with those alone.
|
||||
const (
|
||||
PhaseBefore = "before-delivery"
|
||||
PhaseWrite = "write"
|
||||
)
|
||||
|
||||
// Dependency is one declaration of a manifest other than the controller's on the controller: a read of one of its
|
||||
// states, an invoke of one of its verbs or tools, a consume or a wait on one of its events, or a send to its seat.
|
||||
type Dependency struct {
|
||||
Module string `json:"module"`
|
||||
// By is the account declaring it when it is not the module's own: `process <id>` or `tools <binary>`.
|
||||
By string `json:"by,omitempty"`
|
||||
Kind string `json:"kind"`
|
||||
What string `json:"what"`
|
||||
// Kept is the record that keeps it until the lease write, from the named list; empty when none does.
|
||||
Kept string `json:"kept,omitempty"`
|
||||
// ToTheWrite says it is one of the three kept to the write itself, which the lease write's own delivery keeps.
|
||||
ToTheWrite bool `json:"to_the_write,omitempty"`
|
||||
}
|
||||
|
||||
// KeptDependency is one entry of the named, temporary list ADR 0571 §13 allows: a read, invoke or consume another
|
||||
// accepted record keeps until the lease write, with that record. ToTheWrite marks the three kept to the write itself.
|
||||
type KeptDependency struct {
|
||||
Module, Kind, What, Record string
|
||||
ToTheWrite bool
|
||||
}
|
||||
|
||||
// KeepList is the named list. Each entry names the clause that keeps it; a dependency not on it fails the check. The
|
||||
// third entry kept to the write, the bus node agent's following of the controller's user list (ADR 0571 §8, ADR 0547
|
||||
// §8), is no manifest's declaration but the node-engine's own, so no manifest matches it; it stays named here so the
|
||||
// list reads as the record has it.
|
||||
var KeepList = []KeptDependency{
|
||||
// Kept to the write itself (ADR 0571 §4, §8, §13).
|
||||
{Module: "mesh-delivery-builder", Kind: DependsConsume, What: "mesh-controller.artifacts-gone", Record: "ADR 0571 §4, §13", ToTheWrite: true},
|
||||
{Module: "artifact-store-tools", Kind: DependsInvoke, What: "seat:mesh-controller.collect", Record: "ADR 0571 §4, §13", ToTheWrite: true},
|
||||
{Module: "node-engine", Kind: DependsRead, What: "the controller's user list of the bus module's composition", Record: "ADR 0571 §8, §13; ADR 0547 §8", ToTheWrite: true},
|
||||
// Kept until the lease write's own delivery.
|
||||
{Module: "mesh-delivery-assigner", Kind: DependsInvoke, What: "seat:mesh-controller.placement-export", Record: "ADR 0567 §7"},
|
||||
{Module: "mesh-delivery-assigner", Kind: DependsInvoke, What: "seat:mesh-controller.assign", Record: "ADR 0567 §8"},
|
||||
{Module: "mesh-delivery-assigner", Kind: DependsInvoke, What: "seat:mesh-controller.unassign", Record: "ADR 0567 §8"},
|
||||
{Module: "mesh-delivery-assigner", Kind: DependsInvoke, What: "seat:mesh-controller.pin", Record: "ADR 0567 §8"},
|
||||
{Module: "mesh-delivery-assigner", Kind: DependsInvoke, What: "seat:mesh-controller.unpin", Record: "ADR 0567 §8"},
|
||||
{Module: "mesh-delivery-assigner", Kind: DependsRead, What: "mesh-controller.assignments", Record: "ADR 0567 §8"},
|
||||
{Module: "mesh-delivery-assigner", Kind: DependsRead, What: "mesh-controller.composed", Record: "ADR 0571 §8"},
|
||||
{Module: "mesh-grants", Kind: DependsRead, What: "mesh-controller.assignments", Record: "ADR 0549 §5"},
|
||||
{Module: "mesh-grants", Kind: DependsConsume, What: "mesh-controller.condition-raised", Record: "ADR 0564 §9"},
|
||||
{Module: "mesh-grants", Kind: DependsConsume, What: "mesh-controller.condition-cleared", Record: "ADR 0564 §9"},
|
||||
{Module: "messenger", Kind: DependsConsume, What: "mesh-controller.condition-raised", Record: "ADR 0564 §9"},
|
||||
{Module: "messenger", Kind: DependsConsume, What: "mesh-controller.condition-cleared", Record: "ADR 0564 §9"},
|
||||
{Module: "messenger", Kind: DependsConsume, What: "mesh-controller.condition-changed", Record: "ADR 0564 §9"},
|
||||
{Module: "messenger", Kind: DependsInvoke, What: "seat:mesh-controller.conditions", Record: "ADR 0564 §9"},
|
||||
{Module: "mesh-watcher", Kind: DependsConsume, What: "mesh-controller.doctor-heartbeat", Record: "ADR 0564 §9"},
|
||||
{Module: "mesh-conditions", Kind: DependsRead, What: "mesh-controller.conditions", Record: "ADR 0564 §9"},
|
||||
{Module: "mesh-config", Kind: DependsRead, What: "mesh-controller.declared-config", Record: "ADR 0565 §4"},
|
||||
{Module: "mesh-config", Kind: DependsRead, What: "mesh-controller.predefined", Record: "ADR 0565 §4"},
|
||||
{Module: "mesh-healers", Kind: DependsRead, What: "mesh-controller.hand-acts", Record: "ADR 0565 §11"},
|
||||
{Module: "mesh-hand-acts", Kind: DependsRead, What: "mesh-controller.hand-acts", Record: "ADR 0565 §11"},
|
||||
// pacman's fallback for a node whose engine has not written its own packages yet.
|
||||
{Module: "pacman", Kind: DependsRead, What: "mesh-controller.declared-packages", Record: "ADR 0565 §6"},
|
||||
}
|
||||
|
||||
// EndedBy names, for a declaration no entry keeps, the record that already ended it, so a refusal says where it moved.
|
||||
// The controller's `grants` is one: ADR 0571 §9 has no manifest read it from L7's delivery, which changes ADR 0567 §10
|
||||
// and ADR 0547 §5's shadow compare, so neither the assigner's nor the grants part's read of it is kept.
|
||||
var EndedBy = map[string]string{
|
||||
DependsRead + " mesh-controller.grants": "ADR 0571 §9: no manifest reads the controller's grants; the grants part's key is the one read",
|
||||
DependsInvoke + " seat:mesh-controller.hand-act": "ADR 0565 §11: every recorder calls mesh-hand-acts",
|
||||
DependsInvoke + " seat:mesh-controller.warranted": "ADR 0565 §11: every recorder calls mesh-hand-acts",
|
||||
DependsInvoke + " seat:mesh-controller.root-free": "ADR 0571 §11: each machine's engine answers root-free (seat:node-engine.root-free@machine)",
|
||||
DependsInvoke + " seat:mesh-controller.held": "ADR 0571 §2: held is the seat mesh-artifact-sweep's",
|
||||
}
|
||||
|
||||
// keptFor is the list's entry for one dependency, or nil.
|
||||
func keptFor(d Dependency) *KeptDependency {
|
||||
for i, k := range KeepList {
|
||||
if k.Module == d.Module && k.Kind == d.Kind && k.What == d.What {
|
||||
return &KeepList[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// onController says whether a name `<owner>.<rest>` is the controller's.
|
||||
func onController(name string) bool { return strings.HasPrefix(name, ControllerSeatName+".") }
|
||||
|
||||
// invokesController says whether an invoke names a verb of the controller's seat or a tool of its module. `*` names none:
|
||||
// it is the console's grant to every tool, judged by no record as a dependency.
|
||||
func invokesController(invoke string) bool {
|
||||
if seat, _, ok := SeatInvoke(invoke); ok {
|
||||
return seat == ControllerSeatName
|
||||
}
|
||||
return onController(invoke)
|
||||
}
|
||||
|
||||
// Dependencies are every declaration on the controller in the manifests given, by module, each with the list's entry
|
||||
// that keeps it. The controller's own manifest is not judged, nor a read of its lease key: it names the lease's holder,
|
||||
// whoever that is, and parts read it to learn that the write happened (ADR 0571 §13).
|
||||
func Dependencies(manifests map[string]Manifest) []Dependency {
|
||||
var out []Dependency
|
||||
add := func(module, by, kind, what string) {
|
||||
d := Dependency{Module: module, By: by, Kind: kind, What: what}
|
||||
if k := keptFor(d); k != nil {
|
||||
d.Kept, d.ToTheWrite = k.Record, k.ToTheWrite
|
||||
}
|
||||
if !slices.Contains(out, d) {
|
||||
out = append(out, d)
|
||||
}
|
||||
}
|
||||
for name, m := range manifests {
|
||||
if name == ControllerSeatName || m.Module == ControllerSeatName {
|
||||
continue
|
||||
}
|
||||
for _, r := range m.Reads {
|
||||
if onController(r) && r != ControllerLease {
|
||||
add(name, "", DependsRead, r)
|
||||
}
|
||||
}
|
||||
for _, i := range m.Invokes {
|
||||
if invokesController(i) {
|
||||
add(name, "", DependsInvoke, i)
|
||||
}
|
||||
}
|
||||
for _, c := range m.Consumes {
|
||||
if onController(c) {
|
||||
add(name, "", DependsConsume, c)
|
||||
}
|
||||
}
|
||||
for _, w := range m.Waits {
|
||||
if onController(w) {
|
||||
add(name, "", DependsWaits, w)
|
||||
}
|
||||
}
|
||||
for _, u := range m.Uses {
|
||||
if u == ControllerSeatName {
|
||||
add(name, "", DependsUses, u)
|
||||
}
|
||||
}
|
||||
for _, a := range m.BusAccounts() {
|
||||
by := "process " + a.ID
|
||||
if a.Tools {
|
||||
by = "tools " + a.ID
|
||||
}
|
||||
for _, i := range a.Bus.Invokes {
|
||||
if invokesController(i) {
|
||||
add(name, by, DependsInvoke, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool {
|
||||
a, b := out[i], out[j]
|
||||
if a.Module != b.Module {
|
||||
return a.Module < b.Module
|
||||
}
|
||||
if a.Kind != b.Kind {
|
||||
return a.Kind < b.Kind
|
||||
}
|
||||
if a.What != b.What {
|
||||
return a.What < b.What
|
||||
}
|
||||
return a.By < b.By
|
||||
})
|
||||
return out
|
||||
}
|
||||
|
||||
// Verdict is the lease write's check in one phase.
|
||||
type Verdict struct {
|
||||
Phase string `json:"phase"`
|
||||
Passes bool `json:"passes"`
|
||||
Reasons []string `json:"reasons"`
|
||||
}
|
||||
|
||||
// Coverage is how much of the mesh's manifests the dependencies were derived from: the manifests read, the modules the
|
||||
// indexer holds (-1 when its keys could not be listed, which Unread then names), and each manifest that could not be
|
||||
// read.
|
||||
type Coverage struct {
|
||||
Read, Indexed int
|
||||
Unread []Unread
|
||||
}
|
||||
|
||||
// Unread is one manifest, or one listing of them, that could not be read, and why.
|
||||
type Unread struct {
|
||||
Input string `json:"input"`
|
||||
Why string `json:"why"`
|
||||
}
|
||||
|
||||
// Judge runs the lease write's check over the dependencies (ADR 0571 §13) in one phase: before the lease write's own
|
||||
// delivery, every dependency must be on the named list; at the write, only the three kept to the write itself may stay.
|
||||
// It passes only over every module the indexer holds: no manifest read, fewer read than the indexer holds, or one that
|
||||
// could not be read fails it, since what was not read could hide a dependency. A lease already handed away leaves
|
||||
// nothing to write.
|
||||
func Judge(deps []Dependency, phase string, cov Coverage, handedAway bool, leaseSays string) Verdict {
|
||||
v := Verdict{Phase: phase, Reasons: []string{}}
|
||||
if handedAway {
|
||||
v.Reasons = append(v.Reasons, "there is nothing to write: "+leaseSays)
|
||||
}
|
||||
switch {
|
||||
case cov.Read == 0:
|
||||
v.Reasons = append(v.Reasons, fmt.Sprintf("no manifest was read from the indexer (it holds %d modules): with nothing read, nothing on the controller can be judged absent", max(cov.Indexed, 0)))
|
||||
case cov.Indexed >= 0 && cov.Read < cov.Indexed:
|
||||
v.Reasons = append(v.Reasons, fmt.Sprintf("only %d of the %d modules the indexer holds had their manifest read, so what the others declare on the controller is not known", cov.Read, cov.Indexed))
|
||||
}
|
||||
for _, d := range deps {
|
||||
who := d.Module
|
||||
if d.By != "" {
|
||||
who += " (" + d.By + ")"
|
||||
}
|
||||
switch {
|
||||
case d.Kept == "":
|
||||
why := fmt.Sprintf("%s declares %s %s, which no record keeps until the lease write (ADR 0571 §13)", who, d.Kind, d.What)
|
||||
if ended := EndedBy[d.Kind+" "+d.What]; ended != "" {
|
||||
why += "; ended by " + ended
|
||||
}
|
||||
v.Reasons = append(v.Reasons, why)
|
||||
case phase == PhaseWrite && !d.ToTheWrite:
|
||||
v.Reasons = append(v.Reasons, fmt.Sprintf("%s declares %s %s, kept by %s only until the lease write's own delivery, which drops it", who, d.Kind, d.What, d.Kept))
|
||||
}
|
||||
}
|
||||
for _, g := range cov.Unread {
|
||||
v.Reasons = append(v.Reasons, fmt.Sprintf("%s could not be read (%s), so what it declares on the controller is not known", g.Input, g.Why))
|
||||
}
|
||||
v.Passes = len(v.Reasons) == 0
|
||||
return v
|
||||
}
|
||||
|
||||
// ManifestRefusals is the lease write's check over a repository's manifests, by module name, in one phase (PhaseBefore
|
||||
// or PhaseWrite): each reason the check gives, none when it passes. Every manifest given counts as read: the caller reads
|
||||
// them from its own tree, and none given fails it.
|
||||
func ManifestRefusals(manifests map[string]Manifest, phase string) []string {
|
||||
return Judge(Dependencies(manifests), phase, Coverage{Read: len(manifests), Indexed: -1}, false, "").Reasons
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package catalogue
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Issue 774 (ADR 0571 §13, ADR 0582 clause 2): the lease write's check judges every manifest's dependency on the
|
||||
// controller against the named list, at the write (the three kept to it alone) and before its delivery. Moved with the
|
||||
// judge from mesh-grants' leasecheck_test.go (TestTheLeaseCheckJudgesEveryDependencyOnTheController's manifest rows and
|
||||
// TestTheKeepListNamesItsRecordsAndThreeToTheWrite), whose manifests were mesh-grants' fixtures; the conditions row is
|
||||
// mesh-conditions' manifest at mesh-catalog ce3bedd5 (module.json:31-32).
|
||||
func TestTheLeaseWritesCheckJudgesEveryDependencyOnTheController(t *testing.T) {
|
||||
toTheWrite := 0
|
||||
for _, k := range 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)
|
||||
}
|
||||
|
||||
type want struct {
|
||||
deps []string // "<module> <kind> <what> <kept by>", kept by the record or "unkept"
|
||||
write string // a reason the check at the write names, or "" when it passes
|
||||
before string // a reason the check before the delivery names, or "" when it passes
|
||||
}
|
||||
for _, row := range []struct {
|
||||
name string
|
||||
manifests map[string]string
|
||||
want want
|
||||
}{
|
||||
// 774/S4/AC2.
|
||||
{"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"]}`},
|
||||
want{}},
|
||||
// 774/S4/AC1.
|
||||
{"the keeper's read of the controller's conditions is kept before the write's delivery and refused at the write",
|
||||
map[string]string{"mesh-conditions": `{"module":"mesh-conditions","version":"1","reads":["mesh-controller.conditions","mesh-controller.lease"]}`},
|
||||
want{deps: []string{"mesh-conditions read mesh-controller.conditions kept by ADR 0564 §9"},
|
||||
write: "mesh-conditions declares read mesh-controller.conditions, kept by ADR 0564 §9 only until the lease write's own delivery"}},
|
||||
{"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"]}`},
|
||||
want{deps: []string{"mesh-delivery-builder consume mesh-controller.artifacts-gone kept by ADR 0571 §4, §13"}}},
|
||||
{"a read no record keeps fails both phases, named",
|
||||
map[string]string{"mod-u": `{"module":"mod-u","version":"1","reads":["mesh-controller.declared-packages"]}`},
|
||||
want{deps: []string{"mod-u read mesh-controller.declared-packages unkept"},
|
||||
write: "mod-u declares read mesh-controller.declared-packages, which no record keeps until the lease write",
|
||||
before: "mod-u declares read mesh-controller.declared-packages, which no record keeps until the lease write"}},
|
||||
{"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"]}`},
|
||||
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"},
|
||||
write: "mod-i declares invoke seat:mesh-controller.root-free, which no record keeps until the lease write (ADR 0571 §13); ended by ADR 0571 §11",
|
||||
before: "node-tools declares waits mesh-controller.checked"}},
|
||||
{"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"]}}]}`},
|
||||
want{deps: []string{"mod-p invoke seat:mesh-controller.held unkept"},
|
||||
write: "mod-p (process worker) declares invoke seat:mesh-controller.held", before: "mod-p (process worker)"}},
|
||||
{"no manifest fails both phases", map[string]string{}, want{write: "no manifest was read", before: "no manifest was read"}},
|
||||
} {
|
||||
manifests := map[string]Manifest{}
|
||||
for name, raw := range row.manifests {
|
||||
m, err := Decode([]byte(raw))
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %s: %v", row.name, name, err)
|
||||
}
|
||||
manifests[name] = m
|
||||
}
|
||||
var deps []string
|
||||
for _, d := range Dependencies(manifests) {
|
||||
kept := "unkept"
|
||||
if d.Kept != "" {
|
||||
kept = "kept by " + d.Kept
|
||||
}
|
||||
deps = append(deps, d.Module+" "+d.Kind+" "+d.What+" "+kept)
|
||||
}
|
||||
if !slices.Equal(deps, row.want.deps) {
|
||||
t.Errorf("%s: dependencies %q, want %q", row.name, deps, row.want.deps)
|
||||
}
|
||||
for phase, reason := range map[string]string{PhaseWrite: row.want.write, PhaseBefore: row.want.before} {
|
||||
got := ManifestRefusals(manifests, phase)
|
||||
if (reason == "") != (len(got) == 0) || !slices.ContainsFunc(got, func(r string) bool { return strings.Contains(r, reason) }) && reason != "" {
|
||||
t.Errorf("%s: at %s refused %q, want %q", row.name, phase, got, reason)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user