What answers a requirement is applied before what asked for it

The host does not sort, so the order written here is the order a machine
applies. Selection walks outward from what was assigned, which puts a consumer
before the thing it pulled in — and a service that reads a file another module
writes then starts before the file exists.

It fails, and the next reconcile fixes it. That is the worst shape a fault can
take: what gets remembered is that it works, and nobody looks again. It is
04-ISSUES/013 one level up from where that was found — there, the mesh's own
computed files came after a module's resources; here, a whole module comes
after the one that needed it.

Nothing had hit it because no module until now both required something with
resources of its own and had a resource depending on it. Writing the resolver
module was what made it reachable, and it would have shown up as dnsmasq
failing once on every fresh machine and working ever after.

Unrelated modules keep the order selection gave them — assigned first, then
what they pulled in. That order is meaningful, and reshuffling it would make
every declaration's diff unreadable for no gain.

Two modules requiring each other are both applied rather than refused: a cycle
is not a machine that cannot work, and refusing would make a cooperating pair
impossible to assign.
This commit is contained in:
2026-08-31 12:54:27 +02:00
parent bff893f8af
commit 68b0d0c10a
2 changed files with 142 additions and 1 deletions
+66 -1
View File
@@ -422,7 +422,7 @@ func Resolve(catalogue map[string]Manifest, assigned []string, node Node, world
}
resolution := Resolution{Node: node.Name, At: node.At, Because: because, Needs: needs}
for _, n := range order {
for _, n := range providersFirst(order, catalogue) {
resolution.Modules = append(resolution.Modules, catalogue[n])
}
@@ -569,3 +569,68 @@ func FromAnywhere(names ...string) []Offer {
// ships and this package must not depend on the thing it resolves. The name being wrong would
// show up as a refusal naming a module nobody can assign, which a test checks.
const meshNetwork = "networking"
// providersFirst orders a node's modules so that what answers a requirement comes before what
// asked for it.
//
// **The host does not sort** (novox/hq ADR 0005), so the order written here is the order a machine
// applies. Selection walks outward from what was assigned, which puts a consumer before the thing
// it pulled in — and a module whose service reads a file another module writes then starts before
// the file exists.
//
// It fails, and the next reconcile fixes it. That is the worst shape a fault can take: what gets
// remembered is that it works, and nobody looks again
// ([04-ISSUES/013](novox/hq)). This is the same fault one level up from where that was found.
//
// **Stable where nothing requires anything.** Modules with no relation keep the order selection
// gave them, because that order is meaningful — assigned first, then what they pulled in — and a
// sort that reshuffled unrelated modules would make every declaration's diff unreadable.
func providersFirst(order []string, shelf map[string]Manifest) []string {
// Who offers what, among the modules actually chosen.
offeredBy := map[string]string{}
for _, name := range order {
for _, offered := range shelf[name].Offers() {
if _, taken := offeredBy[offered]; !taken {
offeredBy[offered] = name
}
}
}
needs := map[string][]string{}
for _, name := range order {
for _, want := range shelf[name].Wants() {
provider, known := offeredBy[want]
if !known || provider == name {
continue
}
needs[name] = append(needs[name], provider)
}
}
var out []string
placed := map[string]bool{}
var place func(string, map[string]bool)
place = func(name string, visiting map[string]bool) {
if placed[name] {
return
}
if visiting[name] {
// Two modules requiring each other. Left in the order selection gave them rather
// than refused: a cycle here is not a machine that cannot work — both are applied,
// and one of them starts before the other is ready and settles on the next pass.
// Refusing would make a pair of modules that cooperate impossible to assign.
return
}
visiting[name] = true
for _, provider := range needs[name] {
place(provider, visiting)
}
delete(visiting, name)
placed[name] = true
out = append(out, name)
}
for _, name := range order {
place(name, map[string]bool{})
}
return out
}