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:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user