Keep a recorded module at the build a node runs, per node: Node.Kept and Node.KeptBuilds overlay that node's own resolution and the commit it carries, never another node's (review round 1, finding 1; mirrors plan.go keepRecorded and carriedOn). Both tests failed first; the suite is 1251 tests in 1.05 s

This commit is contained in:
2026-10-20 06:59:38 +00:00
parent 57d852e7da
commit 12319ce9dd
6 changed files with 93 additions and 7 deletions
+6 -1
View File
@@ -29,7 +29,7 @@ import (
// (ADR 0446 clause 2).
type Input struct {
// Catalogue is the shelf: every module's manifest by name (inv.Catalogue), as it is to be composed. A recorded
// module kept at the build a machine runs (keepRecorded) is already in it, put there by the caller.
// module kept at the build a machine runs (keepRecorded) is that node's Kept, applied to its own resolution only.
Catalogue map[string]catalogue.Manifest
// Nodes is every node the mesh has (inv.Nodes and inv.Overlays together, one row per node).
Nodes []Node
@@ -176,6 +176,11 @@ type Node struct {
SentBuilds map[string]string
SentKnown bool
Runs map[string]Run
// Kept is, by module, the manifest of a recorded or core module kept at the build this machine runs (plan.go keepRecorded:
// the caller reads it with recordedKept and ManifestAt), and KeptBuilds the commit of that build. They apply to this node's
// own resolution only, never to another node's, and a declaration carries the kept commit for the module.
Kept map[string]catalogue.Manifest
KeptBuilds map[string]string
// Contract is the contract of its node-engine's newest statement (inv.HealthOf); Stated is whether it ever stated one.
Contract int
Stated bool
+1 -1
View File
@@ -219,7 +219,7 @@ func (c *composer) onTheNetwork() []Node {
if len(n.Assigned) == 0 {
continue
}
got, err := catalogue.Resolve(c.in.Catalogue, n.Assigned,
got, err := catalogue.Resolve(c.shelfFor(n), n.Assigned,
catalogue.Node{Name: n.Name, Site: n.Place.Site, Capabilities: n.Capabilities},
catalogue.World{Unchecked: true, Holdings: c.in.Holdings})
if err != nil {
+4
View File
@@ -247,6 +247,10 @@ func (c *composer) carriedOn(node Node, plan catalogue.Resolution, leftOut map[s
continue
}
out[m.Module] = c.in.CurrentBuilds[m.Module]
// A module kept at the build the machine runs is carried at that build (plan.go carriedOn).
if kept, ok := node.KeptBuilds[m.Module]; ok && kept != "" {
out[m.Module] = kept
}
}
return out
}
+59
View File
@@ -323,3 +323,62 @@ func TestIssue263AnIdentityARealMachineNameOverflowsIsARefusal(t *testing.T) {
t.Errorf("withheld %+v", d.Withheld)
}
}
// Round 1, finding 1 (plan.go keepRecorded): a recorded module kept at the build a machine runs is that machine's own resolution
// and no other's; the commit carried for it is the kept one.
func TestAKeptModuleChangesOnlyTheResolutionOfTheNodeThatKeepsIt(t *testing.T) {
in := aSmallMesh(t)
in.Catalogue["tool"] = catalogue.Manifest{Module: "tool", Version: "2"}
for _, name := range []string{"anchor", "laptop"} {
n := nodeOf(t, &in, name)
n.Assigned = append(n.Assigned, "tool")
}
anchor := nodeOf(t, &in, "anchor")
anchor.Kept = map[string]catalogue.Manifest{"tool": {Module: "tool", Version: "1"}}
versionOn := func(in Input, node string) string {
t.Helper()
got, err := Resolve(in, node)
if err != nil {
t.Fatal(err)
}
for _, m := range got.Plan.Modules {
if m.Module == "tool" {
return m.Version
}
}
t.Fatalf("no tool on %s", node)
return ""
}
if v := versionOn(in, "anchor"); v != "1" {
t.Errorf("the node that keeps it runs version %s", v)
}
if v := versionOn(in, "laptop"); v != "2" {
t.Errorf("another node runs version %s: a kept build leaked", v)
}
if got := in.Catalogue["tool"].Version; got != "2" {
t.Errorf("the Input's catalogue was changed: %s", got)
}
// And the commit carried for it is the kept one.
c := newComposer(in)
plan := catalogue.Resolution{Modules: []catalogue.Manifest{{Module: "tool"}, {Module: "other"}}}
in.CurrentBuilds = map[string]string{"tool": "current", "other": "current-other"}
c = newComposer(in)
node := Node{KeptBuilds: map[string]string{"tool": "kept-commit"}}
if got := c.carriedOn(node, plan, nil); got["tool"] != "kept-commit" || got["other"] != "current-other" {
t.Errorf("carried builds are %v", got)
}
}
// And the rest of the mesh, as a node composes against it, offers what each other node's kept build offers, not the current one.
func TestTheRestOfTheMeshReadsEachOtherNodesKeptBuild(t *testing.T) {
in := threeNodes(t)
w, _, _, err := TheRestOfTheMesh(in, "laptop")
if err != nil || len(providers(w, "s3-bucket")) != 1 {
t.Fatalf("before: %v %v", providers(w, "s3-bucket"), err)
}
nodeOf(t, &in, "anchor").Kept = map[string]catalogue.Manifest{"objects": {Module: "objects", Version: "0"}}
w, _, _, err = TheRestOfTheMesh(in, "laptop")
if err != nil || len(providers(w, "s3-bucket")) != 0 {
t.Errorf("a node keeping a build that offers nothing still offers: %v %v", providers(w, "s3-bucket"), err)
}
}
+19 -2
View File
@@ -45,7 +45,7 @@ func (c *composer) planFor(nodeName string) (Resolved, error) {
if err != nil {
return Resolved{}, err
}
shelf := c.in.Catalogue
shelf := c.shelfFor(node)
var out Resolved
// The credential for each thing this node takes from elsewhere.
@@ -195,7 +195,7 @@ func (c *composer) resolution(nodeName string) (catalogue.Resolution, error) {
if err != nil {
return catalogue.Resolution{}, err
}
shelf := c.in.Catalogue
shelf := c.shelfFor(node)
world, _, _, err := c.theRestOfTheMesh(nodeName)
if err != nil {
return catalogue.Resolution{}, err
@@ -225,6 +225,23 @@ func (c *composer) resolution(nodeName string) (catalogue.Resolution, error) {
return resolved, nil
}
// shelfFor is the catalogue as one node composes its own set: the Input's, with each recorded or core module the node keeps at the
// build it runs put in place of the current one (plan.go keepRecorded). A copy: the Input's catalogue, and so every other node's
// resolution, is untouched.
func (c *composer) shelfFor(node Node) map[string]catalogue.Manifest {
if len(node.Kept) == 0 {
return c.in.Catalogue
}
shelf := make(map[string]catalogue.Manifest, len(c.in.Catalogue)+len(node.Kept))
for name, m := range c.in.Catalogue {
shelf[name] = m
}
for name, m := range node.Kept {
shelf[name] = m
}
return shelf
}
// licencesFor is what this node can be answered with by record, and what it was put on. A mesh with no licences at all
// is the ordinary case and not an error.
func (c *composer) licencesFor(node string) (map[string][]catalogue.Record, map[string]map[string]catalogue.Record) {
+4 -3
View File
@@ -71,6 +71,7 @@ func (c *composer) theRestOfTheMesh(exclude string) (catalogue.World, Passes, []
node catalogue.Node
assigned []string
pins map[string]catalogue.Chosen
shelf map[string]catalogue.Manifest
}
var others []candidate
// Every machine's assignments, before the holdings are read by any resolution (novox/hq ADR 0449 clause 4): a seat
@@ -94,14 +95,14 @@ func (c *composer) theRestOfTheMesh(exclude string) (catalogue.World, Passes, []
others = append(others, candidate{
node: catalogue.Node{Name: n.Name, Site: n.Place.Site, Capabilities: n.Capabilities,
At: onNetwork[n.Name], PublicDomain: n.PublicDomain},
assigned: theirs, pins: n.Pins})
assigned: theirs, pins: n.Pins, shelf: c.shelfFor(n)})
}
var left []LeftOut
offered := map[string][]catalogue.Provider{}
var firstHeld []catalogue.Held
for _, o := range others {
got, err := catalogue.Resolve(shelf, o.assigned, o.node, catalogue.World{Unchecked: true, Holdings: holdings})
got, err := catalogue.Resolve(o.shelf, o.assigned, o.node, catalogue.World{Unchecked: true, Holdings: holdings})
if err != nil {
// Said, not skipped: a machine dropped here offers nothing and holds nothing.
left = append(left, LeftOut{Node: o.node.Name, Err: err})
@@ -138,7 +139,7 @@ func (c *composer) theRestOfTheMesh(exclude string) (catalogue.World, Passes, []
// Each machine is resolved with its own pins, as its plan is.
theirs := world
theirs.Pinned = o.pins
got, err := catalogue.Resolve(shelf, o.assigned, o.node, theirs)
got, err := catalogue.Resolve(o.shelf, o.assigned, o.node, theirs)
if err != nil {
// Said only for the whole-mesh view: with one machine excluded, the view is partial by design.
if exclude == "" {