A consumer beside a held provider is told what it serves without the names it does not compose, never their placeholders (issue 1921, milestone 47/G)

The provider held on its machine is composed apart, so no module of the composed set serves the provision; its
manifest's served values reached the consumer's binding raw and the binding refused the consumer for a
${bound:route:name-web} it does not require (the group check of 8a80e8e86858 on novox, mesh-build-node beside a held
gitea). Each such key is now left out and said on the need, as a provider elsewhere leaves it out (ADR 0346).

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
2026-11-07 07:19:58 +01:00
parent 5b75e866d0
commit b6c174a324
2 changed files with 56 additions and 0 deletions
@@ -4,6 +4,8 @@ import (
"encoding/json"
"errors"
"fmt"
"maps"
"slices"
"sort"
"strconv"
"strings"
@@ -240,7 +242,14 @@ func resolutionCompose(r mcat.Resolution, with mcat.Rendering, owner map[string]
}
if answered {
r.Needs[i].Serves = serves
continue
}
// No module of the composed set serves it: its provider is held on this machine (composed apart, as what runs of
// it), and offers what its manifest serves. None of its own names is composed here, so a key that names one is
// left out and said on the need, as a provider elsewhere leaves it out (ServedLeftOut, novox/hq ADR 0346 §3),
// never handed to the consumer as a placeholder its binding then refuses (issue 1921, novox: "mesh-build-node has
// a file that says ${bound:route:name-web}, and mesh-build-node does not require route").
r.Needs[i].Serves, r.Needs[i].LeftOut = withoutOwnNames(r.Needs[i], r.Node)
}
// Once, from every module's listens -- not per module. A module receiving only its own ports
@@ -2526,3 +2535,26 @@ func resolutionGeneratedHere(r mcat.Resolution, with mcat.Rendering) (map[string
}
return generated, nil
}
// withoutOwnNames is what a need's provider serves without each key whose value names one of the provider's own composed
// names (${bound:…}), and the need's left-out keys with each such key said: a provider not composed in this set
// composes none of its names here.
func withoutOwnNames(n mcat.Needed, node string) (map[string]any, []string) {
var asking []string
for _, key := range sortedAnyKeys(n.Serves) {
if text, ok := n.Serves[key].(string); ok && bound.MatchString(text) {
asking = append(asking, key)
}
}
if len(asking) == 0 {
return n.Serves, n.LeftOut
}
out := maps.Clone(n.Serves)
said := slices.Clone(n.LeftOut)
for _, key := range asking {
delete(out, key)
said = append(said, fmt.Sprintf("%s serves %s without %q: it names %s, and %s composes no names of %s, which it "+
"holds (novox/hq ADR 0346)", n.Module, n.Name, key, bound.FindString(n.Serves[key].(string)), orHere(node), n.Module))
}
return out, said
}
@@ -1,6 +1,7 @@
package resolution
import (
"slices"
"strings"
"testing"
@@ -75,6 +76,29 @@ func TestAConsumerBesideTheProviderIsServedItsName(t *testing.T) {
if strings.Contains(binding, "${bound:") {
t.Fatalf("a placeholder reached the consumer:\n%s", binding)
}
// Issue 1921 (the group check of 8a80e8e86858, novox): the provider held on this machine (composed apart, as what runs
// of it) is not in the composed set, so none of its own names is composed here. The consumer beside it is told
// what it serves without the keys that name them, each said on the need, never the placeholder: its binding refused
// the consumer with "has a file that says ${bound:route:name-web}, and lab does not require route".
held, err := Resolve(shelf(minimalRouteProxy(), m, consumer), []string{"route-proxy", "forge", "lab"}, node, mcat.World{})
if err != nil {
t.Fatal(err)
}
held.Modules = slices.DeleteFunc(slices.Clone(held.Modules), func(m mcat.Manifest) bool { return m.Module == "forge" })
out, err = Declaration(held, mcat.Rendering{})
if err != nil {
t.Fatalf("a consumer beside a held provider: %v", err)
}
for _, r := range out {
if c, _ := r["content"].(string); r["path"] == "/var/lib/lab/package-registry.json" && (strings.Contains(c, "${bound:") || !strings.Contains(c, "npm-path")) {
t.Fatalf("the binding beside a held provider:\n%s", c)
}
}
for _, n := range held.Needs {
if n.Name == "npm-package-registry" && (len(n.LeftOut) != 1 || !strings.Contains(n.LeftOut[0], `"public-root"`)) {
t.Fatalf("the need beside a held provider says not what was left out: %+v", n)
}
}
}
// From another machine, the provider's served values carry what that machine composed, and a key it