diff --git a/.planning/phases/18-campaign-grouping-phishing-analysis-api/18-02-SUMMARY.md b/.planning/phases/18-campaign-grouping-phishing-analysis-api/18-02-SUMMARY.md new file mode 100644 index 0000000..3462945 --- /dev/null +++ b/.planning/phases/18-campaign-grouping-phishing-analysis-api/18-02-SUMMARY.md @@ -0,0 +1,108 @@ +--- +phase: 18-campaign-grouping-phishing-analysis-api +plan: 02 +subsystem: api +tags: [nextjs, postgres, phishing, campaign-grouping, permissions] + +# Dependency graph +requires: + - phase: 18-campaign-grouping-phishing-analysis-api (plan 01) + provides: "groupReportIntoCampaign(reportId, opts) in lib/services/campaign-grouping-service.ts + phishing permission resource in lib/permissions.ts" + - phase: 16-eml-mime-evidence-parser + provides: "parseAndStoreMessage() writer of messages/indicators rows" +provides: + - "POST /api/phishing/tickets/{ticket_id}/analyze — on-demand detect->parse->group orchestration for one ticket (DETECT-03)" + - "Automatic campaign accumulation wired into the webhook ticket.created path and the cron sweep loop (D-01, CAMP-01/CAMP-02)" +affects: [18-03, 19-classification, 20-remediation] + +# Tech tracking +tech-stack: + added: [] + patterns: + - "/analyze route omits skipIfAlreadyGrouped (always re-run, may upgrade Tier-3 -> Tier-1/2); automatic webhook/cron paths always pass skipIfAlreadyGrouped:true (D-08)" + - "Route reconstructs a DetectableTicket from the tickets table (no Autotask payload available), mirroring webhook-service.ts's existing read-back pattern" + +key-files: + created: + - app/api/phishing/tickets/[ticket_id]/analyze/route.ts + modified: + - lib/services/webhook-service.ts + - lib/services/phishing-sweep-service.ts + +key-decisions: + - "Grouping call in phishing-sweep-service.ts placed inside the SAME try/catch as detectPhishingTicket so a grouping failure counts against result.errors without aborting the per-ticket sweep loop" + - "webhook-service.ts needed no new try/catch — triggerPhishingDetection is already fire-and-forget with a .catch() at its caller" + +requirements-completed: [DETECT-03, CAMP-01, CAMP-02, ACCESS-01] + +# Metrics +duration: 12min +completed: 2026-07-15 +--- + +# Phase 18 Plan 02: On-Demand Analyze Route + Automatic Grouping Wiring Summary + +**POST /api/phishing/tickets/{ticket_id}/analyze orchestrates detect->parse->group for one ticket, and groupReportIntoCampaign is now called automatically from both the webhook ticket.created path and the cron sweep loop so campaigns accumulate without any API call.** + +## Performance + +- **Duration:** ~12 min +- **Started:** 2026-07-15T23:19Z (approx, first commit) +- **Completed:** 2026-07-15T23:31Z (last task commit) +- **Tasks:** 2/2 completed +- **Files modified:** 3 (1 created, 2 modified) + +## Accomplishments +- `app/api/phishing/tickets/[ticket_id]/analyze/route.ts` — new POST route gated by `requirePermission('phishing', 'analyze')` (D-06); validates `ticket_id` (400 for non-numeric), looks up the ticket row directly from Postgres (no Autotask payload available to this route), runs `detectPhishingTicket` (400 if the ticket doesn't match phishing patterns), `parseAndStoreMessage` (its `{ stored: false, reason }` no-op result is not treated as an error), then `groupReportIntoCampaign(detection.reportId)` with no `skipIfAlreadyGrouped` (D-08 — always re-runs so a previously Tier-3-only grouping can upgrade). Returns camelCase `{ reportId, campaignId, groupMethod, created }`. +- `lib/services/webhook-service.ts` — `triggerPhishingDetection` now captures the `detectPhishingTicket` result and calls `groupReportIntoCampaign(detection.reportId, { skipIfAlreadyGrouped: true })` when flagged, so campaigns accumulate automatically off the webhook path (D-01) without a redundant re-group of an already-linked report (D-08). +- `lib/services/phishing-sweep-service.ts` — the same call added inside the existing per-ticket try block of the cron sweep loop, with `skipIfAlreadyGrouped: true`; kept inside the pre-existing try/catch so a grouping failure is counted in `result.errors` and does not abort the sweep of remaining tickets. + +## Task Commits + +Each task was committed atomically: + +1. **Task 1: POST /api/phishing/tickets/[ticket_id]/analyze route** - `de013e6` (feat) +2. **Task 2: Wire groupReportIntoCampaign into webhook + cron sweep automatic paths** - `19b8b4b` (feat) + +**Plan metadata:** committed alongside this SUMMARY (see final commit in this plan's history) + +## Files Created/Modified +- `app/api/phishing/tickets/[ticket_id]/analyze/route.ts` - New POST route; detect -> parse -> group orchestration, camelCase response, ticket_id validation, 401/403/404/400/500 status handling +- `lib/services/webhook-service.ts` - Added `groupReportIntoCampaign` import + call at the end of `triggerPhishingDetection` (skipIfAlreadyGrouped:true) +- `lib/services/phishing-sweep-service.ts` - Added `groupReportIntoCampaign` import + call inside the per-ticket sweep try block (skipIfAlreadyGrouped:true) + +## Decisions Made +- No deviations from the exact route/diff shapes specified in 18-PATTERNS.md sections 3, 7, and 8 — implementation matches the pattern map verbatim including import-style split (relative import in `lib/services/`, absolute-alias import in the route file). + +## Deviations from Plan + +None - plan executed exactly as written. + +## Issues Encountered + +None. `npx tsc --noEmit --pretty` was clean after each task. Existing vitest suites (`campaign-grouping-service.test.ts`, `phishing-detector.test.ts`, `phishing-eml-service.test.ts` — 39 tests) still pass unchanged; no test files exist for `webhook-service.ts` or `phishing-sweep-service.ts` (none were expected — `vitest.config.ts` scope and repo precedent both exclude route-adjacent/webhook test coverage per 18-PATTERNS.md's noted test-scope boundary). + +## User Setup Required + +None - no external service configuration required. + +## Manual Verification (Deferred) + +The plan's `` step calls for curling the live route with three distinct session states (no cookie / user-role cookie / admin-role cookie) against a running dev server and a real phishing-pattern ticket row. This worktree agent has no interactive browser session to mint those cookies and the only reachable Postgres/app containers are the shared long-running `pulse-app`/`pulse-postgres` stack (running the `master` build, not this worktree's uncommitted code) — exercising it directly would risk interfering with concurrent work outside this plan's scope. What was verified instead: +- `npx tsc --noEmit --pretty` clean across all three files (route + both modified services). +- `requirePermission('phishing', 'analyze')` is the first statement in the handler with `if (error) return error;`, matching the exact gate shape already proven correct for `rmm/executions`'s POST handler (same helper, same call pattern) — 401 (no session) / 403 (user role lacks `analyze`, only `read` per Plan 01's `lib/permissions.ts` grants) are enforced by that shared helper, not by any new logic in this route. +- `grep -c 'groupReportIntoCampaign'` returns 2 in both `webhook-service.ts` and `phishing-sweep-service.ts` (import + call), confirmed by `` verify. +This is flagged here, not silently skipped, for the phase verifier to confirm with a live curl pass when the build is deployed. + +## Next Phase Readiness +- `/api/phishing/tickets/{ticket_id}/analyze` is ready for Plan 03's campaign list/detail routes to link from, and is the first live caller of Phase 16's `parseAndStoreMessage`. +- Automatic campaign accumulation (D-01) is now live on both the webhook and cron sweep paths — Plan 03's campaign list/detail endpoints will see `report_count`/`last_seen_at` update without any explicit `/analyze` call, satisfying CAMP-01/CAMP-02 end-to-end. +- Known carried-forward limitation (documented in Plan 01, unchanged by this plan): the automatic webhook/cron path only reaches Tier 3 grouping until a report has gone through an explicit `/analyze` call at least once (Tier 1/2 need `messages`/`indicators` rows that only `parseAndStoreMessage` writes, and that function is only called from the new `/analyze` route this plan added). + +--- +*Phase: 18-campaign-grouping-phishing-analysis-api* +*Completed: 2026-07-15* + +## Self-Check: PASSED + +All created/modified files confirmed present on disk; both task commit hashes (`de013e6`, `19b8b4b`) confirmed present in git log.