Files
mesh-delivery/modules/mesh-grants/busgrants/busgrants.go
T

132 lines
6.7 KiB
Go

// Package busgrants is what the grants part grants other parts' accounts that those parts' own code needs to state
// as well: the whole authority of an account whose bus declares removals (the nats keeper's tools), and the subjects the
// watchman hears (its code subscribes the very ones its grant lets it hear). The part composes every grant; a part that
// needs the same words reads them here, the grants part's own published package (novox/hq ADR 0634 §4, ADR 0635 §4).
package busgrants
import (
"fmt"
"regexp"
"slices"
"strings"
"git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshselfcheck"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/declaration"
"git.novox.be/novox/mesh-sdk/go/membership"
)
const (
// EngineModule is the node-engine's manifest's module name, and EngineActsBucket the bucket of its state acts.
EngineModule = "mesh-host"
EngineActsBucket = EngineModule + "_acts"
// DeadLetterNoticesStream is the stream of the notices a consumer gave up on a message, and DeadLetterPrefix the
// subjects the copies kept are on.
DeadLetterNoticesStream = "DEAD_LETTER_NOTICES"
DeadLetterPrefix = "mesh.events.dead."
// ProvAppliedSubjects is every machine's provisioner client's reports: `prov.applied.<node>.<module>`.
ProvAppliedSubjects = "prov.applied.>"
// ControllerName and MergesConsumerName are the names of the ended controller's consumers.
ControllerName = "controller"
MergesConsumerName = "controller-merges"
// endedController is the name the ended controller had as a module: its buckets are named for it (ADR 0590 §6).
endedController = "mesh-controller"
)
var safeSubject = regexp.MustCompile(`^[A-Za-z0-9_-]+$`)
// ControllerLeftoverConsumers are the ended controller's durable consumers left on the bus, one leftover of the module
// mesh-delivery placed nowhere (issue 1126, milestone section 3), and mesh-catalog's of the controller's built-before,
// which its manifest no longer consumes and no part composes; each `<stream>.<consumer>`.
var ControllerLeftoverConsumers = []string{
"EVENTS." + ControllerName, "EVENTS." + MergesConsumerName, "EVENTS.novox_mesh-delivery", "EVENTS.novox_mesh-catalog", "EVENTS.novox_gitea",
"CONTROL." + ControllerName, DeadLetterNoticesStream + "." + ControllerName,
// The controller's consumers on three modules' buckets (issue 1126): the take-in on the builder's builds and on the
// indexer's modules, and the config follow on the resolved configuration.
"KV_mesh-delivery-builder_builds.mesh-controller-take-in", "KV_mesh-delivery-indexer_modules.mesh-controller-take-in",
"KV_mesh-config_resolved.mesh-controller-config-follow",
}
// ControllerLeftoverConsumersOn are ControllerLeftoverConsumers and, for each node given, the consumer the controller
// made for the node's engine (`EVENTS.<node>_mesh-host`, of restic's backed-up): the engine hears that event without a
// durable consumer, and the grants part composes none for it, so nothing reads it (issue 1126).
func ControllerLeftoverConsumersOn(nodes []string) []string {
out := slices.Clone(ControllerLeftoverConsumers)
for _, n := range nodes {
if safeSubject.MatchString(n) {
out = append(out, "EVENTS."+n+"_"+EngineModule)
}
}
return out
}
// ControllerLeftoverBuckets are the ended controller's buckets, each by its local name (`KV_mesh-controller_<name>`).
var ControllerLeftoverBuckets = []string{"asked", "assignments", "builds", "calls", "checks-running", "composed",
"condition-history", "conditions", "declared-config", "declared-packages", "delivery-merges", "grants", "hand-acts",
"lease", "predefined", "rollouts", "states-announced"}
// ControllerLeftoverStreams are the ended controller's streams, by their names: CONTROL, ASSIGNMENTS, each of its buckets'
// and, for each node given, the declaration files' object bucket the controller wrote (ADR 0486 §2).
func ControllerLeftoverStreams(nodes []string) []string {
out := []string{"CONTROL", membership.AssignmentsStream}
for _, b := range ControllerLeftoverBuckets {
out = append(out, "KV_"+endedController+"_"+b)
}
for _, n := range nodes {
if safeSubject.MatchString(n) {
out = append(out, "OBJ_"+declaration.FileBucketPrefix+n)
}
}
return out
}
// BusRemovalGrants are the whole authority of an account whose bus declares removals (ADR 0564 Limits 4, ADR 0590 clause
// 6), each removal its grant: 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; and
// the ended controller's leftovers, each consumer and each stream read and deleted by its name, for the nodes given (the
// declaration files' object buckets). A removal outside the set is refused, as the indexer's judge refuses it in a
// manifest (novox/hq ADR 0578 §1).
func BusRemovalGrants(removes, nodes []string) ([]string, error) {
var out []string
for _, r := range removes {
switch r {
case catalogue.RemovesEngineActs:
out = append(out, "$JS.API.STREAM.INFO.KV_"+EngineActsBucket, "$KV."+EngineActsBucket+".>")
case catalogue.RemovesControllerLeftovers:
for _, c := range ControllerLeftoverConsumersOn(nodes) {
out = append(out, "$JS.API.CONSUMER.INFO."+c, "$JS.API.CONSUMER.DELETE."+c)
}
for _, s := range ControllerLeftoverStreams(nodes) {
out = append(out, "$JS.API.STREAM.INFO."+s, "$JS.API.STREAM.DELETE."+s)
}
default:
return nil, fmt.Errorf("a bus account removes %q, which is not one of %s (novox/hq ADR 0564 Limits 4, ADR 0590)", r,
strings.Join([]string{catalogue.RemovesEngineActs, catalogue.RemovesControllerLeftovers}, ", "))
}
}
return out, nil
}
// 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), the dead letters kept, and the lease write's events. 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." + meshselfcheck.Seat + ".event." + meshselfcheck.EventChecked,
"$JS.EVENT.ADVISORY.CONSUMER.DELETED.>",
"mesh.mod.nats.event.bus.slow-consumer",
"mesh.mod.nats.event.bus.connection-closed",
ProvAppliedSubjects,
DeadLetterPrefix + ">",
}
}