# `metrics-source` — fastpace's pluggable observability connector

*Module:* `packages/fp/src/integrations/metrics-source/`
*Shipped:* v0.41.0 (Wave 5c.B). *Threat-model alignment:* `THREAT-MODEL.md` §v0.41.0.

A single `MetricsSource` interface that lets fastpace read time-series
metrics from external observability stacks. One interface, multiple
adapters, used by:

- `fastpace deploy verify <env>` (post-deploy validation, 5c.B.1)
- `fastpace rollback assess <tag>` (rollback decision support, 5c.B.4)
- (future) `fastpace canary` (5c.7), `fastpace reviews sla` (5b.3),
  `fastpace control monitor` (5d.2)

## Shipped adapters (v0.41.0)

| Provider | Auth | Endpoint | Status |
|---|---|---|---|
| `datadog` | `FASTPACE_DATADOG_API_KEY` + `FASTPACE_DATADOG_APP_KEY` (+ optional `FASTPACE_DATADOG_SITE`) | `api.<site>.datadoghq.{com,eu}` | SaaS |
| `prometheus` | optional `FASTPACE_PROMETHEUS_TOKEN` *or* `FASTPACE_PROMETHEUS_BASIC_USER` + `_PASS`; `FASTPACE_PROMETHEUS_URL` is required | operator-configured | Self-hosted / air-gap |
| `sentry` | `FASTPACE_SENTRY_TOKEN` + `FASTPACE_SENTRY_ORG` (+ optional `FASTPACE_SENTRY_BASE_URL`) | `sentry.io` (SaaS) or operator-configured | SaaS or self-hosted |

CloudWatch / New Relic / Splunk follow in point releases (see
`exec-plans/metrics-source-connector-prep.md` §1).

## Adapter contract

Every adapter exports the same shape:

```js
module.exports = {
  name: '<provider>',                            // string id, matches PROVIDERS key
  query(metric, range, opts) -> Promise<Series>, // read a time-series
  health() -> Promise<{ ok, latency_ms, message }>,
};
```

Where `range` is `{ from, to, step_seconds }` and `Series` is:

```js
{
  provider: '<provider>',
  query: '<vendor-specific query string>',
  range: { from, to, ... },
  series: [
    {
      metric: '<metric name>',
      scope: { ... } | null,        // labels / tags
      pointlist: [{ ts: <unix>, v: <number> }, ...],
      unit: null,
      aggr: null,
    },
    ...
  ],
}
```

## Trust-boundary explanation

When fastpace queries an external observability vendor, it crosses a new
trust boundary. The threat model walkthrough lives in
`THREAT-MODEL.md` §v0.41.0; the controls baked into this module are:

- **TLS verification required** on every call (`rejectUnauthorized: true`
  in `transport.js`; no `--insecure` flag, no cert-pinning bypass).
- **Per-provider hostname allowlist** for SaaS adapters
  (`datadog.com|.eu` for Datadog, `sentry.io` for Sentry). Customers
  self-hosting Prometheus/Sentry bypass the allowlist by configuring an
  explicit base URL.
- **GET-only transport.** The shared `httpGet` rejects any other HTTP
  method by construction. A leaked metrics-API token cannot be used to
  *write* through fastpace — even if the customer accidentally minted a
  read-write token.
- **Env-var-only auth.** No plaintext keys in any fastpace config file.
  Decision recorded in `exec-plans/metrics-source-connector-prep.md` §2.
- **Audit emission.** Every query handled via `runQuery()` emits a
  chained `metrics-source.queried` event into `fastpace/audit.log`:
  `{ provider, query_hash, response_hash, timestamp, latency_ms, cache_hit }`.
- **Cache for replay + DoS resilience.** Query results land in
  `fastpace/metrics-cache/<provider>/<hash>.json` (gitignored by
  default). Default TTL: 5 minutes. The hash binds the cached payload to
  the query string so a stale cache can't be served for a different
  question.

## How to add a new adapter

1. Create `packages/fp/src/integrations/metrics-source/<provider>.js`
   exporting `{ name, query, health }`.
2. Require `./transport` for HTTPS; **do not** roll your own HTTP — the
   shared transport enforces GET-only + TLS-verify by construction.
3. Pick an env-var prefix `FASTPACE_<PROVIDER>_*`. Document each
   required + optional env var at the top of the file.
4. Add a per-provider hostname allowlist if the adapter targets a SaaS
   endpoint; pass it to every `httpGet` call.
5. Wire the adapter into `index.js`'s `PROVIDERS` map.
6. Add a row to the "Shipped adapters" table above + an entry to
   `THREAT-MODEL.md` §v0.41.0 if the new vendor introduces controls
   beyond the existing ones (e.g. AWS IAM assume-role for CloudWatch).
7. Add a smoke test in `packages/fp/test/sprint30.test.js` (or the
   then-current sprint test) covering: (a) `health()` shape, (b)
   `name` matches the module name, (c) interface keys present.

## Usage from feature code

```js
const ms = require('../integrations/metrics-source');

// One-shot — emits audit + caches automatically.
const { series, cache_hit, latency_ms } = await ms.runQuery({
  cwd: process.cwd(),
  providerName: 'datadog',
  metric: 'sum:trace.servlet.request.errors{env:production}.as_rate()',
  range: { from: Date.now() / 1000 - 3600, to: Date.now() / 1000 },
});

// Or grab the adapter directly for finer control.
const adapter = ms.get('prometheus');
const h = await adapter.health();
```

## Open questions deferred to v0.42+

- **Multi-source cross-check.** Customer declares two providers and the
  `deploy verify` step warns when they disagree. Forecast'd in the
  threat model; not implemented in v0.41.0.
- **Cost-per-query telemetry.** Datadog charges per query; emitting a
  cost-event to the audit chain is on the v0.43 radar.
- **fastpace-internal query DSL.** Vendor-specific in v0.41.0 (Option B,
  decision §3 of the prep doc). Layer an abstraction over the most
  common queries (error rate / latency / throughput) in v0.42+ once we
  see real customer patterns.
