Problem: signal lifecycle (upsert, resolve, health aggregation) and
observe-pass orchestration (load checks, resolve targets, run probes,
aggregate health) were embedded in scheduler/scheduler.go — 1095 lines
of monolith with no port abstraction.
Change:
- app/signals.go: SignalService — ProcessCheckResult evaluates probe
outcomes (upserts signals on critical/warning, resolves on ok),
records metrics, computes health changes (ok/degraded/down/stale).
WorstHealthForTarget aggregates open signals into entity health.
- app/observation.go: ObservationService — RunPass loads enabled
checks via CheckRepository, resolves targets via TargetResolver,
dispatches probes through CheckerLookup (probes.Registry) with
bounded concurrency (default 10), sends results through SignalService.
- adapters/postgres/signals.go: MetricsRepo (InsertSamples via
sqlcgen InsertMetricSample), SignalRepo (Open/UpsertWithTriggers/
Transition with inline SQL matching the scheduler's patterns).
Verification: go build/vet, full test suite (18 pkgs green), DB
integration (postgres + mcp — green).