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-`, 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 (`..` and `.node`, all under // `.`, so the bucket's whole) and their read, its own wants (`want..` and `want..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 and manifest verdicts to answer // `compose-candidate` (ADR 0593 §7, ADR 0578 §3): each bucket read whole as the builder's builds are (a key is read // through a short consumer), never written. var CandidateReads = append(readGrants(delivery.CandidatesBucket, ""), readGrants(delivery.VerdictsBucket, "")...) // 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..tool..`. 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..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"