Keep libraries under groups of their own, and give modules users in them (hq ADR 0328, issue 567)

ADR 0328 supersedes ADR 0325: shared data is a library with one home, kept usable by
its group rather than by ACL entries. The engine now makes each library's group and
each module's user in its libraries' groups; keeps a library's root and requested
sub-paths in its group with group write and setgid on its home, making missing
sub-paths as the root is; waits for the operator's word before touching a root that
holds data, and on the take act walks it by descriptor (no link, same filesystem,
bounded and resumed, files with other names left); holds the containers of a library
whose disk or mount is not there; and puts the tool runner's account in the group.
The ACL keeping, mask repair and watch are gone; an access is again as before ADR 0325.
This commit is contained in:
2026-10-12 22:11:09 +02:00
parent 303d4037e8
commit 2a2be687f3
35 changed files with 2583 additions and 5004 deletions
+87 -34
View File
@@ -262,7 +262,7 @@ func ApplyMindingWindows(
}
report = Report{}
// **No module identity is root** (novox/hq ADR 0325 §11): an identity or a container told a root number refuses
// **No module identity is root** (novox/hq ADR 0328 §15): an identity or a container told a root number refuses
// the declaration whole, before anything of it is made or started.
if err := refuseRootIdentities(d); err != nil {
return report, known, &Error{Resource: "the declaration's identities", Err: err, Done: report}
@@ -349,12 +349,8 @@ func ApplyMindingWindows(
case declaration.TypeUser:
action, detail, err = removeUser(ctx, sys, orphan, run, wanted, handed)
case declaration.TypeAccess:
// The folder is left as it is: what the mesh added to it is taken out by the walk once no access names
// it (novox/hq ADR 0325 §8), never by removing the access's record.
// The path is left as it is (novox/hq ADR 0051); what ADR 0312's grant put an account in is taken back.
action, detail = "forgotten", "an operator-owned path is never the host's to remove"
if _, kept := keptRecord(filepath.Clean(orphan.Target)); kept {
detail += "; the entries the mesh added go once no access names it"
}
gave, left, said := takeBackGranted(ctx, sys, orphan, run, wanted, handed)
if said != "" {
detail = joinDetail(detail, said)
@@ -539,13 +535,6 @@ func ApplyMindingWindows(
// which declared directories were already here: one this apply makes is the mesh's, one found may not be.
refreshRuntimeRoots(ctx)
in.refused = refusedPlaces(d.Resources)
// What every declared access asks of its folder, several accesses of one folder as one (novox/hq ADR 0325 §5),
// and every accessed folder, which no container mounts while it is not there (§9).
// Every tool runner's account that came with no number, read by name here (ADR 0325 §4).
in.runners = ResolveRunners(ctx, d)
in.folders = foldersOf(d.Resources, in.refused)
in.identities = len(d.Identities) > 0
in.accesses = accessPaths(d.Resources, in.refused, in.identities)
in.agentHomes = rootNeverHomes(d)
in.dirsBefore = map[string]bool{}
for _, resource := range d.Resources {
@@ -587,17 +576,46 @@ func ApplyMindingWindows(
sort.Strings(out)
return out
}
// **Every module's identity is made before anything of it is applied** (novox/hq ADR 0325 §3): a directory
// it owns is given to it, and a container is told to run as it. One that cannot be made — its name or numbers
// another's here — fails, naming whose, and its module is not attempted; the rest of the machine is.
// **Every library's group, then every module's identity, is made before anything of it is applied** (novox/hq
// ADR 0328 §3, §4): a directory a module owns is given to it, and its user is in its libraries' groups. One that
// cannot be made — its name or numbers another's here — fails, naming whose, and what needs it is not
// attempted; the rest of the machine is.
var identityFailures []*Error
if len(d.Identities) > 0 {
brokenGroups := map[int]bool{}
if len(d.Groups) > 0 || len(d.Identities) > 0 {
tool, toolErr := toolFor(sys)
for _, id := range d.Identities {
for _, g := range d.Groups {
var outcome Outcome
err := toolErr
if err == nil {
outcome, err = applyIdentity(ctx, tool, id, &known, run)
outcome, err = applyGroup(ctx, tool, g, &known, run)
}
if err != nil {
identityFailures = append(identityFailures, &Error{Resource: "group " + g.Name, Err: err})
log(fmt.Sprintf(" failed group %s: %v", g.Name, err))
brokenGroups[g.GID] = true
continue
}
report.Outcomes = append(report.Outcomes, outcome)
if outcome.Action != "unchanged" {
log(fmt.Sprintf(" %s %s: %s", outcome.Action, outcome.ID, outcome.Detail))
}
}
names := map[int]string{}
for _, g := range d.Groups {
names[g.GID] = g.Name
}
for _, id := range d.Identities {
var outcome Outcome
err := toolErr
for _, gid := range append([]int{id.GID}, id.Groups...) {
if brokenGroups[gid] && err == nil {
err = fmt.Errorf("its library group %s could not be made; nothing of the module was attempted",
names[gid])
}
}
if err == nil {
outcome, err = applyIdentity(ctx, tool, id, names, &known, run)
}
if err != nil {
// Counted with the others once the resources are through: here, before the guard, it would read
@@ -615,6 +633,37 @@ func ApplyMindingWindows(
}
}
}
// **Every library this machine is reached by, before anything mounts it** (ADR 0328 §6, §9, §11): kept on its
// home, its tool runner's account in its group, and the containers that mount one whose disk or mount is not
// there held. A tool runner that joined a group is a change of `library:<name>`, which the tool runner's unit
// names under restart-on to be restarted once.
verdicts := map[string]libraryVerdict{}
for _, l := range d.Libraries {
outcome, v, err := applyLibrary(ctx, l)
verdicts[l.Library] = v
if err == nil {
var moved bool
var said string
moved, said, err = joinRunner(ctx, sys, l, &known, run)
if moved {
changed[LibraryAccess(l.Library)] = true
outcome.Detail = joinDetail(outcome.Detail, said)
if outcome.Action == "unchanged" {
outcome.Action = "updated"
}
}
}
if err != nil {
identityFailures = append(identityFailures, &Error{Resource: LibraryAccess(l.Library), Err: err})
log(fmt.Sprintf(" failed %s (%s): %v", LibraryAccess(l.Library), l.Root, err))
continue
}
report.Outcomes = append(report.Outcomes, outcome)
if outcome.Action != "unchanged" {
log(fmt.Sprintf(" %s %s (%s): %s", outcome.Action, outcome.ID, outcome.Target, outcome.Detail))
}
}
in.held = heldRoots(verdicts, d.Libraries)
for i, resource := range ordered {
if !orphansRemoved && i == guardFirst {
// **Only a guard that is up may let the filter go.** Removing the derived filter's
@@ -1001,14 +1050,22 @@ func ApplyMindingWindows(
// Only the mesh says what is assigned: a carried bundle's silence undeclares no identity and no folder.
if origin == store.OriginDeclared {
// A folder no access names any more has what the mesh added taken out by the next walk (ADR 0325 §8).
forgetUndeclared(in.folders)
// And an identity no longer declared goes, once what ran as it went above (ADR 0325 §3).
// A tool runner no longer put in a library's group leaves it (ADR 0328 §11, ADR 0252 §3).
for _, o := range leaveRunners(ctx, sys, d.Libraries, &known, run, wanted) {
report.Outcomes = append(report.Outcomes, o)
log(fmt.Sprintf(" %s %s: %s", o.Action, o.ID, o.Detail))
}
// And an identity, then a library group, no longer declared goes, once what ran as it went above (ADR 0328
// §3, §4).
if tool, err := toolFor(sys); err == nil {
for _, o := range removeIdentities(ctx, tool, d.Identities, &known, run) {
report.Outcomes = append(report.Outcomes, o)
log(fmt.Sprintf(" %s %s: %s", o.Action, o.ID, o.Detail))
}
for _, o := range removeGroups(ctx, tool, d.Groups, &known, run) {
report.Outcomes = append(report.Outcomes, o)
log(fmt.Sprintf(" %s %s: %s", o.Action, o.ID, o.Detail))
}
}
}
@@ -1139,12 +1196,13 @@ func applyOne(ctx context.Context, sys system.System, r declaration.Resource, ru
if err := refuseMounts(res, in.refused); err != nil {
return begin(r), err
}
// Not created over an accessed folder not there, which the runtime would make as root (novox/hq ADR 0325 §9).
if id := mountsMissing(ctx, res, in.accesses); id != "" {
// Not created over a library whose disk or mount is not there: the runtime would make the root as root,
// and the container's writes would land on the filesystem below (novox/hq ADR 0328 §9).
if name := mountsHeld(res, in.held); name != "" {
out := begin(r)
out.Action = "waiting"
out.Detail = "not created: it mounts the access " + id + ", whose folder or its disk is not there; " +
"created once it is (novox/hq ADR 0325 §9)"
out.Detail = "not created: it mounts the library " + name + ", whose disk or mount is not there; " +
"created once it is (novox/hq ADR 0328 §9)"
return out, nil
}
}
@@ -2196,14 +2254,9 @@ type inputs struct {
// dirsBefore is every declared directory that was on the machine when this apply began (novox/hq issue
// 339): one the apply itself made — a file's parent — is the mesh's, one that was there may not be.
dirsBefore map[string]bool
// folders is what every declared access asks of its folder, by path (novox/hq ADR 0325 §5); accesses every
// accessed folder with its access's id, which no container mounts while it is not there (§9).
folders map[string]*folderWant
// identities is that the declaration carries identities: its controller speaks ADR 0325 for every access.
identities bool
// runners is every access refused for its tool runner's account (not here, or root's), by id.
runners map[string]error
accesses map[string]string
// held is the root of every library whose disk or mount is not there, with its name: no container that mounts
// it is created (novox/hq ADR 0328 §9).
held map[string]string
// handed is every group a record that went this apply handed to a resource still asking for it.
handed Handed
}