Files
mesh-delivery/modules/mesh-delivery-checker/internal/check/graph_edges.go
T
jschoubben 2159375873 Merge the trunk: B0's facts and checking program carry the registry's steps (issue 1921, milestone 47/G D3-D11)
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
2026-11-07 02:57:21 +01:00

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
}