Merge pull request 'Grant a module's bus state asked in requires to the accounts that run its code (issue 1966, recovery)' (#188) from fix/1966-requires-state into main
This commit was merged in pull request #188.
This commit is contained in:
@@ -3051,6 +3051,93 @@ func TestGrants(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Issue 1966 (F2), the conformance row: an entry asked in `requires` is granted where its old section is. For a
|
||||||
|
// key-value entry and an object store, the old section and the new form compose the same subjects for the module's
|
||||||
|
// own account, its process naming it and the tool runner carrying it, and issue the same state; a stream is granted
|
||||||
|
// its own subjects (the bus module names them under mesh.bus.<module>.<local>). Another module's account is granted
|
||||||
|
// nothing of them, and a binding naming another module's entry is granted to nobody.
|
||||||
|
t.Run("a requires entry is granted where its old section is", func(t *testing.T) {
|
||||||
|
runner := buscompose.Declared{Module: "node-tools", ToolRunner: true, Serves: []string{"node_tools"}}
|
||||||
|
other := buscompose.Declared{Module: "mod-d", Serves: []string{"mod_d"}}
|
||||||
|
withC := func(c buscompose.Declared) buscompose.Records {
|
||||||
|
return buscompose.Records{Nodes: []string{"a"}, Assigned: map[string][]buscompose.Declared{"a": {runner, other, c}}}
|
||||||
|
}
|
||||||
|
proc := []buscompose.ProcessPrincipal{{Process: "p", State: []string{"kept"}}}
|
||||||
|
old := buscompose.Declared{Module: "mod-c", Serves: []string{"mod_c"}, Processes: proc,
|
||||||
|
State: []buscompose.Bucket{{Module: "mod-c", Name: "kept"}}, Objects: []buscompose.ObjectBucket{{Module: "mod-c", Name: "files", MaxMiB: 64}}}
|
||||||
|
kv := membership.BusBinding{Local: "kept", Storage: catalogue.StorageKeyValue, Template: catalogue.TemplateState, Bucket: "mod-c_kept"}
|
||||||
|
obj := membership.BusBinding{Local: "files", Storage: catalogue.StorageObjectStore, Template: catalogue.TemplateObjects, ObjectStore: "mod-c_files"}
|
||||||
|
seen := membership.BusBinding{Local: "seen", Storage: catalogue.StorageStream, Template: catalogue.TemplateEvents, Stream: "mod-c_seen", Subject: "mesh.bus.mod-c.seen"}
|
||||||
|
moved := buscompose.Declared{Module: "mod-c", Serves: []string{"mod_c"}, Processes: proc, Bindings: []membership.BusBinding{kv, obj}}
|
||||||
|
perms := func(r buscompose.Records) map[string]buscompose.Permissions {
|
||||||
|
users, err := buscompose.Users(r)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
out := map[string]buscompose.Permissions{}
|
||||||
|
for _, u := range users {
|
||||||
|
p, err := buscompose.PermissionsFor(u)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
name := string(u.Kind) + " " + u.Module + " " + u.Process
|
||||||
|
p.Publish, p.Subscribe = slices.Compact(slices.Sorted(slices.Values(p.Publish))), slices.Compact(slices.Sorted(slices.Values(p.Subscribe)))
|
||||||
|
out[name] = p
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
was, now := perms(withC(old)), perms(withC(moved))
|
||||||
|
kinds := map[buscompose.Kind]bool{}
|
||||||
|
for name, p := range was {
|
||||||
|
kinds[buscompose.Kind(strings.Fields(name)[0])] = true
|
||||||
|
if !slices.Equal(p.Publish, now[name].Publish) || !slices.Equal(p.Subscribe, now[name].Subscribe) {
|
||||||
|
t.Errorf("%s: the new form composes otherwise than the old section:\nold publish %v\nnew publish %v\nold subscribe %v\nnew subscribe %v",
|
||||||
|
name, p.Publish, now[name].Publish, p.Subscribe, now[name].Subscribe)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, k := range []buscompose.Kind{buscompose.KindModule, buscompose.KindProcess, buscompose.KindNodeTools} {
|
||||||
|
if !kinds[k] {
|
||||||
|
t.Errorf("no %s principal composed: %v", k, slices.Collect(maps.Keys(was)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, subject := range []string{"$KV.mod-c_kept.x", "$O.mod-c_files.M.x", "$JS.API.STREAM.INFO.KV_mod-c_kept"} {
|
||||||
|
for name, p := range now {
|
||||||
|
if name == string(buscompose.KindModule)+" mod-c " || name == string(buscompose.KindNodeTools)+" node-tools " {
|
||||||
|
if !buscompose.MayPublish(p, subject) {
|
||||||
|
t.Errorf("%s may not publish %s", name, subject)
|
||||||
|
}
|
||||||
|
} else if strings.Contains(name, " mod-d ") && buscompose.MayPublish(p, subject) {
|
||||||
|
t.Errorf("another module's account %s may publish %s", name, subject)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The same state issued: the old section's, and the binding's through mesh-sdk's one rule.
|
||||||
|
oldIssued := buscompose.MembershipFor("a", old, buscompose.Assignments{})
|
||||||
|
newIssued := buscompose.MembershipFor("a", moved, buscompose.Assignments{})
|
||||||
|
wantKept, _ := oldIssued.StateIssued("kept")
|
||||||
|
if gotKept, ok := newIssued.StateIssued("kept"); !ok || gotKept != wantKept || len(newIssued.Bindings) != 2 {
|
||||||
|
t.Errorf("the issued state differs: old %+v, new %+v (%v), bindings %+v", wantKept, gotKept, ok, newIssued.Bindings)
|
||||||
|
}
|
||||||
|
// A stream is granted its own subjects to the module and the runner; a binding naming another module's entry, to nobody.
|
||||||
|
streamed := moved
|
||||||
|
streamed.Bindings = append(slices.Clone(moved.Bindings), seen,
|
||||||
|
membership.BusBinding{Local: "theirs", Storage: catalogue.StorageKeyValue, Bucket: "mod-d_kept"},
|
||||||
|
membership.BusBinding{Local: "elsewhere", Storage: catalogue.StorageStream, Stream: "mod-c_elsewhere", Subject: "mesh.bus.mod-d.elsewhere"})
|
||||||
|
for name, p := range perms(withC(streamed)) {
|
||||||
|
owner := name == string(buscompose.KindModule)+" mod-c " || name == string(buscompose.KindNodeTools)+" node-tools "
|
||||||
|
for _, subject := range []string{"mesh.bus.mod-c.seen.x", "$JS.API.STREAM.MSG.GET.mod-c_seen"} {
|
||||||
|
if buscompose.MayPublish(p, subject) != owner {
|
||||||
|
t.Errorf("%s may publish %s: %v", name, subject, !owner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, subject := range []string{"$KV.mod-d_kept.x", "$JS.API.STREAM.INFO.KV_mod-d_kept", "mesh.bus.mod-d.elsewhere.x"} {
|
||||||
|
if buscompose.MayPublish(p, subject) {
|
||||||
|
t.Errorf("%s may publish %s, another module's entry", name, subject)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// TLS-5 (A7's request; ADR 0615 §5): the tool runner, which consumes the owners' views events and keeps a findings state,
|
// TLS-5 (A7's request; ADR 0615 §5): the tool runner, which consumes the owners' views events and keeps a findings state,
|
||||||
// gets its durable consumer on each views stream, made by the part, and its own findings bucket, as any manifest's.
|
// gets its durable consumer on each views stream, made by the part, and its own findings bucket, as any manifest's.
|
||||||
t.Run("the tool runner's consumes of the owners' views get their consumers, and its findings state its grant", func(t *testing.T) {
|
t.Run("the tool runner's consumes of the owners' views get their consumers, and its findings state its grant", func(t *testing.T) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||||
|
"git.novox.be/novox/mesh-sdk/go/membership"
|
||||||
)
|
)
|
||||||
|
|
||||||
// **The bus module's process has a bus account of its own** (novox/hq ADR 0595 §4, §5; design 25 § 12.2, § 12.3;
|
// **The bus module's process has a bus account of its own** (novox/hq ADR 0595 §4, §5; design 25 § 12.2, § 12.3;
|
||||||
@@ -224,3 +225,43 @@ func withoutActing(served []string) []string {
|
|||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// boundGrants are a module's bus state asked in `requires`, as the grants of the account running its code take it
|
||||||
|
// (ADR 0595 §1, ADR 0596 §2, design 25 § 12.2; issue 1966): the local names of its key-value entries, granted as an old
|
||||||
|
// `state` is; the bus names of its object stores, granted as an old `objects` is; and what its streams (events, work
|
||||||
|
// queue, log, bare) are published and read on. A ledger has its own account (ledger.go) and is not among them.
|
||||||
|
type boundGrants struct {
|
||||||
|
States, Objects, Streams []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// boundOf is one module's bindings as boundGrants, for its own account and the tool runner carrying it, through this one
|
||||||
|
// function. **Only the owner reaches it**: a binding is granted only where it names the module's own entry, its names on
|
||||||
|
// the bus being the mesh's `<module>_<local>` (BusStateStreams) and a stream's subjects under `mesh.bus.<module>.<local>`;
|
||||||
|
// a binding naming any other is granted nothing.
|
||||||
|
func boundOf(module string, bindings []membership.BusBinding) boundGrants {
|
||||||
|
var g boundGrants
|
||||||
|
for _, b := range bindings {
|
||||||
|
base := module + "_" + b.Local
|
||||||
|
if !safeSubject.MatchString(module) || !safeSubject.MatchString(b.Local) || strings.Contains(b.Local, "_") || strings.Contains(module, "_") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch b.Storage {
|
||||||
|
case catalogue.StorageKeyValue:
|
||||||
|
if b.Bucket == base {
|
||||||
|
g.States = append(g.States, b.Local)
|
||||||
|
}
|
||||||
|
case catalogue.StorageObjectStore:
|
||||||
|
if b.ObjectStore == base {
|
||||||
|
g.Objects = append(g.Objects, base)
|
||||||
|
}
|
||||||
|
case catalogue.StorageStream:
|
||||||
|
if b.Stream != base || b.Subject != "mesh.bus."+module+"."+b.Local {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
g.Streams = append(g.Streams, b.Subject+".>",
|
||||||
|
"$JS.API.STREAM.INFO."+base, "$JS.API.STREAM.MSG.GET."+base, "$JS.API.DIRECT.GET."+base+".>",
|
||||||
|
"$JS.API.CONSUMER.CREATE."+base+".>", "$JS.API.CONSUMER.DELETE."+base+".>", "$JS.FC."+base+".>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|||||||
@@ -58,6 +58,9 @@ func MembershipFor(node string, d Declared, where Assignments) Membership {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
m.State = stateIssuedFor(d, node)
|
m.State = stateIssuedFor(d, node)
|
||||||
|
// Its bindings, from the source its grants are composed from (Declared.Bindings, issue 1966): one composition carries
|
||||||
|
// both, so the composed side and the pass's agree.
|
||||||
|
m.Bindings = d.Bindings
|
||||||
m.Logs = append(logsIssuedFor(d), readLogsIssuedFor(withReadLogs(d, where.Logs))...)
|
m.Logs = append(logsIssuedFor(d), readLogsIssuedFor(withReadLogs(d, where.Logs))...)
|
||||||
m.Streams = streamsIssuedFor(d)
|
m.Streams = streamsIssuedFor(d)
|
||||||
m.Hears = HeardIssuedFor(node, d)
|
m.Hears = HeardIssuedFor(node, d)
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import (
|
|||||||
mv "git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault"
|
mv "git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault"
|
||||||
sdkbc "git.novox.be/novox/mesh-sdk/go/buscompose"
|
sdkbc "git.novox.be/novox/mesh-sdk/go/buscompose"
|
||||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||||
|
"git.novox.be/novox/mesh-sdk/go/membership"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A Kind is what a principal is, which decides the shape of its authority rather than its
|
// A Kind is what a principal is, which decides the shape of its authority rather than its
|
||||||
@@ -132,6 +133,9 @@ type Principal struct {
|
|||||||
// Objects are the bus names of its module's own object buckets, and ReadsObjects those of others it reads (novox/hq
|
// Objects are the bus names of its module's own object buckets, and ReadsObjects those of others it reads (novox/hq
|
||||||
// ADR 0592 §5, §9).
|
// ADR 0592 §5, §9).
|
||||||
Objects, ReadsObjects []string
|
Objects, ReadsObjects []string
|
||||||
|
// Bound are its module's bus state as the bus module created it (Declared.Bindings), for a KindModule principal: each
|
||||||
|
// entry granted as its old section's is (boundOf, issue 1966).
|
||||||
|
Bound []membership.BusBinding
|
||||||
// Nodes are every node the records know, for a read of a state kept one bucket per node (ComposedRead, ADR
|
// Nodes are every node the records know, for a read of a state kept one bucket per node (ComposedRead, ADR
|
||||||
// 0593 §6): set only where the principal or a module it carries reads one.
|
// 0593 §6): set only where the principal or a module it carries reads one.
|
||||||
Nodes []string
|
Nodes []string
|
||||||
@@ -996,14 +1000,17 @@ func PermissionsFor(p Principal) (Permissions, error) {
|
|||||||
|
|
||||||
// 5. Its state, and the state of others it reads (novox/hq ADR 0201): every one read and
|
// 5. Its state, and the state of others it reads (novox/hq ADR 0201): every one read and
|
||||||
// watched, its own written too.
|
// watched, its own written too.
|
||||||
kept := stateAccess{Module: p.Module, Node: p.Node, Keeps: p.State, PerMachine: p.PerMachine, ReadOnly: p.ReadOnly, Reads: p.Reads,
|
// Its bus state asked in `requires` is granted as the old sections are (boundOf, ADR 0595 §1, issue 1966).
|
||||||
KeyedReads: p.KeyedReads, Nodes: p.Nodes}
|
bound := boundOf(p.Module, p.Bound)
|
||||||
|
kept := stateAccess{Module: p.Module, Node: p.Node, Keeps: append(slices.Clone(p.State), bound.States...), PerMachine: p.PerMachine,
|
||||||
|
ReadOnly: p.ReadOnly, Reads: p.Reads, KeyedReads: p.KeyedReads, Nodes: p.Nodes}
|
||||||
pub = append(pub, stateGrants(kept)...)
|
pub = append(pub, stateGrants(kept)...)
|
||||||
|
pub = append(pub, bound.Streams...)
|
||||||
// And it hears each of those buckets created, so one not on the bus at its start is bound when it appears
|
// And it hears each of those buckets created, so one not on the bus at its start is bound when it appears
|
||||||
// (novox/hq issue 1790; mesh-sdk process.AwaitBucket).
|
// (novox/hq issue 1790; mesh-sdk process.AwaitBucket).
|
||||||
sub = append(sub, stateCreatedHeard(kept)...)
|
sub = append(sub, stateCreatedHeard(kept)...)
|
||||||
// And its object buckets (novox/hq ADR 0592 §5, §9): its own written, others' its owners grant it read only.
|
// And its object buckets (novox/hq ADR 0592 §5, §9): its own written, others' its owners grant it read only.
|
||||||
pub = append(pub, objectGrants(p.Objects, p.ReadsObjects)...)
|
pub = append(pub, objectGrants(append(slices.Clone(p.Objects), bound.Objects...), p.ReadsObjects)...)
|
||||||
// And its logs (novox/hq ADR 0297): appended to and read directly, its own alone.
|
// And its logs (novox/hq ADR 0297): appended to and read directly, its own alone.
|
||||||
pub = append(pub, logGrants(p.Module, p.Logs)...)
|
pub = append(pub, logGrants(p.Module, p.Logs)...)
|
||||||
pub = append(pub, readLogGrants(p.ReadLogs)...)
|
pub = append(pub, readLogGrants(p.ReadLogs)...)
|
||||||
@@ -1103,9 +1110,12 @@ func PermissionsFor(p Principal) (Permissions, error) {
|
|||||||
// does not write another's bucket through it is the runtime's to keep, from the membership
|
// does not write another's bucket through it is the runtime's to keep, from the membership
|
||||||
// each assignment is issued, as it keeps each module's events under that module's own name.
|
// each assignment is issued, as it keeps each module's events under that module's own name.
|
||||||
for _, d := range p.Carries {
|
for _, d := range p.Carries {
|
||||||
pub = append(pub, stateGrants(stateAccess{Module: d.Module, Node: p.Node, Keeps: stateNames(d.State),
|
// Its bus state asked in `requires` as its old sections' (boundOf, ADR 0595 §1, §9, issue 1966).
|
||||||
|
bound := boundOf(d.Module, d.Bindings)
|
||||||
|
pub = append(pub, stateGrants(stateAccess{Module: d.Module, Node: p.Node, Keeps: append(stateNames(d.State), bound.States...),
|
||||||
PerMachine: perMachineNames(d.State), ReadOnly: readOnlyNames(d.State), Reads: d.Reads, KeyedReads: d.KeyedReads, Nodes: p.Nodes})...)
|
PerMachine: perMachineNames(d.State), ReadOnly: readOnlyNames(d.State), Reads: d.Reads, KeyedReads: d.KeyedReads, Nodes: p.Nodes})...)
|
||||||
pub = append(pub, objectGrants(objectNames(d.Objects), d.ReadsObjects)...)
|
pub = append(pub, objectGrants(append(objectNames(d.Objects), bound.Objects...), d.ReadsObjects)...)
|
||||||
|
pub = append(pub, bound.Streams...)
|
||||||
// A bundle reads every node's compositions on the runtime's own connection (ADR 0593 §6), which hears a node's
|
// A bundle reads every node's compositions on the runtime's own connection (ADR 0593 §6), which hears a node's
|
||||||
// bucket made later by any stream's creation.
|
// bucket made later by any stream's creation.
|
||||||
if slices.Contains(d.Reads, ComposedRead) && !slices.Contains(sub, familyCreatedAdvisory) {
|
if slices.Contains(d.Reads, ComposedRead) && !slices.Contains(sub, familyCreatedAdvisory) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package buscompose
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"github.com/nats-io/nats.go/jetstream"
|
"github.com/nats-io/nats.go/jetstream"
|
||||||
|
|
||||||
@@ -97,14 +98,15 @@ func objectNames(objects []ObjectBucket) []string {
|
|||||||
func writesOwnObjects(p Principal, pattern string) bool {
|
func writesOwnObjects(p Principal, pattern string) bool {
|
||||||
switch p.Kind {
|
switch p.Kind {
|
||||||
case KindModule:
|
case KindModule:
|
||||||
for _, b := range p.Objects {
|
// Its own, from the old section or bound in `requires` (boundOf, issue 1966).
|
||||||
|
for _, b := range append(slices.Clone(p.Objects), boundOf(p.Module, p.Bound).Objects...) {
|
||||||
if pattern == "$O."+b+".>" {
|
if pattern == "$O."+b+".>" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case KindNodeTools:
|
case KindNodeTools:
|
||||||
for _, d := range p.Carries {
|
for _, d := range p.Carries {
|
||||||
for _, b := range objectNames(d.Objects) {
|
for _, b := range append(objectNames(d.Objects), boundOf(d.Module, d.Bindings).Objects...) {
|
||||||
if pattern == "$O."+b+".>" {
|
if pattern == "$O."+b+".>" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault"
|
"git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault"
|
||||||
"git.novox.be/novox/mesh-sdk/go/announce"
|
"git.novox.be/novox/mesh-sdk/go/announce"
|
||||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||||
|
"git.novox.be/novox/mesh-sdk/go/membership"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Every user the composed file should contain, derived from what the mesh knows.
|
// Every user the composed file should contain, derived from what the mesh knows.
|
||||||
@@ -109,6 +110,10 @@ type Declared struct {
|
|||||||
// Ledgers are the local names of the ledgers it asks for in `requires` (catalogue.StorageLedger); each assignment of a
|
// Ledgers are the local names of the ledgers it asks for in `requires` (catalogue.StorageLedger); each assignment of a
|
||||||
// module with any gets a ledger principal of its own (issue 1966 D5, ADR 0597 §9).
|
// module with any gets a ledger principal of its own (issue 1966 D5, ADR 0597 §9).
|
||||||
Ledgers []string
|
Ledgers []string
|
||||||
|
// Bindings are its bus state as the bus module created it (membership.BusBinding, ADR 0595 §1, §7), kept by the grants
|
||||||
|
// part from the bus module's `created`: the module's own account and the tool runner carrying it are granted each
|
||||||
|
// entry's subjects (boundOf, issue 1966), in the same pass that writes the binding into its membership.
|
||||||
|
Bindings []membership.BusBinding
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProcessPrincipal is one process of a module that declares bus (novox/hq ADR 0436 §1, §2), as the broker
|
// ProcessPrincipal is one process of a module that declares bus (novox/hq ADR 0436 §1, §2), as the broker
|
||||||
@@ -295,7 +300,7 @@ func Users(r Records) ([]Principal, error) {
|
|||||||
State: stateNames(d.State), Reads: d.Reads, ReadLogs: d.ReadLogs, SnapshotsTheBus: d.SnapshotsTheBus,
|
State: stateNames(d.State), Reads: d.Reads, ReadLogs: d.ReadLogs, SnapshotsTheBus: d.SnapshotsTheBus,
|
||||||
PerMachine: perMachineNames(d.State), ReadOnly: readOnlyNames(d.State), KeyedReads: d.KeyedReads,
|
PerMachine: perMachineNames(d.State), ReadOnly: readOnlyNames(d.State), KeyedReads: d.KeyedReads,
|
||||||
Logs: logNames(d.Logs), Streams: d.Streams, Hears: ComposedHears(d), Queues: d.Queues,
|
Logs: logNames(d.Logs), Streams: d.Streams, Hears: ComposedHears(d), Queues: d.Queues,
|
||||||
Objects: objectNames(d.Objects), ReadsObjects: d.ReadsObjects,
|
Objects: objectNames(d.Objects), ReadsObjects: d.ReadsObjects, Bound: d.Bindings,
|
||||||
})
|
})
|
||||||
// **The grants part creates what a manifest declares** (novox/hq ADR 0547 §5; the operator, 2026-10-26
|
// **The grants part creates what a manifest declares** (novox/hq ADR 0547 §5; the operator, 2026-10-26
|
||||||
// 08:54): every placed module's buckets, logs and streams, each by its stream's name.
|
// 08:54): every placed module's buckets, logs and streams, each by its stream's name.
|
||||||
|
|||||||
@@ -190,6 +190,24 @@ func OwnHears(s Store, node string) ([]membership.HeardIssued, error) {
|
|||||||
return m.Hears, nil
|
return m.Hears, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// withBound sets what the bus module said it created on in: the streams of every binding kept (Inputs.Bound) and the
|
||||||
|
// bindings themselves (Inputs.Bindings), read from the part's own state in one place for the pass and for its tools.
|
||||||
|
func (p *Part) withBound(in *Inputs) error {
|
||||||
|
bound, err := p.boundStreams()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
in.Bound, in.Bindings = bound, map[string][]membership.BusBinding{}
|
||||||
|
for module := range bound {
|
||||||
|
binds, err := p.bindingsOf(module)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
in.Bindings[module] = binds
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// boundStreams are, by module, the streams on the bus of every binding kept (BoundKey): what the bus module's process
|
// boundStreams are, by module, the streams on the bus of every binding kept (BoundKey): what the bus module's process
|
||||||
// may still define, an orphan's among them (buscompose.BusStateNames).
|
// may still define, an orphan's among them (buscompose.BusStateNames).
|
||||||
func (p *Part) boundStreams() (map[string][]string, error) {
|
func (p *Part) boundStreams() (map[string][]string, error) {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"git.novox.be/novox/mesh-sdk/go/membership"
|
||||||
"slices"
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -140,6 +141,9 @@ type Inputs struct {
|
|||||||
// Bound is, by module, the streams of each bus-state entry the bus module said it created and has not dropped, from
|
// Bound is, by module, the streams of each bus-state entry the bus module said it created and has not dropped, from
|
||||||
// the part's own state memberships (bindings.go): set by the pass, never read from another part.
|
// the part's own state memberships (bindings.go): set by the pass, never read from another part.
|
||||||
Bound map[string][]string
|
Bound map[string][]string
|
||||||
|
// Bindings are, by module, the bindings kept (bindings.go), in local-name order: set by the pass with Bound, never read
|
||||||
|
// from another part. The module's own account and the tool runner carrying it are granted each (issue 1966).
|
||||||
|
Bindings map[string][]membership.BusBinding
|
||||||
// Digests is each module's manifest digest, by module.
|
// Digests is each module's manifest digest, by module.
|
||||||
Digests map[string]string
|
Digests map[string]string
|
||||||
// Fingerprints is the fingerprint of the new form the indexer's trunk value names for each module, by module: what a
|
// Fingerprints is the fingerprint of the new form the indexer's trunk value names for each module, by module: what a
|
||||||
|
|||||||
@@ -294,10 +294,8 @@ func (p *Part) Compose() Result {
|
|||||||
p.Views.SayUnsaid()
|
p.Views.SayUnsaid()
|
||||||
in := p.inputs()
|
in := p.inputs()
|
||||||
p.keepBrokers(in)
|
p.keepBrokers(in)
|
||||||
if bound, err := p.boundStreams(); err != nil {
|
if err := p.withBound(&in); err != nil {
|
||||||
res.Errors = append(res.Errors, err.Error())
|
res.Errors = append(res.Errors, err.Error())
|
||||||
} else {
|
|
||||||
in.Bound = bound
|
|
||||||
}
|
}
|
||||||
for _, g := range in.Refused {
|
for _, g := range in.Refused {
|
||||||
res.Refused = append(res.Refused, g.Input+": "+g.Why)
|
res.Refused = append(res.Refused, g.Input+": "+g.Why)
|
||||||
|
|||||||
@@ -96,6 +96,9 @@ func records(in Inputs, union bool) (buscompose.Records, []Gap) {
|
|||||||
}
|
}
|
||||||
d.Hears, d.HearsAStream, d.Consumes = hearsOf(m, declared, node, values, d.Consumes)
|
d.Hears, d.HearsAStream, d.Consumes = hearsOf(m, declared, node, values, d.Consumes)
|
||||||
d.Reads, d.ReadsObjects, d.ReadsRefused = readsOfObjects(m.Module, node, d.Reads, d.ReadsRefused, declared, values)
|
d.Reads, d.ReadsObjects, d.ReadsRefused = readsOfObjects(m.Module, node, d.Reads, d.ReadsRefused, declared, values)
|
||||||
|
// Its bindings as kept (bindings.go): the same source its membership's `bindings` is written from in this pass,
|
||||||
|
// so a grant and its binding are written together, never one before the other (issue 1966, the kind of issue 249).
|
||||||
|
d.Bindings = in.Bindings[m.Module]
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
d := declare(m)
|
d := declare(m)
|
||||||
|
|||||||
@@ -111,6 +111,9 @@ func (p *Part) MembershipOf(node, module string) (any, error) {
|
|||||||
return nil, errors.New("membership names a node and a module, or neither for every membership held back")
|
return nil, errors.New("membership names a node and a module, or neither for every membership held back")
|
||||||
}
|
}
|
||||||
in := p.inputs()
|
in := p.inputs()
|
||||||
|
// What the bus module said it created, as the pass reads it, so the composed side carries the grants and bindings the
|
||||||
|
// pass writes (issue 1966).
|
||||||
|
boundErr := p.withBound(&in)
|
||||||
recs, left := ListRecords(in)
|
recs, left := ListRecords(in)
|
||||||
notFree := WithRootFree(&recs, p.Call)
|
notFree := WithRootFree(&recs, p.Call)
|
||||||
m, ok := Memberships(recs)[node+"."+module]
|
m, ok := Memberships(recs)[node+"."+module]
|
||||||
@@ -139,6 +142,9 @@ func (p *Part) MembershipOf(node, module string) (any, error) {
|
|||||||
out["published"] = map[string]any{"revision": rev, "digest": digestOf(was), "membership": was}
|
out["published"] = map[string]any{"revision": rev, "digest": digestOf(was), "membership": was}
|
||||||
}
|
}
|
||||||
out["last_pass"] = p.passOf(key)
|
out["last_pass"] = p.passOf(key)
|
||||||
|
if boundErr != nil {
|
||||||
|
out["bindings"] = "not readable: " + boundErr.Error()
|
||||||
|
}
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user