Count a bus module's running build as offering bus-state while its newer build waits for a yes (ADR 0614 §5, A-352)
mesh/delivery merged: source.main moved to novox/mesh-delivery@93e222a8a8b6
mesh/delivery-group group feat/47-G-join12 merged: every member is merged
mesh/check checking bc8126efefa9 on 93e222a8a8b6 | novox/mesh-delivery#214 in group feat/47-G-join12

A have in phase waiting-yes keeps the build that still runs and that build's health (the node-engine writes the
running fingerprint and composition, and its check's health), and that build serves the bus state. So the
bus-state rule no longer holds a consumer that needs none of the newer build's forms: live, zfs on ace waited
while nats on novox waited for a yes with its old build running. A consumer of a form only the newer build reads
still waits by its kind-4 edge (§50), unchanged.

Builds: ADR 0614 §5 and §2, A-352

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
2026-11-13 13:11:28 +01:00
parent 8d40d90950
commit bc8126efef
2 changed files with 14 additions and 7 deletions
@@ -1391,13 +1391,18 @@ func aWantWaitsOnTheVerbEdgesTheGraphKeeps(t *testing.T) {
}
}
// ADR 0614 §5: bus-state counts as offered only from a healthy build on a node present; the bus module's build on an
// offline node offers nothing, and one whose manifest is not read counts its trunk manifest's offer.
// offline node offers nothing, and one whose manifest is not read counts its trunk manifest's offer. A-352: a have waiting
// for a person's yes on a newer build names the build that still runs, healthy, and it offers bus-state too.
bus := map[string]catalogue.Manifest{"bus": {Module: "bus", Provides: []catalogue.Offer{{Name: graph.BusState, Scope: catalogue.ScopeMesh}}}}
for _, online := range []bool{false, true} {
offering := p.offering(bus, assign.Nodes{Online: map[string]bool{"n2": online},
Haves: map[string]map[string]nodeengine.HaveByNodeModuleValue{"n2": {"bus": at(fp("2"))}}})
if offering(graph.BusState) != online {
t.Errorf("with the bus module's node online %v, bus-state offered %v", online, offering(graph.BusState))
for _, phase := range []string{nodeengineclient.PhaseRunning, nodeengineclient.PhaseWaitingYes} {
h := at(fp("2"))
h.Phase = phase
offering := p.offering(bus, assign.Nodes{Online: map[string]bool{"n2": online},
Haves: map[string]map[string]nodeengine.HaveByNodeModuleValue{"n2": {"bus": h}}})
if offering(graph.BusState) != online {
t.Errorf("with the bus module's node online %v in phase %s, bus-state offered %v", online, phase, offering(graph.BusState))
}
}
}
}
@@ -59,7 +59,9 @@ func (p *passer) served(g graph.Graph, n assign.Nodes) func(string) bool {
}
// offering is, for this pass, whether a build whose manifest offers a provision runs healthy on a node present (ADR 0603
// §5, ADR 0614 §5): every running healthy have on an online node, its build's manifest read as servesOf reads it. A
// §5, ADR 0614 §5): every running healthy have on an online node, its build's manifest read as servesOf reads it. A have
// waiting for a person's yes on a newer build counts too: it names the build that still runs and that build's health, and
// that build serves what it offers (A-352); a consumer needing the newer build's forms waits by its kind-4 edge instead. A
// healthy build whose manifest cannot be read counts what its module's trunk manifest offers, so a build read late never
// holds a consumer for good.
func (p *passer) offering(trunk map[string]catalogue.Manifest, n assign.Nodes) func(string) bool {
@@ -69,7 +71,7 @@ func (p *passer) offering(trunk map[string]catalogue.Manifest, n assign.Nodes) f
continue
}
for module, h := range haves {
if h.Phase != nodeengineclient.PhaseRunning || h.Health != nodeengineclient.HealthOK {
if h.Phase != nodeengineclient.PhaseRunning && h.Phase != nodeengineclient.PhaseWaitingYes || h.Health != nodeengineclient.HealthOK {
continue
}
m, ok := p.manifestOf(module, h.Fp)