feat: implement problem resolution (009)
Populates the five real problem-management stubs (investigation, root-causes, solutions, verification, resolutions -- problems is confirmed dead/unwired scaffold and stays untouched) with doc04's sequential workflow engine: - investigation: version-row-per-attempt (never overwritten), with a customer-safe read path that always strips internalNotes. - root-causes/solutions/verification: a strict existence chain (investigation -> root cause -> solution -> approval -> implementation -> verification), each step resolve-or-409 on its own precondition, matching doc06's schema field-for-field with no invented columns. - resolutions: gated on a successfully verified solution (no stored solutionId FK, per doc06 -- resolved via a join at write time), moving the ticket to RESOLUTION_PENDING_CUSTOMER; explicit customer confirmation and a durable auto-close sweep (the previously-unregistered CLEANUP queue stub, mirroring 008's breach-detection job) both resolve it from there. - reopen (ticketing/tickets): two real, separately-audited transitions (RESOLVED|CLOSED -> REOPENED -> IN_PROGRESS), touching no prior problem-resolution record and no SLARun -- closes the loop 008's own spec.md left open. Verification-failure escalation reuses 003/007's existing HUMAN_ESCALATION transition directly rather than adding an eleventh trigger type to 008's already-shipped escalation rules. Customer-facing confirm-resolution/reopen needed a body-shape variant of 002's inbound trust boundary that didn't previously exist: fastify.authenticateProductIntegration hard-required a full ticket-creation-shaped body. Extracted the shared token/scope/replay verification into verifyIntegrationIdentity and added a narrower authenticateProductIntegrationIdentity decorator + identityOnlyRequestSchema on top of it -- purely additive, ticket creation's own behavior is unchanged. Also fixes a real test-data-hygiene bug surfaced by running this feature's suite alongside 008's: a wildcard-scoped HierarchyNode and an intentionally-global SLAPolicy in 008's own test fixtures were silently affecting other test files' tickets sharing the same live Postgres. Verified against throwaway Docker Postgres/Redis: typecheck, lint, architecture-check all clean; full regression (tests/unit + tests/integration together, 172 tests) passes except the 2 pre-existing MinIO-dependent attachment failures, unrelated to this feature. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
aaa51ef475
commit
16daf8d32d
@@ -50,3 +50,31 @@
|
||||
transition rather than inventing a new escalation-rule trigger type in 008's system — flagged
|
||||
explicitly in Assumptions as a scope decision, not an oversight.
|
||||
- All items pass; no revision iterations were needed.
|
||||
|
||||
## Implementation Notes (added during /speckit-implement)
|
||||
|
||||
- `fastify.authenticateProductIntegration` (002) turned out to unconditionally require a full
|
||||
ticket-creation-shaped body (`source`/`problem` included) — reusing it as planned for
|
||||
confirm-resolution/reopen made every call fail validation before token verification ran. Fixed
|
||||
by extracting the shared verification logic (everything after the body's own shape is known)
|
||||
into `verifyIntegrationIdentity` in `product-integration-auth.plugin.ts`, and adding a new,
|
||||
narrower `identityOnlyRequestSchema` (`{productId, tenantId, userId}`) plus a new
|
||||
`authenticateProductIntegrationIdentity` decorator built on the same shared function — purely
|
||||
additive, `POST /v1/support/requests`'s own behavior is unchanged.
|
||||
- Two pre-existing scaffold gaps were closed for this feature's FK validation needs:
|
||||
`TicketsRepository` gained `findPendingCustomerConfirmationOlderThan` (the auto-close sweep's
|
||||
own query), and `ticketsRepository`/`TicketsRepository` are now exported from
|
||||
`ticketing/tickets`'s public `index.ts` (same "extend an existing module's public surface"
|
||||
precedent as `problemsRepository` before it).
|
||||
- Running this feature's own integration suite alongside 008's surfaced a real test-data-hygiene
|
||||
bug in 008's already-committed test file: its second hierarchy node used `productScope: []`
|
||||
(a wildcard matching *every* product, per `HierarchyNode`'s own documented scope-matching rule)
|
||||
purely to have a valid, different target node for its own scoped-escalation test — but since
|
||||
every test file's tickets share one live Postgres database, that wildcard node (and, similarly,
|
||||
008's intentionally-global `SLAPolicy` test fixture) silently affected *other* files' tickets
|
||||
running in the same suite, including this feature's own. Fixed by scoping that node to its own
|
||||
test's product (it never needed to be global) and by deactivating the global `SLAPolicy`
|
||||
fixture immediately after the one scenario that needs it, rather than leaving it live for the
|
||||
rest of the file's run — both fixes are to `tests/integration/sla-escalation-flow.test.ts`
|
||||
only, no production code changed. Full regression (`tests/unit` + `tests/integration` together,
|
||||
172 tests) is clean except the 2 pre-existing MinIO-dependent attachment failures.
|
||||
|
||||
@@ -86,28 +86,43 @@
|
||||
feature, for a flow that doesn't need the rule-matching machinery at all (there's exactly one
|
||||
outcome: HUMAN_ESCALATION, not "evaluate every matching rule"), is unjustified complexity.
|
||||
|
||||
## Decision: Customer-facing confirm-resolution and reopen reuse 002's inbound trust boundary; agent reopen uses `fastify.authenticate`
|
||||
## Decision: Customer-facing confirm-resolution and reopen reuse 002's trust boundary via a new, narrower `authenticateProductIntegrationIdentity` decorator; agent reopen uses `fastify.authenticate`
|
||||
|
||||
- **Decision**: Two new customer-reachable routes, `POST /v1/support/tickets/:ticketId/confirm-
|
||||
resolution` and `POST /v1/support/tickets/:ticketId/reopen`, are gated by the same
|
||||
`fastify.authenticateProductIntegration` + `fastify.checkIntegrationRateLimit` preHandler pair
|
||||
`POST /v1/support/requests` already uses (002-saas-integration) — verifying the caller's
|
||||
`externalTenantId`/`externalUserId` (from the signed token) matches the ticket's own recorded
|
||||
values before allowing the action. A third route, `POST /admin/tickets/:ticketId/reopen`, is
|
||||
gated by `fastify.authenticate` for the agent-initiated reopen path FR-017 also requires.
|
||||
Confirm-resolution has no agent-initiated equivalent (spec.md US5 only ever has the customer
|
||||
confirming explicitly; an agent's own path to close things out is the existing auto-close job,
|
||||
not a manual override this feature adds).
|
||||
resolution` and `POST /v1/support/tickets/:ticketId/reopen`, are gated by a new
|
||||
`fastify.authenticateProductIntegrationIdentity` + the existing `fastify.
|
||||
checkIntegrationRateLimit` preHandler pair, then additionally verify the caller's
|
||||
`externalTenantId`/`externalUserId` (from `request.reqContext`) matches the ticket's own
|
||||
recorded values before allowing the action. A third route,
|
||||
`POST /admin/tickets/:ticketId/reopen`, is gated by `fastify.authenticate` for the
|
||||
agent-initiated reopen path FR-017 also requires. Confirm-resolution has no agent-initiated
|
||||
equivalent (spec.md US5 only ever has the customer confirming explicitly; an agent's own path
|
||||
to close things out is the existing auto-close job, not a manual override this feature adds).
|
||||
- **Implementation note (found during /speckit-implement, not anticipated at planning time)**:
|
||||
`fastify.authenticateProductIntegration` (002) unconditionally validates `request.body` against
|
||||
the full `inboundRequestSchema` — which requires `source`/`problem`, ticket-*creation*-specific
|
||||
fields neither new route has any reason to send. Reusing it as originally planned made every
|
||||
call to these two routes fail Zod validation before token verification ever ran. Fixed by
|
||||
extracting steps 2-10 of `authenticateProductIntegration`'s logic (everything after the body's
|
||||
own shape is known — token verification, replay/revocation/scope checks, `reqContext`
|
||||
population) into a shared `verifyIntegrationIdentity` function in
|
||||
`product-integration-auth.plugin.ts`, and adding a new `identityOnlyRequestSchema`
|
||||
(`{productId, tenantId, userId}` only) plus a new `authenticateProductIntegrationIdentity`
|
||||
decorator that parses that narrower shape and calls the same shared function. The original
|
||||
`authenticateProductIntegration` (and `POST /v1/support/requests`) is unchanged in behavior —
|
||||
purely additive.
|
||||
- **Rationale**: `inbound-request.routes.ts`'s own comment ("Acting further on the ticket...
|
||||
belongs to later features that don't exist yet") names exactly this need — 002's trust boundary
|
||||
was already built generically enough to reuse, not something this feature has to reinvent.
|
||||
Requiring the caller's own token to match the ticket's tenant/user prevents one customer from
|
||||
confirming or reopening another tenant's ticket.
|
||||
was already built to be extended, just not with a body shape that happened to fit an action on
|
||||
an *existing* ticket. Requiring the caller's own token to match the ticket's tenant/user
|
||||
prevents one customer from confirming or reopening another tenant's ticket.
|
||||
- **Alternatives considered**: A single unauthenticated or `fastify.authenticate`-gated endpoint
|
||||
for both actor types — rejected; a customer is never an authenticated SupportHub principal
|
||||
(Constitution Principle I — SaaS is the sole identity authority for its own end users), so reusing
|
||||
the internal-agent auth mechanism for a customer-initiated action would be a security regression,
|
||||
not a simplification.
|
||||
not a simplification. Sending a dummy `source`/`problem` value to satisfy the existing schema —
|
||||
rejected as a hack that would misrepresent the request and pollute `validatedInboundBody` for a
|
||||
handler that was never meant to receive it.
|
||||
|
||||
## Decision: Auto-close is a repeatable BullMQ job on the existing, unclaimed `CLEANUP` queue
|
||||
|
||||
|
||||
@@ -27,19 +27,19 @@ All file paths are relative to `supporthub-api/` (repo root).
|
||||
|
||||
## Phase 1: Setup
|
||||
|
||||
- [ ] T001 [P] Populate `src/modules/problem-management/investigation/` with the full standard
|
||||
- [x] T001 [P] Populate `src/modules/problem-management/investigation/` with the full standard
|
||||
shape (`controller/`, `routes/`, `schema/`, `repository/`, `service/`, `types/`, `mapper/`,
|
||||
`constants/`, `index.ts`), replacing the `InvestigationService.getInvestigationStatus` stub
|
||||
- [ ] T002 [P] Populate `src/modules/problem-management/root-causes/` the same way, replacing the
|
||||
- [x] T002 [P] Populate `src/modules/problem-management/root-causes/` the same way, replacing the
|
||||
`RootCausesService.getRootCause` stub
|
||||
- [ ] T003 [P] Populate `src/modules/problem-management/solutions/` the same way, replacing the
|
||||
- [x] T003 [P] Populate `src/modules/problem-management/solutions/` the same way, replacing the
|
||||
`SolutionsService.getSolutions` stub
|
||||
- [ ] T004 [P] Populate `src/modules/problem-management/verification/` the same way, replacing
|
||||
- [x] T004 [P] Populate `src/modules/problem-management/verification/` the same way, replacing
|
||||
the `VerificationService.verifySolution` stub
|
||||
- [ ] T005 [P] Populate `src/modules/problem-management/resolutions/` the same way, replacing the
|
||||
- [x] T005 [P] Populate `src/modules/problem-management/resolutions/` the same way, replacing the
|
||||
`ResolutionsService.getResolutions` stub — this module additionally gets the auto-close
|
||||
sweep and the two new customer-facing routes (later tasks)
|
||||
- [ ] T006 [P] Add `src/config/problem-resolution.ts` (`problemResolutionConfig
|
||||
- [x] T006 [P] Add `src/config/problem-resolution.ts` (`problemResolutionConfig
|
||||
.autoCloseWaitingHours`, reading a new `RESOLUTION_AUTO_CLOSE_WAITING_HOURS` env var,
|
||||
default `72`) and register it in `src/config/index.ts`'s re-export list
|
||||
|
||||
@@ -51,11 +51,11 @@ All file paths are relative to `supporthub-api/` (repo root).
|
||||
|
||||
**⚠️ CRITICAL**: No user-story stage work can begin until this phase is complete.
|
||||
|
||||
- [ ] T007 Add `Investigation`, `RootCause`, `Solution`, `SolutionImplementation`,
|
||||
- [x] T007 Add `Investigation`, `RootCause`, `Solution`, `SolutionImplementation`,
|
||||
`SolutionVerification`, `Resolution` models to `prisma/schema.prisma` per data-model.md,
|
||||
plus `Problem.investigations`/`Problem.rootCauses`/`Problem.solutions` and
|
||||
`Ticket.resolution` back-relations (depends on T001-T005)
|
||||
- [ ] T008 Run `npm run prisma:generate` and create the migration (`npm run prisma:migrate`) for
|
||||
- [x] T008 Run `npm run prisma:generate` and create the migration (`npm run prisma:migrate`) for
|
||||
T007 (depends on T007)
|
||||
|
||||
**Checkpoint**: Schema migrated. User stories can now be built.
|
||||
@@ -71,25 +71,25 @@ exclusion from customer-facing reads.
|
||||
|
||||
### Tests for User Story 1
|
||||
|
||||
- [ ] T009 [US1] Integration test covering Quickstart Scenario 1 (create, retrieve with every
|
||||
- [x] T009 [US1] Integration test covering Quickstart Scenario 1 (create, retrieve with every
|
||||
field intact, customer-safe variant omits `internalNotes`, a second investigation preserves
|
||||
the first) against a real Postgres in `tests/integration/problem-resolution-flow.test.ts`
|
||||
(depends on T008)
|
||||
|
||||
### Implementation for User Story 1
|
||||
|
||||
- [ ] T010 [US1] Add `InvestigationRepository` (`create`, `findAllForProblem` ordered newest
|
||||
- [x] T010 [US1] Add `InvestigationRepository` (`create`, `findAllForProblem` ordered newest
|
||||
first, `findMostRecentForProblem`) in `investigation/repository/` (depends on T008)
|
||||
- [ ] T011 [US1] Add Zod create schema (`investigator`, `findings`, `evidence?`,
|
||||
- [x] T011 [US1] Add Zod create schema (`investigator`, `findings`, `evidence?`,
|
||||
`internalNotes?`, `status?`) in `investigation/schema/`
|
||||
- [ ] T012 [US1] Add `InvestigationService.record`/`listForProblem` (agent-facing, includes
|
||||
- [x] T012 [US1] Add `InvestigationService.record`/`listForProblem` (agent-facing, includes
|
||||
`internalNotes`) and `listForProblemCustomerSafe` (strips `internalNotes`, FR-003) in
|
||||
`investigation/service/` (depends on T010, T011)
|
||||
- [ ] T013 [US1] Add `POST/GET /admin/problems/:problemId/investigations` (gated by
|
||||
- [x] T013 [US1] Add `POST/GET /admin/problems/:problemId/investigations` (gated by
|
||||
`fastify.authenticate`) and `GET /problems/:problemId/investigations` (ungated, customer-
|
||||
safe) routes in `investigation/controller/` + `routes/`, registered from `src/api/routes.ts`
|
||||
(depends on T012)
|
||||
- [ ] T014 [US1] Run Quickstart Scenario 1 locally and confirm all 3 steps pass
|
||||
- [x] T014 [US1] Run Quickstart Scenario 1 locally and confirm all 3 steps pass
|
||||
|
||||
**Checkpoint**: Investigations can be recorded and read correctly, with the customer-safe
|
||||
redaction guarantee in place.
|
||||
@@ -104,25 +104,25 @@ redaction guarantee in place.
|
||||
|
||||
### Tests for User Story 2
|
||||
|
||||
- [ ] T015 [US2] Integration test covering Quickstart Scenario 2 (rejected with no investigation,
|
||||
- [x] T015 [US2] Integration test covering Quickstart Scenario 2 (rejected with no investigation,
|
||||
accepted after one exists, rejected with an invalid type) — implemented as the "Scenario 2"
|
||||
case in `tests/integration/problem-resolution-flow.test.ts` (depends on T009, T014)
|
||||
- [ ] T016 [P] [US2] Unit test for the type-validation Zod schema (five valid values, everything
|
||||
- [x] T016 [P] [US2] Unit test for the type-validation Zod schema (five valid values, everything
|
||||
else rejected) in `tests/unit/problem-management/root-cause-schema.test.ts`
|
||||
|
||||
### Implementation for User Story 2
|
||||
|
||||
- [ ] T017 [US2] Add `RootCauseRepository` (`create`, `findAllForProblem`) in
|
||||
- [x] T017 [US2] Add `RootCauseRepository` (`create`, `findAllForProblem`) in
|
||||
`root-causes/repository/` (depends on T008)
|
||||
- [ ] T018 [US2] Add Zod create schema (`type` as a 5-value enum, `description`) in
|
||||
- [x] T018 [US2] Add Zod create schema (`type` as a 5-value enum, `description`) in
|
||||
`root-causes/schema/`
|
||||
- [ ] T019 [US2] Add `RootCausesService.record`: resolve-or-`409` on the problem having at least
|
||||
- [x] T019 [US2] Add `RootCausesService.record`: resolve-or-`409` on the problem having at least
|
||||
one investigation (T010's `findMostRecentForProblem`, via `investigation`'s public
|
||||
`index.ts`) — in `root-causes/service/` (depends on T012, T017, T018)
|
||||
- [ ] T020 [US2] Add `POST /admin/problems/:problemId/root-causes` route (gated by
|
||||
- [x] T020 [US2] Add `POST /admin/problems/:problemId/root-causes` route (gated by
|
||||
`fastify.authenticate`) in `root-causes/controller/` + `routes/`, registered from
|
||||
`src/api/routes.ts` (depends on T019)
|
||||
- [ ] T021 [US2] Run Quickstart Scenario 2 locally and confirm all 3 steps pass
|
||||
- [x] T021 [US2] Run Quickstart Scenario 2 locally and confirm all 3 steps pass
|
||||
|
||||
**Checkpoint**: Root causes are correctly gated on investigation existing first.
|
||||
|
||||
@@ -137,28 +137,28 @@ implementation gated on approval, one-to-one.
|
||||
|
||||
### Tests for User Story 3
|
||||
|
||||
- [ ] T022 [US3] Integration test covering Quickstart Scenario 3 (rejected with no root cause,
|
||||
- [x] T022 [US3] Integration test covering Quickstart Scenario 3 (rejected with no root cause,
|
||||
created with `approved: false`, implementation rejected before approval, accepted after,
|
||||
a second implementation rejected) — "Scenario 3" case in
|
||||
`tests/integration/problem-resolution-flow.test.ts` (depends on T015, T021)
|
||||
|
||||
### Implementation for User Story 3
|
||||
|
||||
- [ ] T023 [US3] Add `SolutionRepository` (`create`, `findById`, `approve`,
|
||||
- [x] T023 [US3] Add `SolutionRepository` (`create`, `findById`, `approve`,
|
||||
`findMostRecentForProblem`) and `SolutionImplementationRepository` (`create`, `findBySolutionId`)
|
||||
in `solutions/repository/` (depends on T008)
|
||||
- [ ] T024 [US3] Add Zod schemas (`proposed`; implementation's `notes?`, `implementedBy`) in
|
||||
- [x] T024 [US3] Add Zod schemas (`proposed`; implementation's `notes?`, `implementedBy`) in
|
||||
`solutions/schema/`
|
||||
- [ ] T025 [US3] Add `SolutionsService.propose`: resolve-or-`409` on the problem having at least
|
||||
- [x] T025 [US3] Add `SolutionsService.propose`: resolve-or-`409` on the problem having at least
|
||||
one root cause (T017's repository, via `root-causes`'s public `index.ts`) — `approve` —
|
||||
`recordImplementation`: resolve-or-`409` on `approved: true` and no existing implementation
|
||||
— in `solutions/service/` (depends on T019, T023, T024)
|
||||
- [ ] T026 [US3] Add `POST /admin/problems/:problemId/solutions`,
|
||||
- [x] T026 [US3] Add `POST /admin/problems/:problemId/solutions`,
|
||||
`PATCH /admin/solutions/:solutionId/approve`,
|
||||
`POST /admin/solutions/:solutionId/implementation` routes (gated by `fastify.authenticate`)
|
||||
in `solutions/controller/` + `routes/`, registered from `src/api/routes.ts` (depends on
|
||||
T025)
|
||||
- [ ] T027 [US3] Run Quickstart Scenario 3 locally and confirm all 5 steps pass
|
||||
- [x] T027 [US3] Run Quickstart Scenario 3 locally and confirm all 5 steps pass
|
||||
|
||||
**Checkpoint**: All three P1 record-keeping user stories are complete — the full investigation
|
||||
through implementation chain is enforced and correct. This is the feature's structural MVP.
|
||||
@@ -174,7 +174,7 @@ verification supports either a fresh investigation or the existing `HUMAN_ESCALA
|
||||
|
||||
### Tests for User Story 4
|
||||
|
||||
- [ ] T028 [US4] Integration test covering Quickstart Scenario 4 (successful verification;
|
||||
- [x] T028 [US4] Integration test covering Quickstart Scenario 4 (successful verification;
|
||||
failed verification recorded but unusable for resolution; failure + reinvestigate creates a
|
||||
fresh Investigation row; failure + escalate transitions the ticket to `HUMAN_ESCALATION`
|
||||
and 007 auto-assigns) — "Scenario 4" case in
|
||||
@@ -182,17 +182,17 @@ verification supports either a fresh investigation or the existing `HUMAN_ESCALA
|
||||
|
||||
### Implementation for User Story 4
|
||||
|
||||
- [ ] T029 [US4] Add `SolutionVerificationRepository` (`create`, `findBySolutionId`) in
|
||||
- [x] T029 [US4] Add `SolutionVerificationRepository` (`create`, `findBySolutionId`) in
|
||||
`verification/repository/` (depends on T008)
|
||||
- [ ] T030 [US4] Add Zod schema (`method` as a 4-value enum, `result`, `evidence?`) in
|
||||
- [x] T030 [US4] Add Zod schema (`method` as a 4-value enum, `result`, `evidence?`) in
|
||||
`verification/schema/`
|
||||
- [ ] T031 [US4] Add `VerificationService.record`: resolve-or-`409` on the solution having an
|
||||
- [x] T031 [US4] Add `VerificationService.record`: resolve-or-`409` on the solution having an
|
||||
implementation (T023's repository) and no existing verification — in `verification/
|
||||
service/` (depends on T023, T029, T030)
|
||||
- [ ] T032 [US4] Add `POST /admin/solutions/:solutionId/verification` route (gated by
|
||||
- [x] T032 [US4] Add `POST /admin/solutions/:solutionId/verification` route (gated by
|
||||
`fastify.authenticate`) in `verification/controller/` + `routes/`, registered from
|
||||
`src/api/routes.ts` (depends on T031)
|
||||
- [ ] T033 [US4] Run Quickstart Scenario 4 locally and confirm all 4 steps pass (steps 3-4 call
|
||||
- [x] T033 [US4] Run Quickstart Scenario 4 locally and confirm all 4 steps pass (steps 3-4 call
|
||||
T012's `InvestigationService.record` and `ticketsService.updateStatus` directly — no new
|
||||
production code beyond what US1/003/007 already provide, per research.md's decision to
|
||||
reuse the existing transition rather than add new escalation machinery)
|
||||
@@ -211,11 +211,11 @@ rather than inventing new ones.
|
||||
|
||||
### Tests for User Story 5
|
||||
|
||||
- [ ] T034 [P] [US5] Unit test for the auto-close due-window predicate (a pending ticket older
|
||||
- [x] T034 [P] [US5] Unit test for the auto-close due-window predicate (a pending ticket older
|
||||
than the configured waiting period is due; a pending ticket younger than it is not; a
|
||||
non-pending ticket is never selected) in
|
||||
`tests/unit/problem-management/auto-close-sweep.test.ts`
|
||||
- [ ] T035 [US5] Integration test covering Quickstart Scenario 5 (resolution rejected without a
|
||||
- [x] T035 [US5] Integration test covering Quickstart Scenario 5 (resolution rejected without a
|
||||
successful verification; accepted after, ticket reaches `RESOLUTION_PENDING_CUSTOMER`;
|
||||
customer confirmation via the trust-boundary route reaches `RESOLVED`; a second ticket aged
|
||||
past the configured window reaches `RESOLVED` via a direct call to the sweep) — "Scenario 5"
|
||||
@@ -223,32 +223,32 @@ rather than inventing new ones.
|
||||
|
||||
### Implementation for User Story 5
|
||||
|
||||
- [ ] T036 [US5] Add `ResolutionRepository` (`create`, `findByTicketId`) in
|
||||
- [x] T036 [US5] Add `ResolutionRepository` (`create`, `findByTicketId`) in
|
||||
`resolutions/repository/` (depends on T008)
|
||||
- [ ] T037 [US5] Add Zod schema (`outcome`, `resolvedBy`) in `resolutions/schema/`
|
||||
- [ ] T038 [US5] Add `ResolutionsService.record(ticketId, outcome, resolvedBy)`: resolves the
|
||||
- [x] T037 [US5] Add Zod schema (`outcome`, `resolvedBy`) in `resolutions/schema/`
|
||||
- [x] T038 [US5] Add `ResolutionsService.record(ticketId, outcome, resolvedBy)`: resolves the
|
||||
ticket's `problemId`, resolve-or-`409` on a `Solution` with `verification.result: 'success'`
|
||||
existing for it (T023/T029's repositories), creates the `Resolution`, and transitions the
|
||||
ticket to `RESOLUTION_PENDING_CUSTOMER` via `ticketsService.updateStatus` — in
|
||||
`resolutions/service/resolutions.service.ts` (depends on T023, T029, T036, T037)
|
||||
- [ ] T039 [US5] Add `ResolutionsService.confirmByCustomer(ticketId)` /
|
||||
- [x] T039 [US5] Add `ResolutionsService.confirmByCustomer(ticketId)` /
|
||||
`runAutoCloseSweep()`: the former transitions `RESOLUTION_PENDING_CUSTOMER → RESOLVED`
|
||||
directly; the latter queries every `RESOLUTION_PENDING_CUSTOMER` ticket whose `updatedAt` is
|
||||
older than `problemResolutionConfig.autoCloseWaitingHours` and transitions each the same way
|
||||
— a single, directly-callable, side-effect-only method (research.md — no worker process
|
||||
needed to invoke it in tests) — in `resolutions/service/resolutions.service.ts` (depends on
|
||||
T006, T038)
|
||||
- [ ] T040 [US5] Add `POST /admin/tickets/:ticketId/resolution` (gated by `fastify.authenticate`)
|
||||
- [x] T040 [US5] Add `POST /admin/tickets/:ticketId/resolution` (gated by `fastify.authenticate`)
|
||||
and `POST /v1/support/tickets/:ticketId/confirm-resolution` (gated by
|
||||
`fastify.authenticateProductIntegration` + `fastify.checkIntegrationRateLimit`, verifying
|
||||
the token's tenant/user matches the ticket's own — research.md) routes in
|
||||
`resolutions/controller/` + `routes/`, registered from `src/api/routes.ts` (depends on
|
||||
T038, T039)
|
||||
- [ ] T041 [US5] Replace `registerCleanupWorker()`'s stub body in `src/jobs/cleanup/index.ts`:
|
||||
- [x] T041 [US5] Replace `registerCleanupWorker()`'s stub body in `src/jobs/cleanup/index.ts`:
|
||||
schedule a repeatable job (every 5 minutes) on `QueueName.CLEANUP` whose processor calls
|
||||
T039's `runAutoCloseSweep` — and register it from `src/bootstrap/queue.bootstrap.ts`
|
||||
(depends on T039)
|
||||
- [ ] T042 [US5] Run Quickstart Scenario 5 locally and confirm all 4 steps pass
|
||||
- [x] T042 [US5] Run Quickstart Scenario 5 locally and confirm all 4 steps pass
|
||||
|
||||
**Checkpoint**: Every P1 user story is complete. The full investigation-to-resolution chain
|
||||
works, gated correctly at every step, with both an explicit and a durable-fallback path to
|
||||
@@ -265,22 +265,22 @@ works, gated correctly at every step, with both an explicit and a durable-fallba
|
||||
|
||||
### Tests for User Story 6
|
||||
|
||||
- [ ] T043 [US6] Integration test covering Quickstart Scenario 6 (customer reopen reaches
|
||||
- [x] T043 [US6] Integration test covering Quickstart Scenario 6 (customer reopen reaches
|
||||
`IN_PROGRESS` via `REOPENED`; the prior `Resolution` and any `SLARun` are unchanged; agent
|
||||
reopen of a `CLOSED` ticket produces the same result attributed to the agent) — "Scenario 6"
|
||||
case in `tests/integration/problem-resolution-flow.test.ts` (depends on T035)
|
||||
|
||||
### Implementation for User Story 6
|
||||
|
||||
- [ ] T044 [US6] Add `TicketsService.reopen(ticketId, actor)` (007/003's existing
|
||||
- [x] T044 [US6] Add `TicketsService.reopen(ticketId, actor)` (007/003's existing
|
||||
`ticketing/tickets` module): resolve-or-`409` if status isn't `RESOLVED`/`CLOSED`, then two
|
||||
sequential `updateStatus` calls (`REOPENED`, then `IN_PROGRESS`) — in `ticketing/tickets/
|
||||
service/tickets.service.ts` (depends on T008 — no new schema, reuses 003's own state
|
||||
machine and repository)
|
||||
- [ ] T045 [US6] Add `POST /v1/support/tickets/:ticketId/reopen` (customer, trust boundary) and
|
||||
- [x] T045 [US6] Add `POST /v1/support/tickets/:ticketId/reopen` (customer, trust boundary) and
|
||||
`POST /admin/tickets/:ticketId/reopen` (agent, `fastify.authenticate`) routes in
|
||||
`ticketing/tickets/controller/` + `routes/` (depends on T044)
|
||||
- [ ] T046 [US6] Run Quickstart Scenario 6 locally and confirm all 4 steps pass
|
||||
- [x] T046 [US6] Run Quickstart Scenario 6 locally and confirm all 4 steps pass
|
||||
|
||||
**Checkpoint**: All six user stories work independently and together — the full doc 04 workflow,
|
||||
from first investigation through resolution, confirmation, auto-close, and reopen.
|
||||
@@ -289,10 +289,10 @@ from first investigation through resolution, confirmation, auto-close, and reope
|
||||
|
||||
## Phase 9: Polish & Cross-Cutting Concerns
|
||||
|
||||
- [ ] T047 [P] Update `specs/009-problem-resolution/checklists/requirements.md` Notes with any
|
||||
- [x] T047 [P] Update `specs/009-problem-resolution/checklists/requirements.md` Notes with any
|
||||
implementation-time findings
|
||||
- [ ] T048 Run `npx tsx scripts/check-architecture.ts` and `npm run lint`/`npm run typecheck`
|
||||
- [ ] T049 Full regression: `npm run test:unit` (scoped to `tests/unit`) to confirm nothing broke
|
||||
- [x] T048 Run `npx tsx scripts/check-architecture.ts` and `npm run lint`/`npm run typecheck`
|
||||
- [x] T049 Full regression: `npm run test:unit` (scoped to `tests/unit`) to confirm nothing broke
|
||||
elsewhere, then the full integration suite (including 003's and 007's own suites, since
|
||||
T044 modifies `ticketing/tickets`) against real Docker-provisioned Postgres/Redis
|
||||
|
||||
|
||||
Reference in New Issue
Block a user