Merge pull request 'One rule for which provisions a module waits for, so the assigner, the plans and the provisioner agree and grafana gets its route (issue 774)' (#143) from fix/one-rule-for-provisions into main

This commit was merged in pull request #143.
This commit is contained in:
2026-10-30 22:33:49 +00:00
7 changed files with 266 additions and 6 deletions
+5 -3
View File
@@ -1065,9 +1065,11 @@ func (r Resolution) compose(with Rendering, owner map[string]string,
if err != nil {
return nil, err
}
// **What the mesh calls the module through each provision it requires** (issue 792): the names its binding
// file carries, composed by the same call, handed to the provider through the plan's entry.
if _, ok := handed.plans[m.Module]; ok && handing != nil {
// **A plan for what the seat keys of it all the same** (catalogue.Keyed, issue 774): a module not handed whose
// provider's helper answers a provision of it, its route the commonest, has that provision in a plan of its own,
// so the seat keys it and the helper hears it.
keyedOnly := handing == nil && handed.keyed(r.Node, m, r.Needs)
if _, ok := handed.plans[m.Module]; ok && (handing != nil || keyedOnly) {
for _, to := range requirementsContributedTo(m) {
own, err := r.ownNames(m, to, with.Settings[m.Module])
if err != nil {
+39 -3
View File
@@ -129,11 +129,12 @@ type PlanProvision struct {
// provisionsOf is, from a resolution's needs, each provision module requires and its provider, by provision then
// local name. A need answered by a record (a licence) or by no module names no provider on a machine and is left
// out: the vault has no provider's value to make for it.
func provisionsOf(needs []Needed, module, as string, contributes map[string]map[string]any,
func provisionsOf(needs []Needed, module string, handed bool, as string, contributes map[string]map[string]any,
named map[string]bool) []PlanProvision {
var out []PlanProvision
for _, n := range needs {
if n.For != module || n.ByRecord || n.Module == "" || n.From == "" {
// **The one rule** (Keyed, issue 774): what the seat keys and the want waits for.
if n.For != module || !Keyed(handed, n) {
continue
}
p := PlanProvision{Provision: n.Name, Local: n.Local, Provider: n.From, ProviderModule: n.Module,
@@ -589,6 +590,40 @@ type handedFiles struct {
required map[string]map[string]bool
// names is the names composed for each handed module through each requirement it contributes to (issue 792).
names map[string]map[string]map[string]string
// keyedOnly is each module with a plan only for what Keyed keys of a consumer not handed: a provision its
// provider's helper answers (issue 774), such as a route.
keyedOnly map[string]bool
}
// keyed starts the plan of a module begin started none for, when the composition keys any of its needs as a consumer
// not handed (Keyed, issue 774): one whose provider's helper answers a provision of it (grafana's route). A module
// handed that keeps no template or item (a held one's stub, issue 1126) is not handed here either: what it keeps
// stays the controller's but for what a helper answers. Its plan names provisions alone: no templates, files or
// secrets. Answers whether it started one.
func (h *handedFiles) keyed(node string, m Manifest, needs []Needed) bool {
if _, started := h.plans[m.Module]; started {
return false
}
if !slices.ContainsFunc(needs, func(n Needed) bool { return n.For == m.Module && Keyed(false, n) }) {
return false
}
if h.identities == nil {
h.identities = map[string]string{}
h.required = map[string]map[string]bool{}
}
h.identities[m.Module] = ConsumerIdentity(node, IdentitySource(m.Slug, m.Module))
h.plans[m.Module] = Plan{}
if h.keyedOnly == nil {
h.keyedOnly = map[string]bool{}
}
h.keyedOnly[m.Module] = true
if len(m.Contributes) > 0 {
if h.contributes == nil {
h.contributes = map[string]map[string]map[string]any{}
}
h.contributes[m.Module] = m.Contributes
}
return true
}
// name records the names composed for a handed module through one requirement, as its binding file carries them.
@@ -768,7 +803,8 @@ func (h *handedFiles) finished(needs []Needed) map[string]Plan {
for module, plan := range h.plans {
// **Every provision it requires, with its provider** (issue 760); a credential the vault keeps for it alone
// is that provision's entry, with its name and file (issue 757).
provisions := provisionsOf(needs, module, h.identities[module], h.contributes[module], h.required[module])
provisions := provisionsOf(needs, module, !h.keyedOnly[module], h.identities[module], h.contributes[module],
h.required[module])
for _, credential := range h.provisions[module] {
i := slices.IndexFunc(provisions, func(p PlanProvision) bool {
return p.Provision == credential.Provision && p.Local == credential.Local
+67
View File
@@ -0,0 +1,67 @@
package catalogue
import "slices"
// The one rule of which provisions the seat mesh-provisioner keys and a want waits for (novox/hq ADR 0570 §1, §2, §4;
// issue 774). The plan composition puts a need in a consumer's plan by it, the holder of mesh-provisioner keys what the
// plans name, and the assigner's rule 8 waits for what Awaited answers: three parts, one rule, so they cannot drift
// again (on 2026-10-30 the assigner waited for every `requires` while the plans named only some, and every new build of
// grafana, dnsmasq, mesh-knowledge, mesh-mail and the builders waited for a key nobody would make).
//
// A need a module answers (not a record, not nothing) is keyed when its consumer is handed to the provisioner, or the
// provider's offer says the provider helper answers it. A need of a consumer not handed whose provider's helper does
// not answer it is still answered by the controller's composition: it is neither keyed nor waited for.
// HandedByManifest is whether a module is handed to the provisioner as far as its manifest says (Rendering.Handing
// without the machine's own words, and begin's): marked moved, not a held module's stub, with a template its build
// kept or an item.
func (m Manifest) HandedByManifest() bool {
return m.Moved && m.Handed != nil && !m.Stubbed && (len(m.Handed.Templates) > 0 || len(m.Items) > 0)
}
// Keyed is whether a need is a provision of the seat mesh-provisioner: a key, an entry in its consumer's plan, and a
// wait of the consumer's want. consumerHanded is whether the composition hands the consumer to the provisioner.
func Keyed(consumerHanded bool, n Needed) bool {
if n.ByRecord || n.Module == "" || n.From == "" {
return false
}
return consumerHanded || n.AnsweredByHelper
}
// Awaited is, of what consumer requires (not optionally), each provision Keyed answers yes for, judged from manifests
// alone, as the assigner reads them: providers answers the modules providing a provision. A provision is a need when a
// provider offers it to the mesh, or one on the consumer's machine serves facts about it (Resolve: one answered on the
// machine with nothing served binds nothing). It is keyed when the consumer is handed, or every provider's offer is
// answered by the helper: where the providers disagree, which one answers is the composition's, and nothing is waited
// for that might never be keyed. Sorted, each once.
func Awaited(consumer Manifest, providers func(provision string) []Manifest) []string {
var out []string
for _, r := range consumer.Requires {
if consumer.OptionalRequires[r] || slices.Contains(out, r) {
continue
}
by := providers(r)
if len(by) == 0 {
continue
}
need, helper := false, true
for _, p := range by {
for _, o := range p.Provides {
if o.Name != r {
continue
}
if o.At() == ScopeMesh || len(p.Serves[r]) > 0 {
need = true
}
}
if !p.AnsweredByHelperFor(r) {
helper = false
}
}
if need && (consumer.HandedByManifest() || helper) {
out = append(out, r)
}
}
slices.Sort(out)
return out
}
+112
View File
@@ -0,0 +1,112 @@
package catalogue
import (
"reflect"
"slices"
"strings"
"testing"
)
// The one rule replays the live cases of 2026-10-30 (issue 774): what the assigner's rule 8 waits for is what the
// composition keys, judged from the manifests alone.
func TestAwaitedIsWhatTheCompositionKeys(t *testing.T) {
built := &Handed{Source: "store://t", Digest: "sha256:t", Templates: map[string]string{"c": "c"}}
offer := func(module, name string, scope string, helper bool, serves bool) Manifest {
o := Offer{Name: name, Scope: scope}
if helper {
o.AnsweredBy = AnsweredByHelper
}
m := Manifest{Module: module, Provides: []Offer{o}}
if serves {
m.Serves = map[string]map[string]any{name: {"port": 1}}
}
return m
}
gitea, distribution := offer("gitea", "forge-account", ScopeMesh, false, false), offer("distribution", "artifact-store", ScopeMesh, false, false)
goTools, nodeJS := offer("go", "go-toolchain", ScopeNode, false, false), offer("nodejs", "node-toolchain", ScopeNode, false, false)
keycloak, proxy := offer("keycloak", "oidc-client", ScopeMesh, false, false), offer("route-proxy", "route", ScopeMesh, true, false)
adapter := offer("route-adapter", "route", ScopeMesh, false, false)
ollama := offer("ollama", "llm", ScopeNode, false, true)
by := func(ms ...Manifest) func(string) []Manifest {
return func(p string) []Manifest {
var out []Manifest
for _, m := range ms {
if slices.Contains(m.Offers(), p) {
out = append(out, m)
}
}
return out
}
}
for _, c := range []struct {
name string
consumer Manifest
providers func(string) []Manifest
want []string
}{
{"a handed builder waits for what binds, not for a toolchain on its own machine (mesh-build-go on ace)",
Manifest{Module: "mesh-build-go", Moved: true, Handed: built,
Requires: []string{"forge-account", "go-toolchain", "artifact-store", "node-tools"}},
by(gitea, distribution, goTools), []string{"artifact-store", "forge-account"}},
{"node-toolchain alike (mesh-build-node)",
Manifest{Module: "mesh-build-node", Moved: true, Handed: built, Requires: []string{"node-toolchain", "forge-account"}},
by(gitea, nodeJS), []string{"forge-account"}},
{"a module not handed waits for its route, which the helper answers, and not for its login the controller still makes (grafana on ace)",
Manifest{Module: "grafana", Requires: []string{"route", "oidc-client", "influxdb-api"},
OptionalRequires: map[string]bool{"influxdb-api": true}},
by(keycloak, proxy), []string{"route"}},
{"where providers disagree on the helper, nothing is waited for that might never be keyed",
Manifest{Module: "grafana", Requires: []string{"route"}}, by(proxy, adapter), nil},
{"a moved module with no template is not handed (a held stub, issue 1126)",
Manifest{Module: "bot", Moved: true, Handed: &Handed{}, Requires: []string{"oidc-client", "route"}},
by(keycloak, proxy), []string{"route"}},
{"a provision on its own machine that serves facts binds, and is keyed for a handed consumer",
Manifest{Module: "chat", Moved: true, Handed: built, Requires: []string{"llm", "llm"}}, by(ollama), []string{"llm"}},
} {
if got := Awaited(c.consumer, c.providers); !reflect.DeepEqual(got, c.want) {
t.Errorf("%s: awaited %v, want %v", c.name, got, c.want)
}
}
}
// A module not handed has a plan for what a provider's helper answers of it, and for nothing else (issue 774: grafana
// had no plan, so no route key, and route-proxy served no route for it); a need the controller still answers stays out.
func TestAModuleNotHandedHasAPlanForWhatAHelperAnswers(t *testing.T) {
consumer := notifierConsumer(t, false, false)
consumer.Requires = append(consumer.Requires, "route")
consumer.Contributes = map[string]map[string]any{"route": {"host": "notify.example.org"}}
node := workstation()
node.At = "workstation.internal"
proxy := Manifest{Module: "proxy", Version: "1", Provides: []Offer{{Name: "route", Scope: ScopeMesh, AnsweredBy: AnsweredByHelper}}}
store := Manifest{Module: "store", Version: "1", Provides: []Offer{{Name: "postgres-database", Scope: ScopeMesh,
Credential: &OfferCredential{Form: CredentialFormPair}}}, Serves: map[string]map[string]any{"postgres-database": {"port": 5432}}}
w := twoStores("anchor")
w.Offered["route"] = []Provider{{Node: "anchor", At: "anchor.internal", Module: "proxy"}}
r, err := Resolve(shelf(consumer, proxy, store), []string{consumer.Module}, node, w)
if err != nil {
t.Fatal(err)
}
r.Account = "op"
for i, n := range r.Needs {
// As the plan sets it (compose's answeredByHelper).
r.Needs[i].AnsweredByHelper = n.Name == "route"
}
_, before := composeWith(t, r, Rendering{})
c, got := composeWith(t, r, handingTo("store:5000"))
if got != before {
t.Fatalf("the plan changed what is composed:\nbefore: %s\nnow: %s", before, got)
}
plan, ok := c.Plans["notify"]
if !ok || len(plan.Provisions) != 1 || plan.Provisions[0].Provision != "route" || plan.Provisions[0].ProviderModule != "proxy" {
t.Fatalf("the plan is %+v (%v), want the route alone", plan, ok)
}
if plan.Templates != (PlanTemplates{}) || plan.Files != nil || plan.Secrets != nil ||
!strings.Contains(plan.Provisions[0].As, "notify") || plan.Provisions[0].Contributes["host"] != "notify.example.org" {
t.Fatalf("the plan names %+v", plan)
}
if want := Awaited(consumer, func(p string) []Manifest {
return map[string][]Manifest{"route": {proxy}, "postgres-database": {store}}[p]
}); !reflect.DeepEqual(want, []string{"route"}) {
t.Fatalf("the assigner waits for %v, and the plan keys the route alone", want)
}
}
+4
View File
@@ -263,6 +263,10 @@ type Needed struct {
// then name `${bound:<provision>:<key>}` and `${secret:<provision>}`, which its machine's client fills from the
// provider's entry and the vault. Set by the plan, which reads the provider's manifest.
ProviderMoved bool
// AnsweredByHelper is set when the providing module's offer says the provider helper answers it (issue 792): a
// provision the seat mesh-provisioner keys whether or not its consumer is handed (Keyed). Set by the plan, which
// reads the provider's manifest.
AnsweredByHelper bool
}
// Refusal is why a set of assignments cannot become a declaration.
+11
View File
@@ -54,6 +54,7 @@ func (c *composer) planFor(nodeName string) (Resolved, error) {
// Issued for this holder alone and kept by the vault (issue 757).
resolved.Needs[i].IssuedToHolder = issuedToHolder(shelf, n)
resolved.Needs[i].ProviderMoved = providerMoved(shelf, n)
resolved.Needs[i].AnsweredByHelper = answeredByHelper(shelf, n)
if n.ByRecord {
// Answered by something the mesh holds, so there is no pair-wise secret between two machines.
licence, _ := c.licence(n.From)
@@ -189,6 +190,16 @@ func providerMoved(shelf map[string]catalogue.Manifest, n catalogue.Needed) bool
return known && (movedProvider(provider) || provider.AnsweredByHelperFor(n.Name))
}
// answeredByHelper is whether a need's provider offers it answered by the provider helper (issue 792): keyed by the seat
// mesh-provisioner for every consumer, handed or not (catalogue.Keyed, issue 774).
func answeredByHelper(shelf map[string]catalogue.Manifest, n catalogue.Needed) bool {
if n.ByRecord || n.Module == "" {
return false
}
provider, known := shelf[n.Module]
return known && provider.AnsweredByHelperFor(n.Name)
}
// movedProvider is whether a provider's credentials are the vault's: the module is marked moved, or it holds the
// mesh-vault seat (issue 793).
func movedProvider(m catalogue.Manifest) bool {
+28
View File
@@ -358,3 +358,31 @@ func TestALicenceKeyTheInputDoesNotCarryIsAStandInAndNamedForeseen(t *testing.T)
t.Errorf("a key the mesh holds none of: %q %v", n.Sealed, got.Foreseen)
}
}
// A consumer not handed of a provision whose provider's offer says the helper answers it has that provision in a plan of
// its own (issue 774: grafana's route): the composition marks the need answered by the helper, and the plan keys it.
func TestANeedAnsweredByTheHelperIsInTheConsumersPlan(t *testing.T) {
in := theStoreMesh(t)
store := in.Catalogue["store"]
store.Provides = []catalogue.Offer{{Name: "postgres-database", Scope: catalogue.ScopeMesh, AnsweredBy: catalogue.AnsweredByHelper}}
in.Catalogue["store"] = store
got, err := Resolve(in, "laptop")
if err != nil {
t.Fatal(err)
}
if n := need(t, got.Plan, "board", "postgres-database"); !n.AnsweredByHelper {
t.Fatalf("the need is not marked answered by the helper: %+v", n)
}
with := rendered(t, in, "laptop", Foreseeing).Rendering
with.Hands, with.Provisioner = true, "mesh-provisioner-client"
if with.Generators, err = newComposer(in).generators(); err != nil {
t.Fatal(err)
}
c, err := got.Plan.Compose(with)
if err != nil {
t.Fatal(err)
}
if p := c.Plans["board"].Provisions; len(p) != 1 || p[0].Provision != "postgres-database" || p[0].ProviderModule != "store" {
t.Fatalf("board's plan is %+v", c.Plans["board"])
}
}