# Phase 23: Classification Disposition + Per-Client Automation Gate - Context

**Gathered:** 2026-07-16
**Status:** Ready for planning

<domain>
## Phase Boundary

Two related capabilities in the phishing triage pipeline:

1. **A dedicated "confirmed simulated phish" verdict.** The classifier
   (`campaign-classifier.ts`) already detects known phishing-simulation
   vendors (KnowBe4/Breach Secure Now) via sender-domain allowlist and
   explicitly skips the THREAT tier for them — but the result still lands in
   the generic `UNWANTED` bucket (the same disposition as run-of-the-mill
   marketing spam), with the recommended action `warn_user`. This phase adds
   a distinct 4th verdict with a new, distinct recommended action.

2. **A per-client automation gate.** With the Autotask webhook now fixed
   (delivering ticket create/update events again after ~3 months dead), the
   pipeline's parse/classify/report stages *could* run automatically per
   report. Today they're always manual (an explicit design limitation from
   Phase 18 — `parseAndStoreMessage` and `classifyCampaign` are never called
   by the automatic webhook/cron path). This phase adds an admin page letting
   an admin choose, per Autotask company, which of these stages run
   automatically instead of requiring the existing manual triggers.

Does NOT cover: changing the classifier's detection logic itself (the
KnowBe4/BSN sender-domain allowlist already exists and works — this phase
only changes what verdict/action it maps to), changing Phase 20's remediation
approval model for real threats (quarantine/block/etc. always require manual
approval, regardless of this phase's automation gate), or re-litigating the
webhook fix (already shipped as quick task 260716-n46 in this same session).

</domain>

<decisions>
## Implementation Decisions

### New Verdict: User Acknowledgment
- **D-01:** Add a 4th `Verdict` value (working name `USER_AWARENESS` — exact
  string is Claude's discretion, pick something that reads clearly in
  `StatusBadge`/reporting) for campaigns where the classifier's own reasoning
  already shows a phishing-simulation-vendor sender-domain match. This is
  additive to the existing `SPAM | UNWANTED | THREAT` union, not a
  replacement.
- **D-02:** This verdict maps to a NEW recommended action, `acknowledge_user`
  (not `warn_user`) — a professional message thanking the employee for
  reporting it and reinforcing that their vigilance helps keep the company
  secure. Exact copy is Claude's discretion; tone should be genuinely
  appreciative, not templated-sounding.
- **D-03:** `acknowledge_user` delivers as a **customer-visible Autotask
  ticket note** — not an internal-only note like Phase 21's existing
  triage-note, and not a separate email. Reuses the existing `TicketNotes`
  write path from `triage-note-service.ts`.
  **Verified against Autotask's live `TicketNotes/entityInformation/fields`
  API (not assumed from code/migration comments, which conflicted):**
  `publish` is NOT a client-visibility field — its full picklist is
  `1=All Autotask Users`, `2=Internal Project Team`, `4=Internal & Co-Managed`,
  all internal-staff-only tiers. The actual client-visibility field is
  `noteType`, which has a dedicated value `18="Client Portal Note"`. Phase
  21's existing triage-note uses `noteType: 1` ("Task Summary") — an
  unrelated internal categorization. **Correct implementation:** set
  `noteType: 18` for `acknowledge_user` (instead of copying Phase 21's
  `noteType: 1`); leave `publish: 1` unchanged (still required by the API,
  orthogonal to client visibility).
- **D-04:** `acknowledge_user` is exempt from Phase 20's proposed-only /
  manual-approval model — for companies with the automation gate's
  "report-to-ticket" stage enabled, it posts automatically without an
  operator clicking Approve first. This is a deliberate, narrow carve-out:
  it applies ONLY to `acknowledge_user` (a thank-you message, not a security
  action). Every other recommended action (quarantine, block sender,
  warn_user, etc.) always requires manual approval regardless of the
  automation gate, unchanged from Phase 20.

### Automation Gate: Granularity and Defaults
- **D-05:** Three independent per-company toggles, not one master switch:
  **parse**, **classify**, **report-to-ticket**. A company can, e.g., have
  auto-parse+classify enabled but auto-report disabled (an operator reviews
  before anything reaches the client's ticket).
- **D-06:** Default state for a company with no explicit row is **all three
  OFF** (opt-in, not opt-out). A client only gets automatic pipeline
  execution once someone deliberately enables it for them — mirrors Phase
  20's "proposed-only by default" safety posture. This also means: even with
  the webhook now delivering ticket-create events for every company, nothing
  changes in current pipeline behavior until an admin opts a company in via
  this new page.
- **D-07 (derived, not separately asked):** Detection + Tier-3 grouping
  (already automatic via the webhook, unconditionally, for every company —
  fixed in quick task 260716-n46) are UNAFFECTED by this gate. The gate only
  ever controls the three previously-manual-only stages (parse, classify,
  report). Detection/grouping are non-destructive (just flag + bucket into a
  campaign) and were never gated behind per-company consent even before this
  phase — no reason to add friction there.

### Automation Gate: Admin Surface
- **D-08:** New dedicated page at `/admin/phishing-automation`, `admin` role
  (not super-admin-restricted) — consistent with other `/admin/*` pages.
  Structurally mirrors the existing `/admin/client-scope` page: a searchable/
  filterable company table (reuse `components/admin/DataTable.tsx`), one row
  per company, with the three stage toggles as `Switch` components per row
  (see Reusable Assets below for the exact analog).

### Claude's Discretion
- Exact new verdict string value (`USER_AWARENESS` vs. alternatives) and its
  `StatusBadge` color/variant.
- Exact `acknowledge_user` message copy (tone direction given: professional,
  genuinely appreciative — see D-02).
- Schema shape for the automation-gate settings (new table keyed by
  `company_id` with three boolean columns vs. some other normalization) —
  whatever is cleanest given `company_scope`'s existing precedent (migration
  082: absent row = default, explicit row = override).
- Whether the automatic parse/classify/report pipeline runs synchronously
  inline in the webhook handler (mirroring today's automatic detect+group
  call in `triggerPhishingDetection()`) or is queued for a worker to pick up
  — an implementation detail, not a user-facing behavior choice, as long as
  it eventually runs without a human clicking a button.
- Audit/logging of automation-gate toggle changes (who/when) — reasonable to
  add given `integration_settings`' existing `disabled_by`/`disabled_at`
  pattern, but not something the user was asked about explicitly.

</decisions>

<canonical_refs>
## Canonical References

**Downstream agents MUST read these before planning or implementing.**

### Existing pipeline stages this phase gates
- `lib/services/campaign-classifier.ts` — `Verdict` type (`SPAM | UNWANTED |
  THREAT`), `mapVerdictToActions()`, and the existing KnowBe4/Breach Secure
  Now sender-domain-allowlist detection that already skips the THREAT tier
  (search for "phishing-simulation vendor allowlist" in its reasoning
  strings) — this phase changes what verdict/action that detection maps to,
  not the detection itself.
- `lib/services/phishing-eml-service.ts` — `parseAndStoreMessage()`, the
  "parse" stage.
- `lib/services/campaign-grouping-service.ts` — module doc-comment's
  documented D-07 limitation: `parseAndStoreMessage` and classification are
  "not wired into the automatic webhook/cron path" — this phase is what
  wires them in, per-company, behind the new gate.
- `lib/services/webhook-service.ts` — `triggerPhishingDetection()`, the
  existing automatic detect+Tier-3-group call fired on every `Ticket` webhook
  create event (fixed this session — see quick task
  `.planning/quick/260716-n46-fix-mimecast-blast-radius-date-window-fu/`).
  The new automatic parse/classify/report calls should follow this same
  call site's shape (read the ticket row back from Postgres, run the next
  stage, never block the webhook response).
