From 6e78203959523f39ba6b6a8384367043baf94505 Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 30 Oct 2026 16:30:27 +0100 Subject: [PATCH] Compose no placed user from the node-engine's manifest, so the tool runner never becomes a second writer of the engine's state (issue 774) Second meeting of the engine-state-in-runner kind (mesh-host_acts this morning, mesh-host_health now); thorough: the runner never carries the engine module. One predicate, Composed, keeps the engine out of the placed loop (no user, no process, nothing carried); CarriedByRunner is the one test of what the runner carries. The engine's grants stay the node account's, its state created through Records.Engine. --- go/buscompose/creates_streams_test.go | 12 ++++++++++++ go/buscompose/users.go | 19 ++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/go/buscompose/creates_streams_test.go b/go/buscompose/creates_streams_test.go index 98892d1..73047bf 100644 --- a/go/buscompose/creates_streams_test.go +++ b/go/buscompose/creates_streams_test.go @@ -22,6 +22,10 @@ func TestTheGrantsPartCreatesOnlyDeclaredStreams(t *testing.T) { Streams: []ModuleStream{{Module: "mesh-delivery-builder", Name: "BUILDASKS", Events: []string{"ask"}}}}, {Module: GrantsModule, State: []Bucket{{Module: GrantsModule, Name: "assertions"}}, Reads: []string{VaultBusAccounts}}, {Module: RuntimeModule}, + // The node-engine's manifest as a placement names it (issue 774, met twice on 2026-10-30): no account of its + // own, so the runner would carry it, and its state is the node's to write alone. + {Module: EngineModule, NoAccount: true, Consumes: []string{"restic.backed-up"}, + State: []Bucket{{Module: EngineModule, Name: "health"}, {Module: EngineModule, Name: EngineActsState}}}, }}, // The node-engine's own manifest, placed on every node though no node is assigned it (novox/hq ADR 0563 §2, §3). Engine: &Declared{Module: EngineModule, State: []Bucket{{Module: EngineModule, Name: EngineActsState}}}, @@ -32,6 +36,9 @@ func TestTheGrantsPartCreatesOnlyDeclaredStreams(t *testing.T) { } perms := map[string]Permissions{} for _, u := range users { + if u.Module == EngineModule { + t.Errorf("the node-engine's manifest composed a user of its own: %s", u.Username()) + } if u.Kind == KindNode || u.Kind == KindNodeTools { p, err := PermissionsFor(u) if err != nil { @@ -75,6 +82,11 @@ func TestTheGrantsPartCreatesOnlyDeclaredStreams(t *testing.T) { {"node.ace", "$JS.API.DIRECT.GET.KV_mesh-host_acts.$KV.mesh-host_acts.ace.ask.hand-over-1", true}, {"node.ace", "$JS.API.DIRECT.GET.KV_mesh-host_acts.$KV.mesh-host_acts.novox.ask.hand-over-1", false}, {"novox.node-tools", "$KV.mesh-host_acts.novox.act.hand-over-1", false}, + // The runner never carries the engine (issue 774): its health report is the node's own key alone. + {"novox.node-tools", "$KV.mesh-host_health.novox", false}, + {"novox.node-tools", "$KV.mesh-host_acts.novox.ask.x", false}, + {"node.novox", "$KV.mesh-host_health.novox", true}, + {"node.novox", "$KV.mesh-host_health.ace", false}, // The sealed user list (ADR 0547 clause 7): the grants part writes its own bucket and no other. {GrantsModule, "$JS.API.STREAM.CREATE.OBJ_mesh-grants-user-list", true}, {GrantsModule, "$O.mesh-grants-user-list.C.abc", true}, diff --git a/go/buscompose/users.go b/go/buscompose/users.go index c370f5d..662d38b 100644 --- a/go/buscompose/users.go +++ b/go/buscompose/users.go @@ -200,6 +200,11 @@ func Users(r Records) ([]Principal, error) { } } for _, d := range r.Assigned[node] { + // **The node-engine's manifest is no placed module's** (novox/hq ADR 0563 §2, §3; issue 774): where a placement + // names it, it adds no user, no process and nothing the runner carries; its grants are the node's account's. + if !Composed(d) { + continue + } // A state with named readers is read by them alone (ADR 0547 clause 9): a read named for another module is // granted nothing, here and in the runtime carrying the module. d = withReadLogs(withNamedReaders(d), logs) @@ -253,7 +258,7 @@ func Users(r Records) ([]Principal, error) { if runtimeHere { var carried []Declared for _, d := range r.Assigned[node] { - if d.RunsAs == "" && runnerServes(d) { + if CarriedByRunner(d) { d = withReadLogs(withNamedReaders(d), logs) // Never a restricted stream for what the tool runner carries (novox/hq ADR 0376 §1). d.Hears = ComposedHears(d) @@ -450,6 +455,18 @@ func boundBucketsOf(r Records) []string { return out } +// Composed says a placed module is composed as one: its own user, its processes and what the runner carries of it. +// The node-engine's manifest (EngineModule) is not, though a placement may name it: each node's engine speaks as the +// node's account, which alone writes the engine's state under its own key (engineGrants, novox/hq ADR 0563 §2, §3), and +// Records.Engine creates that state. Granted to another account it is a second writer, and the writers table refuses +// the whole list (issue 774: met for mesh-host_acts and then mesh-host_health, 2026-10-30). +func Composed(d Declared) bool { return d.Module != EngineModule } + +// CarriedByRunner says the machine's tool runner carries this placed module: a module composed at all (Composed) that +// runs as no account of its own and has something for the runner to serve (runnerServes). The one test of what the +// runner's grants are composed from. +func CarriedByRunner(d Declared) bool { return Composed(d) && d.RunsAs == "" && runnerServes(d) } + // runnerServes says the machine's tool runner has something of this module to serve (novox/hq ADR 0436 §1, // ADR 0437 §4, issue 1329): tools or loads it answers, a seat it holds, event families a session waits on, // a bundle the runtime loads (Loads), or a check its health asks. A module with none of these is a process of its own on its own credential, and