catalogue: deliver a keyless same-node provider's served facts #18
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user