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
262 lines
13 KiB
Go
262 lines
13 KiB
Go
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
|
|
}
|