docs(18-02): complete on-demand analyze route + automatic grouping wiring plan
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
19b8b4b415
commit
a544024e75
1 changed files with 108 additions and 0 deletions
|
|
@ -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 `<verify><human-check>` 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 `<automated>` 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue