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>
3.2 KiB
3.2 KiB
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
- Every metric already exposed today (the default
prom-clientprocess metrics, andsupporthub_http_request_duration_seconds) continues to appear, with the same name and label set — FR-010.supporthub_http_request_duration_secondsgains real observations where today it has none; its metric name/labels/type do not change. - Each of the eleven new series in data-model.md
appears on
/metricsfrom process start (aCounter/Histogramwith zero observations still exports its metadata —# HELP/# TYPElines — even before its first increment; a consumer's dashboard/alert config can reference it immediately without waiting for the first event). - 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. /health,/health/live,/health/readyresponse shapes are unchanged (FR-010) — this feature does not touchhealth.service.tsorhealth.routes.ts.
Example (illustrative, not exhaustive)
# 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.