Let fleet members find their peers without any central lookup call.
FleetPublisher reads the active-service roster and publishes, per bucket
(each cluster plus the reserved "infra" bucket) and per consumer service
type, the pre-filtered set of providers that type should discover. A
subscriber listens on exactly one subject,
cc.fleet.discovery.<cluster|infra>.<own-service-type>, and gets a
ready-to-use list. Rosters are re-published periodically because NATS
core pub/sub is fire-and-forget, so a service that subscribes late still
converges.
The publish function is injected rather than taken from the NATS client
directly, so the component has no hard broker dependency and is unit
testable without one; the wiring layer supplies nats.GetClient().Publish.
ProviderInfo is the entire on-wire shape and deliberately carries no
instance_id (the registration credential), no config content and no
config_revision: the discovery subjects have no application-layer auth,
so anything published there is readable by every subscriber.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Serve central configuration to fleet members from a hand-edited,
hierarchical tree of JSON files. Editing redundant options in UI forms
was rejected as too tedious, so humans author the tree directly; because
options are shared, config is resolved by deep-merging from broad to
specific (global -> service_type -> cluster -> host, with the cluster
layer skipped for infra-scope members) so a value set once high in the
tree is inherited and only overridden where it differs.
Pulls never touch the filesystem. A loader parses the whole tree into an
immutable snapshot published by an atomic pointer swap, which keeps the
hot path allocation-light and lock-free and gives two guarantees while
the tree is being edited: a file caught mid-write fails to parse (or its
size/mtime changes across the read) so that scan is discarded and the
previous good snapshot keeps serving, and a broken edit therefore never
reaches running services -- they keep the last known-good generation
until the tree parses cleanly again.
The revision is an fnv-1a content hash of the merged result, so it
changes exactly when the config changes, needs no counter kept in sync
with the files, and fits both the config_revision INTEGER column and the
existing AckConfig(int64) handshake.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Replace the free-form service_type string with a ServiceType enum (ccms,
ccmc, ccb, cces, ccsa, ccnc, ccem). The short code is the canonical value
stored in the DB, the config-tree directory name and a NATS discovery
subject token; Description() gives the human-readable name.
Add a second scope alongside per-node agents: ScopeInfra covers
cluster-independent monitoring infrastructure services, which are
identified by (hostname, service_type) and carry an empty cluster.
InfraRegistry is the ScopeInfra sibling of Registry and shares the same
table, state machine and config-revision handshake. It deliberately
exposes no StartSweep: MarkStale ages rows by last_heartbeat regardless
of scope, so a single sweep goroutine per process covers both scopes.
On the repository side ServiceDB gains a Scope column and the two
queries the discovery publisher and infra enumeration need, ListActive()
and ListByScope(). ResetConnection() now also resets the fleet
repository singleton so tests get a fresh handle.
Migration 13 is edited in place rather than superseded by a new
migration: the service table is unreleased, so no existing deployment
has it yet.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>