From 4a77b0544ce2922388288ac38fc8e7410468328b Mon Sep 17 00:00:00 2001 From: lorentz Date: Fri, 17 Jul 2026 22:37:25 -0400 Subject: [PATCH] docs(quick-260717-v6c): Add Mark as accidental report action to phishing Action Area --- .planning/STATE.md | 3 +- .../260717-v6c-SUMMARY.md | 114 ++++++++++++++++++ 2 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 .planning/quick/260717-v6c-add-a-mark-as-accidental-report-action-t/260717-v6c-SUMMARY.md diff --git a/.planning/STATE.md b/.planning/STATE.md index f4292e5..4e951f1 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -28,7 +28,7 @@ See: .planning/PROJECT.md (updated 2026-07-14) Phase: Milestone v3.0 complete Plan: — Status: Awaiting next milestone -Last activity: 2026-07-17 — Completed quick task 260717-a19: phishing allowlist/timing/idempotency fixes +Last activity: 2026-07-18 — Completed quick task 260717-v6c: Mark as accidental report action ## Performance Metrics @@ -127,6 +127,7 @@ None yet. | 260716-n46 | Fix Mimecast blast-radius future-end-date swallow bug + multi-tenant gap (per-company tenant resolution) | 2026-07-16 | 9951e53 | [260716-n46-fix-mimecast-blast-radius-date-window-fu](./quick/260716-n46-fix-mimecast-blast-radius-date-window-fu/) | | 260716-pgr | Fix confidence display bug (0-1 scale rendered as raw percent, e.g. "1%" instead of "100%") | 2026-07-16 | 3f16268 | [260716-pgr-fix-confidence-display-bug-in-classifica](./quick/260716-pgr-fix-confidence-display-bug-in-classifica/) | | 260717-a19 | Fix phishing simulation-vendor allowlist gaps (3 missing KnowBe4 domains), auto-parse timing race (retry on ticket.update), and parseAndStoreMessage idempotency; reclassified 6 stale Seubert campaigns (all flipped UNWANTED → USER_AWARENESS) | 2026-07-17 | cf04f07 | [260717-a19-fix-phishing-simulation-vendor-allowlist](./quick/260717-a19-fix-phishing-simulation-vendor-allowlist/) | +| 260717-v6c | Add "Mark as accidental report" action to the phishing Action Area — closes out a campaign and posts a fixed customer-facing note to the reporter (distinct from the silent "Mark as false positive" action) | 2026-07-18 | 565a0c1 | [260717-v6c-add-a-mark-as-accidental-report-action-t](./quick/260717-v6c-add-a-mark-as-accidental-report-action-t/) | ## Deferred Items diff --git a/.planning/quick/260717-v6c-add-a-mark-as-accidental-report-action-t/260717-v6c-SUMMARY.md b/.planning/quick/260717-v6c-add-a-mark-as-accidental-report-action-t/260717-v6c-SUMMARY.md new file mode 100644 index 0000000..c4d0957 --- /dev/null +++ b/.planning/quick/260717-v6c-add-a-mark-as-accidental-report-action-t/260717-v6c-SUMMARY.md @@ -0,0 +1,114 @@ +--- +phase: quick-260717-v6c +plan: 01 +subsystem: api +tags: [phishing, remediation, autotask, nextjs, react] + +requires: + - phase: 20-remediation-approval-audit-safety + provides: markCampaignFalsePositive template, D-04 guard pattern, RemediationConflictError/RemediationValidationError + - phase: 21-autotask-triage-note + provides: generateAndPostAcknowledgment template, TriageNoteResult/TriageNotePostResult shapes +provides: + - markCampaignAccidentalReport service function (status flip + audit + post-commit customer note) + - generateAndPostAccidentalReportNote service function (fixed-template customer note) + - POST /api/phishing/campaigns/[id]/mark-accidental-report route + - Mark as accidental report button + confirm dialog in ActionAreaCard + - campaign_marked_accidental_report timeline entry +affects: [phishing-campaign-review, remediation-service, triage-note-service] + +tech-stack: + added: [] + patterns: + - "Mirrored existing false-positive/acknowledgment templates verbatim, changing only status value/event type/note copy" + +key-files: + created: + - "app/api/phishing/campaigns/[id]/mark-accidental-report/route.ts" + modified: + - "lib/services/remediation-service.ts" + - "lib/services/triage-note-service.ts" + - "components/phishing/action-area-card.tsx" + - "components/phishing/timeline-card.tsx" + - "lib/services/remediation-service.test.ts" + - "lib/services/triage-note-service.test.ts" + +key-decisions: + - "campaigns.status is unconstrained TEXT (migration 097) — 'accidental_report' needed no schema migration" + - "Note-post failure never propagates past markCampaignAccidentalReport — the committed status change is always returned, with notePosted/noteError reflecting the note outcome (mirrors the acknowledge_user post-commit pattern)" + +patterns-established: [] + +requirements-completed: [] + +duration: 25min +completed: 2026-07-18 +--- + +# Phase quick-260717-v6c: Add "Mark as accidental report" action Summary + +Added a parallel-to-false-positive campaign resolution path that both closes out a phishing campaign and posts a fixed-template customer-visible Autotask note thanking the reporting employee and confirming no action is needed — mirroring the existing `markCampaignFalsePositive`/`generateAndPostAcknowledgment` templates verbatim except for the status value, event type, and note copy. + +## Performance + +- **Duration:** ~25 min +- **Started:** 2026-07-18T02:10:00Z (approx, base commit 2b48dc0) +- **Completed:** 2026-07-18T02:35:20Z +- **Tasks:** 4/4 completed +- **Files modified:** 6 modified, 1 created + +## Accomplishments +- `markCampaignAccidentalReport` (lib/services/remediation-service.ts) — D-04 guard against approved/completed remediation, flips `campaigns.status` to `'accidental_report'`, writes a `campaign_marked_accidental_report` audit event, and posts the customer note after the transaction commits (never inside the FOR UPDATE lock), returning `{ campaignId, status, auditEventId, notePosted, noteError? }` even when the note post fails. +- `generateAndPostAccidentalReportNote` (lib/services/triage-note-service.ts) — fixed two-paragraph customer-visible template (`noteType: 18`, `publish: 1`), zero evidence/URL/classification interpolation (T-23-01 invariant), per-ticket try/catch isolation. +- New route `POST /api/phishing/campaigns/[id]/mark-accidental-report`, gated by `requirePermission('phishing', 'approve')`, mirroring the mark-false-positive route's UUID validation, optional-reason body parsing, campaign-existence check, and 409/400/500 error mapping. +- `ActionAreaCard` — new "Mark as accidental report" button + confirm `AlertDialog` with an optional reason `Textarea`; `resolved`/tooltip logic now also treats `campaignStatus === 'accidental_report'` as resolved; success/partial-failure toasts distinguish a fully-successful mark (note posted) from a status-change-only outcome (note failed, surfaces `noteError`). +- `TimelineCard` — new `campaign_marked_accidental_report` case using the existing blue/`CheckCircle2` tint (matching `remediation_approved`), visually distinct from the slate/`XCircle` false-positive entry and from THREAT-toned entries. +- Mirrored test coverage in both service test files: D-04 guard rejection, successful status flip + audit event, note-post-failure-still-commits path, `noteType`/`publish` assertions, and per-ticket isolation. + +## Task Commits + +Each task was committed atomically: + +1. **Task 1: Add backend service functions (status flip + customer note)** - `aea4fd2` (feat) +2. **Task 2: Add the mark-accidental-report API route** - `97804f2` (feat) +3. **Task 3: Add frontend button, dialog, and timeline case** - `74e43e2` (feat) +4. **Task 4: Mirror test coverage** - `0d6cd25` (test) + +_Note: No TDD RED/GREEN split was applied — tasks 1 and 4 mirror existing verbatim templates from already-tested code paths (markCampaignFalsePositive/generateAndPostAcknowledgment), so implementation and its test coverage were written together per task rather than test-first, consistent with how the plan's `` blocks were scoped._ + +## Files Created/Modified +- `lib/services/remediation-service.ts` - Added `markCampaignAccidentalReport` + `MarkAccidentalReportResult` interface, imports `generateAndPostAccidentalReportNote` +- `lib/services/triage-note-service.ts` - Added `generateAndPostAccidentalReportNote` +- `app/api/phishing/campaigns/[id]/mark-accidental-report/route.ts` - New POST route +- `components/phishing/action-area-card.tsx` - New button, dialog, state, handler, disabled-reason logic +- `components/phishing/timeline-card.tsx` - New `campaign_marked_accidental_report` timeline case +- `lib/services/remediation-service.test.ts` - New `describe('markCampaignAccidentalReport', ...)` block + mock wiring +- `lib/services/triage-note-service.test.ts` - New `describe('generateAndPostAccidentalReportNote', ...)` block + import + +## Decisions Made +- No new migration needed — `campaigns.status` is unconstrained TEXT (migration 097), so the new `'accidental_report'` value required no schema change. +- Kept the same post-commit-note-never-throws contract as `remediateApprovedActions`/`autoPostAcknowledgment`: a note-post failure is caught, logged with a distinct `[MARK-ACCIDENTAL-REPORT]` prefix, and surfaced to the caller via `notePosted: false` / `noteError` rather than aborting the already-committed status change. +- Used `variant="outline"` (not `destructive`) for the new button, since — unlike false-positive — this action posts a warm, appreciative note rather than a silent closure; visually distinct from the destructive false-positive button per the plan's UI-tint guidance. + +## Deviations from Plan + +None - plan executed exactly as written. All interface templates (markCampaignFalsePositive, generateAndPostAcknowledgment, the mark-false-positive route, the false-positive button/dialog, the campaign_marked_false_positive timeline case) were mirrored per the plan's embedded exact templates. + +## Verification + +- `npx tsc --noEmit --pretty` — clean, no errors. +- `npx vitest run lib/services/remediation-service.test.ts lib/services/triage-note-service.test.ts` — 36/36 tests passing across both files. + +## Self-Check: PASSED + +- FOUND: `lib/services/remediation-service.ts` (markCampaignAccidentalReport present) +- FOUND: `lib/services/triage-note-service.ts` (generateAndPostAccidentalReportNote present) +- FOUND: `app/api/phishing/campaigns/[id]/mark-accidental-report/route.ts` +- FOUND: `components/phishing/action-area-card.tsx` (accidental-report button/dialog present) +- FOUND: `components/phishing/timeline-card.tsx` (campaign_marked_accidental_report case present) +- FOUND: `lib/services/remediation-service.test.ts` (markCampaignAccidentalReport describe block present) +- FOUND: `lib/services/triage-note-service.test.ts` (generateAndPostAccidentalReportNote describe block present) +- FOUND commit `aea4fd2` +- FOUND commit `97804f2` +- FOUND commit `74e43e2` +- FOUND commit `0d6cd25`