AS14 (a): an assignment entry held for want of a provider names the candidates

A requirement neither bound nor pinned points to the provider on the
consumer's node, else the only provider; otherwise the entry carries the
new field held (seat mesh-wants' AssignmentEntry, generated by seatgen),
naming each placed provider as a candidate, and nothing is guessed. The
named seat's holder and the contribution edge wait for their design line.

Builds: design 47 G §357; lead answer A-167

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
2026-11-12 05:54:38 +01:00
parent 00c888546a
commit f86ccc378c
5 changed files with 145 additions and 5 deletions
@@ -58,13 +58,16 @@ type placedAt struct {
entry meshwants.AssignmentEntry
langs map[string]bool
acts []actingRule
// provides is what its catalog entry provides; open its requirements its placement neither binds nor its node pins.
provides, open []string
}
type actingRule struct{ seat, step, on, bound string }
// Compute is the assignment graph revision of in: one entry per module placed on a node whose branch's catalog graph
// holds it, its version the build the placement names or else the catalog entry's fingerprint (design 47 lines 909-914),
// with the edges of provisioning to the providers its placement binds or its node pins, and of acting from each acting
// with the edges of provisioning to the providers its placement binds or its node pins, else the provider on its node or
// the only provider (an entry held naming the candidates where none is), and of acting from each acting
// part's version to each version its rule names (design 47 *An acting part's new version acts first*: "the assigner
// gives an edge from the acting part's version to each version its rule names").
func Compute(in Inputs) Computed {
@@ -115,6 +118,7 @@ func Compute(in Inputs) Computed {
for _, r := range ce.Acts {
p.acts = append(p.acts, actingRule{seat: r.Seat, step: r.Step, on: r.On, bound: r.Bound})
}
p.provides = ce.Provides
for _, r := range ce.Requires {
prov, ok := m.Bindings[r]
if !ok {
@@ -122,12 +126,42 @@ func Compute(in Inputs) Computed {
}
if ok {
p.entry.Edges = append(p.entry.Edges, meshwants.Edge{Kind: EdgeProvisioning, Node: prov.Node, Module: prov.Module, Provision: r})
} else {
p.open = append(p.open, r)
}
}
placed = append(placed, p)
branches[branch] = true
}
}
// A requirement neither bound nor pinned points, by design 27's order (design 47 G §357), to the provider on the
// consumer's node, else to the only provider; otherwise the entry is held, naming the candidates, and nothing is guessed.
for _, p := range placed {
for _, r := range p.open {
var here, all []*placedAt
for _, q := range placed {
if q != p && slices.Contains(q.provides, r) {
all = append(all, q)
if q.node == p.node {
here = append(here, q)
}
}
}
switch {
case len(here) == 1:
p.entry.Edges = append(p.entry.Edges, meshwants.Edge{Kind: EdgeProvisioning, Node: here[0].node, Module: here[0].placed.Module, Provision: r})
case len(here) == 0 && len(all) == 1:
p.entry.Edges = append(p.entry.Edges, meshwants.Edge{Kind: EdgeProvisioning, Node: all[0].node, Module: all[0].placed.Module, Provision: r})
default:
h := meshwants.Held{Provision: r}
for _, q := range all {
h.Candidates = append(h.Candidates, meshwants.Edge{Kind: EdgeProvisioning, Node: q.node, Module: q.placed.Module, Fp: q.entry.Fp, Provision: r})
}
sortEdges(h.Candidates)
p.entry.Held = append(p.entry.Held, h)
}
}
}
at := map[string]*placedAt{}
for _, p := range placed {
at[p.node+"/"+p.placed.Module] = p
@@ -91,11 +91,16 @@ func TestTheAssignmentGraphIsComputedFromItsInputsAndKept(t *testing.T) {
if fmt.Sprint(webA.Edges) != fmt.Sprint(want) {
t.Errorf("web on anchor's edges:\n got %+v\nwant %+v", webA.Edges, want)
}
// laptop's web: its own node's node-engine composes it, the build module placed once builds it, and no binding or pin
// names a provider there.
if e := webL.Edges; len(e) != 2 || e[0].Module != "build-go" || e[0].Node != "anchor" || e[1].Module != "node-engine" || e[1].Node != "laptop" {
// laptop's web: its own node's node-engine composes it, the build module placed once builds it, and with no binding or
// pin its database is the only provider's (design 47 G §357).
if e := webL.Edges; len(e) != 3 || e[0].Module != "build-go" || e[0].Node != "anchor" || e[1].Module != "node-engine" || e[1].Node != "laptop" ||
e[2] != (meshwants.Edge{Kind: EdgeProvisioning, Node: "anchor", Module: "postgres", Fp: "src2:p1", Provision: "database"}) {
t.Errorf("web on laptop's edges: %+v", e)
}
// Its secrets no placed module provides: held on both nodes, naming no candidate.
if h := webA.Held; len(h) != 1 || h[0].Provision != "secrets" || len(h[0].Candidates) != 0 {
t.Errorf("web on anchor is held on %+v", h)
}
if e := byNode["anchor/postgres"].Edges; len(e) != 1 || e[0].Module != "node-engine" {
t.Errorf("postgres builds no Go, so only the node-engine acts on it: %+v", e)
}
@@ -149,6 +154,63 @@ func TestTheAssignmentGraphIsComputedFromItsInputsAndKept(t *testing.T) {
}
}
// Design 47 G §357 and A-167: a requirement neither bound nor pinned points to the provider on the consumer's node, else
// to the only provider; otherwise the entry is held, naming the candidates, and nothing is guessed.
func TestARequirementPointsByTheOrderOrIsHeld(t *testing.T) {
h := half("main", 3,
meshcatalog.CatalogEntry{Module: "kv", Fp: "src2:k1", Provides: []string{"cache"}},
meshcatalog.CatalogEntry{Module: "web", Fp: "src2:w1", Requires: []string{"cache"}})
for _, c := range []struct {
name string
kv []string // the nodes kv is placed on
pin bool // web's node pins kv on laptop
edge string // the node of the provisioning edge, "" for none
held string // the candidates' nodes of the hold, "-" for no hold
}{
{name: "the provider on the consumer's node", kv: []string{"anchor", "laptop"}, edge: "anchor", held: "-"},
{name: "the only provider", kv: []string{"laptop"}, edge: "laptop", held: "-"},
{name: "a pin before the node's own", kv: []string{"anchor", "laptop"}, pin: true, edge: "laptop", held: "-"},
{name: "two providers elsewhere: held", kv: []string{"laptop", "server"}, held: "laptop,server"},
{name: "none: held naming none", held: ""},
} {
t.Run(c.name, func(t *testing.T) {
pl := map[string]mma.Assignments{"anchor": {Node: "anchor", Modules: []mma.PlacedModule{{Module: "web"}}}}
if c.pin {
pl["anchor"] = mma.Assignments{Node: "anchor", Modules: pl["anchor"].Modules, Pins: map[string]mma.Provider{"cache": {Node: "laptop", Module: "kv"}}}
}
for _, n := range c.kv {
a := pl[n]
a.Node, a.Modules = n, append(a.Modules, mma.PlacedModule{Module: "kv"})
pl[n] = a
}
out := Compute(Inputs{Halves: map[string]meshcatalogclient.Half{"main": h}, Placements: pl, FirstBranch: func(string) string { return "main" }})
var web meshwants.AssignmentEntry
for _, ref := range out.Revision.Entries {
if ref.Node == "anchor" && ref.Module == "web" {
web = out.Entries[ref.Digest]
}
}
edge := ""
for _, e := range web.Edges {
if e.Kind == EdgeProvisioning && e.Provision == "cache" {
edge = e.Node
}
}
held := "-"
if len(web.Held) == 1 && web.Held[0].Provision == "cache" {
var nodes []string
for _, cand := range web.Held[0].Candidates {
nodes = append(nodes, cand.Node)
}
held = strings.Join(nodes, ",")
}
if edge != c.edge || held != c.held || len(web.Held) > 1 {
t.Errorf("web points to %q, held on %q (%+v); want %q, %q", edge, held, web.Held, c.edge, c.held)
}
})
}
}
// Design 47 line 1385: a whole assignment graph revision of about 120 modules on four nodes is under the bus's 256 KiB
// value limit.
func TestARevisionOf120ModulesOnFourNodesFitsOneValue(t *testing.T) {
@@ -472,7 +472,9 @@ type AssignmentEntry struct {
Catalog string `json:"catalog"`
Edges []Edge `json:"edges,omitempty"`
// the fingerprint it is to run
Fp string `json:"fp"`
Fp string `json:"fp"`
// the requirements no step of the order resolves (design 47 G section 357): the entry is held, naming the candidates, and nothing is guessed
Held []Held `json:"held,omitempty"`
Module string `json:"module"`
Node string `json:"node"`
// the layout version
@@ -582,6 +584,13 @@ type Fill struct {
Store string `json:"store"`
}
// Held is one requirement an entry is held on, for want of a provider.
type Held struct {
// a provisioning edge to each placed provider of it, none chosen; none when no placed module provides it
Candidates []Edge `json:"candidates,omitempty"`
Provision string `json:"provision"`
}
// NodeEntryRef is a type of the contract.
type NodeEntryRef struct {
// the entry's digest, its object's name
@@ -825,6 +825,13 @@
"items": {
"$ref": "#/defs/Edge"
}
},
"held": {
"type": "array",
"description": "the requirements no step of the order resolves (design 47 G section 357): the entry is held, naming the candidates, and nothing is guessed",
"items": {
"$ref": "#/defs/Held"
}
}
},
"required": [
@@ -835,6 +842,25 @@
"catalog"
]
},
"Held": {
"type": "object",
"description": "one requirement an entry is held on, for want of a provider",
"properties": {
"provision": {
"type": "string"
},
"candidates": {
"type": "array",
"description": "a provisioning edge to each placed provider of it, none chosen; none when no placed module provides it",
"items": {
"$ref": "#/defs/Edge"
}
}
},
"required": [
"provision"
]
},
"NodeEntryRef": {
"type": "object",
"properties": {
@@ -143,6 +143,8 @@ export interface AssignmentEntry {
"edges"?: Edge[];
/** the fingerprint it is to run */
"fp": string;
/** the requirements no step of the order resolves (design 47 G section 357): the entry is held, naming the candidates, and nothing is guessed */
"held"?: Held[];
"module": string;
"node": string;
/** the layout version */
@@ -250,6 +252,13 @@ export interface Fill {
"store": string;
}
/** one requirement an entry is held on, for want of a provider */
export interface Held {
/** a provisioning edge to each placed provider of it, none chosen; none when no placed module provides it */
"candidates"?: Edge[];
"provision": string;
}
export interface NodeEntryRef {
/** the entry's digest, its object's name */
"digest": string;