Files
mesh-delivery/modules/mesh-grants/internal/buscompose/busstate.go
T
jschoubben 8a78e00bb0 DLV-G1, DLV-W1: the grants part and the watchman hear mesh-broker's events on the seat's subject; the keeper is granted them
The lead's ruling of 13:0x (registry milestone): mesh-broker's events are mesh.seat.mesh-broker.event.<event> on the
shared events stream like every seat's. The grants part and the watchman consume mesh-broker.<event> and no longer hear
the bus module's own stream, which is its history for its tool. The keeper's process account is composed the seat's
event subjects, and the acknowledgement of a snapshot's chunks of each entry it keeps, which ADR 0626 §1 grants and the
composition lacked; a row holds it to no subscription of a restricted stream's subjects and no consumer of one.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 13:05:27 +01:00

203 lines
8.5 KiB
Go

package buscompose
import (
"slices"
"sort"
"strings"
"git.novox.be/novox/mesh-sdk/go/catalogue"
)
// **The bus module's process has a bus account of its own** (novox/hq ADR 0595 §4, §5; design 25 § 12.2, § 12.3;
// DLV-G2): composed for the process of the module holding the seat mesh-broker (a seat, never a module's name), it
// creates, updates, reads, snapshots and deletes every module's bus state under that module's names only, each stream
// by its name (a key-value bucket's and an object store's by the stream the bus keeps it in); it hears what its module
// consumes (the indexer's manifest-accepted) on a durable consumer of its own, says its module's own stream's events,
// and raises its conditions through the operator channel. As every process account it serves nothing (ADR 0436 §3): the
// seat's four reading verbs are served by its module's tools, and the four acting verbs by nobody on the bus, a
// person's ask reaching the process through its own state. No account holds the acting verbs' subjects, the tool
// runner's included: each is denied to every principal whose publish would reach it.
//
// The names are one rule (BusStateStreams), read by the bus module where it creates them: until mesh-sdk carries it
// as one function (asked of the mesh-sdk member of the milestone), this is the grants part's side of it.
// The verbs of the seat mesh-broker (design 25 § 12.3), until its definition is generated in mesh-contracts: the four
// reading verbs any caller its role grants may ask, and the four acting verbs the bus module's process alone serves.
var (
BrokerReadingVerbs = []string{"list", "show", "check", "orphans"}
BrokerActingVerbs = []string{"create", "apply", "recreate", "drop"}
)
// BrokerVerbs are the eight.
func BrokerVerbs() []string {
return append(append([]string(nil), BrokerReadingVerbs...), BrokerActingVerbs...)
}
// brokerSeat is the seat mesh-broker as its verbs' subjects name it: held once, for the mesh.
var brokerSeat = Seat{Name: catalogue.BrokerSeat}
// BrokerVerbSubjects are where the seat's verbs are asked: each verb's tool subject, and its caller-named form (issue
// 365).
func BrokerVerbSubjects(verbs []string) []string {
var out []string
for _, v := range verbs {
out = append(out, seatToolSubject(brokerSeat, v, ""), seatSubject(brokerSeat, CallKind, v)+".>")
}
sort.Strings(out)
return out
}
// BrokerActingOnly are the subjects of the seat's acting verbs, which no principal publishes on, however wide its
// grant: the bus module's process serves them, and a person's ask reaches it on its own tool subject instead
// (design 25 § 12.3).
func BrokerActingOnly() []string { return BrokerVerbSubjects(BrokerActingVerbs) }
// BusStateStreams are the streams one bus-state entry of module is kept in on the bus, by the mesh's names
// `<module>_<local>` (design 25 § 12.1): a stream's own; a key-value bucket's KV_ stream; an object store's OBJ_
// stream; a ledger's history stream, its snapshot stream, and its state and index buckets' KV_ streams. A name that is
// not one plain subject token names nothing.
func BusStateStreams(module string, e catalogue.BusStateEntry) []string {
base := module + "_" + e.Name
if !safeSubject.MatchString(base) || strings.Contains(e.Name, "_") || strings.Contains(module, "_") {
return nil
}
var out []string
switch e.Storage {
case catalogue.StorageKeyValue:
out = []string{"KV_" + base}
case catalogue.StorageObjectStore:
out = []string{"OBJ_" + base}
case catalogue.StorageLedger:
out = []string{base, base + "_snapshot", "KV_" + base + "_state"}
for _, t := range e.Types {
for _, ix := range t.Indexes {
if safeSubject.MatchString(ix.Name) {
out = append(out, "KV_"+base+"_index-"+ix.Name)
}
}
}
default:
out = []string{base}
}
sort.Strings(out)
return slices.Compact(out)
}
// keeperConsumer is the durable consumer the bus module's process hears its module's consumes on, on EVENTS:
// `<node>_<module>_<process>`, as a process's own consumer is named (QueueConsumer), filtered on the consumed events.
func keeperConsumer(p Principal) (Consumer, bool) {
var filters []string
for _, c := range p.Consumes {
if s, err := consumedSubject(c); err == nil && !strings.HasPrefix(s, moduleStreamPrefix) {
filters = append(filters, s)
}
}
if len(filters) == 0 {
return Consumer{}, false
}
sort.Strings(filters)
return Consumer{Name: p.Node + "_" + p.Module + "_" + p.Process, Stream: "EVENTS", Filters: slices.Compact(filters),
AckWaitSeconds: 30, MaxDeliver: 5, FromNow: true,
Why: "what the bus module's process " + p.Process + " hears of its module's consumes (novox/hq ADR 0595 §4): the " +
"indexer's accepted verdicts, from now, its first view at start holding what came before"}, true
}
// keeperConsumerGrants are the subjects the process reads and acknowledges its consumer through.
func keeperConsumerGrants(c Consumer) []string {
return []string{"$JS.API.CONSUMER.INFO." + c.Stream + "." + c.Name, "$JS.API.CONSUMER.MSG.NEXT." + c.Stream + "." + c.Name,
"$JS.ACK." + c.Stream + "." + c.Name + ".>"}
}
// ownStreamReadGrants are what reading a module's own streams back needs (the bus module's tool history): each stream's
// information and an ordered consumer of its own, as a log's reader has (readLogGrants).
func ownStreamReadGrants(module string, streams []ModuleStream) []string {
var out []string
for _, s := range streams {
if s.Module != module || !safeSubject.MatchString(s.Name) {
continue
}
out = append(out, "$JS.API.STREAM.INFO."+s.Name, "$JS.API.CONSUMER.CREATE."+s.Name+".>",
"$JS.API.CONSUMER.MSG.NEXT."+s.Name+".*", "$JS.API.CONSUMER.INFO."+s.Name+".*", "$JS.API.CONSUMER.DELETE."+s.Name+".*")
}
return out
}
// BusStateNames are every stream the bus module's process may define: those of every entry a manifest asks for now
// (asked), and those the bus module said it created and has not dropped (bound, by module: names of streams), so an
// orphan whose manifest left the trunk can still be dropped. Sorted, once each.
func BusStateNames(asked map[string][]catalogue.BusStateEntry, bound map[string][]string) []string {
var out []string
for module, entries := range asked {
for _, e := range entries {
out = append(out, BusStateStreams(module, e)...)
}
}
for _, names := range bound {
for _, n := range names {
if safeSubject.MatchString(n) {
out = append(out, n)
}
}
}
sort.Strings(out)
return slices.Compact(out)
}
// busStateKeeperGrants are what the bus module's process publishes to keep the bus state named: each stream's create,
// update, information, deletion and snapshot with the acknowledgement of the snapshot's chunks (ADR 0626 §1; the server
// waits on `$JS.SNAPSHOT.ACK.<stream>.…` past its window), and the stream list it finds orphans in. Never a consumer of
// one, nor any stream's own subjects: a snapshot carries an entry's data to the keeper's inbox alone, and the keeper
// hears no message of it (ADR 0626 §1, restricted streams included).
func busStateKeeperGrants(names []string) []string {
out := []string{"$JS.API.STREAM.NAMES", "$JS.API.STREAM.LIST"}
for _, n := range names {
for _, op := range []string{"CREATE", "UPDATE", "INFO", "DELETE", "SNAPSHOT"} {
out = append(out, "$JS.API.STREAM."+op+"."+n)
}
out = append(out, "$JS.SNAPSHOT.ACK."+n+".>")
}
return out
}
// brokerSeatEvents are the subjects of the seat mesh-broker's events, from the held seat's definition: what the bus
// module's keeper says them on, like every seat's holder (the lead's ruling, registry milestone 13:0x).
func brokerSeatEvents(holds []Seat) []string {
var out []string
for _, s := range holds {
if s.Name != catalogue.BrokerSeat {
continue
}
for _, e := range s.Emits {
if safeSubject.MatchString(e) {
out = append(out, seatSubject(s, "event", e))
}
}
}
return out
}
// keepsBusState is the bus module's process defining one of the bus-state streams it keeps: the writers table's
// exemption from stream definitions being the grants part's (ADR 0595 §4).
func keepsBusState(p Principal, pattern string) bool {
if p.Kind != KindProcess || len(p.KeepsBusState) == 0 {
return false
}
for _, op := range []string{"CREATE", "UPDATE", "DELETE"} {
if name, ok := strings.CutPrefix(pattern, "$JS.API.STREAM."+op+"."); ok {
return slices.Contains(p.KeepsBusState, name)
}
}
return false
}
// withoutActing are a holder's served verbs of mesh-broker without the acting four.
func withoutActing(served []string) []string {
var out []string
for _, v := range served {
if !slices.Contains(BrokerActingVerbs, v) {
out = append(out, v)
}
}
return out
}