Hold a joining module at the build it ran, own its directories by its own group, and take a library only when its act is done (hq ADR 0328 review, ADR 0334)

A held module is composed exactly as before it joined, at the build its machine runs, so its port, resources and
contributions stay. A joining module's directories belong to its user and its own group, and its tools' directories
are refused closed to that group; the runner joins it where the engine can say so. A take records the root and is
taken only when the engine states the act done. Libraries' paths and verdicts count modules pulled in by a
requirement, and a unit takes the identity.
This commit is contained in:
2026-10-13 00:03:18 +02:00
parent 7ea6d36eab
commit 96e15f8693
16 changed files with 763 additions and 106 deletions
+67 -8
View File
@@ -371,6 +371,7 @@ func libraryProblems(m Manifest) []string {
if !Joins(m) {
return problems
}
problems = append(problems, toolsDirectoryProblems(m)...)
for _, r := range m.Resources {
if fmt.Sprint(r["type"]) == "directory" {
if owner, said := r["owner"]; said {
@@ -383,6 +384,55 @@ func libraryProblems(m Manifest) []string {
return problems
}
// toolsDirectoryProblems refuses a joining module whose tools name one of its directories whose mode closes it to its
// group (novox/hq ADR 0334 §3): the tool runner reads it through the module's own group, so it needs group read and
// enter.
func toolsDirectoryProblems(m Manifest) []string {
modes := map[string]string{}
for _, r := range m.Resources {
if fmt.Sprint(r["type"]) == "directory" {
mode, _ := r["mode"].(string)
modes[fmt.Sprint(r["id"])] = mode
}
}
var words []string
for _, b := range m.Bundles {
for _, v := range b.Env {
words = append(words, v)
}
}
if m.Build != nil {
for _, a := range m.Build.Artifacts {
for _, v := range a.Env {
words = append(words, v)
}
}
}
seen := map[string]bool{}
var problems []string
for _, w := range words {
for _, match := range dirRef.FindAllStringSubmatch(w, -1) {
id := match[1]
mode, declared := modes[id]
if !declared || seen[id] {
continue
}
seen[id] = true
var n uint64
if _, err := fmt.Sscanf(mode, "%o", &n); err != nil || mode == "" {
continue // its mode is judged where the directory is
}
if n&0o050 != 0o050 {
problems = append(problems, fmt.Sprintf("%s's tools name its directory %s, whose mode %s closes it to its "+
"own group: the tool runner reads it through that group, so it needs group read and enter (0750) "+
"(novox/hq ADR 0334 §3)", m.Module, id, mode))
}
}
}
sort.Strings(problems)
return problems
}
// LibraryWarnings is what the module check warns of a module's libraries (§4): an image that drops added groups
// reaches only the first library the module uses.
func LibraryWarnings(m Manifest) []string {
@@ -511,8 +561,9 @@ func joinedOf(m Manifest, with Rendering) (Joined, error) {
}
// libraryLeftOut is why module m is left out of this machine's declaration for its libraries, or "": a library it
// uses not defined or not reaching the machine (§2), an engine that cannot be told it joins, or a container held at
// the user it ran as until every library it uses is taken (§8).
// uses not defined or not reaching the machine (§2), or an engine that cannot be told it joins. A module held at the
// user it ran as until its libraries are taken (§8) is not left out: it is composed at the build it ran before it
// joined (cmd/mesh-controller holdJoining).
func libraryLeftOut(m Manifest, node string, with Rendering) string {
if !Joins(m) {
return ""
@@ -534,10 +585,6 @@ func libraryLeftOut(m Manifest, node string, with Rendering) string {
if _, has := with.Identities[m.Module]; !has {
return fmt.Sprintf("%s joins, and its number is allocated at its first send", m.Module)
}
if lib := with.Held[m.Module]; lib != "" {
return fmt.Sprintf("%s keeps the user it ran as until the library %s is taken: the operator is asked once "+
"(%s §8)", m.Module, lib, ADR0328)
}
return ""
}
@@ -583,8 +630,10 @@ func joinedInto(resource map[string]any, m Manifest, j Joined, paths map[string]
}
resource[k] = filled
}
// Its own directories belong to its user and its own group (ADR 0334 §1): the library's group is its processes'
// primary, so what they make in a library keeps that group, and its own data stays its own.
if fmt.Sprint(resource["type"]) == "directory" {
resource["owner"] = fmt.Sprintf("%d:%d", j.UID, j.PrimaryGID)
resource["owner"] = fmt.Sprintf("%d:%d", j.UID, j.UID)
}
if fmt.Sprint(resource["type"]) != "container" {
return nil
@@ -734,6 +783,9 @@ type IdentityEntry struct {
UID int `json:"uid"`
GID int `json:"gid"`
Groups []int `json:"groups,omitempty"`
// Runner is the tool runner's account, put in this module's own group, where its tools run there (ADR 0334 §2).
// Only to an engine that reads it (Rendering.RunnerJoinsOwnGroups).
Runner string `json:"runner,omitempty"`
}
// libraryEnvelope is what a machine's declaration says of libraries and joining modules: the group of every library
@@ -753,13 +805,20 @@ func (r Resolution) libraryEnvelope(with Rendering, joined []Joined) ([]GroupEnt
for _, j := range joined {
e := IdentityEntry{Module: j.Module, Name: IdentityName(j.Module), UID: j.UID, GID: j.PrimaryGID,
Groups: append([]int(nil), j.AddedGIDs...)}
if with.RunnerJoinsOwnGroups {
for _, m := range r.Modules {
if m.Module == j.Module {
e.Runner = r.toolsAccount(m, with)
}
}
}
ids = append(ids, e)
}
sort.Slice(ids, func(a, b int) bool { return ids[a].Module < ids[b].Module })
used := map[string]map[string]bool{}
runner := map[string]string{}
for _, m := range r.Modules {
if with.Held[m.Module] == "" && libraryLeftOut(m, r.Node, with) != "" {
if libraryLeftOut(m, r.Node, with) != "" {
continue
}
for _, u := range m.Libraries {