docs(quick-260717-v6c): Add Mark as accidental report action to phishing Action Area

This commit is contained in:
lorentz 2026-07-17 22:37:25 -04:00
parent 565a0c1ee2
commit 4a77b0544c
2 changed files with 116 additions and 1 deletions

View file

@ -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

View file

@ -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 `<action>` 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`