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 (`.`): 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 ` or `tools `. 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. It // holds the three kept to the write itself and nothing else: the lease write's own delivery dropped every other entry // (ADR 0571 §13; issue 774), so its check runs with these alone in either phase. The third, 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{ {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}, } // 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 `.` 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 }