---
id: w1/m2.5/t005
title: Extract internal/metrics (metrics feature)
worker: worker1
status: done
estimate: 40m
depends_on: [w1/m2.5/t001]
---

## Objective

Move the metrics feature into `internal/metrics`: the metrics verbs, the four injected sources (`ResourceMetrics`, `RequestMetrics`, `MonthToDateBandwidth`, `MetricsFilterValues`), the view models, and the REST/GraphQL/MCP fragments — plus tests.

## Context

- Source spans `internal/api/metrics.go`, `metricsource.go`, `rest_metrics.go`, the metrics source fields on `Core`, and `metrics_test.go`.
- Metrics has **no persistent data** — it reads metrics-server + Traefik-via-Prometheus live. This is the concrete reason the earlier `features/<x>/data` idea didn't fit; here metrics is simply a package with `service.go` + sources, no data layer.
- `ErrMetricsUnavailable` semantics (source not wired ⇒ 503, App exists) must be preserved per metric kind.

## Steps

1. Create `internal/metrics` with `service.go`, `models.go`, `source.go` (the four source interfaces + Prometheus/metrics-server adapters), and `rest.go`/`graphql.go`/`mcp.go` fragments.
2. Depend on `core` for base/gate; the metrics service holds only metrics sources (not the kitchen-sink `Core`).
3. Move `metrics_test.go`; keep the per-source availability and filter-discovery assertions.

## Files

- `lego/backend/internal/metrics/{service.go,models.go,source.go,rest.go,graphql.go,mcp.go,*_test.go}`
- `lego/backend/internal/api/{metrics.go,metricsource.go,rest_metrics.go,core.go}` (remove moved code)

## Acceptance criteria

- [ ] `go build ./... && go test ./...` green.
- [ ] Each metric kind reachable over its surfaces; unwired source ⇒ `ErrMetricsUnavailable` (503) as before.
- [ ] Instance count still works with no source wired (counts pods).

## Out of scope

- apikeys/postgres (t006); finalize/docs (t007).
