From 222d605f6f61d928ac684e1fcff516c9e1df4a1e Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 28 Oct 2026 09:43:40 +0100 Subject: [PATCH] =?UTF-8?q?The=20node=20seat=20node-engine:=20held=20by=20?= =?UTF-8?q?every=20node's=20engine,=20its=20six=20verbs=20and=20three=20ev?= =?UTF-8?q?ents,=20and=20no=20module=20may=20claim=20it=20(issue=20774,=20?= =?UTF-8?q?ADR=200563=20=C2=A71)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go/catalogue/engine_seat.go | 57 +++++++++++++++++++++++++++++++++++++ go/catalogue/seats.go | 16 ++++++++++- go/catalogue/seats_test.go | 24 ++++++++++++++-- 3 files changed, 93 insertions(+), 4 deletions(-) create mode 100644 go/catalogue/engine_seat.go diff --git a/go/catalogue/engine_seat.go b/go/catalogue/engine_seat.go new file mode 100644 index 0000000..71a8afd --- /dev/null +++ b/go/catalogue/engine_seat.go @@ -0,0 +1,57 @@ +package catalogue + +// The node seat node-engine (novox/hq ADR 0563 §1): held on every node by that node's engine (mesh-host), which serves +// its verbs on its own bus account. No module claims it and no assignment holds it: what the mesh knows of its holder +// is the node's record and the engine's haves (ADR 0563 §1 changes ADR 0110 §6 and §7 for this one seat). +// +// Its verbs carry no signature (§7): the runtime proves the caller, which matters only for the record. The read-only +// ones change nothing or are bounded in the engine's own state (§2); hand-over and access-act perform nothing on the +// call, and act only on the operator's warrant to the engine's own ask (§3). + +// EngineSeat is the node seat every node's engine holds. +const EngineSeat = "node-engine" + +// EngineVerbs are the node-engine seat's verbs, in the order ADR 0563 §1 names them. +func EngineVerbs() []Verb { + return []Verb{ + {Name: "hand-over", Description: "Hand a directory this machine uses as found to the mesh (novox/hq ADR 0563 §3, " + + "§5): the engine judges the path as it does every hand-over (absolute, as it states it, used as found, " + + "reached through no link) and refuses in words, or asks the operator on their channel at the level approve, " + + "binding the owner, group and mode it will set. Nothing changes on the call: only the operator's yes hands " + + "it over. One ask is open per path; acts shows where it stands.", + Input: schema(map[string]string{"path": "the directory's absolute path, as the engine states it", + "why": "why it is asked, in words the operator reads"}, []string{"path"})}, + {Name: "access-act", Description: "Ask the operator for the take act on a library whose root holds data outside its " + + "group (novox/hq ADR 0328 §7, ADR 0563 §3, §4), on the library's home engine: the access as declared and its " + + "root as last declared, judged and refused in words or asked at the level approve. Only the operator's yes " + + "sets the library's group, group write and setgid under its root. One ask is open per access.", + Input: schema(map[string]string{"act": "the act: take", "access": "the access, library:", + "why": "why it is asked, in words the operator reads"}, []string{"act", "access"})}, + {Name: "judge-directories", Description: "Which of this machine's declared directories the engine would refuse to " + + "make or keep, one line each (novox/hq ADR 0563 §2). Read-only: changes no owner or mode.", + Input: schema(map[string]string{}, nil)}, + {Name: "setuid-search", Description: "Throw away the engine's last search for setuid programs and start a full one " + + "(novox/hq ADR 0563 §2); until it completes root-free answers that the machine is not judged yet. Refused while " + + "one runs, and when one was started within the hour, naming when the next may start.", + Input: schema(map[string]string{"why": "why it is asked, in words"}, nil)}, + {Name: "root-free", Description: "Whether this machine is root-free (novox/hq ADR 0259 §8, ADR 0563 §6): free only " + + "on the engine's own verdict, at most fifteen minutes old, that the account agents run as cannot become root " + + "without a person, and the login shell's execute not served here. Anything else is no pass, and says why. " + + "Answered by this machine's engine alone.", + Input: schema(map[string]string{}, nil)}, + {Name: "acts", Description: "Every act this machine's engine was asked through this seat (novox/hq ADR 0563 §9): " + + "each hand-over, access act and setuid search, open, done or refused, with who asked, the ask and the " + + "operator's warrant. Read-only.", + Input: schema(map[string]string{"limit": "at most this many, newest"}, nil)}, + } +} + +// EngineEvents are what the seat says of an act, each naming the act, the path or access, the caller as the runtime +// proved it and the ask (ADR 0563 §1). +var EngineEvents = []string{"asked", "done", "refused"} + +// engineSeat is the seat's row in the closed set. +func engineSeat() Seat { + return Seat{Name: EngineSeat, Scope: ScopeNode, Serves: EngineVerbs(), Emits: append([]string(nil), EngineEvents...), + HeldByTheEngine: true, Decision: "novox/hq ADR 0563"} +} diff --git a/go/catalogue/seats.go b/go/catalogue/seats.go index 69343ac..c8ed163 100644 --- a/go/catalogue/seats.go +++ b/go/catalogue/seats.go @@ -67,6 +67,10 @@ type Seat struct { // names paths. Compiled, never stored, like Receives: adding or taking away a path is a decision, // recorded, and the store's rows carry no column for it. Paths []SeatPath + // HeldByTheEngine says the seat is held on every node by that node's engine, which serves its verbs on its own bus + // account (novox/hq ADR 0563 §1): no module claims it and no assignment holds it. Compiled, never stored, like + // Receives. + HeldByTheEngine bool // Decision is the record that made it a seat. Decision string } @@ -456,6 +460,8 @@ var defaultSeats = append([]Seat{ // A node's files, read and managed (novox/hq ADR 0394, files.go): beside node-reads until mesh-files is assigned // on every node, then node-reads goes (ADR 0394 §9). {Name: FilesSeat, Scope: ScopeNode, Decision: "novox/hq ADR 0394", Serves: filesVerbs()}, + // The node's engine's own verbs and asks (novox/hq ADR 0563 §1): held by the engine on every node, by no module. + engineSeat(), }, // The graphical session's roles (novox/hq ADR 0208), last because they are a workstation's. graphicalSessionSeats()...) @@ -529,7 +535,8 @@ func UseSeats(s []Seat) { row.Paths = d.Paths row.Replicated = d.Replicated row.PulledByKind = d.PulledByKind - row.Yields = d.Yields // never stored (ADR 0553) + row.Yields = d.Yields // never stored (ADR 0553) + row.HeldByTheEngine = d.HeldByTheEngine // never stored (ADR 0563) row.Serves = optionalAsCompiled(row.Serves, d.Serves) } merged = append(merged, row) @@ -642,6 +649,13 @@ func claimProblems(m Manifest) []string { } for _, c := range m.Claims { + if s, known := SeatNamed(c.Name); known && s.HeldByTheEngine { + // The node's engine holds it on every node, and no module may (novox/hq ADR 0563 §1): a module answering + // root-free or a hand-over would be an agent answering it. + problems = append(problems, fmt.Sprintf("%s claims %s, which every node's engine holds itself and no "+ + "module may (novox/hq ADR 0563 §1)", m.Module, c.Name)) + continue + } if _, known := SeatNamed(c.Name); known { // **A seat's scope and what it delivers are not judged here** (novox/hq ADR 0122). // This function runs wherever a manifest is parsed, and one of those places is the diff --git a/go/catalogue/seats_test.go b/go/catalogue/seats_test.go index 4e58e45..79036d6 100644 --- a/go/catalogue/seats_test.go +++ b/go/catalogue/seats_test.go @@ -64,6 +64,24 @@ func TestTheSeatsAreAClosedSetAndEachNamesItsDecision(t *testing.T) { if strings.Join(checkerVerbs, ",") != "try,try-read,try-end" { t.Errorf("the checker seat serves %v", checkerVerbs) } + // The node seat node-engine is the node's engine's (novox/hq ADR 0563 §1): at node scope, delivering nothing, its six + // verbs and three events, held by no assignment, and a module's claim of it refused. + if engine, ok := SeatNamed(EngineSeat); !ok || engine.Scope != ScopeNode || engine.Delivers != "" || !engine.HeldByTheEngine { + t.Errorf("node-engine is %+v, not a node seat delivering nothing that the engine holds", engine) + } else { + var verbs []string + for _, v := range engine.Serves { + verbs = append(verbs, v.Name) + } + if strings.Join(verbs, ",") != "hand-over,access-act,judge-directories,setuid-search,root-free,acts" || + strings.Join(engine.Emits, ",") != "asked,done,refused" { + t.Errorf("node-engine serves %v and emits %v", verbs, engine.Emits) + } + } + if _, err := ParseManifest([]byte(`{"module":"thing","version":"1","claims":[{"name":"node-engine","scope":"node"}]}`)); err == nil || + !strings.Contains(err.Error(), "node-engine") { + t.Errorf("a module's claim of node-engine was not refused: %v", err) + } // Forty-six with the three build seats replacing node-build-agent and the retired mesh-build-machine (novox/hq // ADR 0525 §2, §10); forty-five with node-files (novox/hq ADR 0394); forty-four again once node-reads goes. // Forty-four with node-provisioner (novox/hq ADR 0349 clause 3); forty-three with mesh-provisioner (novox/hq ADR 0349 clause 3); forty-two with mesh-config (novox/hq ADR 0348 §9); forty-one with node-reads (novox/hq ADR 0310); forty with node-nfs-server and node-mounts (novox/hq ADR 0263); thirty-eight with node-resolver (novox/hq ADR 0247); thirty-seven with mesh-delivery (novox/hq ADR 0239); thirty-six since node-resolver-config retired @@ -77,9 +95,9 @@ func TestTheSeatsAreAClosedSetAndEachNamesItsDecision(t *testing.T) { // node-container-runtime (ADR 0207); nineteen with node-environment and node-login-shell (ADR 0203, // ADR 0204); seventeen with node-build-agent (ADR 0190). One fewer once the retired // mesh-build-machine row goes, when no registered manifest claims it. - // Forty-eight with mesh-delivery-checker (novox/hq ADR 0547 §10, issue 774). - if len(Seats()) != 48 { - t.Errorf("the mesh defines %d seats rather than 48; the set is closed, so a change here is "+ + // Forty-eight with mesh-delivery-checker (novox/hq ADR 0547 §10, issue 774); forty-nine with node-engine (novox/hq ADR 0563 §1). + if len(Seats()) != 49 { + t.Errorf("the mesh defines %d seats rather than 49; the set is closed, so a change here is "+ "a decision (novox/hq ADR 0110): %s", len(Seats()), seatNames()) } }