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
+11 -17
View File
@@ -328,16 +328,15 @@ type DirectoryJudgements interface {
HearDirectoryJudgements(JudgeDirectoriesFunc)
}
// The take act on an accessed folder, asked over the bus (novox/hq ADR 0325 §8, issue 567).
// The take act on a library, asked over the bus (novox/hq ADR 0328 §7, issue 567).
//
// A folder that already holds data is kept only on the operator's word: the access waits (§7), the controller asks
// the operator once for that folder on that machine (ADR 0259), and on the approved warrant asks this node's engine
// on AskAccessActSubject, signed as a hand-over is and held to the same checks (believed), under a signing context
// of its own, naming the act, the access, its folder as the engine states it, and the warrant. The engine judges
// every value against what it was last declared and what is on disk, answers at once that the act started or why
// it is refused, adds the users' entries to everything under the folder (following no link, entering no other
// filesystem), and states the act on the access's verdict (Act). Adding or dropping a user of a folder already
// taken asks nobody: the engine does it on the apply that declares it.
// A library whose root already holds data is kept only on the operator's word: it waits (§8), the controller asks
// the operator once for that library (ADR 0259), and on the approved warrant asks the library's home engine on
// AskAccessActSubject, signed as a hand-over is and held to the same checks (believed), under a signing context of
// its own, naming the act, the access `library:<name>`, its root as the engine states it, and the warrant. The engine
// judges every value against what it was last declared and what is on disk, answers at once that the act started or
// why it is refused, sets the library's group, group write and setgid on everything under the root (following no
// link, entering no other filesystem), and states the act on the library's verdict (Act).
// AskAccessActSubject is where the controller asks this node's engine to run an act on one access's folder.
func AskAccessActSubject(node string) string { return "mesh.node." + node + ".ask.access-act" }
@@ -345,14 +344,9 @@ func AskAccessActSubject(node string) string { return "mesh.node." + node + ".as
// AccessActContext is prefixed to an act ask's bytes before signing: never another ask's, nor a declaration's.
const AccessActContext = "novox-mesh access-act v2\n"
// The walks under an accessed folder. Only ActTake is asked; ActAdd and ActDrop are what an apply starts.
const (
// ActTake adds the users' entries to the folder and everything under it, on a warrant (ADR 0325 §8).
ActTake = "take"
// ActAdd adds the entries of a user that joined a taken folder; ActDrop removes exactly a dropped user's.
ActAdd = "add"
ActDrop = "drop"
)
// ActTake is the one act asked: the library's group, group write and setgid set on everything under its root, on
// a warrant (ADR 0328 §7).
const ActTake = "take"
// AccessActAsk is an act's own fields, beside a hand-over's (node, path, by, expires, nonce): which act, the
// access by its declared resource id exactly as the declaration names it (`/` allowed), and the warrant the
+2 -2
View File
@@ -66,7 +66,7 @@ type natsLink struct {
searching *nats.Subscription
// judging hears the controller asking for a read-only judgement of this node's directories (issue 358).
judging *nats.Subscription
// acting hears the controller asking for the take act on an access's folder (novox/hq ADR 0325 §8).
// acting hears the controller asking for the take act on a library (novox/hq ADR 0328 §7).
acting *nats.Subscription
node string
// signer is the mesh's key, which a hand-over ask is verified with as a declaration is (issue 356).
@@ -262,7 +262,7 @@ func (l *natsLink) HearDirectoryJudgements(handle JudgeDirectoriesFunc) {
}
// HearAccessActs listens for the controller asking this node to run the take act on an access's folder (novox/hq ADR
// 0325 §8), on this machine's own subject, verified as a hand-over is (AnswerAccessAct). A bus whose user list is older
// 0328 §7), on this machine's own subject, verified as a hand-over is (AnswerAccessAct). A bus whose user list is older
// than the grant refuses the subscription, said by the client; the controller then hears no answer.
func (l *natsLink) HearAccessActs(handle AccessActFunc) {
if handle == nil {
+29 -40
View File
@@ -266,6 +266,14 @@ const GrantContract = 4
// tells the controller this host may be sent those fields; an older host refuses the whole declaration for them.
const IdentityContract = 5
// LibraryContract is the statement of a host that reads a declaration's `groups` and `libraries` and an identity's
// `groups` (novox/hq ADR 0328, which supersedes ADR 0325): it makes each library's group and each module's user in
// its libraries' groups, keeps each library it is home to under its group, holds the containers of a library whose
// disk is not there, states a library it may not keep yet as waiting, and hears the take act for `library:<name>`.
// It keeps no ACL entries: an access's `users` and `taken` are accepted and not acted on. Like the fields before
// it, its presence is what tells the controller this host may be sent those fields.
const LibraryContract = 6
// Health is one statement of every long-running resource's state on this machine (to-be 48 §4). Said in
// every report, as an event on each change, and again every minute while anything is not healthy — so a
// lost statement is not a lost fault.
@@ -304,15 +312,14 @@ const KindAccount = "account"
// starting ReasonUsedAsFound, until the operator hands it to the mesh (`nox node hand-over <node> <path>` on the control-node, novox/hq issue 356).
const KindDirectory = "directory"
// KindAccess is an access whose folder the engine keeps for its users (novox/hq ADR 0325): healthy while every user
// has its effective right on the folder and the sweep's sample under it (§10), waiting while the mesh may not keep
// it yet or its disk is not there (§7), unhealthy otherwise — root's data on its placed filesystem among it (§12).
// KindAccess is an access (novox/hq ADR 0051): no engine since ADR 0328 states one; kept for the controller's reading
// of an older engine's statements.
const KindAccess = "access"
// KindKeptEntries is the mesh's entries on a shared folder that nobody wants and that could not be taken out yet
// (novox/hq ADR 0325 §8): a folder no access names whose disk is away, or entries left on a filesystem no longer at
// a folder. Unhealthy, said under the access that last named it, until the drop walk has run.
const KindKeptEntries = "kept-entries"
// KindLibrary is a library this machine is reached by (novox/hq ADR 0328), said under resource `library:<name>`
// with no module: healthy while it is kept (or, not home, while its mount is there), waiting with an access wait of
// reason keep or disk, unknown while its filesystem could not be named, unhealthy otherwise.
const KindLibrary = "library"
// ReasonUsedAsFound starts the reason of a directory used as found.
const ReasonUsedAsFound = "used as found:"
@@ -426,24 +433,21 @@ type ResourceHealth struct {
// Waits is what a resource stated waiting waits for (novox/hq ADR 0283): each one own secret or one
// setting of the module by name, the part, and what the operator gives, in words. Absent otherwise.
Waits []health.Wait `json:"waits,omitempty"`
// AccessWaits is what an access stated waiting waits for (novox/hq ADR 0325 §7). Beside Waits rather than in
// AccessWaits is what a library stated waiting waits for (novox/hq ADR 0328 §8, §9). Beside Waits rather than in
// it because mesh-sdk's health.Wait names a secret or a setting and nothing else; it moves there once that type
// has a kind. Absent otherwise.
AccessWaits []AccessWait `json:"access-waits,omitempty"`
// Act is the last walk under an access's folder (ADR 0325 §8): a take on a warrant, or an add or a drop of a
// user's entries. Absent on every other verdict, and on a folder never walked.
// Act is the last walk under a library's root (ADR 0328 §7): the take on a warrant. Absent on every other
// verdict, and on a library never walked.
Act *AccessActRecord `json:"act,omitempty"`
// Entries is the listing of a kept folder's users, for an access's verdict (ADR 0325 §4): each identity, its
// use, and a note beside the tool runner's entry. Absent on a folder not kept.
Entries []KeptEntry `json:"entries,omitempty"`
}
// WaitKindAccess is the kind of a wait on an access (novox/hq ADR 0325 §7).
// WaitKindAccess is the kind of a wait on a library (novox/hq ADR 0328 §8, §9; first ADR 0325 §7).
const WaitKindAccess = "access"
// AccessWait is one access waiting for the operator: the access by its module's own id for it, the part (its
// module), and what the operator gives, in words with no path (ADR 0234 §6): "let the mesh keep the shared folder
// movies", or "mount the disk that holds books".
// AccessWait is one library waiting for the operator (ADR 0328 §8, §9): the access `library:<name>`, the part (the
// library), and what the operator gives, in words with no path (ADR 0234 §6): "let the mesh keep the library media",
// or "mount the disk that holds the library media".
type AccessWait struct {
// Kind is WaitKindAccess.
Kind string `json:"kind"`
@@ -456,31 +460,14 @@ type AccessWait struct {
// Reason is what the wait is for, for the controller to read without parsing words: WaitKeep (the operator's
// word to keep it) or WaitDisk (its disk mounted).
Reason string `json:"reason"`
// ACL is said on a keep wait whose folder's filesystem refuses access entries now: ACLNeedsEnabling when the
// take act will let it (a ZFS dataset's acltype, which the ask then says it sets), ACLUnsupported when no act
// can (the operator mounts it with ACLs). Absent when it allows them.
ACL string `json:"acl,omitempty"`
}
// The reasons of an access wait, and what its filesystem says of ACLs.
// The reasons of an access wait.
const (
WaitKeep = "keep"
WaitDisk = "disk"
ACLNeedsEnabling = "needs-enabling"
ACLUnsupported = "unsupported"
WaitKeep = "keep"
WaitDisk = "disk"
)
// KeptEntry is one user of a kept folder as its listing says it.
type KeptEntry struct {
Identity string `json:"identity"`
UID int `json:"uid"`
Use string `json:"use"`
From string `json:"from"`
// Note is said beside the tool runner's entry (ADR 0325 §4): it stands for every module whose tools use the
// folder, at the widest use any of them declares.
Note string `json:"note,omitempty"`
}
// The states of a walk under an accessed folder.
const (
ActRunning = "running"
@@ -491,10 +478,10 @@ const (
ActFailed = "failed"
)
// AccessActRecord is one walk under an access's folder, as the engine records it (novox/hq ADR 0325 §8). Counts
// only: never a path.
// AccessActRecord is one walk under a library's root, as the engine records it (novox/hq ADR 0328 §7). Counts only:
// never a path.
type AccessActRecord struct {
// Act is ActTake, ActAdd or ActDrop.
// Act is ActTake.
Act string `json:"act"`
Access string `json:"access"`
Warrant string `json:"warrant,omitempty"`
@@ -511,6 +498,8 @@ type AccessActRecord struct {
Links int `json:"links,omitempty"`
Other int `json:"other,omitempty"`
Mounts int `json:"mounts,omitempty"`
// Shared is the files with another name elsewhere (more than one link), left as they are.
Shared int `json:"shared,omitempty"`
// Why is what stopped or failed it, in words with no path.
Why string `json:"why,omitempty"`
}
+1 -1
View File
@@ -63,7 +63,7 @@ type Queue struct {
// link (novox/hq issue 358). Nil answers every such ask with a refusal.
JudgeDirectories JudgeDirectoriesFunc
// AccessAct judges and runs the take act on an access's folder, asked by the controller on an approved
// warrant (novox/hq ADR 0325 §8). Nil answers every such ask with a refusal.
// warrant (novox/hq ADR 0328 §7). Nil answers every such ask with a refusal.
AccessAct AccessActFunc
Say Announce
Timeout time.Duration
+1 -1
View File
@@ -252,7 +252,7 @@ func serve(ctx context.Context, link Link, m Membership, queue *Queue, say Annou
if j, ok := link.(DirectoryJudgements); ok {
j.HearDirectoryJudgements(queue.JudgeDirectories)
}
// And asking for the take act on an access's folder, on a warrant (novox/hq ADR 0325 §8).
// And asking for the take act on a library, on a warrant (novox/hq ADR 0328 §7).
if s, ok := link.(AccessActs); ok {
s.HearAccessActs(queue.AccessAct)
}