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.
This commit is contained in:
@@ -22,6 +22,10 @@ func TestTheGrantsPartCreatesOnlyDeclaredStreams(t *testing.T) {
|
|||||||
Streams: []ModuleStream{{Module: "mesh-delivery-builder", Name: "BUILDASKS", Events: []string{"ask"}}}},
|
Streams: []ModuleStream{{Module: "mesh-delivery-builder", Name: "BUILDASKS", Events: []string{"ask"}}}},
|
||||||
{Module: GrantsModule, State: []Bucket{{Module: GrantsModule, Name: "assertions"}}, Reads: []string{VaultBusAccounts}},
|
{Module: GrantsModule, State: []Bucket{{Module: GrantsModule, Name: "assertions"}}, Reads: []string{VaultBusAccounts}},
|
||||||
{Module: RuntimeModule},
|
{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).
|
// 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}}},
|
Engine: &Declared{Module: EngineModule, State: []Bucket{{Module: EngineModule, Name: EngineActsState}}},
|
||||||
@@ -32,6 +36,9 @@ func TestTheGrantsPartCreatesOnlyDeclaredStreams(t *testing.T) {
|
|||||||
}
|
}
|
||||||
perms := map[string]Permissions{}
|
perms := map[string]Permissions{}
|
||||||
for _, u := range users {
|
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 {
|
if u.Kind == KindNode || u.Kind == KindNodeTools {
|
||||||
p, err := PermissionsFor(u)
|
p, err := PermissionsFor(u)
|
||||||
if err != nil {
|
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.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},
|
{"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},
|
{"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.
|
// 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, "$JS.API.STREAM.CREATE.OBJ_mesh-grants-user-list", true},
|
||||||
{GrantsModule, "$O.mesh-grants-user-list.C.abc", true},
|
{GrantsModule, "$O.mesh-grants-user-list.C.abc", true},
|
||||||
|
|||||||
+18
-1
@@ -200,6 +200,11 @@ func Users(r Records) ([]Principal, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, d := range r.Assigned[node] {
|
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
|
// 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.
|
// granted nothing, here and in the runtime carrying the module.
|
||||||
d = withReadLogs(withNamedReaders(d), logs)
|
d = withReadLogs(withNamedReaders(d), logs)
|
||||||
@@ -253,7 +258,7 @@ func Users(r Records) ([]Principal, error) {
|
|||||||
if runtimeHere {
|
if runtimeHere {
|
||||||
var carried []Declared
|
var carried []Declared
|
||||||
for _, d := range r.Assigned[node] {
|
for _, d := range r.Assigned[node] {
|
||||||
if d.RunsAs == "" && runnerServes(d) {
|
if CarriedByRunner(d) {
|
||||||
d = withReadLogs(withNamedReaders(d), logs)
|
d = withReadLogs(withNamedReaders(d), logs)
|
||||||
// Never a restricted stream for what the tool runner carries (novox/hq ADR 0376 §1).
|
// Never a restricted stream for what the tool runner carries (novox/hq ADR 0376 §1).
|
||||||
d.Hears = ComposedHears(d)
|
d.Hears = ComposedHears(d)
|
||||||
@@ -450,6 +455,18 @@ func boundBucketsOf(r Records) []string {
|
|||||||
return out
|
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,
|
// 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,
|
// 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
|
// 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
|
||||||
|
|||||||
Reference in New Issue
Block a user