Documents implementation-time findings in the requirements checklist: all three suspected races were confirmed real then fixed, the ticket-status mechanism needed no fix, a real pre-existing test-infrastructure issue (throwaway DB ticket-code collisions at high accumulated volume) was found and resolved by resetting the throwaway database and replaying its full migration history, two full-suite-only integration failures were confirmed as pre-existing cross-file contamination (not a regression), and the load-test tooling surfaced a real Anthropic API cost consideration for ticket creation itself. All 23 tasks marked complete. Full quality gate green: typecheck, lint, architecture check, full unit suite (119/119), full integration suite against a freshly reset throwaway database (122/124 — the 2 failures are the project's own already-accepted MinIO baseline), and all 6 concurrency test files (11/11). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
11 KiB
description
| description |
|---|
| Task list for 016-load-concurrency-testing |
Tasks: Load and Concurrency Testing
Input: Design documents from specs/016-load-concurrency-testing/
Organization: Tasks are grouped by user story (US1 = assignment race, US2 = SLA race, US3 = escalation idempotency, US4 = ticket-status race proof, US5 = load-test tooling). US1-US4 share one Foundational phase (the schema migration all four rely on); US5 has no schema dependency and can proceed independently of it.
Format: [ID] [P?] [Story] Description
All file paths are relative to supporthub-api/ (repo root).
Phase 1: Setup
- T001 [P] Add
autocannonas a devDependency (package.json) and addtests/load/reports/to.gitignore(run artifacts, not fixtures)
Phase 2: Foundational (Blocking Prerequisites for US1-US4)
Purpose: The one shared schema migration US1, US2, and US3's fixes each depend on. US4 (no schema change, see research.md §4) and US5 (no schema dependency) do not need this phase and can proceed in parallel with it.
- T002 In
prisma/schema.prisma, addversion Int @default(0)toSLARun; generate one migration (npx prisma migrate dev --name concurrency_guards) that also includes, as raw SQL,CREATE UNIQUE INDEX assignments_one_current_per_ticket ON assignments (ticket_id) WHERE is_current = true;andCREATE UNIQUE INDEX escalation_events_ticket_rule_unique ON escalation_events (ticket_id, rule_id) WHERE rule_id IS NOT NULL;(data-model.md); apply to the throwaway test Postgres (supporthub-test-pg, port 5433) and the real dev Postgres (postgres-development, port 5434, viaprisma migrate diff+ directpsqlper this project's own established non-destructive dev-sync approach); regenerate the Prisma client
Checkpoint: Schema ready — US1, US2, US3 implementation can now begin.
Phase 3: User Story 1 - Assignment never double-assigned under concurrency (Priority: P1)
Goal: Two concurrent assignment attempts on the same ticket always leave exactly one current assignment.
Independent Test: Run tests/concurrency/assignment-race.test.ts alone against the
throwaway Postgres — it creates its own ticket and needs nothing from US2-US5.
- T003 [US1] Write
tests/concurrency/assignment-race.test.ts: create one ticket, fire >=20 genuinely concurrent assignment attempts at it (via the real assignment engine/service entry point, not the repository directly), then queryassignmentsdirectly and assert exactly one row hasis_current = truefor that ticket (depends on T002) - T004 [US1] Fix
AssignmentRepository.createAssignmentinsrc/modules/orchestration/assignments/repository/assignment.repository.tsto catch theassignments_one_current_per_ticketunique-violation (PrismaP2002) and retry the whole supersede-then-create transaction, bounded to 3 attempts, per research.md §1 (depends on T002) - T005 [US1] Re-run
assignment-race.test.tsat least 10 times in a row (or extend the test with its own internal repeat loop) confirming zero failures — SC-001 (depends on T003, T004)
Checkpoint: Quickstart Scenario 1 passes against real infrastructure, consistently.
Phase 4: User Story 2 - SLA clock never corrupted by overlapping pause/resume/sweep (Priority: P1)
Goal: Concurrent pause/resume/breach-sweep activity against the same SLA run always leaves it in one internally-consistent state.
Independent Test: Run tests/concurrency/sla-race.test.ts alone against the throwaway
Postgres — it creates its own ticket + SLA run and needs nothing from US1/US3/US4/US5.
- T006 [US2] Replace
SlaRunRepository.updatewithupdateWithVersion(id, expectedVersion, data)insrc/modules/orchestration/sla/repository/sla-run.repository.ts, mirroringTicketsRepository.updateStatus's atomicupdateMany({where:{id, version: expectedVersion}, data:{...data, version:{increment:1}}})pattern exactly (depends on T002) - T007 [US2] Update
pause,resume,complete, andrunBreachDetectionSweepinsrc/modules/orchestration/sla/service/sla.service.tsto callupdateWithVersionwith each run's last-read version, and to re-read + recompute + retry (bounded to 3 attempts) on a version-conflictnullresult, per research.md §2 (depends on T006) - T008 [US2] Write
tests/concurrency/sla-race.test.ts: create a ticket with an active SLA run, fire concurrentpause/resumecalls and arunBreachDetectionSweep()pass against it, then query the run directly and assert its final state is internally consistent (neverpausedwithpausedAt: null, never a legitimatelybreachedrun silently reverted torunning) (depends on T007) - T009 [US2] Re-run
sla-race.test.tsat least 10 times confirming zero contradictory-state outcomes — SC-002 (depends on T008)
Checkpoint: Quickstart Scenario 2 passes against real infrastructure, consistently.
Phase 5: User Story 3 - An escalation trigger fired twice never duplicates (Priority: P1)
Goal: The same escalation trigger delivered twice for the same ticket always results in exactly one escalation event and one reassignment.
Independent Test: Run tests/concurrency/escalation-idempotency.test.ts alone against the
throwaway Postgres — it creates its own ticket + escalation rule and needs nothing from
US1/US2/US4/US5 (though it exercises the same Assignment table US1 protects, as a
cross-check).
- T010 [US3] Fix
EscalationEventRepository.createinsrc/modules/orchestration/escalation/repository/escalation-event.repository.tsto catch theescalation_events_ticket_rule_uniqueunique-violation (PrismaP2002) and return the pre-existing row for that(ticketId, ruleId)pair via afindFirstfallback instead of throwing, per research.md §3 (depends on T002) - T011 [US3] Confirm
EscalationService.fireinsrc/modules/orchestration/escalation/service/escalation.service.tsbehaves correctly whencreatereturns a pre-existing event (it must not also re-runassignToSpecificNodefor a duplicate trigger) — adjustfireif needed so a duplicate-conflict short-circuits before reassignment (depends on T010) - T012 [US3] Write
tests/concurrency/escalation-idempotency.test.ts: create a ticket eligible for a specific escalation rule, call the real trigger path (e.g.escalationService.handleBreach) twice concurrently for the identical trigger, then queryescalation_eventsandassignmentsdirectly and assert exactly one of each resulted (depends on T011) - T013 [US3] Re-run
escalation-idempotency.test.tsat least 10 times confirming zero duplicate outcomes — SC-003 (depends on T012)
Checkpoint: Quickstart Scenario 3 passes against real infrastructure, consistently.
Phase 6: User Story 4 - Ticket status optimistic concurrency, proven (Priority: P2)
Goal: Prove the existing version-checked ticket-status update holds under genuine concurrency.
Independent Test: Run tests/concurrency/ticket-status-race.test.ts alone against the
throwaway Postgres — no dependency on T002 or any other user story (research.md §4: no
implementation change expected).
- T014 [US4] Write
tests/concurrency/ticket-status-race.test.ts: create a ticket at a known status/version, fire >=20 genuinely concurrentticketsRepository.updateStatuscalls all starting from that same version, and assert exactly one returns the updated ticket while every other call returnsnull— SC-004
Checkpoint: Quickstart Scenario 4 passes, confirming the existing mechanism (no fix expected; a failure here would mean research.md's assumption was wrong and needs revisiting).
Phase 7: User Story 5 - Repeatable load/throughput baseline (Priority: P2)
Goal: Repeatable autocannon-based load-test tooling and a baseline report for the three
named critical endpoint groups.
Independent Test: Run each tests/load/*.load.ts script alone against a real running dev
server — no dependency on T002 or any other user story.
- T015 [P] [US5] Create
tests/load/autocannon.config.ts: a shared runner helper wrappingautocannon's programmatic API, producing the report shape from data-model.md (requestsPerSec,latencyP50Ms/P90Ms/P99Ms,non2xxCount,rateLimitedCount), printing a console summary and writing JSON totests/load/reports/(depends on T001) - T016 [P] [US5] Create
tests/load/ticket-creation.load.tsusing the T015 helper againstPOST /v1/support/requests(depends on T015) - T017 [P] [US5] Create
tests/load/ai-support-flow.load.tsusing the T015 helper against the AI support flow's own endpoints (depends on T015) - T018 [P] [US5] Create
tests/load/admin-reporting.load.tsusing the T015 helper, signing in as the seeded admin first, against the 015-reporting-dashboards endpoints (depends on T015) - T019 [US5] Run all three scripts against a real running dev server, confirm each produces a report, and run each twice to confirm consistent-shape output for comparison — SC-005 (depends on T016, T017, T018)
Checkpoint: Quickstart Scenario 5 passes; a baseline report exists for each endpoint group.
Phase 8: Polish & Cross-Cutting Concerns
- T020 Update
specs/016-load-concurrency-testing/checklists/requirements.mdNotes with any implementation-time findings - T021
npx tsc --noEmit/npm run lint/npx tsx scripts/check-architecture.tsclean - T022 Full existing unit + integration + concurrency suite re-run (throwaway DB), confirming
no regression in 007-orchestration-assignment's, 008-sla-escalation's,
012-admin-list-views's, and 015-reporting-dashboards's own existing coverage of
Assignment/SLARun/EscalationEvent - T023 Mark all of this file's checkboxes complete once verified
Dependencies & Execution Order
- Setup (Phase 1): No dependencies — can start immediately
- Foundational (Phase 2): No dependencies — BLOCKS User Stories 1, 2, 3 only
- User Story 4: No dependency on Phase 2 or any other story — can start immediately
- User Story 5: No dependency on Phase 2 or any other story — can start immediately (only
needs Phase 1's
autocannondevDependency) - User Stories 1, 2, 3: Each depends only on Phase 2 — independent of each other and of User Stories 4/5
- Polish (Phase 8): Depends on all five user stories
Parallel Example: Foundational-independent stories
# Once Phase 1 completes, these can start immediately in parallel, without waiting on Phase 2:
Task: "Write tests/concurrency/ticket-status-race.test.ts" (US4, T014)
Task: "Create tests/load/autocannon.config.ts" (US5, T015)
Implementation Strategy
Suggested order
- Phase 1 (Setup) and Phase 2 (Foundational) — Phase 2 unblocks the three highest-severity real-bug fixes (US1, US2, US3)
- User Stories 1, 2, 3 (all P1) — each is a real, currently-unguarded race; fix and prove each in turn, or in parallel across files since they touch different modules
- User Story 4 (P2) — quick to add, proves existing protection, can be done any time after Phase 1
- User Story 5 (P2) — independent tooling work, can be done any time after Phase 1, in parallel with 1-4
- Phase 8 (Polish) once all five stories are verified