Pin the node-engine at its ADR 0328 merge (#95), and speak its contract: a library's verdict is said for the modules that use it

This commit is contained in:
2026-10-12 23:02:31 +02:00
parent f634e33792
commit 7ea6d36eab
10 changed files with 291 additions and 44 deletions
+12 -11
View File
@@ -468,6 +468,7 @@ type Joined struct {
Primary string
PrimaryGID int
Added []string
AddedGIDs []int
}
// librariesOf is the libraries a module uses, by name, in the order its entries first name them.
@@ -504,6 +505,7 @@ func joinedOf(m Manifest, with Rendering) (Joined, error) {
continue
}
j.Added = append(j.Added, MeshName(lib))
j.AddedGIDs = append(j.AddedGIDs, gid)
}
return j, nil
}
@@ -723,14 +725,15 @@ type GroupEntry struct {
GID int `json:"gid"`
}
// IdentityEntry is one joining module's user as a machine's declaration names it (§4): its own group has its uid,
// and Groups are its library groups, the primary first.
// IdentityEntry is one joining module's user as a machine's declaration names it (§4; mesh-host internal/declaration
// Identity): the engine always makes its own group with its uid; GID is its primary group's number — its first
// library's, or its own — and Groups the numbers of its added groups.
type IdentityEntry struct {
Module string `json:"module"`
Name string `json:"name"`
UID int `json:"uid"`
GID int `json:"gid"`
Groups []string `json:"groups,omitempty"`
Module string `json:"module"`
Name string `json:"name"`
UID int `json:"uid"`
GID int `json:"gid"`
Groups []int `json:"groups,omitempty"`
}
// libraryEnvelope is what a machine's declaration says of libraries and joining modules: the group of every library
@@ -748,10 +751,8 @@ func (r Resolution) libraryEnvelope(with Rendering, joined []Joined) ([]GroupEnt
sort.Slice(groups, func(i, j int) bool { return groups[i].Name < groups[j].Name })
var ids []IdentityEntry
for _, j := range joined {
e := IdentityEntry{Module: j.Module, Name: IdentityName(j.Module), UID: j.UID, GID: j.UID}
if j.Primary != IdentityName(j.Module) {
e.Groups = append([]string{j.Primary}, j.Added...)
}
e := IdentityEntry{Module: j.Module, Name: IdentityName(j.Module), UID: j.UID, GID: j.PrimaryGID,
Groups: append([]int(nil), j.AddedGIDs...)}
ids = append(ids, e)
}
sort.Slice(ids, func(a, b int) bool { return ids[a].Module < ids[b].Module })
+22 -1
View File
@@ -6,6 +6,8 @@ import (
"reflect"
"strings"
"testing"
"github.com/novox/mesh-host/validate"
)
// Defends novox/hq ADR 0328 on the controller's side, row by row of its "How it is checked" table where the row is the
@@ -90,7 +92,7 @@ func TestAJoiningModuleIsComposedWithItsIdentityAndLibraries(t *testing.T) {
t.Errorf("its directory is owned by %v; want its user and primary group", owner)
}
if len(composed.Identities) != 1 || !reflect.DeepEqual(composed.Identities[0], IdentityEntry{Module: "radarr",
Name: "mesh-radarr", UID: 50020, GID: 50020, Groups: []string{"mesh-media", "mesh-books"}}) {
Name: "mesh-radarr", UID: 50020, GID: 50010, Groups: []int{50011}}) {
t.Errorf("the identities are %+v", composed.Identities)
}
if w := LibraryWarnings(aRadarr(t)); len(w) != 1 || !strings.Contains(w[0], "reaches only media") {
@@ -317,3 +319,22 @@ func TestTheLibrariesAreTheOperatorsSetting(t *testing.T) {
t.Fatal("a library in the machine's own tree was accepted")
}
}
// What the node-engine this controller pins (mesh-host #95, ADR 0328's half) makes of a composed declaration with
// libraries and a joining module: nothing refused.
func TestTheEngineTakesWhatIsComposedForLibraries(t *testing.T) {
for _, node := range []string{"ace", "novox"} {
r := Resolution{Node: node, Account: "jochen", Modules: []Manifest{aRadarr(t), theRuntime(t)}}
with := librariesRendering()
with.Libraries["books"] = Library{Name: "books", Home: "ace", Root: "/storage/books", Reach: []string{"novox"}}
composed := composeLibraries(t, r, with)
body, err := json.Marshal(map[string]any{"declaration": validate.Version, "resources": composed.Resources,
"groups": composed.Groups, "identities": composed.Identities, "libraries": composed.Libraries})
if err != nil {
t.Fatal(err)
}
if problems := validate.Declaration(body); len(problems) > 0 {
t.Fatalf("%s: the pinned node-engine refuses it: %v", node, problems)
}
}
}