docs(22-02): complete review page read surface plan
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6RuWdiUiXrPK6FLBHjtpY
This commit is contained in:
parent
c70b30a000
commit
de13cd73b3
1 changed files with 101 additions and 0 deletions
|
|
@ -0,0 +1,101 @@
|
|||
---
|
||||
phase: 22-approval-ui-livelink-addressable-campaign-review-and-approve
|
||||
plan: 02
|
||||
subsystem: api
|
||||
tags: [nextjs, postgres, mimecast, phishing-triage]
|
||||
|
||||
# Dependency graph
|
||||
requires:
|
||||
- phase: 22-approval-ui-livelink-addressable-campaign-review-and-approve
|
||||
provides: "resolveTicketToCampaign() (lib/services/phishing-ticket-resolver.ts) and mergeTimeline() (lib/services/phishing-timeline.ts) from plan 22-01"
|
||||
provides:
|
||||
- "GET /api/phishing/tickets/{ticket_id}/campaign — ticket -> campaign resolver route"
|
||||
- "GET /api/phishing/campaigns/{id} — enriched with remediationActions, auditEvents, widened classifications, widened message evidence, fresh blastRadius, merged timeline"
|
||||
- "GET /api/phishing/campaigns — enriched with firstReportTicketId per campaign"
|
||||
affects: [22-03, 22-04, 22-05]
|
||||
|
||||
# Tech tracking
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- "Route additively widens an existing SELECT (add columns, map new camelCase fields) rather than adding a second query round-trip"
|
||||
- "completedAt derived at read-time from audit_events.payload.actionId rather than a dedicated column"
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- app/api/phishing/tickets/[ticket_id]/campaign/route.ts
|
||||
modified:
|
||||
- app/api/phishing/campaigns/[id]/route.ts
|
||||
- app/api/phishing/campaigns/route.ts
|
||||
|
||||
key-decisions:
|
||||
- "D-07 (resolver route): missing report is { found: false } at HTTP 200, not a 404 — page distinguishes 'valid ticket, not triaged yet' from a hard error"
|
||||
- "blastRadius sender/recipient/subject derivation copied verbatim from campaign-classifier.ts's gatherCampaignEvidence (not triage-note-service.ts's empty-string call) to avoid an unscoped Mimecast fan-out"
|
||||
|
||||
patterns-established:
|
||||
- "Ticket-id-addressable resolver route pattern (Number.isFinite param validation, GET not POST for pure lookups)"
|
||||
|
||||
requirements-completed: [REVIEW-01, REVIEW-02, REVIEW-03, REVIEW-04]
|
||||
|
||||
# Metrics
|
||||
duration: 35min
|
||||
completed: 2026-07-16
|
||||
---
|
||||
|
||||
# Phase 22 Plan 02: Review Page Read Surface Summary
|
||||
|
||||
**New GET ticket->campaign resolver route plus additive enrichment of both existing campaign endpoints (evidence, timeline, remediation actions, audit trail, fresh blast radius, firstReportTicketId) — the entire backend read surface the Phase 22 review page needs.**
|
||||
|
||||
## Performance
|
||||
|
||||
- **Duration:** 35 min
|
||||
- **Started:** 2026-07-16T18:09:00Z
|
||||
- **Completed:** 2026-07-16T18:44:21Z
|
||||
- **Tasks:** 3 completed
|
||||
- **Files modified:** 3 (1 created, 2 extended)
|
||||
|
||||
## Accomplishments
|
||||
- New `GET /api/phishing/tickets/{ticket_id}/campaign` thin resolver route wrapping plan 22-01's `resolveTicketToCampaign()`, auth-gated and param-validated, with the deliberate `found:false`-at-200 design (D-07).
|
||||
- `GET /api/phishing/campaigns/{id}` additively extended: widened `messages` query returns `headers`/`urls`/`attachments`/`bodyPreview`; widened `classifications` query returns `reasons`/`recommendedActions`/`requiresApproval`; new `remediationActions` (with `completedAt` derived from `audit_events`) and `auditEvents` arrays; a fresh per-request `blastRadius` lookup; and a merged chronological `timeline`.
|
||||
- `GET /api/phishing/campaigns` additively extended with `firstReportTicketId` per campaign (correlated subquery on the earliest linked report) for row-click navigation on the list page.
|
||||
|
||||
## Task Commits
|
||||
|
||||
Each task was committed atomically:
|
||||
|
||||
1. **Task 1: NEW ticket->campaign resolver route** - `ca63910` (feat)
|
||||
2. **Task 2: EXTEND campaigns/[id] detail route (evidence + timeline + classification + blast radius)** - `9e83ec0` (feat)
|
||||
3. **Task 3: EXTEND campaigns list route with firstReportTicketId** - `c70b30a` (feat)
|
||||
|
||||
**Plan metadata:** committed alongside this SUMMARY (docs commit, see final commit in this worktree)
|
||||
|
||||
## Files Created/Modified
|
||||
- `app/api/phishing/tickets/[ticket_id]/campaign/route.ts` - New GET resolver route; auth + param validation + `resolveTicketToCampaign()` call
|
||||
- `app/api/phishing/campaigns/[id]/route.ts` - Additively extended: widened messages/classifications SELECTs, new remediationActions/auditEvents queries, blastRadius derivation, mergeTimeline() call
|
||||
- `app/api/phishing/campaigns/route.ts` - Additively extended: `c` alias, correlated subquery for `first_report_ticket_id`, added to `items.map()`
|
||||
|
||||
## Decisions Made
|
||||
- Followed the plan's D-07 design exactly: the resolver route returns 200 with `found: false` for an untriaged ticket rather than 404, since the ticket itself is valid — only the triage state is "not yet known."
|
||||
- Copied the blast-radius sender/recipient/subject/dateWindow derivation verbatim from `campaign-classifier.ts`'s `gatherCampaignEvidence` (not `triage-note-service.ts`'s empty-string call) per the plan's explicit Pitfall 3 warning, to avoid an unscoped Mimecast fan-out query.
|
||||
- Extracted the widened per-row arrays (`reports`, `messages`, `indicators`, `classifications`, `remediationActions`, `auditEvents`) into named `const`s before the final `NextResponse.json(...)` return, so the same camelCased data can feed both the response body and the `blastRadius`/`timeline` derivations without a second query or duplicated mapping logic.
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
None - plan executed exactly as written. One micro-adjustment made during self-verification: the plan's acceptance criterion for Task 2 requires zero occurrences of the literal string `completed_at` anywhere in the file (to confirm no reference to the nonexistent column), and an initial code comment explaining the `completedAt` derivation happened to spell out `completed_at` in prose. Reworded the comment to describe the same fact without using that literal string — pure documentation wording, no code/behavior change.
|
||||
|
||||
## Issues Encountered
|
||||
None.
|
||||
|
||||
## User Setup Required
|
||||
|
||||
None - no external service configuration required.
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
- The full backend read surface for the Phase 22 review page (`app/phishing/tickets/[ticketId]/page.tsx`, planned in 22-03/22-04) is now in place: resolver route, enriched campaign detail, enriched campaign list.
|
||||
- Write routes (approve/remediate/mark-false-positive) already exist from Phase 20 and are reused verbatim — no additional backend work needed before the UI plans (22-03 onward) can wire up against real data.
|
||||
- Verified via `npx tsc --noEmit --pretty` (fully clean, zero errors in this plan's files or anywhere else) and `npm test` (411 passed, 2 pre-existing/out-of-scope `itglue-search.test.ts` failures — already documented in `deferred-items.md`, unrelated to this plan's files).
|
||||
|
||||
---
|
||||
*Phase: 22-approval-ui-livelink-addressable-campaign-review-and-approve*
|
||||
*Completed: 2026-07-16*
|
||||
Loading…
Add table
Add a link
Reference in a new issue