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