From 7869e0bcfd6afb2811395daefd8c80f37909604b Mon Sep 17 00:00:00 2001 From: lorentz Date: Thu, 16 Jul 2026 19:47:21 -0400 Subject: [PATCH] docs(23-05): complete gated automation pipeline plan Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01W6RuWdiUiXrPK6FLBHjtpY --- .../23-05-SUMMARY.md | 108 ++++++++++++++++++ .../deferred-items.md | 11 ++ 2 files changed, 119 insertions(+) create mode 100644 .planning/phases/23-classification-disposition-per-client-automation-gate/23-05-SUMMARY.md create mode 100644 .planning/phases/23-classification-disposition-per-client-automation-gate/deferred-items.md diff --git a/.planning/phases/23-classification-disposition-per-client-automation-gate/23-05-SUMMARY.md b/.planning/phases/23-classification-disposition-per-client-automation-gate/23-05-SUMMARY.md new file mode 100644 index 0000000..cb71353 --- /dev/null +++ b/.planning/phases/23-classification-disposition-per-client-automation-gate/23-05-SUMMARY.md @@ -0,0 +1,108 @@ +--- +phase: 23-classification-disposition-per-client-automation-gate +plan: 05 +subsystem: phishing-triage-automation +tags: [webhook, phishing, postgres, vitest, autotask, gate-pattern] + +# Dependency graph +requires: + - phase: 23-01 + provides: "USER_AWARENESS verdict on Verdict union, classifyCampaign, generateAndPostAcknowledgment (acknowledge_user note writer)" + - phase: 23-03 + provides: "phishing_automation_gate table (migration 100) + admin CRUD routes for per-company auto_parse/auto_classify/auto_report toggles" +provides: + - "getCompanyAutomationGate(companyId) reusable COALESCE-false reader" + - "Automatic parse -> classify -> acknowledge_user pipeline wired into the live Autotask webhook, gated per-company" +affects: [23-remaining-plans, phishing-triage-runbook] + +# Tech tracking +tech-stack: + added: [] + patterns: + - "Per-company opt-in settings reader (COALESCE(..., false), absent row = all-false) as a standalone testable module, mirroring the admin route's LEFT JOIN pattern but scoped to a single company_id" + - "Gated webhook stage chain: each automation stage wrapped in its own try/catch so a stage failure logs-and-continues without blocking the fire-and-forget webhook response or aborting later stages" + +key-files: + created: + - lib/services/phishing-automation-gate.ts + - lib/services/phishing-automation-gate.test.ts + modified: + - lib/services/webhook-service.ts + +key-decisions: + - "auto_report's automatic behavior is narrowly scoped to generateAndPostAcknowledgment for USER_AWARENESS verdicts only — no other action/verdict is ever auto-posted or auto-approved (D-04 carve-out, T-23-08)" + - "When auto_report is enabled but auto_classify did not run in the same pass (or failed), the current verdict is read from the most recent classifications row for the campaign rather than re-running classification" + - "Detection + grouping remain unconditional for every company regardless of gate state (D-07) — only the three post-grouping stages are gated" + +patterns-established: + - "runGatedPhishingStages(input) as the single gated-stage entry point, called only when groupReportIntoCampaign resolves a campaignId" + +requirements-completed: [AUTOGATE-03] + +# Metrics +duration: 15min +completed: 2026-07-16 +--- + +# Phase 23 Plan 05: Wire Gated Automation Pipeline Into Webhook Summary + +**Adds a tested `getCompanyAutomationGate` reader and wires the opted-in parse -> classify -> acknowledge_user chain into the live Autotask webhook, with detection/grouping staying always-on and every other remediation action staying manual-approval-gated.** + +## Performance + +- **Duration:** ~15 min +- **Started:** 2026-07-16T23:31:00Z (approx) +- **Completed:** 2026-07-16T23:46:41Z +- **Tasks:** 2/2 completed +- **Files modified:** 3 (2 created, 1 modified) + +## Accomplishments +- `getCompanyAutomationGate(companyId)` reusable reader: absent row / null / NaN companyId all resolve to all-false without throwing; present row maps snake_case -> camelCase. +- `triggerPhishingDetection` now captures `groupReportIntoCampaign`'s result and, when a campaign exists, runs the new `runGatedPhishingStages` method — automatically parsing, classifying, and (only for USER_AWARENESS) posting the acknowledge_user note for opted-in companies, entirely within the existing fire-and-forget call site. +- Detection and grouping remain unconditional (D-07); no other verdict/action (block_sender, purge_message, warn_user, reset_password, etc.) is ever auto-posted or auto-approved by this change. + +## Task Commits + +Each task was committed atomically: + +1. **Task 1: Create getCompanyAutomationGate reader + tests** - `0d7974c` (test, RED) -> `e0f22f2` (feat, GREEN) +2. **Task 2: Wire gated parse->classify->acknowledge chain into triggerPhishingDetection** - `e1193bf` (feat) + +_TDD gate sequence for Task 1 confirmed in git log: test commit `0d7974c` precedes feat commit `e0f22f2`._ + +## Files Created/Modified +- `lib/services/phishing-automation-gate.ts` - `getCompanyAutomationGate(companyId)`: COALESCE(..., false)-backed per-company gate reader, never throws. +- `lib/services/phishing-automation-gate.test.ts` - 4 vitest cases: absent row, present row mapping, null companyId, NaN companyId. +- `lib/services/webhook-service.ts` - `triggerPhishingDetection` now captures the grouping result and calls new private `runGatedPhishingStages`, which reads the gate and conditionally runs `parseAndStoreMessage`, `classifyCampaign`, and `generateAndPostAcknowledgment` (guarded by `verdict === 'USER_AWARENESS'`), each in its own try/catch. + +## Decisions Made +- Verdict-for-report lookup: when `auto_classify` didn't run in the same pass (either disabled or its try/catch caught an error), `auto_report`'s USER_AWARENESS check falls back to `SELECT verdict FROM classifications WHERE campaign_id = $1 ORDER BY created_at DESC LIMIT 1` rather than skipping the report stage entirely — this lets a company with only `auto_report` enabled (classification done via the existing manual route) still get the automatic acknowledge_user post once a human/prior automated pass has classified the campaign as USER_AWARENESS. +- Kept `runGatedPhishingStages` as a new private method (not inlined into `triggerPhishingDetection`) to keep the always-on detect+group logic visually and structurally separate from the gated stages, per the plan's D-07 emphasis. + +## Deviations from Plan + +None - plan executed exactly as written. Both tasks matched the plan's `` and interface contracts (`ParseAndStoreResult`, `ClassifyResult.verdict`, `TriageNoteResult`, `GroupReportResult`) exactly as they already existed on disk from plans 23-01 and 23-03. + +## Known Stubs + +None. + +## Threat Flags + +None — this plan's threat model (T-23-08 through T-23-11) was fully addressed as designed; no new unmodeled surface was introduced. `runGatedPhishingStages` adds no new inbound endpoint, only conditional internal calls to existing, already-reviewed service functions. + +## Verification + +- `npx vitest run lib/services/phishing-automation-gate.test.ts` — 4/4 passed. +- `npx tsc --noEmit --pretty` — clean, no errors. +- Full `npx vitest run` — 434 passed, 2 failed. The 2 failures are pre-existing and unrelated (`lib/services/analyzer/itglue-search.test.ts`, files never touched by this plan) — logged to `.planning/phases/23-classification-disposition-per-client-automation-gate/deferred-items.md` per the scope-boundary rule rather than fixed here. +- Acceptance-criteria greps for Task 2 all pass: `getCompanyAutomationGate` called with `companyId` (the ticket's `company_id`), `USER_AWARENESS` guards the `generateAndPostAcknowledgment` call, no auto-call to approve/remediate/block/purge/warn_user services was added, `detectPhishingTicket`/`groupReportIntoCampaign` remain unconditional, and each of the three gated stages has its own try/catch. + +## Self-Check: PASSED + +- FOUND: lib/services/phishing-automation-gate.ts +- FOUND: lib/services/phishing-automation-gate.test.ts +- FOUND: lib/services/webhook-service.ts (modified) +- FOUND commit 0d7974c +- FOUND commit e0f22f2 +- FOUND commit e1193bf diff --git a/.planning/phases/23-classification-disposition-per-client-automation-gate/deferred-items.md b/.planning/phases/23-classification-disposition-per-client-automation-gate/deferred-items.md new file mode 100644 index 0000000..70d162c --- /dev/null +++ b/.planning/phases/23-classification-disposition-per-client-automation-gate/deferred-items.md @@ -0,0 +1,11 @@ +# Deferred Items — Phase 23 + +## Pre-existing test failures (out of scope for 23-05) + +`lib/services/analyzer/itglue-search.test.ts` has 2 pre-existing failures +(`tolerates per-call failures` test cases) unrelated to plan 23-05's changes. +Plan 23-05 only touched `lib/services/phishing-automation-gate.ts`, +`lib/services/phishing-automation-gate.test.ts`, and +`lib/services/webhook-service.ts` — no analyzer/itglue files were modified. +Full suite: `434 passed, 2 failed` both in this one unrelated file. Logged +per SCOPE BOUNDARY rule — not fixed here.