package main
import (
"context"
"errors"
"craftdesign.group/quarry/internal/oltp"
)
// adminsTopic is the group every fleet-wide alert is addressed to: the
// error journal's first sighting of a fault, and a route that blows its
// render budget. It is a TOPIC, not a person. "admins" as a subscriber
// id names nobody, and notify answers a trigger for an unknown
// subscriber with a success — so an alert addressed that way is
// accepted, delivered to no one, and reported as sent.
const adminsTopic = "admins"
// topicPlane is the slice of notify provisioning the reconcile uses.
type topicPlane interface {
EnsureSubscriber(ctx context.Context, account string) error
EnsureTopic(ctx context.Context, key, name string) error
AddTopicMember(ctx context.Context, key, account string) error
RemoveTopicMember(ctx context.Context, key, account string) error
}
// accountBook is what the forge knows about who exists and what they
// hold — the truth the reconcile makes notify match.
type accountBook interface {
Accounts() ([]oltp.Account, error)
GrantsFor(account string) (map[string]oltp.Role, error)
}
// notifySync is what a reconcile did, for the startup log line.
type notifySync struct{ Subscribers, Members int }
// reconcileNotify makes notify's subscribers and topics match the
// forge's accounts and grants.
//
// Provisioning has always happened on the WRITE: account.add creates a
// subscriber, a grant joins the repository's activity topic. Every
// account and grant older than that code is therefore unprovisioned,
// and the failure is silent in the worst way — a trigger addressed to a
// topic that was never created is refused with a 404 the caller
// discards, so the forge reports nothing while nobody is told anything.
// This is the sweep that fixes the fleet rather than the next write.
//
// Every call it makes is idempotent, so it runs on every start. A
// failure on one account is collected and the sweep goes on: one
// account notify cannot hold must not cost the other nineteen their
// alerts.
func reconcileNotify(ctx context.Context, np topicPlane, book accountBook) (notifySync, error) {
accounts, err := book.Accounts()
if err != nil {
return notifySync{}, err
}
var sync notifySync
var errs []error
if err := np.EnsureTopic(ctx, adminsTopic, "quarry admins"); err != nil {
// Without the topic there is no point adding members to it, but
// the repository topics below are independent and still worth
// doing.
errs = append(errs, err)
}
for _, a := range accounts {
if err := np.EnsureSubscriber(ctx, a.Name); err != nil {
errs = append(errs, err)
// A subscriber that does not exist cannot join a topic;
// notify answers those with a 422 that says exactly that.
continue
}
sync.Subscribers++
// Disabled is WHEN the account was shut, empty while open. A
// shut account is not an admin for this purpose: alerts name
// production faults, and someone who can no longer sign in
// should not keep receiving them.
if a.Admin && a.Disabled == "" {
if err := np.AddTopicMember(ctx, adminsTopic, a.Name); err != nil {
errs = append(errs, err)
} else {
sync.Members++
}
} else if err := np.RemoveTopicMember(ctx, adminsTopic, a.Name); err != nil {
// Removing an absent member is already success in the
// client, so this is a real failure.
errs = append(errs, err)
}
if a.Disabled != "" {
continue
}
grants, err := book.GrantsFor(a.Name)
if err != nil {
errs = append(errs, err)
continue
}
for repo := range grants {
key := repoTopicKey(repo)
if err := np.EnsureTopic(ctx, key, repo+" activity"); err != nil {
errs = append(errs, err)
continue
}
if err := np.AddTopicMember(ctx, key, a.Name); err != nil {
errs = append(errs, err)
continue
}
sync.Members++
}
}
return sync, errors.Join(errs...)
}