From 5a7ed56f618d72fe2063e2da2761c66b0f293741 Mon Sep 17 00:00:00 2001 From: jochens Date: Thu, 1 Oct 2026 17:34:17 +0200 Subject: [PATCH] The first pass does not refuse two providers beside the consumer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #195 refused two modules beside a consumer that both answer a bound provision — in every pass. The first pass exists only to answer what a node offers, and a refusal there makes the machine vanish from every other node's world (resolve.go says so for its sibling case): with novox refused for its own acme-ca, ace's plan lost the vault and the identity provider and read "nothing in this mesh provides secret". Seen live within minutes of the rollout. The second pass refuses it, where it is asked, as before. --- internal/catalogue/pin_names_module_test.go | 10 ++++++++++ internal/catalogue/resolve.go | 9 +++++++++ 2 files changed, 19 insertions(+) diff --git a/internal/catalogue/pin_names_module_test.go b/internal/catalogue/pin_names_module_test.go index ad7c893..8191e5a 100644 --- a/internal/catalogue/pin_names_module_test.go +++ b/internal/catalogue/pin_names_module_test.go @@ -103,3 +103,13 @@ func TestAPinSettlesTwoProvidersBesideTheConsumer(t *testing.T) { t.Fatalf("no need for acme-ca was created: %+v", got.Needs) } } + +func TestTheFirstPassDoesNotRefuseTwoProvidersBesideTheConsumer(t *testing.T) { + // The first pass answers only what a node offers. Refused there, the node vanishes from every + // other node's world — and the whole mesh loses its vault for an ambiguity one machine has to + // settle. The second pass is where it is refused, and the test above proves it is. + if _, err := Resolve(issuerShelf(), []string{"route-proxy", "public-acme", "step-ca"}, reachable(), + World{Unchecked: true}); err != nil { + t.Fatalf("the first pass refused what only the second may: %v", err) + } +} diff --git a/internal/catalogue/resolve.go b/internal/catalogue/resolve.go index 74be21e..a33f761 100644 --- a/internal/catalogue/resolve.go +++ b/internal/catalogue/resolve.go @@ -342,6 +342,15 @@ func Resolve(catalogue map[string]Manifest, assigned []string, node Node, world // Two modules on this machine answer it. Taking whichever a map walk met first // was the rule until novox/hq #258 — random, per plan — and the same stance as // across machines applies: ambiguity is refused, never resolved by picking. + // + // **Not in the first pass.** That pass exists only to answer *what does this node + // offer*, and refusing there makes the machine vanish rather than report a problem + // (the sibling case below says why): every other node then loses what this one + // provides — the vault, the identity provider — and refuses for a fault that is + // this node's to settle. The second pass refuses it properly, where it is asked. + if world.Unchecked { + continue + } c, pinned := world.Pinned[want] if !pinned || c.Node != node.Name || !oneOf(matter, c.Module) { reported[want] = true -- 2.54.0