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:
@@ -0,0 +1,80 @@
|
||||
package resolution
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
mcat "git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
)
|
||||
|
||||
// The fault 04-ISSUES/036 records: a media stack is several modules — a library server, the
|
||||
// acquisition managers, a download client — that must share directories on one machine. Written
|
||||
// as owned `directory` resources, two of them on one node are refused as rivalrous owners, which
|
||||
// is right in general and wrong here: sharing those directories is the whole point of the stack.
|
||||
//
|
||||
// Declared as accesses to an operator-owned path (novox/hq ADR 0051), they co-resolve. An access
|
||||
// is not a resource and never enters the duplicate-owner map, so this is the exact case the
|
||||
// resolver refuses above when the same path is owned — and does not, when it is merely accessed.
|
||||
func TestTwoModulesAccessingOnePathCoResolve(t *testing.T) {
|
||||
a := mod("a", nil, nil, nil)
|
||||
a.Accesses = []mcat.Access{{Path: "/services/media/downloads", Mode: mcat.AccessReadWrite}}
|
||||
b := mod("b", nil, nil, nil)
|
||||
b.Accesses = []mcat.Access{{Path: "/services/media/downloads", Mode: mcat.AccessRead}}
|
||||
|
||||
got, err := Resolve(shelf(a, b), []string{"a", "b"}, workstation(), mcat.World{})
|
||||
if err != nil {
|
||||
t.Fatalf("two modules accessing one operator-owned path were refused: %v", err)
|
||||
}
|
||||
|
||||
// And each accessing module's grant reaches the machine as its own `access` resource, so the
|
||||
// host can find the path present or refuse — both naming the one operator-owned path.
|
||||
var accesses int
|
||||
for _, r := range mustDeclare(t, got) {
|
||||
if r["type"] == "access" && r["path"] == "/services/media/downloads" {
|
||||
accesses++
|
||||
}
|
||||
}
|
||||
if accesses != 2 {
|
||||
t.Errorf("expected both modules' accesses in the declaration, got %d", accesses)
|
||||
}
|
||||
}
|
||||
|
||||
// The mirror of the case above, so the sharing vocabulary does not weaken the rule it sits beside:
|
||||
// two modules OWNING one path is still refused, exactly as before accesses existed.
|
||||
func TestTwoModulesOwningOnePathAreStillRefused(t *testing.T) {
|
||||
a := mod("a", nil, nil, nil)
|
||||
a.Resources = []map[string]any{
|
||||
{"id": "lib", "type": "directory", "path": "/services/media/movies", "mode": "0755"}}
|
||||
b := mod("b", nil, nil, nil)
|
||||
b.Resources = []map[string]any{
|
||||
{"id": "lib", "type": "directory", "path": "/services/media/movies", "mode": "0755"}}
|
||||
|
||||
_, err := Resolve(shelf(a, b), []string{"a", "b"}, workstation(), mcat.World{})
|
||||
if err == nil {
|
||||
t.Fatal("two modules owning the same directory were both assigned")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "/services/media/movies") {
|
||||
t.Errorf("the refusal does not name the path: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Shared data is the operator's, owned by no module (novox/hq ADR 0051). A module owning the path
|
||||
// another module was told to expect the operator to provide would create and chown it — and the
|
||||
// two intentions cannot both hold, so it is refused by name. This is what keeps the distinction
|
||||
// machine-checkable rather than a convention nobody enforces.
|
||||
func TestAModuleMayNotOwnWhatAnotherAccesses(t *testing.T) {
|
||||
owns := mod("owns", nil, nil, nil)
|
||||
owns.Resources = []map[string]any{
|
||||
{"id": "lib", "type": "directory", "path": "/services/media/movies", "mode": "0755"}}
|
||||
uses := mod("uses", nil, nil, nil)
|
||||
uses.Accesses = []mcat.Access{{Path: "/services/media/movies", Mode: mcat.AccessRead}}
|
||||
|
||||
_, err := Resolve(shelf(owns, uses), []string{"owns", "uses"}, workstation(), mcat.World{})
|
||||
if err == nil {
|
||||
t.Fatal("a module owning a path another accesses was allowed")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "/services/media/movies") ||
|
||||
!strings.Contains(err.Error(), "the operator's") {
|
||||
t.Errorf("the refusal does not explain the ownership conflict: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user