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).
88 lines
2.7 KiB
Go
88 lines
2.7 KiB
Go
package db
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/dtoro/oikos/internal/adapters/postgres/sqlcgen"
|
|
"github.com/dtoro/oikos/internal/core/domain"
|
|
"github.com/dtoro/oikos/internal/core/ports"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// MetricsRepo implements ports.MetricsRepository.
|
|
type MetricsRepo struct {
|
|
pool *Pool
|
|
}
|
|
|
|
var _ ports.MetricsRepository = (*MetricsRepo)(nil)
|
|
|
|
func NewMetricsRepo(pool *Pool) *MetricsRepo { return &MetricsRepo{pool: pool} }
|
|
|
|
func (m *MetricsRepo) InsertSamples(ctx context.Context, entityID domain.UUID, samples []ports.MetricSample) error {
|
|
for _, s := range samples {
|
|
if err := sqlcgen.New(m.pool).InsertMetricSample(ctx, sqlcgen.InsertMetricSampleParams{
|
|
EntityID: mustUUID(entityID), Metric: s.Metric, Value: s.Value,
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SignalRepo implements ports.SignalRepository with inline SQL.
|
|
type SignalRepo struct {
|
|
pool *Pool
|
|
}
|
|
|
|
var _ ports.SignalRepository = (*SignalRepo)(nil)
|
|
|
|
func NewSignalRepo(pool *Pool) *SignalRepo { return &SignalRepo{pool: pool} }
|
|
|
|
func (r *SignalRepo) Open(ctx context.Context) ([]domain.Signal, error) {
|
|
rows, err := r.pool.Query(ctx,
|
|
`SELECT entity_id, kind, severity, state FROM signals WHERE state NOT IN ('resolved','failed') ORDER BY severity DESC`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []domain.Signal
|
|
for rows.Next() {
|
|
var s domain.Signal
|
|
var id uuid.UUID
|
|
if err := rows.Scan(&id, &s.Kind, &s.Severity, &s.State); err != nil {
|
|
return nil, err
|
|
}
|
|
s.EntityID = domain.UUID(id.String())
|
|
items = append(items, s)
|
|
}
|
|
return items, rows.Err()
|
|
}
|
|
|
|
func (r *SignalRepo) History(ctx context.Context, entityID domain.UUID, limit int) ([]domain.Signal, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (r *SignalRepo) UpsertWithTriggers(ctx context.Context, input ports.SignalUpsertInput) error {
|
|
eid := mustUUID(input.Signal.EntityID)
|
|
_, err := r.pool.Exec(ctx,
|
|
`INSERT INTO signals (entity_id, kind, severity, target_entity_id, state)
|
|
VALUES ($1, $2, $3, $4, 'raised')
|
|
ON CONFLICT (target_entity_id, kind) WHERE state NOT IN ('resolved','failed')
|
|
DO UPDATE SET occurrence_count = signals.occurrence_count + 1,
|
|
last_seen_at = now(), updated_at = now()`,
|
|
eid, input.Signal.Kind, input.Signal.Severity, eid)
|
|
return err
|
|
}
|
|
|
|
func (r *SignalRepo) Transition(ctx context.Context, input ports.SignalTransitionInput) (domain.Signal, error) {
|
|
tag, err := r.pool.Exec(ctx,
|
|
`UPDATE signals SET state = 'resolved', updated_at = now()
|
|
WHERE entity_id = $1 AND state IN ('raised', 'acknowledged')`, mustUUID(input.SignalID))
|
|
if err != nil {
|
|
return domain.Signal{}, err
|
|
}
|
|
if tag.RowsAffected() == 0 {
|
|
return domain.Signal{}, domain.ErrNotFound
|
|
}
|
|
return domain.Signal{}, nil
|
|
} |