From 8c3d3723224daae395a70984ceed47f98a6cd290 Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 30 Oct 2026 18:23:42 +0100 Subject: [PATCH] Compose no placed user from the node-engine's manifest in the controller's bus composer too, as mesh-sdk #136 does (issue 774) mesh-sdk #136 (42988a7) keeps the node-engine's manifest out of every placed user in buscompose: where a placement names it, no user, no process and nothing the tool runner carries; the node's account alone writes the engine's state, and a second writer has the writers table refuse the whole list (met twice on 2026-10-30: mesh-host_acts, then mesh-host_health). The controller still composes the bus's users with its own copy (internal/broker Users), which lacked the guard. The same two predicates, Composed and CarriedByRunner, now guard it. Read on the bus at 18:30: the bus follows the grants part's list (mesh-grants_user-list.sealed), so the controller's copy is not what the bus loads; it still composes the bus module's files and the controller's own grants, where a refusal would stop that composition. Quick fix: the same guard here. Thorough fix: delete the controller's copy of the bus composer and use buscompose (about 18,000 lines of internal/broker with its tests, its genesis list and its golden file). Chose quick: the controller is being deprecated and the grants part already holds the list; the copy goes with the controller's composition of the bus module, not in a sweep. Research 072 piece: 03-domains, mesh-grants (membership and grants leave the controller): until the copy is removed, it composes as the SDK does. --- internal/broker/engine_guard_test.go | 39 ++++++++++++++++++++++++++++ internal/broker/users.go | 19 +++++++++++++- 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 internal/broker/engine_guard_test.go diff --git a/internal/broker/engine_guard_test.go b/internal/broker/engine_guard_test.go new file mode 100644 index 00000000..8ffdd9b4 --- /dev/null +++ b/internal/broker/engine_guard_test.go @@ -0,0 +1,39 @@ +package broker + +import "testing" + +// Sweep 2026-10-30 (issue 774), the guard of mesh-sdk #136 in the controller's copy: where a placement names the +// node-engine's manifest, it adds no user, no process and nothing the tool runner carries; a module beside it does. +func TestTheNodeEnginesManifestComposesNoPlacedUser(t *testing.T) { + engine := Declared{Module: EngineModule, Serves: []string{"health"}, State: []Bucket{{Name: "health"}}, + Processes: []ProcessPrincipal{{Process: "apply"}}} + beside := Declared{Module: "power", Serves: []string{"power_draw"}} + for name, c := range map[string]struct { + assigned []Declared + module bool + }{ + "with the runner": {[]Declared{{Module: RuntimeModule, NoAccount: true}, engine, beside}, false}, + "without the runner": {[]Declared{engine, beside}, true}, + } { + users, err := Users(Records{Nodes: []string{"anchor"}, Assigned: map[string][]Declared{"anchor": c.assigned}}) + if err != nil { + t.Fatalf("%s: %v", name, err) + } + besideSeen := false + for _, u := range users { + if u.Module == EngineModule && (u.Kind == KindModule || u.Kind == KindProcess) { + t.Errorf("%s: the node-engine's manifest composed a %v user", name, u.Kind) + } + besideSeen = besideSeen || u.Module == beside.Module + for _, carried := range u.Carries { + if carried.Module == EngineModule { + t.Errorf("%s: the tool runner carries the node-engine's manifest", name) + } + besideSeen = besideSeen || carried.Module == beside.Module + } + } + if !besideSeen { + t.Errorf("%s: the module beside it is composed nowhere", name) + } + } +} diff --git a/internal/broker/users.go b/internal/broker/users.go index c48b1662..d35375a0 100644 --- a/internal/broker/users.go +++ b/internal/broker/users.go @@ -181,6 +181,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; sweep 2026-10-30, issue + // 774): the guard mesh-sdk #136 put in buscompose, kept here while the controller composes with its own copy. + if !Composed(d) { + continue + } // **A process account is its own, delivered in its own secret** (broker-, novox/hq // ADR 0436 §1, §2), never the module's `broker` — so it is composed whether or not the module // itself has an account, and is never carried by the machine's runtime, which runs as the @@ -227,7 +232,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) { // Never a restricted stream for what the tool runner carries (novox/hq ADR 0376 §1). d.Hears = ComposedHears(d) carried = append(carried, d) @@ -454,3 +459,15 @@ func declaredStreamsOf(r Records) []string { sort.Strings(out) return out } + +// EngineModule is the node-engine's manifest's module name (mesh-sdk buscompose.EngineModule). +const EngineModule = "mesh-host" + +// 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 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 (ADR 0563 §2, §3); granted to another account it is a second writer, and the writers +// table refuses the whole list (issue 774: mesh-host_acts, then mesh-host_health, 2026-10-30). mesh-sdk buscompose.Composed. +func Composed(d Declared) bool { return d.Module != EngineModule } + +// CarriedByRunner says the machine's tool runner carries this placed module (mesh-sdk buscompose.CarriedByRunner). +func CarriedByRunner(d Declared) bool { return Composed(d) && d.RunsAs == "" && runnerServes(d) }