- `lib/services/triage-note-service.ts` — `generateAndPostTriageNote()`, the
  existing internal-note writer this phase's `acknowledge_user` action reuses
  with a flipped visibility flag; also the "report-to-ticket" automation
  stage's existing manual equivalent.
- `app/api/phishing/campaigns/[id]/classify/route.ts` — the only existing
  trigger of `classifyCampaign`; the new automatic "classify" stage should
  call the same underlying service function this route calls, not duplicate
  classification logic.

### Automation-gate UI/API precedent
- `app/admin/client-scope/page.tsx` — the exact UI pattern to mirror: company
  table with search/type-filter + `Switch` per row, backed by
  `app/api/admin/company-scope/route.ts`.
- `migrations/082_company_scope.sql` — the opt-out-table schema precedent
  (`company_id` + boolean, absent row = default true, explicit row =
  override) to adapt for this phase's opt-IN, three-boolean shape.
- `migrations/081_*.sql` (`integration_settings` table) — the
  `disabled_by`/`disabled_at`/`disabled_reason` audit-column precedent, if
  audit logging is added per Claude's Discretion above.

### Phase 20 safety model this phase narrowly carves out of
- `.planning/phases/20-remediation-approval-audit-safety/` (SUMMARY/PLAN
  files) — the "proposed-only, human-approved" remediation model.
  `acknowledge_user` is the ONLY action exempted from manual approval by this
  phase (D-04); every other action type is unaffected.

