Files
support_backend/specs/014-full-observability/plan.md
T
saqib mirandClaude Sonnet 5 5a0fe9f847 docs(014-full-observability): plan, research, data model, contract, quickstart
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>
2026-09-08 10:51:22 +05:30

10 KiB

Implementation Plan: Full Observability

Branch: 014-full-observability | Date: 2026-09-07 | Spec: spec.md

Input: Feature specification from specs/014-full-observability/spec.md

Summary

Wires three already-scaffolded-but-inert observability primitives into something real: a per-request structured access log (none exists today — Fastify's own request logging is fully disabled), the existing-but-never-observed request-duration histogram, and a real OpenTelemetry tracer provider behind the existing-but-never-called getTracer() helper. Adds eleven live Prometheus counters/histograms for the business-health metrics docs/09-testing-observability- cicd.md names, each wired at one existing choke point per metric (an event-bus subscriber where one already exists for the transition, a single already-existing method otherwise) rather than scattered across every call site. No new endpoints, no schema changes, no supporthub-web work — see research.md for the exact hook point chosen for each of the fourteen instrumentation targets (3 infra + 11 named metrics) and why.

Technical Context

Language/Version: TypeScript 5.4 / Node.js 20+ (unchanged).

Primary Dependencies: New — @opentelemetry/exporter-trace-otlp-http (OTLP/HTTP span export), @opentelemetry/resources (service-name resource attribute). Reused, already installed — @opentelemetry/api, @opentelemetry/sdk-trace-base (provider, processors, and both the console and in-memory exporters used here all come from this one package), prom-client, pino. Reused Node built-in — async_hooks' AsyncLocalStorage.

Storage: No schema change. All new state is either in-process (Prometheus metric registry, the ALS request-context store, the tracer provider) or exported to wherever tracing is configured to send it — no new Postgres/Redis reads or writes beyond a handful of existing-table lookups already needed to label a metric correctly (e.g. resolutionRepository.findByTicketId to distinguish AI vs. human resolution).

Testing: Vitest — unit tests for the ALS-based logger mixin (a log call inside a request context carries requestId/correlationId; one outside carries neither) and for the SLA-compliance metric's "don't double-count an already-breached run as met" guard. Integration tests against real Postgres/Redis for: the access-log line's presence/shape (captured via a logger.info spy, same technique as 013's password-reset test), /metrics scraped before/after real traffic showing the duration histogram and each of the eleven business counters/histograms change by the expected amount when their real underlying event is driven through the real API, and a real multi-span trace (read back from the test-environment InMemorySpanExporter) for the two named cross-module paths.

Target Platform: Same Fastify modular monolith. Modifies infrastructure/observability/* (logger, metrics, tracing, a new request-context store) and plugins/request-context.plugin.ts (the new onResponse hook); adds small, single-call-site instrumentation lines inside ai-support/sessions, ai-support/knowledge, ai-support/tools, ticketing/tickets, ticketing/messages, orchestration/sla, and a handful of new subscribers in src/events/handlers/index.ts. No module gains a new public export surface beyond what getTracer() already exposed.

Project Type: Backend service — single project.

Performance Goals: The onResponse hook adds one Pino log call and one histogram .observe per request — both already-paid-for infrastructure (the logger and the metric object already exist), no new I/O on the request hot path. Trace export runs via BatchSpanProcessor (out of the request's own async chain) so span export latency never adds to response time. Metric increments at the eleven business hook points are in-memory counter operations, not database writes — the handful of read lookups needed for correct labeling (e.g. the resolution lookup for #3/#4) are single-row, already-indexed reads on tables these modules already query routinely.

Constraints: FR-007 — tracing must degrade gracefully; the API must start and serve traffic normally with no collector configured or reachable. FR-009 — no new human-facing endpoint, dashboard, or aggregation logic; every FR-008 metric is a raw counter/histogram for an external scraper, full stop. FR-010 — /health* and the existing histogram's shape on /metrics must not change for any existing consumer (only new metrics are added, nothing existing is renamed or removed).

Scale/Scope: Zero new routes. Three modified observability infrastructure files plus one new request-context store. Eleven new metric definitions plus their one-choke-point instrumentation call each. Two new dependencies. No schema migration, no new module.

Constitution Check

GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.

Principle / Section Check Result
I. SaaS Is the Sole Identity & Access Authority Not applicable — no identity/access surface touched. PASS — N/A
II. Configuration Over Hardcoding The tracing exporter destination (OTEL_EXPORTER_OTLP_ENDPOINT) is env-driven, not hardcoded per environment; no business policy value is introduced by this feature (no SLA/routing/threshold numbers). PASS
III. Layered Architecture With Enforced Module Boundaries No new module; existing module boundaries unchanged (each metric's instrumentation call lives inside the module that already owns the event, per research.md's per-metric table). The two repository-layer instrumentation calls (#1/#2, AI session status) are a deliberate, disclosed exception — see research.md §5's justification: observability calls are already a cross-cutting concern used from any layer in this codebase (e.g. logger.error inside tool-executor.ts), not the kind of business-logic leakage this principle targets. PASS
IV. AI Recommends, Deterministic Policy Decides Not applicable — no AI decision logic changed, only observation of its outcomes. PASS — N/A
V. Evidence-Based Verification Not applicable. PASS — N/A
VI. Durable Audit & History Directly implements this principle's own stated requirement — "every log line MUST carry a request ID/correlation ID" is written in the constitution today but not actually true until this feature (FR-001/FR-002). PASS — this feature closes a pre-existing constitutional gap
VII. Concurrency-Safe, Durable Job Handling The first-response-time metric (#5) has a benign, disclosed race (two concurrent first AGENT_MESSAGEs could both read "zero prior messages" and both observe) — acceptable because it is a best-effort observability metric, not the assignment/SLA correctness this principle is protecting; no persisted state or business decision depends on it. PASS
VIII. Problem and Ticket Are Separate, Related Entities Not applicable — no model change. PASS — N/A
Technology & Platform Constraints Two new dependencies (both OpenTelemetry, both already in the stack's declared technology list — "OpenAPI" aside, tracing itself was always part of the stated stack via the pre-existing @opentelemetry/api/sdk-trace-base dependencies) — no new infrastructure category introduced. PASS

No violations requiring Complexity Tracking justification.

Project Structure

Documentation (this feature)

specs/014-full-observability/
├── plan.md
├── research.md
├── data-model.md
├── quickstart.md
├── contracts/
│   └── metrics-contract.md
└── tasks.md

Source Code (repository root)

supporthub-api/
├── src/
│   ├── infrastructure/
│   │   └── observability/
│   │       ├── logger.ts                    # MODIFIED — mixin reads the new ALS store
│   │       ├── metrics.ts                   # MODIFIED — 11 new Counter/Histogram definitions
│   │       ├── tracing.ts                   # MODIFIED — real provider init, exporter selection
│   │       └── request-context.store.ts     # NEW — AsyncLocalStorage<RequestContext>
│   ├── plugins/
│   │   └── request-context.plugin.ts        # MODIFIED — onResponse access-log + histogram hook,
│   │                                            onRequest now runs the rest of the request
│   │                                            inside the ALS store
│   ├── events/
│   │   └── handlers/index.ts                # MODIFIED — 3 new subscribers (human-resolution +
│   │                                            resolution-time on TICKET_UPDATED/RESOLVED,
│   │                                            escalation-rate on ESCALATION_TRIGGERED)
│   └── modules/
│       ├── ai-support/
│       │   ├── sessions/repository/session.repository.ts   # MODIFIED — AI resolution/escalation
│       │   ├── knowledge/service/error-codes.service.ts     # MODIFIED — most-common-errors
│       │   └── tools/service/tools.service.ts                # MODIFIED — tool-failure + knowledge-
│       │                                                        effectiveness
│       ├── ticketing/
│       │   ├── tickets/service/tickets.service.ts            # MODIFIED — recurring-problems, plus
│       │   │                                                    the two named trace spans
│       │   └── messages/service/messages.service.ts           # MODIFIED — first-response-time
│       └── orchestration/
│           └── sla/service/sla.service.ts                     # MODIFIED — SLA-compliance
└── tests/
    ├── unit/observability/                  # ALS mixin, SLA-compliance double-count guard
    └── integration/observability/           # access log, /metrics scrape assertions (11 metrics
                                                 + duration histogram), cross-module trace

Structure Decision: Single project, no new module. All changes are surgical additions inside infrastructure/observability (the module that already owns this concern) plus one small, justified instrumentation line inside each of six existing business modules, following the per-metric hook points research.md already identified against the real, current code.

Complexity Tracking

No constitution violations — table intentionally omitted.