B0's shape wins: the running checker asks and records facts, the checking program decides. The registry's types win: the rules and the program read the generated checker contract and the domain SDK, and no mesh-sdk delivery import is left. The registry's series, renames and unparsed seat definitions become facts with their refusals as rules; the tagged versions ride on the case. The program reads the heads' seat definitions through sdk/seatdefs and the running checker lays them over its reader's list by seat, reading no file of the heads: the reader's list is larger than a run input's bound. Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
117 lines
4.1 KiB
Go
117 lines
4.1 KiB
Go
package check
|
|
|
|
import (
|
|
"cmp"
|
|
"context"
|
|
"fmt"
|
|
"maps"
|
|
"slices"
|
|
"strings"
|
|
|
|
"git.novox.be/novox/mesh-delivery/sdk/composer"
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
|
|
"mesh-delivery-checker/rules"
|
|
|
|
"git.novox.be/novox/mesh-delivery/sdk/graph"
|
|
)
|
|
|
|
// The module graph of a change (novox/hq ADR 0603 §2, ADR 0614 §6): the running checker gathers its ordering edges, the
|
|
// checking program judges whether it has a cycle (rules.JudgeGraph, ADR 0656 §5). The graph is worked out from the
|
|
// catalogue as the change leaves it (every trunk manifest, each one a pull request touches replaced by its candidate's)
|
|
// and as the trunk has it, with the contract's ManifestGraph, the way the indexer works out its graph's edges; the facts
|
|
// carry only each edge's two modules and its kind.
|
|
|
|
// ManifestReader reads a kept manifest by its digest (the indexer's manifests state).
|
|
type ManifestReader interface {
|
|
Manifest(ctx context.Context, digest string) (catalogue.Manifest, error)
|
|
}
|
|
|
|
// gatherGraph records the change's and the trunk's module graph edges as a fact; none when the case touches no manifest
|
|
// or nothing reads manifests. A change's manifest that cannot be read fails the check, said, never passes it; a trunk's
|
|
// that cannot be read leaves the trunk's graph unread, so no cycle is the trunk's.
|
|
func (st *state) gatherGraph(ctx context.Context) {
|
|
if st.j.Manifests == nil || st.j.Read == nil || st.narrowTry() {
|
|
return
|
|
}
|
|
req, touched := judgeRequest(st.j.Manifests.ManifestDigests(), st.c.Pulls)
|
|
if len(touched) == 0 {
|
|
return
|
|
}
|
|
read := func(digests map[string]composer.Manifest) (map[string]catalogue.Manifest, error) {
|
|
out := map[string]catalogue.Manifest{}
|
|
for _, module := range slices.Sorted(maps.Keys(digests)) {
|
|
m, err := st.j.Read.Manifest(ctx, digests[module].Digest)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("the manifest %s of %s: %w", digests[module].Digest, module, err)
|
|
}
|
|
out[module] = m
|
|
}
|
|
return out, nil
|
|
}
|
|
change, err := read(req.Cases[0].Manifests)
|
|
if err != nil {
|
|
st.fail(goModule, "the change's module graph cannot be worked out, so no cycle can be ruled out: "+err.Error())
|
|
return
|
|
}
|
|
g := rules.Graph{}
|
|
changed := orderingEdges(graph.OfManifests(change))
|
|
trunk := map[[2]string]string{}
|
|
if before, err := read(req.Cases[1].Manifests); err == nil {
|
|
g.Trunk = true
|
|
trunk = orderingEdges(graph.OfManifests(before))
|
|
}
|
|
for _, pair := range slices.SortedFunc(maps.Keys(changed), cmpPair) {
|
|
e := rules.Edge{From: pair[0], To: pair[1], Kind: changed[pair]}
|
|
if _, both := trunk[pair]; !both && g.Trunk {
|
|
e.Only = rules.EdgeChange
|
|
}
|
|
g.Edges = append(g.Edges, e)
|
|
}
|
|
for _, pair := range slices.SortedFunc(maps.Keys(trunk), cmpPair) {
|
|
if _, both := changed[pair]; !both {
|
|
g.Edges = append(g.Edges, rules.Edge{From: pair[0], To: pair[1], Kind: trunk[pair], Only: rules.EdgeTrunk})
|
|
}
|
|
}
|
|
st.facts = append(st.facts, rules.Step{Graph: &g})
|
|
}
|
|
|
|
func cmpPair(a, b [2]string) int {
|
|
return cmp.Or(strings.Compare(a[0], b[0]), strings.Compare(a[1], b[1]))
|
|
}
|
|
|
|
// orderingEdges are the graph's ordering edges (graph.Graph.Before) between its modules, each pair once with the kind it
|
|
// is first met by: a base, a provision or bus state awaited of another provider, or a verb the graph keeps served by
|
|
// another module (a base on itself is a cycle). A verb edge the graph leaves out (ADR 0614 §3), a toolchain and a package edge are none (§6).
|
|
func orderingEdges(g graph.Graph) map[[2]string]string {
|
|
out := map[[2]string]string{}
|
|
add := func(from, to, kind string) {
|
|
if _, ours := g[to]; !ours || (from == to && kind != rules.EdgeBase) {
|
|
return
|
|
}
|
|
if _, met := out[[2]string{from, to}]; !met {
|
|
out[[2]string{from, to}] = kind
|
|
}
|
|
}
|
|
for _, name := range slices.Sorted(maps.Keys(g)) {
|
|
n := g[name]
|
|
for _, b := range n.Bases {
|
|
add(name, b, rules.EdgeBase)
|
|
}
|
|
for _, a := range n.Awaits {
|
|
for _, p := range g.Providers(a) {
|
|
add(name, p, rules.EdgeAwaits)
|
|
}
|
|
}
|
|
for _, v := range n.Invokes {
|
|
if slices.Contains(n.LeftOut, v) {
|
|
continue
|
|
}
|
|
for _, s := range g.Servers(v) {
|
|
add(name, s, rules.EdgeInvokes)
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
}
|