Files
mesh-sdk/go/buscompose/health_grants.go
T

144 lines
7.2 KiB
Go

package buscompose
import (
"sort"
"strings"
"git.novox.be/novox/mesh-sdk/go/health"
)
// **The health holders' grants** (novox/hq ADR 0564 clauses 2, 5 and 8, Limits 2, 4 and 8; issue 1952 S5).
//
// - The condition keeper reads the controller's open conditions until the lease write, for what clauses 8 and 9 carry
// from them, and mesh-healers the controller's hand-act log, for S15 (Limits 2): both buckets the controller
// announces readable by nobody, so each is read by its named reader alone. The keeper, mesh-healers and the
// watchman read the controller's lease (Limits 8). Nobody reads mesh-healers' `would` (clause 8): what the healers
// would have done reaches no condition. Every observer's `findings`, the healers' `attempts`, the wants and the
// haves are read through `reads` as any announced state is; and no module writes another's state, so an observer
// can never write the keeper's buckets (clause 2).
// - The watchman makes its own consumer on DEAD_LETTER_NOTICES, by its own name, pulled, and reads whether the
// controller's consumer `controller` still stands; it may neither delete nor change that consumer (clause 5). It
// lists and shows DEAD_LETTERS, which is all it does before the lease write.
// - The bus module's tools run on a process account of their own, BusToolsProcess, which may remove only the
// consumer `controller` on DEAD_LETTER_NOTICES and keys of the node-engine's acts bucket (Limits 4), never the
// backup's account `broker`, which only reads (ADR 0235).
// The states the composer names its readers for, beside the lease (NamedReaders).
var (
// ControllerConditions is the controller's open conditions, read by the keeper alone (ADR 0564 clauses 8, 9).
ControllerConditions = ControllerSeat + ".conditions"
// ControllerHandActs is the controller's hand-act log, read by mesh-healers alone of the four health holders (ADR
// 0564 Limits 2).
ControllerHandActs = ControllerSeat + ".hand-acts"
// HealersWould is what mesh-healers would have done while the lease names a controller: read by nobody (clause 8).
HealersWould = health.HealersModule + "." + health.WouldState
)
// BusToolsProcess is the process of the module holding mesh-broker whose account removes from the bus (ADR 0564 Limits
// 4): its secret is broker-nats-tools, as every process account's is broker-<process id> (ADR 0436 §2).
const BusToolsProcess = "nats-tools"
// BusRemovalGrants is the whole authority of the bus module's tools account (ADR 0564 Limits 4, issue 1952 notes 6 and
// 7): read and delete the controller's consumer on DEAD_LETTER_NOTICES (the lease write's step, clause 5), and read and
// remove keys of the node-engine's acts bucket, which nothing else may empty when it fills. STREAM.INFO with a subject
// filter lists what a removal would take, so the tools read before they remove. A further consumer or bucket is a change
// of this grant.
func BusRemovalGrants() []string {
return []string{
"$JS.API.CONSUMER.INFO." + DeadLetterNoticesStream + "." + ControllerName,
"$JS.API.CONSUMER.DELETE." + DeadLetterNoticesStream + "." + ControllerName,
"$JS.API.STREAM.INFO.KV_" + EngineActsBucket,
"$KV." + EngineActsBucket + ".>",
}
}
// removesEngineActs is the bus module's tools account removing keys of the node-engine's acts bucket: the writers
// table's one exemption from a node's engine being the only writer of its keys, for removal (ADR 0564 Limits 4).
func removesEngineActs(p Principal, pattern string) bool {
return p.Kind == KindProcess && p.RemovesFromTheBus && pattern == "$KV."+EngineActsBucket+".>"
}
// WatchmanDeadLetterConsumer is the watchman's own consumer on DEAD_LETTER_NOTICES, made only once the lease names no
// controller and `controller` is gone (ADR 0564 clause 5).
const WatchmanDeadLetterConsumer = WatchmanModule
// WatchmanDeadLetterGrants are what the watchman may publish for the dead letters (ADR 0564 clause 5): its own pulled
// consumer on DEAD_LETTER_NOTICES (made, read, pulled, acknowledged), whether `controller` still stands (read only), a
// read of DEAD_LETTERS (its list by subject, one kept message, an ordered consumer of its own to page them, a drop), the
// copies kept and the messages delivered again (`mesh.events.dead.>`, `mesh.again.>`), and on each stream a notice can
// name (sources: EVENTS, the seats' work queues and the modules' own) the message given up on read, its consumer read,
// and its message deleted from the work queue when delivered again, each stream by its name. Never a wildcard over the
// streams and never a bucket's or an object store's stream: a read of any message would read every bucket (the lead,
// 2026-10-29, on issue 1952 S5).
func WatchmanDeadLetterGrants(sources []string) []string {
own := DeadLetterNoticesStream + "." + WatchmanDeadLetterConsumer
out := []string{
"$JS.API.STREAM.INFO." + DeadLetterNoticesStream,
"$JS.API.CONSUMER.INFO." + DeadLetterNoticesStream + "." + ControllerName,
"$JS.API.CONSUMER.CREATE." + own,
"$JS.API.CONSUMER.INFO." + own,
"$JS.API.CONSUMER.MSG.NEXT." + own,
"$JS.ACK." + own + ".>",
"$JS.API.STREAM.INFO." + DeadLettersStream,
"$JS.API.STREAM.MSG.GET." + DeadLettersStream,
"$JS.API.STREAM.MSG.DELETE." + DeadLettersStream,
"$JS.API.CONSUMER.CREATE." + DeadLettersStream + ".>",
"$JS.API.CONSUMER.DELETE." + DeadLettersStream + ".*",
"$JS.FC." + DeadLettersStream + ".>",
deadLetterPrefix + ">",
againPrefix + ">",
}
for _, s := range DeadLetterSources(sources) {
out = append(out, "$JS.API.STREAM.MSG.GET."+s, "$JS.API.STREAM.MSG.DELETE."+s, "$JS.API.CONSUMER.INFO."+s+".*")
}
return out
}
// DeadLetterSources are the streams of names a dead letter may come from, sorted, none twice: each a plain name, never a
// bucket's (KV_) or an object store's (OBJ_) stream, the dead letters' own two streams, or a pattern.
func DeadLetterSources(names []string) []string {
seen := map[string]bool{}
var out []string
for _, n := range names {
switch {
case seen[n], !safeSubject.MatchString(n), strings.HasPrefix(n, "KV_"), strings.HasPrefix(n, "OBJ_"),
n == DeadLettersStream, n == DeadLetterNoticesStream:
continue
}
seen[n] = true
out = append(out, n)
}
sort.Strings(out)
return out
}
// deadLetterSourcesOf are the streams the records' consumers give up on: EVENTS, the work queue of every seat that
// accepts work, and every module's and process's own work queue.
func deadLetterSourcesOf(r Records) []string {
out := []string{EventsStream}
for _, mods := range r.Assigned {
for _, d := range mods {
for _, s := range append(append([]Seat(nil), d.Holds...), d.Uses...) {
if len(s.Accepts) > 0 {
out = append(out, seatStreamName(s.Name))
}
}
for _, q := range d.Queues {
out = append(out, q.Stream)
}
for _, pr := range d.Processes {
for _, q := range pr.Queues {
out = append(out, q.Stream)
}
}
}
}
return DeadLetterSources(out)
}
// keepsDeadLetters is the watchman keeping the dead letters: a second writer of the copies and the deliveries again
// beside the controller, which acts until its lease is handed away and the watchman after (ADR 0564 clause 5).
func keepsDeadLetters(p Principal, pattern string) bool {
return isController(p, pattern) || (p.Kind == KindModule && p.Module == WatchmanModule)
}