Take the catalogue's composition rules into the node-engine

ADR 0593 §7: the composer with its refusals leaves mesh-sdk, so mesh-sdk
carries no composition rule. The catalogue functions only the
node-engine's composition reaches (Resolve, Resolution's Compose,
Declaration, DataItems, LeftOut, Rules and the rest, the settings,
placeholder and template refusals, the rendering helpers) are copied
with their tests into internal/compose/resolution; a method of a
mesh-sdk shape becomes a function taking it first. The composition
calls its own copies, and the captured catalogue its tests read lives
here now, shared by mesh-compose's tests.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
2026-11-02 19:15:55 +01:00
parent be0226b624
commit d68e241724
686 changed files with 35703 additions and 80 deletions
@@ -0,0 +1,146 @@
package resolution
import (
"reflect"
"strings"
"testing"
mcat "git.novox.be/novox/mesh-sdk/go/catalogue"
)
// gitea's server.env, as the catalogue keeps it: its database as the provider told it and the consumer's own login.
const giteaServerEnv = "GITEA__security__INTERNAL_TOKEN=${secret:internal-token}\n" +
"GITEA__database__DB_TYPE=postgres\n" +
"GITEA__database__HOST=${bound:postgres-database:at}:${bound:postgres-database:port}\n" +
"GITEA__database__NAME=${bound:postgres-database:as}\n" +
"GITEA__database__USER=${bound:postgres-database:as}\n" +
"GITEA__database__PASSWD=${secret:postgres-database}\n"
// databaseConsumer is a handed module, with the template gitea's server.env is, requiring a database from a provider
// that has moved or not (issue 788); slug is the module's identity source when it declares one.
func databaseConsumer(t *testing.T, slug string, moved bool) mcat.Resolution {
t.Helper()
raw := strings.Replace(botMoved(), `"own-secrets":{"token":"${dir:state}/token"}`,
`"requires":["postgres-database"],"own-secrets":{"internal-token":"${dir:state}/internal-token"}`, 1)
if slug != "" {
raw = strings.Replace(raw, `"version":"1",`, `"version":"1","slug":"`+slug+`",`, 1)
}
return withDatabase(t, raw, giteaServerEnv, moved)
}
// withDatabase resolves the module in raw, whose template is text, against a provider of postgres-database that has
// moved or not.
func withDatabase(t *testing.T, raw, text string, moved bool) mcat.Resolution {
t.Helper()
r, err := Resolve(shelf(held(t, raw, builtBot(text)), mcat.Manifest{Module: "postgres", Version: "1", Provides: []mcat.Offer{{Name: "postgres-database", Scope: mcat.ScopeNode}}}),
[]string{"bot"}, workstation(), mcat.World{})
if err != nil {
t.Fatal(err)
}
r.Account = "op"
// Answered on this machine, it states no need; one from the provider's machine is stated, as the plan does.
r.Needs = []mcat.Needed{{Name: "postgres-database", For: "bot", Module: "postgres", From: "novox", At: "10.7.0.1",
CredentialForm: mcat.CredentialFormPair, IssuedToHolder: moved, ProviderMoved: moved}}
return r
}
// The forge's server.env composes for a handed module when what it requires has moved, its plan entry carrying the
// provider's address, machine and the consumer's identity as today's binding file gives them; and is refused with the
// text it has always had when the provider has not.
func TestAHandedModuleMayNameWhatItRequiresWhereItsProviderHasMoved(t *testing.T) {
c, got := composeWith(t, databaseConsumer(t, "", true), handingTo("store:5000"))
if strings.Contains(got, "sealed") {
t.Fatalf("a handed module's declaration carries a sealed value: %s", got)
}
want := []mcat.PlanProvision{{Name: "postgres-database", Provision: "postgres-database", Provider: "novox",
ProviderModule: "postgres", CredentialForm: mcat.CredentialFormPair, At: "10.7.0.1", From: "novox",
As: mcat.ConsumerIdentity("workstation", "bot")}}
if !reflect.DeepEqual(c.Plans["bot"].Provisions, want) {
t.Fatalf("the plan names %+v, want %+v", c.Plans["bot"].Provisions, want)
}
for _, unmoved := range []mcat.Resolution{databaseConsumer(t, "", false)} {
_, err := Compose(unmoved, handingTo("store:5000"))
if err == nil || !strings.Contains(err.Error(), "which is no secret of its own; a credential it requires moves "+
"with its provider (design 57 B1–B3)") {
t.Fatalf("a provider not moved, the credential: %v", err)
}
bound := strings.Replace(giteaServerEnv, "${secret:postgres-database}", "x", 1)
r := withDatabase(t, strings.Replace(botMoved(), `"own-secrets":{"token":"${dir:state}/token"}`,
`"requires":["postgres-database"],"own-secrets":{"internal-token":"${dir:state}/internal-token"}`, 1),
bound, false)
_, err = Compose(r, handingTo("store:5000"))
if err == nil || !strings.Contains(err.Error(), "the provisioner's client fills only config, secrets, "+
"collections and placement until that domain moves (design 57)") {
t.Fatalf("a provider not moved, the binding: %v", err)
}
}
}
// A handed module's credentials the vault keeps as one login per local name (issue 793): gitea's internal token and
// admin password, which the `secret` provision hands as `${secret:<local>}`. Each is a plan entry naming its local,
// its file declared placed and without a value; the template naming the local is allowed.
func TestAHandedModulesLocalCredentialsOfTheVaultAreNamedByLocalInItsPlan(t *testing.T) {
raw := strings.Replace(botMoved(), `"own-secrets":{"token":"${dir:state}/token"}`,
`"requires":["secret"],"secrets":{"secret":{"internal-token":"${dir:state}/internal-token.secret",`+
`"admin":"${dir:state}/admin.secret"}},"own-secrets":{"token":"${dir:state}/token"}`, 1)
r, err := Resolve(shelf(held(t, raw, builtBot("T=${secret:internal-token}\n")), mcat.Manifest{Module: "vault", Version: "1", Provides: []mcat.Offer{{Name: "secret", Scope: mcat.ScopeNode}}}),
[]string{"bot"}, workstation(), mcat.World{})
if err != nil {
t.Fatal(err)
}
r.Account = "op"
r.Needs = []mcat.Needed{
{Name: "secret", Local: "internal-token", For: "bot", Module: "vault", From: "novox",
CredentialForm: mcat.CredentialFormPair, IssuedToHolder: true, ProviderMoved: true},
{Name: "secret", Local: "admin", For: "bot", Module: "vault", From: "novox",
CredentialForm: mcat.CredentialFormPair, IssuedToHolder: true, ProviderMoved: true},
}
c, got := composeWith(t, r, handingTo("store:5000"))
if strings.Contains(got, "sealed") {
t.Fatalf("the declaration carries a sealed value: %s", got)
}
var names []string
for _, p := range c.Plans["bot"].Provisions {
if p.Provision != "secret" || p.Provider != "novox" || p.Path == "" || p.Name != p.Local {
t.Errorf("the plan entry is %+v", p)
}
names = append(names, p.Name)
}
if !reflect.DeepEqual(names, []string{"admin", "internal-token"}) {
t.Fatalf("the plan names %v, want admin and internal-token", names)
}
}
// **Issue 969: a key its provider serves only at run time.** A moved module's file may name `${bound:…}` of a
// provision whose provider tells it at run time (ssh-client's known_host of the forge). Where the machine takes
// handed files the client fills it and the module composes; where it does not the controller must fill it from what
// the provider serves statically, cannot, and refuses it with the very words an unmarked module is refused with
// (ADR 0349 §17: the old way, byte for byte).
func TestAHandedModuleNamingARunTimeBoundKeyComposesOnlyWhereItIsHanded(t *testing.T) {
text := "Host ${bound:postgres-database:at}\n KnownHost ${bound:postgres-database:known_host}\n"
moved := strings.Replace(botMoved(), `"own-secrets":{"token":"${dir:state}/token"}`,
`"requires":["postgres-database"],"own-secrets":{"token":"${dir:state}/token"}`, 1)
unmoved := strings.Replace(moved, `"moved":true,`, ``, 1)
if unmoved == moved {
t.Fatal("the twin is still marked moved")
}
c, got := composeWith(t, withDatabase(t, moved, text, true), handingTo("store:5000"))
if strings.Contains(got, "known_host") || len(c.Filled) == 0 {
t.Fatalf("handed, the module is filled by the client and the controller names nothing: %v %s", c.Filled, got)
}
for name, with := range map[string]mcat.Rendering{
"an engine that does not take a filled file alone": {Provisioner: "mesh-provisioner-client", ArtifactStore: "store:5000"},
"no provisioner client on the machine": {Hands: true, ArtifactStore: "store:5000"},
} {
_, err := Compose(withDatabase(t, moved, text, true), sealedForBot(with))
if err == nil || !strings.Contains(err.Error(), `asks its postgres-database binding for "known_host"`) {
t.Fatalf("%s: the key is not refused: %v", name, err)
}
_, before := Compose(withDatabase(t, unmoved, text, false), sealedForBot(with))
if before == nil || before.Error() != err.Error() {
t.Fatalf("%s: refused otherwise than the unmarked module: %v / %v", name, err, before)
}
}
}