docs(19-02): complete classify route plan summary
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
3e8d5b83c9
commit
8597908015
1 changed files with 99 additions and 0 deletions
99
.planning/phases/19-classification-engine/19-02-SUMMARY.md
Normal file
99
.planning/phases/19-classification-engine/19-02-SUMMARY.md
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
---
|
||||
phase: 19-classification-engine
|
||||
plan: 02
|
||||
subsystem: security
|
||||
tags: [phishing-triage, route-handler, access-control, next-app-router]
|
||||
|
||||
# Dependency graph
|
||||
requires:
|
||||
- phase: 19-classification-engine
|
||||
plan: 01
|
||||
provides: "classifyCampaign(campaignId) orchestrator + ClassifyResult shape (lib/services/campaign-classifier.ts)"
|
||||
provides:
|
||||
- "POST /api/phishing/campaigns/{id}/classify — on-demand campaign (re-)classification endpoint"
|
||||
affects: ["20 (remediation actions) — consumes recommendedActions/requiresApproval via this route"]
|
||||
|
||||
# Tech tracking
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- "Phase 18 D-06 auth convention: requirePermission('phishing','analyze') early-return, same action as the sibling /analyze route — no new permission introduced"
|
||||
- "UUID_RE shape-validation guard (copied verbatim from campaigns/[id]/route.ts) before any DB query"
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- "app/api/phishing/campaigns/[id]/classify/route.ts"
|
||||
modified: []
|
||||
|
||||
key-decisions:
|
||||
- "Reused the exact UUID_RE regex and 400/404/500 response shapes from campaigns/[id]/route.ts rather than the ticket_id-numeric guard from analyze/route.ts, since {id} here is a campaign UUID, not an Autotask ticket_id — plan explicitly called this out."
|
||||
- "Existence check (SELECT id FROM campaigns WHERE id = $1) happens before calling classifyCampaign, so an unknown-but-well-formed UUID returns 404 rather than surfacing as a classifyCampaign internal error / 500."
|
||||
|
||||
requirements-completed: [CLASSIFY-05]
|
||||
|
||||
# Metrics
|
||||
duration: 12min
|
||||
completed: 2026-07-16
|
||||
---
|
||||
|
||||
# Phase 19 Plan 02: POST /api/phishing/campaigns/[id]/classify Route Summary
|
||||
|
||||
**On-demand campaign classification trigger: `requirePermission('phishing','analyze')` auth gate (same action as the existing `/analyze` route, Phase 18 D-06), UUID_RE shape validation before any query, 404 for unknown campaigns, delegates to `classifyCampaign` and returns its flat camelCase verdict payload unmodified.**
|
||||
|
||||
## Performance
|
||||
|
||||
- **Duration:** ~12 min
|
||||
- **Completed:** 2026-07-16T12:26:26Z
|
||||
- **Tasks:** 1 completed
|
||||
- **Files modified:** 1 (new)
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- Added `app/api/phishing/campaigns/[id]/classify/route.ts` exporting `POST`, following the exact analog mapping specified in `19-PATTERNS.md`:
|
||||
- Auth: `requirePermission('phishing', 'analyze')` early-return (verbatim from `tickets/[ticket_id]/analyze/route.ts`) — reuses the `analyze` action already granted to admin/super-admin only (Phase 18 D-06); no new permission added.
|
||||
- Validation: `UUID_RE` guard (verbatim from `campaigns/[id]/route.ts`) returns 400 before any DB access if `id` isn't a well-formed UUID (V5/T-19-05).
|
||||
- Existence check: `SELECT id FROM campaigns WHERE id = $1` — 404 if no row (mirrors the campaign-detail route's 404 convention).
|
||||
- Delegates to `classifyCampaign(id)` (Plan 01's orchestrator) and returns the `ClassifyResult` directly — already flat camelCase, no re-mapping needed.
|
||||
- `try/catch` wraps the DB check + classify call; on error logs `[PHISHING-CLASSIFY] Failed to classify campaign` and returns 500 with `{ error, message }`.
|
||||
|
||||
## Task Commits
|
||||
|
||||
1. **Task 1: POST /api/phishing/campaigns/[id]/classify route handler**
|
||||
- `3e8d5b8` (feat) — added route handler with auth gate, UUID guard, 404, classifyCampaign delegation
|
||||
|
||||
**Plan metadata:** this SUMMARY.md commit (see below)
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
- `app/api/phishing/campaigns/[id]/classify/route.ts` — new route handler, 51 lines
|
||||
|
||||
## Decisions Made
|
||||
|
||||
- Used the campaign-detail route's `UUID_RE` (not the ticket_id numeric guard from `/analyze`), since this route's path param is a campaign UUID — matches the plan's explicit instruction.
|
||||
- Checked campaign existence with a lightweight `SELECT id` query before invoking `classifyCampaign`, so a syntactically valid but nonexistent UUID cleanly returns 404 instead of letting `classifyCampaign`'s internal `gatherCampaignEvidence` silently produce a zero-evidence classification for a campaign that was never real.
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
None — plan executed exactly as written. One doc-comment wording tweak: the file's header comment originally quoted the literal string `requirePermission('phishing', 'analyze')`, which caused the acceptance criterion's `grep -c ... === 1` check to count 2 matches (the comment + the code line). Reworded the comment to describe the gate without repeating the exact call-site string, so the grep count matches the plan's stated acceptance criterion exactly (1). This is a Rule 3 (blocking-issue) auto-fix — no behavior change, comment-only.
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
- **Manual curl auth matrix (human-check) not run from this worktree.** This isolated worktree has no `.env`/`.env.local` file and thus no Postgres credentials or `BETTER_AUTH_SECRET`/session cookies — there is no way to start a dev server here that can authenticate as admin/super-admin/user roles or reach the real `campaigns` table. The running `pulse-app` Docker container on port 3100 is a standalone build with no live code mount, so it does not reflect this route either. **This step still needs to happen post-merge**: with a real dev server (`npm run dev`) and valid session cookies, run the 5-case matrix from the plan's `<human-check>` block (200 authed-admin / 401 no-session / 403 user-role / 400 malformed-id / 404 unknown-campaign).
|
||||
|
||||
## User Setup Required
|
||||
|
||||
- **Manual verification (see Issues Encountered above):** after this plan merges to a branch/environment with real Postgres + Better Auth credentials, run the curl auth matrix described in `19-02-PLAN.md`'s `<human-check>` block against a real campaign UUID before considering CLASSIFY-05 fully verified end-to-end.
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
- `POST /api/phishing/campaigns/{id}/classify` is wired end-to-end: auth → UUID validation → existence check → `classifyCampaign` → flat verdict response. Phase 20 (remediation actions) can call this route (or `classifyCampaign` directly) and consume `recommendedActions`/`requiresApproval` from the response.
|
||||
- The Plan 01 ASSUMPTION FLAG (click-driven escalation to `reset_password`/`isolate_endpoint`/`disable_forwarding_rule`) still needs a quick user confirmation before Phase 20 — this route does not change or resolve that; it surfaces whatever `classifyCampaign` currently returns.
|
||||
|
||||
---
|
||||
*Phase: 19-classification-engine*
|
||||
*Completed: 2026-07-16*
|
||||
|
||||
## Self-Check: PASSED
|
||||
|
||||
- FOUND: app/api/phishing/campaigns/[id]/classify/route.ts
|
||||
- FOUND commit: 3e8d5b8 (feat: POST /api/phishing/campaigns/[id]/classify route handler)
|
||||
Loading…
Add table
Add a link
Reference in a new issue