173 lines
8.6 KiB
Go
173 lines
8.6 KiB
Go
package buscompose
|
|
|
|
import (
|
|
"fmt"
|
|
"slices"
|
|
"sort"
|
|
"strings"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
"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 an account of their own, declared by their bundle's `bus` with `removes`
|
|
// (catalogue.BusAccounts), 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
|
|
)
|
|
|
|
// BusRemovalGrants are the whole authority of an account whose bus declares removals (ADR 0564 Limits 4, issue 1952
|
|
// notes 6 and 7), each removal its grant: the controller's consumer on DEAD_LETTER_NOTICES read and deleted (the lease
|
|
// write's step, clause 5); the node-engine's acts bucket read (STREAM.INFO with a subject filter lists what a removal
|
|
// would take, so the tools read before they remove) and its keys removed, which nothing else may empty when it fills. A
|
|
// removal outside the set is refused, as the catalogue refuses it (catalogue.BusRemovals).
|
|
func BusRemovalGrants(removes []string) ([]string, error) {
|
|
var out []string
|
|
for _, r := range removes {
|
|
switch r {
|
|
case catalogue.RemovesControllerDeadLetterConsumer:
|
|
out = append(out, "$JS.API.CONSUMER.INFO."+DeadLetterNoticesStream+"."+ControllerName,
|
|
"$JS.API.CONSUMER.DELETE."+DeadLetterNoticesStream+"."+ControllerName)
|
|
case catalogue.RemovesEngineActs:
|
|
out = append(out, "$JS.API.STREAM.INFO.KV_"+EngineActsBucket, "$KV."+EngineActsBucket+".>")
|
|
default:
|
|
return nil, fmt.Errorf("a bus account removes %q, which is not one of %s (novox/hq ADR 0564 Limits 4)", r,
|
|
strings.Join(catalogue.BusRemovals, ", "))
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// removesEngineActs is an account declared to remove 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 && slices.Contains(p.Removes, catalogue.RemovesEngineActs) && 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 in L4 (ADR 0564 clause 5), at least
|
|
// privilege: its own pulled consumer on DEAD_LETTER_NOTICES (made, read, pulled, acknowledged); whether `controller`
|
|
// still stands (read only); a read of DEAD_LETTERS (its information and one kept message); a copy kept
|
|
// (`mesh.events.dead.>`); and on each stream a notice can name (sources: EVENTS, the seats' work queues and the
|
|
// modules' own), its information, for a source's age (ADR 0376), and the message given up on, 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). Delivering again and dropping stay refused in L4, so it may
|
|
// neither delete a message nor publish `mesh.again.>`.
|
|
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,
|
|
deadLetterPrefix + ">",
|
|
// And the provisioner clients' reports, the newest per subject read at its start (S21, ADR 0362 §3).
|
|
"$JS.API.STREAM.INFO." + ProvAppliedStream,
|
|
"$JS.API.STREAM.MSG.GET." + ProvAppliedStream,
|
|
"$JS.API.DIRECT.GET." + ProvAppliedStream + ".>",
|
|
}
|
|
for _, s := range DeadLetterSources(sources) {
|
|
out = append(out, "$JS.API.STREAM.INFO."+s, "$JS.API.STREAM.MSG.GET."+s)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// WatchmanHears are the core subjects the watchman subscribes, with no durable consumer (ADR 0564 clauses 4, 5 and
|
|
// Limits 3, 11): the power seat's words (S1, ADR 0211), the providers' failing and recovered words (S8), each tool
|
|
// runner's beat (S11), the self-check's checked (S10), a consumer deleted and the bus module's slow consumers and closed
|
|
// connections (S9), the provisioner clients' reports (S21), and the dead letters kept. Never the bus's API audit
|
|
// (`$JS.EVENT.ADVISORY.API`): it carries every API call's request and answer in the account, the vault's hashes among
|
|
// them (the lead, 2026-10-29).
|
|
func WatchmanHears() []string {
|
|
return []string{
|
|
"mesh.mod.power.event.*",
|
|
"mesh.mod.*.event.provisioner.failing",
|
|
"mesh.mod.*.event.provisioner.recovered",
|
|
"mesh.control.*.tools-alive",
|
|
"mesh.seat.mesh-self-check.event." + health.EventChecked,
|
|
"$JS.EVENT.ADVISORY.CONSUMER.DELETED.>",
|
|
"mesh.mod.nats.event.bus.slow-consumer",
|
|
"mesh.mod.nats.event.bus.connection-closed",
|
|
ProvAppliedSubjects,
|
|
deadLetterPrefix + ">",
|
|
}
|
|
}
|
|
|
|
// 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 beside the controller, which
|
|
// keeps them until its lease is handed away and the watchman after (ADR 0564 clause 5). Delivering again stays the
|
|
// controller's in L4.
|
|
func keepsDeadLetters(p Principal, pattern string) bool {
|
|
return isController(p, pattern) ||
|
|
(p.Kind == KindModule && p.Module == WatchmanModule && pattern == deadLetterPrefix+">")
|
|
}
|