Documents the exact hook point chosen for each of the 3 dead observability primitives (access log, request-duration histogram, tracer provider) and the 11 named business-health metrics, verified against the real current code rather than assumed — including a pre-existing SLA-run status data quality gap surfaced along the way (documented, not fixed here). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
55 lines
3.2 KiB
Markdown
55 lines
3.2 KiB
Markdown
# Contract: `/metrics` output
|
|
|
|
This feature adds no new HTTP endpoints — `GET /metrics` already exists and its response shape
|
|
(Prometheus text exposition format) is unchanged. This document is the contract for its
|
|
**content**: which metric series a consumer (Prometheus, or any scraper) can rely on after this
|
|
feature ships, replacing the usual per-endpoint request/response contract for a feature with no
|
|
new routes.
|
|
|
|
## Guarantees
|
|
|
|
1. Every metric already exposed today (the default `prom-client` process metrics, and
|
|
`supporthub_http_request_duration_seconds`) continues to appear, with the same name and label
|
|
set — FR-010. `supporthub_http_request_duration_seconds` gains real observations where today
|
|
it has none; its metric name/labels/type do not change.
|
|
2. Each of the eleven new series in [data-model.md](../data-model.md#metrics-prometheus-via-prom-client)
|
|
appears on `/metrics` from process start (a `Counter`/`Histogram` with zero observations
|
|
still exports its metadata — `# HELP`/`# TYPE` lines — even before its first increment; a
|
|
consumer's dashboard/alert config can reference it immediately without waiting for the first
|
|
event).
|
|
3. No metric name or label value is derived from unbounded, request-supplied input — every
|
|
label is one of: a fixed small enum (`outcome`, `resolved_by`, `matched`), a route pattern
|
|
(bounded by the number of registered routes), a tool name (bounded by the tool registry), an
|
|
error code or category ID (bounded by admin-configured product data, not raw user text).
|
|
This is a deliberate constraint, not an incidental one — unbounded label cardinality is a
|
|
well-known way to make a Prometheus deployment fall over, and every label chosen in
|
|
data-model.md was checked against this before being finalized.
|
|
4. `/health`, `/health/live`, `/health/ready` response shapes are unchanged (FR-010) — this
|
|
feature does not touch `health.service.ts` or `health.routes.ts`.
|
|
|
|
## Example (illustrative, not exhaustive)
|
|
|
|
```text
|
|
# HELP supporthub_http_request_duration_seconds Duration of HTTP requests in seconds
|
|
# TYPE supporthub_http_request_duration_seconds histogram
|
|
supporthub_http_request_duration_seconds_bucket{method="POST",route="/tickets",status_code="201",le="0.1"} 3
|
|
supporthub_http_request_duration_seconds_count{method="POST",route="/tickets",status_code="201"} 3
|
|
|
|
# HELP supporthub_ai_session_outcomes_total Count of AI support sessions by terminal outcome
|
|
# TYPE supporthub_ai_session_outcomes_total counter
|
|
supporthub_ai_session_outcomes_total{outcome="resolved"} 12
|
|
supporthub_ai_session_outcomes_total{outcome="escalated"} 4
|
|
|
|
# HELP supporthub_sla_run_outcomes_total Count of SLA runs by outcome
|
|
# TYPE supporthub_sla_run_outcomes_total counter
|
|
supporthub_sla_run_outcomes_total{outcome="met"} 9
|
|
supporthub_sla_run_outcomes_total{outcome="breached"} 1
|
|
```
|
|
|
|
## Verification
|
|
|
|
Integration tests assert against this contract by scraping `GET /metrics` (a real
|
|
`app.inject` call, real registry) before and after driving each metric's real underlying event
|
|
through the real API, parsing the specific series' value out of the text response and asserting
|
|
it moved by exactly the expected amount — never by mocking `prom-client` or the registry itself.
|