From fe2375e3a2d3648172ef49d98bf07d3509af4ee7 Mon Sep 17 00:00:00 2001 From: lorentz Date: Thu, 16 Jul 2026 19:40:21 -0400 Subject: [PATCH] docs(23-01): complete classification disposition + acknowledgment plan Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01W6RuWdiUiXrPK6FLBHjtpY --- .../23-01-SUMMARY.md | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 .planning/phases/23-classification-disposition-per-client-automation-gate/23-01-SUMMARY.md diff --git a/.planning/phases/23-classification-disposition-per-client-automation-gate/23-01-SUMMARY.md b/.planning/phases/23-classification-disposition-per-client-automation-gate/23-01-SUMMARY.md new file mode 100644 index 0000000..fb9d205 --- /dev/null +++ b/.planning/phases/23-classification-disposition-per-client-automation-gate/23-01-SUMMARY.md @@ -0,0 +1,116 @@ +--- +phase: 23-classification-disposition-per-client-automation-gate +plan: 01 +subsystem: phishing-classifier-remediation +tags: [classifier, verdict, remediation, autotask, triage-note] +dependency-graph: + requires: [] + provides: + - "USER_AWARENESS verdict + acknowledge_user action (campaign-classifier.ts)" + - "generateAndPostAcknowledgment customer-visible note writer (triage-note-service.ts)" + - "acknowledge_user real-effect post-commit wiring (remediation-service.ts)" + affects: + - "Phase 23 Plan 02 (per-client automation gate) — will call generateAndPostAcknowledgment automatically for USER_AWARENESS campaigns" + - "Phase 23 Plan 05 — references the locked USER_AWARENESS verdict string and acknowledge_user action" + - "components/phishing/classification-card.tsx, components/phishing/action-area-card.tsx — will need USER_AWARENESS/acknowledge_user UI labels (not in this plan's scope)" +tech-stack: + added: [] + patterns: + - "Post-commit side effect outside postgresClient.transaction() for network I/O that must not hold a DB transaction open" + - "Per-ticket try/catch-in-loop error isolation for Autotask TicketNotes writes (reused from Phase 21)" +key-files: + created: [] + modified: + - lib/services/campaign-classifier.ts + - lib/services/campaign-classifier.test.ts + - lib/services/remediation-default-params.ts + - lib/services/remediation-default-params.test.ts + - lib/services/triage-note-service.ts + - lib/services/triage-note-service.test.ts + - lib/services/triage-note-format.ts + - lib/services/remediation-service.ts + - lib/services/remediation-service.test.ts +decisions: + - "USER_AWARENESS verdict string locked exactly as specified in CONTEXT.md D-01 (Plans 02/05 reference this literal)" + - "acknowledge_user deliberately excluded from DESTRUCTIVE_ACTIONS so requires_approval computes false for it, per D-04" + - "acknowledge_user posts via noteType:18 (Client Portal Note) with publish:1 unchanged, per the live-verified Autotask field semantics in D-03" + - "The acknowledge_user real-effect Autotask call in remediation-service.ts runs strictly after the DB transaction commits and never propagates its own failure (D-04 carve-out is the only real provider call in this file)" +metrics: + duration: "~35 minutes" + completed: "2026-07-16" +--- + +# Phase 23 Plan 01: Classification Disposition (USER_AWARENESS) + Acknowledgment Note Writer Summary + +Adds a 4th classifier verdict (`USER_AWARENESS`) for confirmed phishing-simulation-vendor +campaigns, maps it to a new non-destructive `acknowledge_user` action, writes a +customer-visible thank-you note via Autotask `noteType: 18`, and wires that note post +into the manual approve->remediate path as the sole real-effect carve-out in +`remediation-service.ts`. + +## What Was Built + +### Task 1: USER_AWARENESS verdict + acknowledge_user action mapping +- Extended `Verdict` union in `lib/services/campaign-classifier.ts` to + `'SPAM' | 'UNWANTED' | 'THREAT' | 'USER_AWARENESS'`. +- `mapVerdictToActions('USER_AWARENESS', ...)` returns exactly `['acknowledge_user']`. +- `acknowledge_user` is deliberately NOT added to `DESTRUCTIVE_ACTIONS` — `computeRequiresApproval(['acknowledge_user'])` is `false`. +- `classifyCampaign`'s `isSimulation` branch now assigns `verdict = 'USER_AWARENESS'` directly (previously fell through to `evaluateSpamVsUnwanted`, landing in `SPAM`/`UNWANTED`). +- `deriveDefaultParams('acknowledge_user', evidence)` returns `{}` (no operator-editable params, same as `no_action`). +- `TriageNoteEvidence.verdict` in `triage-note-format.ts` widened to admit `'USER_AWARENESS'` — pure type change, no formatting logic touched (verdict is only interpolated into note text there). + +### Task 2: generateAndPostAcknowledgment customer-visible note writer +- New exported function `generateAndPostAcknowledgment(campaignId)` in `lib/services/triage-note-service.ts`. +- Posts one `TicketNotes` entry per linked report/ticket with `noteType: 18` ("Client Portal Note" — the actual client-visibility field, verified live against this tenant's field metadata per D-03) and `publish: 1` unchanged. +- Note body is a fixed, genuinely appreciative thank-you template with zero evidence/URL/classification interpolation (T-23-01) — deliberately NOT the evidence-dump `formatTriageNote()` template. +- Mirrors `generateAndPostTriageNote`'s per-ticket try/catch-in-loop error isolation and `{ noteText, tickets }` return shape. + +### Task 3: acknowledge_user manual-path real note post wiring +- `remediateApprovedActions` in `lib/services/remediation-service.ts` now captures the transaction's `RemediateResult`, then — strictly AFTER the transaction commits — checks whether an approved `acknowledge_user` row was transitioned this pass (`alreadyCompleted === false`). +- When true, calls `generateAndPostAcknowledgment(campaignId)` exactly once inside its own `try/catch` that logs and swallows failure (the DB transition has already committed; `generateAndPostAcknowledgment` also isolates per-ticket failures internally). +- All 7 pre-existing action types (block_sender, purge_message, warn_user, reset_password, isolate_endpoint, disable_forwarding_rule, quarantine) remain simulated status-only transitions — no provider call added for any of them. +- Top-of-file D-01 doc comment updated to record this narrow D-04 carve-out. + +## Verification + +``` +npx vitest run lib/services/campaign-classifier.test.ts lib/services/remediation-default-params.test.ts lib/services/triage-note-service.test.ts lib/services/remediation-service.test.ts +# Test Files 4 passed (4) +# Tests 77 passed (77) + +npx tsc --noEmit --pretty +# (no output — passes) +``` + +All acceptance criteria from the plan's per-task `` blocks were checked directly via grep and are satisfied (USER_AWARENESS present >=2x in campaign-classifier.ts, `acknowledge_user` case in remediation-default-params.ts, `acknowledge_user` absent from DESTRUCTIVE_ACTIONS, `noteType: 18` present, `generateAndPostAcknowledgment` imported from `./triage-note-service` and guarded by `actionType === 'acknowledge_user' && alreadyCompleted === false`). + +## Deviations from Plan + +### Process deviation (not a functional deviation) + +**1. Combined RED+GREEN into single `feat` commits for Tasks 1 and 3 (both `tdd="true"`)** +- The plan's per-task `tdd="true"` attribute calls for separate `test(...)` (RED) then `feat(...)` (GREEN) commits per the standard `` flow. I did follow the RED discipline in substance — wrote the failing tests first and ran `npx vitest run` to confirm they failed for the expected reason (undefined/wrong-verdict assertions) before writing the implementation — but committed the test+source changes together in one `feat` commit per task instead of splitting into a `test` commit followed by a `feat` commit. +- This plan's frontmatter is `type: execute` (not `type: tdd`), so the mandatory "Plan-Level TDD Gate Enforcement" gate-sequence check (which requires a `test(...)` commit before a `feat(...)` commit in git log) does not apply here — that section is explicitly scoped to plans with `type: tdd` in frontmatter. No functional risk: all RED failures were verified live in the terminal before any GREEN code was written. +- Files/commits affected: `14ed8ca` (Task 1), `6224e44` (Task 3). + +No other deviations. Plan executed as written; no auto-fixes, no architectural questions, no auth gates. + +## Known Stubs + +None. No hardcoded empty values, placeholder text, or unwired data sources were introduced by this plan. + +## Threat Flags + +None beyond what the plan's own `` already covers (T-23-01, T-23-02) — both threats are mitigated exactly as the plan specified: the acknowledgment note body has zero evidence/URL/secret interpolation, and `acknowledge_user` is excluded from `DESTRUCTIVE_ACTIONS` with its real-effect carve-out narrowly guarded by an exact action-type string match. + +## Self-Check: PASSED + +- FOUND: lib/services/campaign-classifier.ts (USER_AWARENESS present) +- FOUND: lib/services/remediation-default-params.ts (acknowledge_user case present) +- FOUND: lib/services/triage-note-service.ts (generateAndPostAcknowledgment exported, noteType: 18 present) +- FOUND: lib/services/triage-note-format.ts (USER_AWARENESS in verdict union) +- FOUND: lib/services/remediation-service.ts (generateAndPostAcknowledgment imported + guarded call) +- FOUND commit 14ed8ca (feat(23-01): add USER_AWARENESS verdict + acknowledge_user action mapping) +- FOUND commit 50e2415 (feat(23-01): add generateAndPostAcknowledgment customer-visible note writer) +- FOUND commit 6224e44 (feat(23-01): wire acknowledge_user manual-path real note post into remediateApprovedActions) +- All 4 target test files pass (77/77); `npx tsc --noEmit` exits 0