# regional-insight-server

Weekly fleet inspection service for observability regions: K8s right-sizing,
node cost analysis, and capacity risk detection, with a web UI and Prometheus
metrics. Alert rules are deferred to a later phase.

## Layout

- `src/engine/` — the analysis pipeline (resolve, inventory, workloads,
  shrink, report model, pricing), pure TS with strict types and zero
  Next/React imports. Data contracts live in `src/engine/types.ts`.
- `src/server/` — orchestration: fleet runner, per-region runner, file-backed
  run store, cron scheduler (production only), Prometheus metrics.
- `src/app/` — API routes (`/api/v1/...`) and the SPA pages (fleet dashboard,
  regions list, insight, observe, compare, run history).
- `scripts/record-goldens.ts` — golden checker/recorder: runs the engine over
  the synthetic cluster corpus and diffs (or, with `--force`, rewrites) the
  golden artifacts in `tests/fixtures/<region>/`.
- `scripts/run-region.ts` — single-region end-to-end smoke through the engine:
  `npm run region -- <region>`.
- `scripts/sync-config.ts` — regenerates `config/fleet.json` from the
  monorepo's vmcluster env directories.
- `tests/` — `engine/synthetic/` (hand-authored cluster specs + payload
  builder — the golden suite's input corpus), `engine/synthetic.spec.ts`
  (hand-verified assertions over the full pipeline), `engine/goldens.spec.ts`
  (artifact diff vs goldens, tolerance rel 1e-9 with exact key-set equality),
  `engine/semantics.spec.ts` (coercion/rounding contracts),
  `conventions/naming.spec.ts` (snake_case guard), plus server and component
  tests.

## Dev

```
npm install
npm run dev                           # web UI on :3000 — scheduler never runs in dev
npm run build && npm run start        # production server — instrumentation starts the scheduler
npm test                              # full suite, no external services
npm run goldens                       # check engine outputs vs golden fixtures
npm run goldens -- --force            # re-record goldens after intended output changes
npm run region -- aws-eu-west-1      # live end-to-end smoke (needs dataplane access)
```

The two synthetic regions cover vendor-specific paths (AWS EKS labels and
capacity types, Azure agentpool labels with the capacity default, pricing
coverage gaps). Evolving the engine's PromQL surface requires no dataplane
access — see `tests/fixtures/README.md`.

### Runtime environment variables

Resolved once per process in `src/server/config.ts`:

| Variable            | Default        | Meaning                                             |
| ------------------- | -------------- | --------------------------------------------------- |
| `SVC_ROOT`          | `process.cwd()`| service root (config/ lives here)                   |
| `DATA_DIR`          | `<SVC_ROOT>/data` | all runtime state (see below)                    |
| `SCHEDULE_CRON`     | `0 2 * * 1`    | weekly fleet run, Mon 02:00 UTC+8 (Asia/Shanghai wall clock; production only)   |
| `LOOKBACK_DAYS`     | `7`            | PromQL lookback window per region (integer 1–365)   |
| `REGION_CONCURRENCY`| `8`            | parallel regions inside one fleet run (integer ≥ 1) |

### API surface

All endpoints serve JSON under the same origin, with an
`{error:{code,message}}` envelope on failures:

- `GET /api/v1/overview` — fleet KPIs + latest snapshot row per region
- `GET /api/v1/regions/{regionKey}` — the artifacts of the region's latest run
- `GET /api/v1/regions/{regionKey}/history?weeks=` — per-run cost/savings points
- `GET /api/v1/regions/{regionKey}/compare?runA=&runB=` — two-run region diff
- `GET /api/v1/regions/{regionKey}/usage-series?ns=&kind=&name=` — live 24h workload trend
- `GET /api/v1/regions/{regionKey}/observe-series?dashboard=&chart=` — live registry dashboard chart series
- `GET /api/v1/runs?limit=` — run summaries, newest first
- `POST /api/v1/runs` — manual fleet-wide trigger; `202 {started, runId}`, `409` while a run is in flight
- `GET /api/v1/runs/current` — live run header (or `null`)
- `GET /api/v1/runs/{runId}` — run detail: summary fields plus per-region entries (status, stage, error) from the run header
- `GET /api/v1/compare/overview?runA=&runB=` — two-run fleet diff
- `GET /metrics` — Prometheus exposition · `GET /api/healthz` — liveness

## Deployment & data directory

All runtime state lives under one directory, resolved once per process in
`src/server/config.ts`:

- `DATA_DIR` env var (default `<repo>/data`)
- contains `runs/` (batch envelopes + per-region artifacts), `pricing.json`
  (accumulating price cache, seeded from `config/pricing-seed.json`),
  `url-cache/` (pricing HTTP response cache), and `manual/` (output of
  `npm run region`, whose `--data-dir` flag overrides the env var)

`data/` is runtime state and never committed (`.gitignore` covers it). In
containers, point `DATA_DIR` at a mounted volume so state survives
restarts — boot recovery (stale `running` headers are rewritten to
`aborted`) and run history both depend on persistence:

```sh
docker run -e DATA_DIR=/data -v regional-insight-data:/data …
```

The same shape works as a Kubernetes PVC mounted at `/data`. Note the
scheduler only starts in production (`NODE_ENV=production`); `npm run dev`
never schedules runs. The service is single-process by design: the run
store's write serialization and the pricing single-writer assume one
process owns `DATA_DIR` — run one replica per data directory.
