ADR 0593 §6, §7: the grants part creates composed-<node> per node, grants each engine the writes of its own bucket and a direct get of the indexer's candidates for compose-candidate, and grants mesh-host.composed read-only, with the stream listing and creation advisory a per-node read needs, to the provisioner, the assigner and the view alone; memberships issue it per node. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
102 lines
5.4 KiB
Go
102 lines
5.4 KiB
Go
package buscompose
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
)
|
|
|
|
// The compositions' buckets (novox/hq ADR 0539 clauses 2 and 4, as ADR 0593 §6 changes them): one per node,
|
|
// `composed-<node>`, which that node's engine alone writes, unsigned, with every composition it makes, and which the
|
|
// grants part creates (EngineComposedBuckets). The provisioner, the assigner and the view read every node's
|
|
// (delivery.ComposedRead, a named-reader state); no engine applies a value read from it.
|
|
|
|
// ComposedReads is what a node's node-engine publishes to keep and follow its compositions and wants (ADR 0593 §6, ADR
|
|
// 0539 clause 9): the writes of its own composition bucket's keys (`<node>.<module>.<fp>` and `<node>.node`, all under
|
|
// `<node>.`, so the bucket's whole) and their read, its own wants (`want.<node>.<module>` and `want.<node>.node`) in the
|
|
// assigner's bucket, and the indexer's candidates by one key's direct get (ADR 0593 §7: `compose-candidate` reads the
|
|
// check's candidate itself; no listing and no watch); nothing of another node's, never the bucket's creation.
|
|
func ComposedReads(node string) ([]string, error) {
|
|
bucket, err := delivery.ComposedBucketFor(node)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !safeSubject.MatchString(node) {
|
|
return nil, fmt.Errorf("%q cannot be part of a subject: a permission is a subject pattern, and this would widen it", node)
|
|
}
|
|
out := append([]string{"$KV." + bucket + ".>"}, readGrants(bucket, "")...)
|
|
out = append(out, readGrants(delivery.AssignerWantsBucket, "want."+node+".>")...)
|
|
return append(out, CandidateReads...), nil
|
|
}
|
|
|
|
// CandidateReads is what a node's engine is granted of the indexer's candidates to answer `compose-candidate` (ADR 0593
|
|
// §7): binding the bucket and a direct get of one key; no consumer, so it neither lists nor watches them.
|
|
var CandidateReads = []string{
|
|
"$JS.API.DIRECT.GET.KV_" + delivery.CandidatesBucket + ".>",
|
|
"$JS.API.STREAM.INFO.KV_" + delivery.CandidatesBucket,
|
|
}
|
|
|
|
// EngineComposedBuckets are the composition buckets of every node, as the node-engine's state (ADR 0593 §6): the grants
|
|
// part creates each, create-only, with the history ADR 0539 clause 4 keeps (delivery.ComposedHistory), as it creates a
|
|
// module's declared state. A node whose name a bucket cannot take is left out (its engine is refused its writes too).
|
|
func EngineComposedBuckets(nodes []string) []Bucket {
|
|
var out []Bucket
|
|
for _, node := range sortedCopy(nodes) {
|
|
bucket, err := delivery.ComposedBucketFor(node)
|
|
if err != nil || !safeSubject.MatchString(node) {
|
|
continue
|
|
}
|
|
out = append(out, Bucket{Module: EngineModule, Name: delivery.ComposedState, Whole: bucket, History: delivery.ComposedHistory})
|
|
}
|
|
return out
|
|
}
|
|
|
|
// composedFamily is every node's composition bucket, for a reader of delivery.ComposedRead.
|
|
func composedFamily(nodes []string) []string {
|
|
var out []string
|
|
for _, b := range EngineComposedBuckets(nodes) {
|
|
out = append(out, b.Bucket())
|
|
}
|
|
return out
|
|
}
|
|
|
|
// What a reader of a per-node state is granted beside each bucket's read (mesh-sdk process.FamilyListSubject and
|
|
// FamilyCreatedAdvisory, ADR 0593 §6): the listing of stream names, by which it finds every node's bucket, and the
|
|
// creation advisory of any stream, by which it binds a node's bucket made after it started. Both say a name only.
|
|
const (
|
|
familyListSubject = "$JS.API.STREAM.NAMES"
|
|
familyCreatedAdvisory = "$JS.EVENT.ADVISORY.STREAM.CREATED.*"
|
|
)
|
|
|
|
// VerbsTheNodeAgentAsks are the seat verbs a node's node-engine calls of its own machine while it applies a want (ADR
|
|
// 0539 clause 16, ADR 0429 §14): its backup holder backs a data module up before it moves, and says when it was. Asked
|
|
// of its own machine only, `mesh.seat.<seat>.tool.<verb>.<node>`.
|
|
var VerbsTheNodeAgentAsks = []SeatVerb{{Seat: "node-backup", Verb: "now"}, {Seat: "node-backup", Verb: "backed-up"}}
|
|
|
|
// NodeAgentAsks are the subjects of VerbsTheNodeAgentAsks for one node.
|
|
func NodeAgentAsks(node string) ([]string, error) {
|
|
if !safeSubject.MatchString(node) {
|
|
return nil, fmt.Errorf("%q cannot be part of a subject: a permission is a subject pattern, and this would widen it", node)
|
|
}
|
|
var out []string
|
|
for _, v := range VerbsTheNodeAgentAsks {
|
|
out = append(out, "mesh.seat."+v.Seat+".tool."+v.Verb+"."+node)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// NodeAgentHears are the events a node's node-engine subscribes while it applies a want: the backup holder's word that a
|
|
// backup is taken (`backed-up`), from whichever module holds node-backup on its machine (restic today).
|
|
var NodeAgentHears = []string{"mesh.mod." + BackupHolderModule + ".event.backed-up"}
|
|
|
|
// BackupHolderModule is the module that holds node-backup on every node, restic: the one whose word a node-engine hears.
|
|
// A second holder of the seat is a change of this grant.
|
|
const BackupHolderModule = "restic"
|
|
|
|
// DeclarationsStream is the stream of each node's last declaration, `mesh.node.<node>.declare` (issue 107). Release 6
|
|
// sends none (ADR 0539 clauses 5 and 11): the stream is kept, with each node's consumer, only so a node away during the
|
|
// switch finds its last declaration and with it the new node-engine; the controller deletes it once every node's have
|
|
// names the new node-engine, and from then on neither asserts it nor any node's consumer on it; nor makes it again once
|
|
// it is gone from the bus for any reason, since a stream made anew would hold no node's last declaration (issue 1945).
|
|
const DeclarationsStream = "NODES"
|