business-metrics.test.ts's "human resolution" case drove a ticket
through a real HUMAN_ESCALATION transition via ticketsService.updateStatus,
which triggers the real orchestration subscriber's default ROUND_ROBIN
auto-assignment against every agent in the shared throwaway database —
reproduced deterministically landing on agent-ticket-queue.test.ts's own
dedicated agent. Fixed by driving the intermediate transitions directly
through ticketsRepository.updateStatus (no domain-event publish),
reserving the real, event-publishing call for only the final RESOLVED
transition the metric subscriber needs to observe.
Also documents (checklist Notes), without fixing, a separate pre-existing
issue confirmed unrelated to this feature via git checkout to the clean
013-auth-hardening tip: nearly every integration test file's product ID
collapses to the same 4-letter ticket-code prefix ("TEST"), so enough
concurrent TEST_*-prefixed files can exceed the fixed retry ceiling on
ticket-code generation and surface as a real 500 — a 003-ticketing
concern, out of scope here.
Marks all 30 tasks.md items complete.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
6.7 KiB
6.7 KiB
Specification Quality Checklist: Full Observability
Purpose: Validate specification completeness and quality before proceeding to planning Created: 2026-09-07 Feature: spec.md
Content Quality
- No implementation details (languages, frameworks, APIs)
- Focused on user value and business needs
- Written for non-technical stakeholders
- All mandatory sections completed
Requirement Completeness
- No [NEEDS CLARIFICATION] markers remain
- Requirements are testable and unambiguous
- Success criteria are measurable
- Success criteria are technology-agnostic (no implementation details)
- All acceptance scenarios are defined
- Edge cases are identified
- Scope is clearly bounded
- Dependencies and assumptions identified
Feature Readiness
- All functional requirements have clear acceptance criteria
- User scenarios cover primary flows
- Feature meets measurable outcomes defined in Success Criteria
- No implementation details leak into specification
Notes
- This is
docs/10-implementation-roadmap.md's own Phase 11, second sub-area, per explicit user direction (the first was 013-auth-hardening's security pass). The user explicitly chose "Full observability" over "Reporting/analytics dashboards" as a distinct, separately-scoped sub-area — FR-009 and several Assumptions exist specifically to keep this feature from drifting into that adjacent, not-yet-started work. - The three named infrastructure gaps (no per-request access log, a dead request-duration histogram, a never-initialized tracer) and all eleven "key metrics to track" being completely untracked today were confirmed by direct code inspection before writing this spec, not assumed.
- All items pass; no revision iterations were needed. No [NEEDS CLARIFICATION] markers were required — every open question had a reasonable, documented default (see Assumptions).
Implementation Notes (post-build)
- Registering a real
TracerProvideralone was not sufficient to make span nesting work across this feature's own async event-bus subscribers: without also registering anAsyncLocalStorageContextManager(@opentelemetry/context-async-hooks, a third new dependency beyond the two research.md originally named), the OpenTelemetry API'scontext.active()is a no-op that does not propagate acrossawaitboundaries at all —orchestration.assignmentcame out as its own unrelated root span/trace instead of nesting underai.escalation. Caught by the tracing integration test's own parent/child assertions actually failing on the first implementation, not assumed correct from reading the SDK's docs. - Installing
@opentelemetry/exporter-trace-otlp-httpalongside the already-pinned@opentelemetry/sdk-trace-base@^1.22.0pulled two incompatible OpenTelemetry core/resources major versions (1.x and 2.x) side by side. Resolved by bumpingsdk-trace-baseto^2.11.0to match — this also happened to close a moderate DoS advisory in@opentelemetry/core <2.8.0that the 1.x line was pinned to. - T016 (graceful degradation under an unreachable OTLP endpoint) ended up as its own unit test
(
tests/unit/observability/tracing-graceful-degradation.test.ts) rather than living intracing.test.tsas tasks.md originally described. Reason:tracing.tsalways uses the in-memory test exporter whenNODE_ENV=test, so the integration suite's own running app can't be pointed at a bad OTLP endpoint to exercise this. The unit test instead constructs a realBasicTracerProvider/BatchSpanProcessor/OTLPTraceExporterpointed at a genuinely unreachable address directly, and — importantly — verifies the SDK's background export path (what production actually exercises) never produces an unhandled rejection, rather than callingforceFlush()directly, which is documented OpenTelemetry behavior that does reject on a failed export by design (the first version of this test asserted the wrong thing and failed against real, correct SDK behavior — not a bug in this feature's own code). sla.service.ts's pre-existing status-overwrite gap (a'breached'run's status silently becomes'completed'if the ticket later resolves — see research.md §5) was worked around for the metric's own correctness (readrun.statusbefore the overwrite) but left unfixed in the underlying data, consistent with how 013-auth-hardening documented a pre-existing bug it found without fixing it.- Found and fixed one genuine cross-file test-isolation bug this feature's own new test caused:
business-metrics.test.ts's "human resolution" case originally drove a ticket through a realHUMAN_ESCALATIONtransition viaticketsService.updateStatus, which — same as any other escalation in this codebase — triggers the real orchestration subscriber's defaultROUND_ROBINauto-assignment against every agent in the shared throwaway database, including other concurrently-running test files' own dedicated agents (reproduced deterministically againstagent-ticket-queue.test.ts). Fixed by driving the intermediate state-machine transitions directly throughticketsRepository.updateStatus(no domain-event publish) instead, reserving the real, event-publishingticketsService.updateStatuscall for only the finalRESOLVEDtransition the metric subscriber actually needs to observe. - Separately, found (not caused by this feature — confirmed via
git checkoutto the clean pre-014 commit and reproducing the identical failure) a pre-existing systemic collision risk in ticket-code generation:ticket-code.ts'sderiveProductCodekeeps only the first 4 alphabetic characters ofexternalProductId, so essentially every integration test file in this codebase (nearly all of which name their test productsTEST_<SOMETHING>) collapses to the identical"TEST"code prefix. Running enoughTEST_*-prefixed files concurrently (as vitest does by default across worker threads/processes) makes independent files race for the sameTEST-<year>-<sequence>numbering space, occasionally exceedingtickets.service.ts's fixedMAX_CODE_RETRIES = 5and surfacing as a real500(Unique constraint failed on the fields: (code)) instead of the retry silently absorbing it. Confirmed independent of this feature (reproduces on79bc2ef, 013-auth-hardening's tip, with none of this feature's code present) and left unfixed here — a ticket-code-generation concurrency fix belongs to 003-ticketing's own module, out of scope for an observability feature. Worth a dedicated future fix (e.g. a longer/hash-based product code, or a database-level sequence rather than aCOUNT-then-retry scheme).