diff --git a/go/provider/provider.go b/go/provider/provider.go index e046861..8871ca0 100644 --- a/go/provider/provider.go +++ b/go/provider/provider.go @@ -370,6 +370,31 @@ func (p *Provider) passIn(ctx context.Context, in Inputs) ([]Outcome, error) { gated[key] = pr } } + // **A key no plan names is bound from the entry it carries** (issue 1126): the seat keys a consumer's provisions + // from its wanted and its newest builds, and keeps the plan at its wanted build alone, so a requirement the newest + // build adds has a key before any plan names it, and the want that waits for it waits on this provider. The plan + // wins where both say it. A key with no entry is said, never left without a word. + planned := map[string]bool{} + for _, b := range binds { + planned[b.Key()] = true + } + for _, key := range sortedKeys(gated) { + pr := gated[key] + if planned[key] || pr.State == provisions.Retiring || unknown[consumerOf(key)] { + continue + } + if pr.Plan == nil { + o := Outcome{Key: key, Status: Waiting, Why: "asked, but no plan names it and its key carries no plan entry, " + + "so whom it binds is not known; mesh-provisioner composes the entry"} + out = append(out, o) + p.say(key, o.Status+" "+o.Why) + continue + } + b := *pr.Plan + binds = append(binds, Entry{Provider: p.opts.Node, ProviderModule: p.opts.Module, Provision: pr.Provision, + Consumer: pr.Consumer.Node, Module: pr.Consumer.Module, Local: pr.Local, Contributes: b.Contributes, At: b.At, + From: b.From, As: b.As, Names: b.Names, CredentialForm: b.CredentialForm, Name: b.Name}) + } want := map[string]bool{} for _, b := range binds { key := b.Key() diff --git a/go/provider/provider_test.go b/go/provider/provider_test.go index eec9d1b..da8d7f4 100644 --- a/go/provider/provider_test.go +++ b/go/provider/provider_test.go @@ -955,3 +955,48 @@ func TestTheSeatsKeysSayWhenAProviderActsAndWhenItLetsGo(t *testing.T) { t.Errorf("gone %v, made %v: let go once, on the warrant, and made from the vault's value", m.gone, m.made) } } + +// Issue 1126 review 1 (mcp-endpoint asked on ace and shanks, 2026-11-02 11:56): a consumer's key composed from its newest +// build carries the plan entry it was composed from, though its wanted build's plan names no such provision; the +// provider binds it from the key and provides it, so the want that waits for it moves. A key of this provider that +// carries no entry and no plan names is said, never bound silently or left without a word. +func TestAKeyNoPlanNamesIsBoundFromTheEntryItCarries(t *testing.T) { + bus, m := newStandIn(), &maker{} + v := &vault{values: map[string]string{"pair/novox/postgres-database/ace/letta": "letta-secret-1"}} + // letta's wanted plan names nothing of postgres; its key, from its newest build, carries the entry. + planned(t, bus, "ace", "letta") + entry := catalogue.PlanProvision{Provision: "postgres-database", Provider: "novox", ProviderModule: "postgres", As: "mesh_ace_letta"} + pr := provisions.Provision{Provision: "postgres-database", Consumer: provisions.End{Module: "letta", Node: "ace"}, + Provider: provisions.End{Module: "postgres", Node: "novox"}, State: provisions.Asked, Plan: &entry} + raw, err := provisions.Encode(pr) + if err != nil { + t.Fatal(err) + } + _ = bus.Put(context.Background(), provisions.ProvisionsBucket, "ace.letta.postgres-database", raw) + // And umami's key carries none: asked, but no plan names it. + bare := provisions.Provision{Provision: "postgres-database", Consumer: provisions.End{Module: "umami", Node: "g14"}, + Provider: provisions.End{Module: "postgres", Node: "novox"}, State: provisions.Asked} + raw, _ = provisions.Encode(bare) + _ = bus.Put(context.Background(), provisions.ProvisionsBucket, "g14.umami.postgres-database", raw) + + out, err := postgres(bus, v, m).Pass(context.Background()) + if err != nil { + t.Fatal(err) + } + if e := entryOf(t, bus, "ace.letta.postgres-database"); e.Consumer != "ace" || e.Module != "letta" || e.Refused != "" { + t.Errorf("the entry %+v", e) + } + said := map[string]Outcome{} + for _, o := range out { + said[o.Key] = o + } + if said["ace.letta.postgres-database"].Status != Answered { + t.Errorf("letta: %+v", said["ace.letta.postgres-database"]) + } + if o := said["g14.umami.postgres-database"]; o.Status != Waiting || !strings.Contains(o.Why, "no plan names it") { + t.Errorf("umami: %+v", o) + } + if bus.get(BucketOf("postgres"), "g14.umami.postgres-database") != nil { + t.Error("a key with no entry and no plan was bound") + } +} diff --git a/go/provisions/provisions.go b/go/provisions/provisions.go index 376b9bc..c7ee20d 100644 --- a/go/provisions/provisions.go +++ b/go/provisions/provisions.go @@ -214,9 +214,13 @@ type Provision struct { Why string `json:"why,omitempty"` Reference *Reference `json:"reference,omitempty"` // Ask is a retiring provision's ask of the operator; Warrant the answer, once given. - Ask string `json:"ask,omitempty"` - Warrant *Warrant `json:"warrant,omitempty"` - At time.Time `json:"at"` + Ask string `json:"ask,omitempty"` + Warrant *Warrant `json:"warrant,omitempty"` + // Plan is the plan entry the key was composed from, at the consumer's wanted build or its newest (issue 1126): the + // provider binds the consumer from it, so a requirement the newest build adds is provided before the want that + // waits for it moves, though the plan the seat keeps at the wanted build names it not yet. + Plan *catalogue.PlanProvision `json:"plan,omitempty"` + At time.Time `json:"at"` } // Key is the provision's key.