refactor: split phase3.go + extract MCP tool registry (R4)
internal/httpapi/phase3.go (2627 lines, 12+ resource domains) split into 15 per-resource files: - actuator.go: SSH execution machinery (initSSH, sshExec, resolveRunTarget, executeApprovedAction, jsonErr, gatewayPreflightPassed, resolveTemplate) - checks.go, classifications.go, executions.go, approvals.go, patterns.go, skills.go, approval_rules.go, autonomy.go, risk_classes.go, relationships.go, entity_types.go, metrics.go, agent_activity.go, helpers.go — one file per resource domain, each with its own imports. internal/mcp/server.go: newServer (708 lines, 33 inline tool registrations) refactored to a registry pattern: - internal/mcp/tools.go (new): toolReg struct + allTools() returning all 33 tool definitions. Handler logic moved verbatim — no changes to tool names, descriptions, schemas, or behavior. - server.go: newServer is now 9 lines (iterate registry, AddTool each). -699 lines. No function logic, names, or signatures changed. go vet, build, and all tests pass (httpapi, mcp, db, policy).
This commit is contained in:
180
internal/httpapi/metrics.go
Normal file
180
internal/httpapi/metrics.go
Normal file
@@ -0,0 +1,180 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/dtoro/oikos/internal/domain"
|
||||
"github.com/dtoro/oikos/internal/httpapi/gen"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
// ─── Metrics ───────────────────────────────────────────────────────────
|
||||
|
||||
func (s *Server) QueryMetrics(ctx context.Context, req gen.QueryMetricsRequestObject) (gen.QueryMetricsResponseObject, error) {
|
||||
if req.Params.EntityId == nil || *req.Params.EntityId == "" {
|
||||
return nil, fmt.Errorf("%w: entity_id is required", domain.ErrInvalidInput)
|
||||
}
|
||||
entityID, err := s.resolveEntityID(ctx, *req.Params.EntityId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
from := time.Now().Add(-24 * time.Hour)
|
||||
if req.Params.From != nil {
|
||||
from = *req.Params.From
|
||||
}
|
||||
to := time.Now()
|
||||
if req.Params.To != nil {
|
||||
to = *req.Params.To
|
||||
}
|
||||
|
||||
var metricNames []string
|
||||
if req.Params.Metric != nil && len(*req.Params.Metric) > 0 {
|
||||
metricNames = *req.Params.Metric
|
||||
} else {
|
||||
// metric omitted: report every metric recorded for this entity in range.
|
||||
rows, err := s.pool.Query(ctx, `
|
||||
SELECT DISTINCT metric FROM metric_samples
|
||||
WHERE entity_id = $1 AND ts >= $2 AND ts <= $3
|
||||
ORDER BY metric`, entityID, from, to)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for rows.Next() {
|
||||
var name string
|
||||
if err := rows.Scan(&name); err != nil {
|
||||
rows.Close()
|
||||
return nil, err
|
||||
}
|
||||
metricNames = append(metricNames, name)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
items := []gen.MetricSeries{}
|
||||
for _, metricName := range metricNames {
|
||||
series := gen.MetricSeries{
|
||||
EntityId: entityID.String(),
|
||||
Metric: metricName,
|
||||
Rollup: gen.MetricSeriesRollupRaw,
|
||||
}
|
||||
|
||||
rows, err := s.pool.Query(ctx, `
|
||||
SELECT ts, value
|
||||
FROM metric_samples
|
||||
WHERE entity_id = $1 AND metric = $2
|
||||
AND ts >= $3 AND ts <= $4
|
||||
ORDER BY ts ASC`,
|
||||
entityID, metricName, from, to)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
samples := []struct {
|
||||
Avg *float32 `json:"avg"`
|
||||
Count *int `json:"count"`
|
||||
Max *float32 `json:"max"`
|
||||
Min *float32 `json:"min"`
|
||||
Ts time.Time `json:"ts"`
|
||||
Value *float32 `json:"value"`
|
||||
}{}
|
||||
|
||||
for rows.Next() {
|
||||
var ts time.Time
|
||||
var val float64
|
||||
if err := rows.Scan(&ts, &val); err != nil {
|
||||
rows.Close()
|
||||
return nil, err
|
||||
}
|
||||
f := float32(val)
|
||||
samples = append(samples, struct {
|
||||
Avg *float32 `json:"avg"`
|
||||
Count *int `json:"count"`
|
||||
Max *float32 `json:"max"`
|
||||
Min *float32 `json:"min"`
|
||||
Ts time.Time `json:"ts"`
|
||||
Value *float32 `json:"value"`
|
||||
}{Value: &f, Ts: ts})
|
||||
}
|
||||
rows.Close()
|
||||
if rows.Err() != nil {
|
||||
return nil, rows.Err()
|
||||
}
|
||||
|
||||
series.Samples = samples
|
||||
items = append(items, series)
|
||||
}
|
||||
|
||||
if items == nil {
|
||||
items = []gen.MetricSeries{}
|
||||
}
|
||||
return gen.QueryMetrics200JSONResponse{Items: items}, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetTrends(ctx context.Context, req gen.GetTrendsRequestObject) (gen.GetTrendsResponseObject, error) {
|
||||
entityID, err := s.resolveEntityID(ctx, req.EntityId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
from := time.Now().Add(-7 * 24 * time.Hour)
|
||||
if req.Params.From != nil {
|
||||
from = *req.Params.From
|
||||
}
|
||||
|
||||
rows, err := s.pool.Query(ctx, `
|
||||
SELECT metric,
|
||||
ROUND(avg(value)::numeric, 2) AS avg_val,
|
||||
ROUND(stddev(value)::numeric, 2) AS std_val,
|
||||
count(*) AS sample_count,
|
||||
ROUND(regr_slope(value, EXTRACT(EPOCH FROM ts)::numeric)::numeric, 4) AS slope
|
||||
FROM metric_samples
|
||||
WHERE entity_id = $1 AND ts >= $2
|
||||
GROUP BY metric
|
||||
ORDER BY metric`, entityID, from)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
items := []gen.Trend{}
|
||||
for rows.Next() {
|
||||
var t gen.Trend
|
||||
var avgVal, stdVal, slopeNum pgtype.Numeric
|
||||
var sampleCount int
|
||||
if err := rows.Scan(&t.Metric, &avgVal, &stdVal, &sampleCount, &slopeNum); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Determine direction.
|
||||
if slopeNum.Valid {
|
||||
f, _ := slopeNum.Float64Value()
|
||||
t.Slope = float32Ptr(float32(f.Float64))
|
||||
if f.Float64 > 0.01 {
|
||||
t.Direction = gen.Improving
|
||||
} else if f.Float64 < -0.01 {
|
||||
t.Direction = gen.Degrading
|
||||
} else {
|
||||
t.Direction = gen.Stable
|
||||
}
|
||||
} else {
|
||||
t.Direction = gen.Unknown
|
||||
}
|
||||
items = append(items, t)
|
||||
}
|
||||
if rows.Err() != nil {
|
||||
return nil, rows.Err()
|
||||
}
|
||||
if items == nil {
|
||||
items = []gen.Trend{}
|
||||
}
|
||||
return gen.GetTrends200JSONResponse{Items: items}, nil
|
||||
}
|
||||
|
||||
func float32Ptr(f float32) *float32 {
|
||||
return &f
|
||||
}
|
||||
Reference in New Issue
Block a user