Key each provision with the plan entry it is composed from

mesh-provisioner keys provisions from a consumer's wanted and newest builds
but writes the wanted build's plan alone, so a requirement the newest build
adds was never bound. Each key now carries its plan entry, and a key ahead of
its want says in its why that no plan names it yet and its provider binds it
from the entry (issue 1126 review 1).

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
2026-11-02 12:29:56 +01:00
parent ecc7621274
commit fda81c129e
2 changed files with 47 additions and 4 deletions
@@ -194,14 +194,20 @@ func (h *Holder) ComposeNode(ctx context.Context, node string) (out Composed, er
lap("plans written")
needed := map[string]provisions.Provision{}
elsewhere := map[string]provisions.Provision{}
// ahead are the keys only the newest build of their consumer requires: the plan written, its wanted build's, names
// them not yet, and the provider binds them from the entry they carry (issue 1126).
ahead := map[string]string{}
vaultNode := h.vaultNode()
for _, plans := range []map[string]catalogue.Plan{atWants, newest} {
for i, plans := range []map[string]catalogue.Plan{atWants, newest} {
for module, plan := range plans {
for _, p := range plan.Provisions {
consumer := provisions.End{Module: module, Node: node}
provider := provisions.End{Module: p.ProviderModule, Node: p.Provider}
// The key carries the plan entry it is composed from (issue 1126): the wanted build's where its plan names
// it, the newest's otherwise, so its provider binds it whichever plan the seat keeps.
entry := p
pv := provisions.Provision{Provision: p.Provision, Local: p.Local, Consumer: consumer, Provider: provider,
DependsOn: provisions.DependsOn(consumer, p, h.providerPlan(p, node, atWants))}
DependsOn: provisions.DependsOn(consumer, p, h.providerPlan(p, node, atWants)), Plan: &entry}
key, err := pv.Key()
if err != nil {
out.Problems = append(out.Problems, err.Error())
@@ -209,6 +215,9 @@ func (h *Holder) ComposeNode(ctx context.Context, node string) (out Composed, er
}
if _, seen := needed[key]; !seen {
needed[key] = pv
if i == 1 {
ahead[key] = module
}
}
// Its credential is a provision of its own, the vault's (clause 6): on the consumer's node for a value of the
// consumer's own, on the provider's for the provider's one shared value.
@@ -237,7 +246,7 @@ func (h *Holder) ComposeNode(ctx context.Context, node string) (out Composed, er
}
}
h.judgeElsewhere(elsewhere, &out)
h.judgeNode(node, needed, heldAtWants, &out)
h.judgeNode(node, needed, ahead, heldAtWants, &out)
lap("provisions judged")
return out, nil
}
@@ -468,7 +477,8 @@ func (h *Holder) writePlans(node string, plans map[string]catalogue.Plan, held m
// judgeNode brings the node's provision keys to what is needed and what each provider said. A key of a module held on
// the node (heldModules) that its composition no longer names is left as it is: not composed is not unneeded.
func (h *Holder) judgeNode(node string, needed map[string]provisions.Provision, heldModules map[string]string, out *Composed) {
func (h *Holder) judgeNode(node string, needed map[string]provisions.Provision, ahead map[string]string,
heldModules map[string]string, out *Composed) {
held := h.held(node)
for key, have := range held {
if _, isNeeded := needed[key]; !isNeeded && have.Consumer.Node == node {
@@ -497,6 +507,15 @@ func (h *Holder) judgeNode(node string, needed map[string]provisions.Provision,
next.Reference = have.Reference
}
h.outcome(key, &next)
if module, isAhead := ahead[key]; isAhead && next.State == provisions.Asked {
// The reason the provider's helper says too: no plan names it yet, and it binds it from this key's entry.
why := "no plan names it yet: the newest build of " + module + " requires it and its wanted build does " +
"not, so its provider binds it from this key's plan entry"
if next.Why != "" {
why += "; " + next.Why
}
next.Why = why
}
case have.State == provisions.Retiring:
next = have
if next.Ask == "" && next.Warrant == nil {
@@ -540,3 +540,27 @@ func TestAProvidersOutcomeIsReadPerOffer(t *testing.T) {
}
}
}
// Issue 1126 review 1 (mcp-endpoint asked on ace and shanks, 2026-11-02 11:56): a requirement the consumer's newest build
// adds is keyed before its want, and the plan the seat keeps is its wanted build's, which names it not; the key carries
// the plan entry it was composed from, so the provider binds it, and its why says no plan names it yet. A key its
// wanted plan names carries that plan's entry.
func TestAKeyAheadOfItsWantCarriesItsPlanEntryAndSaysWhy(t *testing.T) {
m := newMesh()
m.atWant["g14"] = map[string]catalogue.Plan{"gitea": planOf(pgDB)}
ahead := oidc
ahead.As = "gitea-g14"
m.atNewest["g14"] = map[string]catalogue.Plan{"gitea": planOf(pgDB, ahead)}
m.compose(t, "g14")
kc := m.state(t, "g14.gitea.oidc-client")
if kc.Plan == nil || kc.Plan.Provision != "oidc-client" || kc.Plan.ProviderModule != "keycloak" || kc.Plan.As != "gitea-g14" {
t.Errorf("the key ahead of its want carries %+v", kc.Plan)
}
if kc.State != provisions.Asked || !strings.Contains(kc.Why, "no plan names it") {
t.Errorf("the key ahead of its want: %s %q", kc.State, kc.Why)
}
if pg := m.state(t, "g14.gitea.postgres-database"); pg.Plan == nil || pg.Plan.ProviderModule != "postgres" ||
strings.Contains(pg.Why, "no plan names it") {
t.Errorf("the planned key: %+v", pg)
}
}