### Prior phase context carried forward
- `.planning/phases/22-approval-ui-livelink-addressable-campaign-review-and-approve/22-CONTEXT.md`
  — D-06's `hasPermission()` client-side gating pattern (reuse for the new
  admin page's action buttons if any client-side gating is needed beyond the
  page-level `admin` role check).

</canonical_refs>

<code_context>
## Existing Code Insights

### Reusable Assets
- `app/admin/client-scope/page.tsx` + `app/api/admin/company-scope/route.ts`
  — direct structural template for the new automation-gate page (table +
  search/filter + per-row toggle(s), just 3 Switches instead of 1).
- `components/admin/DataTable.tsx` — table wrapper already used by
  `client-scope` and other admin pages.
- `lib/services/triage-note-service.ts` — reuse its `TicketNotes` write call
  shape for `acknowledge_user`, just with the publish/visibility flag
  flipped.
- `lib/permissions.ts` / `requirePermission()` — existing admin-role gate
  pattern for the new page's API routes.

### Established Patterns
- Opt-in/opt-out settings-table pattern already exists twice
  (`company_scope` for analytics, `integration_settings` for integration
  toggles) — this phase's automation-gate table is a third instance of the
  same pattern, just with 3 booleans instead of 1 and opt-in instead of
  opt-out defaults.
- "Absent row = default behavior, explicit row = override" is the
  established convention for these settings tables — follow it rather than
  requiring a row to exist for every company up front.

### Integration Points
- New automatic parse/classify/report calls plug into
  `webhookService`'s existing `triggerPhishingDetection()` call site (or a
  sibling method called from the same place) — after detection+grouping
  succeeds, check the grouped campaign's company against the new gate table
  before calling `parseAndStoreMessage`/`classifyCampaign`/
  `generateAndPostTriageNote`.
- `ClassificationCard` (Phase 22) needs to render the new verdict with its
  own `StatusBadge` variant/color, distinct from `UNWANTED`'s existing
  styling.
- `ActionAreaCard` (Phase 22) needs to handle `acknowledge_user` as a
  recommended-action checkbox like today's other action types, for the
  manual path (companies without report-to-ticket automation enabled still
  see it as a normal proposed action an operator approves).

</code_context>

<specifics>
## Specific Ideas

- The `acknowledge_user` message should read as a genuine thank-you, not a
  templated compliance notice — explicit framing from discussion: "a
  professional message letting them know we appreciate their attention in
  keeping the company secure."
- This phase's motivating example: ticket 699415 (Seubert & Associates,
  "Phishing Report - direccion@it-support.care - Attention Please"),
  correctly classified `UNWANTED` with the BSN-allowlist reasoning already
  present, confidence display bug already fixed separately (quick task
  260716-pgr) — but the disposition itself is what this phase corrects.

</specifics>

<deferred>
## Deferred Ideas

None — discussion stayed within phase scope. (The webhook fix and the
confidence display bug that surfaced during the same investigation were
already resolved as separate quick tasks, not folded into this phase.)

### Reviewed Todos (not folded)
None — no pending todos matched this phase.

</deferred>

---

*Phase: 23-classification-disposition-per-client-automation-gate*
*Context gathered: 2026-07-16*
