diff --git a/go/buscompose/buscompose_test.go b/go/buscompose/buscompose_test.go index 119acae..dea1de5 100644 --- a/go/buscompose/buscompose_test.go +++ b/go/buscompose/buscompose_test.go @@ -64,7 +64,8 @@ func engineBeyond(user, g string) bool { return false } return strings.HasPrefix(g, "mesh.seat."+EngineSeat+".") && strings.Contains(g, "."+node) || - strings.HasPrefix(g, "$SRV.") || strings.Contains(g, ".node-engine-"+node) + strings.HasPrefix(g, "$SRV.") || strings.Contains(g, ".node-engine-"+node) || + strings.Contains(g, "KV_"+EngineActsBucket) || strings.HasPrefix(g, "$KV."+EngineActsBucket+"."+node+".") } // createsAnAccountsConsumer is the grants part's create and read of a durable consumer an account needs, by its stream diff --git a/go/buscompose/creates_streams_test.go b/go/buscompose/creates_streams_test.go index 6ea8184..98892d1 100644 --- a/go/buscompose/creates_streams_test.go +++ b/go/buscompose/creates_streams_test.go @@ -23,6 +23,8 @@ func TestTheGrantsPartCreatesOnlyDeclaredStreams(t *testing.T) { {Module: GrantsModule, State: []Bucket{{Module: GrantsModule, Name: "assertions"}}, Reads: []string{VaultBusAccounts}}, {Module: RuntimeModule}, }}, + // 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}}}, } users, err := Users(records) if err != nil { @@ -64,6 +66,15 @@ func TestTheGrantsPartCreatesOnlyDeclaredStreams(t *testing.T) { {GrantsModule, "$JS.API.STREAM.CREATE.ASSIGNMENTS", false}, {GrantsModule, "$JS.API.STREAM.INFO.EVENTS", false}, {"mesh-delivery-builder", "$JS.API.STREAM.CREATE.KV_mesh-delivery-builder_steps", false}, + // The node-engine's acts: created by the grants part, written by each node's engine under its own keys alone. + {GrantsModule, "$JS.API.STREAM.CREATE.KV_mesh-host_acts", true}, + {"node.ace", "$JS.API.STREAM.CREATE.KV_mesh-host_acts", false}, + {"node.ace", "$KV.mesh-host_acts.ace.act.hand-over-1", true}, + {"node.ace", "$KV.mesh-host_acts.ace.open.hand-over.abc", true}, + {"node.ace", "$KV.mesh-host_acts.novox.act.hand-over-1", false}, + {"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 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/engine.go b/go/buscompose/engine.go index 95fa03d..501084b 100644 --- a/go/buscompose/engine.go +++ b/go/buscompose/engine.go @@ -11,6 +11,17 @@ import ( // EngineSeat is the seat. const EngineSeat = catalogue.EngineSeat +// EngineModule is the node-engine's manifest's module name, and EngineActsState its state the acts asked through the +// node seat and its own asks are kept in (ADR 0563 §2, §3: "the engine's own state"): the bucket mesh-host_acts, keys +// `.act.`, `.open..` and `.ask.`, each written by that node's engine alone. +const ( + EngineModule = "mesh-host" + EngineActsState = "acts" +) + +// EngineActsBucket is that state's bucket. +var EngineActsBucket = BucketName(EngineModule, EngineActsState) + // engineGrants is what a node's engine is granted to hold its node seat and to ask the operator (ADR 0563 §1, §3): it // answers its own node's verbs (the caller-named subjects are added beside them with every principal's), says the // seat's events about its node, answers discovery for the seat, asks who answers what (the login shell's execute, ADR @@ -26,5 +37,9 @@ func engineGrants(p Principal) (pub, sub []string) { sub = append(sub, announcing(EngineSeat)...) pub = append(pub, discovering()...) tp, ts := seatTrafficGrants(SeatTrafficOf(asks.EngineAsker(p.Node), nil, p.Uses, nil)) + // And its acts and asks, under its own node's keys alone; never the bucket's creation, which is the grants part's. + if own, err := NodeKeysGrants(EngineActsBucket, p.Node); err == nil { + pub = append(pub, own...) + } return append(pub, tp...), append(sub, ts...) } diff --git a/go/buscompose/users.go b/go/buscompose/users.go index 01616d1..5629510 100644 --- a/go/buscompose/users.go +++ b/go/buscompose/users.go @@ -134,6 +134,11 @@ type Records struct { // account agents run as, judged unable to become root by its node-engine, and serves no login shell // execute. A machine absent is not free: no record of a pass is no pass. RootFree map[string]bool + // Engine is the node-engine's own declaration, from its manifest (EngineModule): placed on every node though no node is + // assigned it, since every node runs it (novox/hq ADR 0563 §2, §3). Its state, logs and streams are created by the + // grants part as a placed module's are; it adds no principal, since each node's engine speaks as the node's account. + // Nil where its manifest was not read. + Engine *Declared // Announced is each module's last announcement of the states it keeps (novox/hq ADR 0495, issue 1790), as the // controller keeps it; a module absent has announced nothing yet. Nil where none was read. Announced map[string]announce.States @@ -451,7 +456,15 @@ func runnerServes(d Declared) bool { // name. What the grants part may create, and nothing beside. func declaredStreamsOf(r Records) []string { seen := map[string]bool{} + placed := [][]Declared{} for _, mods := range r.Assigned { + placed = append(placed, mods) + } + // The node-engine's manifest is placed on every node (ADR 0563): its declared state is created as a placed module's. + if r.Engine != nil { + placed = append(placed, []Declared{*r.Engine}) + } + for _, mods := range placed { for _, d := range mods { for _, b := range d.State { seen["KV_"+b.Bucket()] = true diff --git a/go/buscompose/writers.go b/go/buscompose/writers.go index fff55d1..7649cb2 100644 --- a/go/buscompose/writers.go +++ b/go/buscompose/writers.go @@ -245,6 +245,11 @@ var WritersTable = []WriterRow{ {State: "a machine's have", Writer: "the node-engine of the machine, under its own keys `.>`", KeptIn: "key-value " + delivery.HaveBucket + ", twenty revisions of each key", Others: "read through `reads` as node-engine.have", Subjects: kvOf(delivery.HaveBucket), Writes: ownNodeKeys}, + // The acts a node's engine was asked through its node seat, and its own asks (novox/hq ADR 0563 §2, §3): the + // node-engine's declared state, created by the grants part, written by each node's engine under its own keys. + {State: "a machine's hand acts", Writer: "the node-engine of the machine, under its own keys `.>`, compare-and-set", + KeptIn: "key-value " + EngineActsBucket + ", the node-engine's declared state acts", + Others: "read through the node seat's verb acts", Subjects: kvOf(EngineActsBucket), Writes: ownNodeKeys}, // The contents of a declaration's files over 64 KiB (novox/hq ADR 0486 §2): the controller alone writes the bucket, each // object under the digest of its content; a node-engine reads one by its name, with no list and no write. {State: "a declaration's large files", Writer: "controller, each object under the sha256 digest of its content; the " +