catalogue: deliver a keyless same-node provider's served facts #18

Merged
jschoubben merged 1 commits from feat/serve-here-keyless into main 2026-09-07 03:25:58 +00:00
2 changed files with 102 additions and 0 deletions
+82
View File
@@ -185,3 +185,85 @@ func TestAnUnanswerableRequirementDoesNotRemoveAMachineFromTheMesh(t *testing.T)
t.Fatal("the requirement nothing answers was accepted when it was actually asked")
}
}
// A same-node provider that mints no credential but serves a port the consumer cannot guess must
// still deliver that port. A `local-model` provider (ollama) provides `model-access` at node scope
// and serves a port and a model name, minting nothing; a co-located consumer requires and binds it
// and has a file that names `${bound:model-access:port}`. The served facts never reached the
// consumer's needs — node scope set `local`, not `brokered`, so the delivering branch was skipped —
// and the consumer's file was refused for naming a provision it "did not require". The endpoint is
// keyless, but a port is still a fact nobody can invent.
func TestAKeylessSameNodeProviderStillDeliversWhatItServes(t *testing.T) {
provider := Manifest{Module: "local-model",
Provides: []Offer{{Name: "model-access", Scope: ScopeNode}},
Serves: map[string]map[string]any{
"model-access": {"port": 11434, "model": "a-model"}}}
consumer := Manifest{Module: "assistant", Requires: []string{"model-access"},
Binds: map[string]string{"model-access": "/etc/assistant/model.json"}}
// node.At empty: a single-node deployment with no private network. The delivered binding must
// still carry a usable address — loopback, because a machine off the network still reaches
// itself, and an empty `at` would be written into the consumer's file as a host that resolves
// to nothing.
got, err := Resolve(
map[string]Manifest{"local-model": provider, "assistant": consumer},
[]string{"assistant", "local-model"},
Node{Name: "workstation"},
World{})
if err != nil {
t.Fatalf("a keyless same-node model endpoint was refused: %v", err)
}
var found *Needed
for i := range got.Needs {
if got.Needs[i].Name == "model-access" && got.Needs[i].For == "assistant" {
found = &got.Needs[i]
}
}
if found == nil {
t.Fatalf("the served endpoint was not delivered to the consumer at all: %+v", got.Needs)
}
if found.ByRecord {
t.Fatal("a same-node endpoint was treated as a record, so the reachability rule would apply")
}
if found.At == "" {
t.Fatalf("the binding carries no address, so its file names a host that resolves to nothing: %+v", found)
}
if found.At != "127.0.0.1" {
t.Fatalf("off the private network the address must fall back to loopback, got %q", found.At)
}
if got, want := plainly(found.Serves["port"]), "11434"; got != want {
t.Fatalf("the port the consumer cannot guess was not delivered: got %q want %q", got, found.Serves)
}
if found.Serves["model"] != "a-model" {
t.Fatalf("the extra served fact was not delivered: %+v", found.Serves)
}
// End to end: a file that names ${bound:model-access:...} is filled rather than refused, which
// is the whole failure this fixes — boundInto reads knownFor, and knownFor reads the needs.
consumer.Resources = []map[string]any{{
"id": "env", "type": "file", "path": "/etc/assistant/.env",
"content": "OPENAI_BASE_URL=http://${bound:model-access:at}:${bound:model-access:port}/v1\n",
}}
got, err = Resolve(
map[string]Manifest{"local-model": provider, "assistant": consumer},
[]string{"assistant", "local-model"},
Node{Name: "workstation"},
World{})
if err != nil {
t.Fatalf("resolution refused the consumer with a bound file: %v", err)
}
out, err := got.Declaration(Rendering{})
if err != nil {
t.Fatalf("the declaration refused the consumer's bound file: %v", err)
}
var env string
for _, res := range out {
if res["path"] == "/etc/assistant/.env" {
env, _ = res["content"].(string)
}
}
if want := "http://127.0.0.1:11434/v1"; !strings.Contains(env, want) {
t.Fatalf("the bound file was not filled with the served endpoint, want %q:\n%s", want, env)
}
}
+20
View File
@@ -251,6 +251,26 @@ func Resolve(catalogue map[string]Manifest, assigned []string, node Node, world
needs = append(needs, Needed{
Name: want, From: node.Name, At: node.At,
Serves: servedHere(catalogue, chosen, want), For: because[want]})
} else if served := servedHere(catalogue, chosen, want); len(served) > 0 {
// Answered here with no credential to mint, but the provider serves facts the
// consumer cannot guess — a port, a model name — and so still needs a binding.
// **The reachability rule does not apply**: both ends are on this same machine, so
// there is no private network to share and nothing to refuse (novox/hq ADR 0024's
// sibling case — a same-node keyless endpoint that the consumer still cannot invent
// the port for, exactly what declaration.go's here() was left to patch after the
// fact). Delivering it here as a need is what lets knownFor see it, so a file that
// says ${bound:<provision>:port} is filled rather than refused.
//
// The address is this machine's own name on the private network when it has one, and
// loopback when it does not — a machine off the network still reaches itself, and the
// consumer's binding must carry a usable `at`. The same fallback declaration.go's
// here() applies, done here because this need is now found before here() would run.
at := node.At
if at == "" {
at = "127.0.0.1"
}
needs = append(needs, Needed{
Name: want, From: node.Name, At: at, Serves: served, For: because[want]})
}
continue
}