From 63b4aabc5ecd7fb46a2f28e7dee288916ee6d096 Mon Sep 17 00:00:00 2001 From: lorentz Date: Wed, 15 Jul 2026 07:49:51 -0400 Subject: [PATCH] docs(15-03): add plan execution summary Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_012wWroM6FXkQJiH3JgYcony --- .../15-03-SUMMARY.md | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 .planning/phases/15-data-model-detection-ticket-evidence/15-03-SUMMARY.md diff --git a/.planning/phases/15-data-model-detection-ticket-evidence/15-03-SUMMARY.md b/.planning/phases/15-data-model-detection-ticket-evidence/15-03-SUMMARY.md new file mode 100644 index 0000000..ca6858d --- /dev/null +++ b/.planning/phases/15-data-model-detection-ticket-evidence/15-03-SUMMARY.md @@ -0,0 +1,95 @@ +--- +phase: 15-data-model-detection-ticket-evidence +plan: 03 +subsystem: services +tags: [phishing-triage, webhook, cron, scheduler, detection] + +# Dependency graph +requires: + - phase: 15-02 + provides: "detectPhishingTicket(ticket) shared detection core (phishing-detector.ts)" +provides: + - "lib/services/phishing-sweep-service.ts — sweepPhishingTickets() bounded cron reconciliation" + - "webhook-service.ts triggerPhishingDetection() — fire-and-forget detection on ticket.created" + - "sync-scheduler.ts phishing-sweep sync_type + dispatch branch + defaultSchedules entry" + - "migrations/098_phishing_sweep_schedule.sql — phishing-sweep schedule seed for existing installs" +affects: [16-message-parsing, 17-mimecast-blast-radius] + +# Tech tracking +tech-stack: + added: [] + patterns: + - "Fire-and-forget webhook hook-in mirroring triggerWorkflowEngine (.catch(err => console.error(...)), never awaited in the request path" + - "Bounded cron sweep mirroring reconcileStaleTickets (LIMIT + recent-activity window + per-row try/catch, never rethrow)" + - "Dynamic await import() in every sync-scheduler dispatch branch (no eager worker import per CLAUDE.md)" + +key-files: + created: [lib/services/phishing-sweep-service.ts, migrations/098_phishing_sweep_schedule.sql] + modified: [lib/services/webhook-service.ts, lib/services/sync-scheduler.ts] + +key-decisions: + - "Reused ticket-reconciliation-service.ts's structure verbatim for the sweep (SELECT-with-LIMIT, per-row try/catch, createSyncLogger, aggregate result object) rather than inventing a new shape" + - "triggerPhishingDetection reads payload.entity.createdByContactID (not creatorContactID, which does not exist) into created_by_contact_id — verified by grep since a typo would compile cleanly (payload.entity is Record) but silently degrade EVID-01 requester capture at runtime" + - "phishing-sweep schedule is registered disabled-by-default (is_enabled: false), matching the tickets-reconcile precedent — an admin must opt in via /admin" + +requirements-completed: [DETECT-01, DETECT-02] + +# Metrics +duration: 9min +completed: 2026-07-15 +--- + +# Phase 15 Plan 03: Webhook + Cron Sweep Wiring Summary + +**Wired the Plan 02 `detectPhishingTicket` core into both of Pulse's established scan triggers — a fire-and-forget hook on the ticket.created webhook and a bounded daily cron sweep (`sweepPhishingTickets`, LIMIT 500 / 7-day window) — plus migration 098 to seed the disabled-by-default `phishing-sweep` schedule row for existing installs.** + +## Performance + +- **Duration:** 9 min +- **Started:** 2026-07-15T11:49:00Z +- **Completed:** 2026-07-15T11:58:00Z +- **Tasks:** 3 completed +- **Files modified:** 4 (2 created, 2 modified) + +## Accomplishments +- Created `lib/services/phishing-sweep-service.ts` exporting `sweepPhishingTickets()`: queries non-deleted tickets with `last_activity_date` in the last 7 days (bounded `LIMIT 500`), calls the shared `detectPhishingTicket` per row inside a try/catch (no rethrow — one bad ticket never aborts the sweep), and returns a `{ scanned, flagged, skippedUnchanged, errors }` aggregate, logged via `createSyncLogger`. +- Wired `webhook-service.ts`: added a `triggerPhishingDetection` private method that builds a `DetectableTicket` from `payload.entity` (preferring the inline entity, falling back to `payload.entityId` with null fields), reading the correct `createdByContactID` Autotask field into `created_by_contact_id`. Called it as a second fire-and-forget alongside the existing `triggerWorkflowEngine` call in the `ticket.created` handler, without awaiting it in the request path. +- Registered the sweep in `sync-scheduler.ts`: extended the `sync_type` union with `'phishing-sweep'`, added a `defaultSchedules` entry (`is_enabled: false`, daily `0 5 * * *` cron), and added a dispatch branch that dynamically imports and calls `sweepPhishingTickets`, logging the scanned/flagged/skippedUnchanged/errors summary. +- Created `migrations/098_phishing_sweep_schedule.sql`, seeding the `phishing-sweep` row via `ON CONFLICT (id) DO NOTHING` (idempotent, disabled by default) for existing installs whose `sync_schedules` table predates this migration. + +## Task Commits + +1. **Task 1: phishing-sweep-service.ts** - `dbd2ebe` (feat) +2. **Task 2: webhook hook-in** - `194b58b` (feat) +3. **Task 3: scheduler branch + migration 098** - `b199d99` (feat) + +**Plan metadata:** (this SUMMARY.md commit) + +## Files Created/Modified +- `lib/services/phishing-sweep-service.ts` - Bounded reconciliation sweep calling the shared `detectPhishingTicket` core (no duplicated match/hash logic) +- `lib/services/webhook-service.ts` - Added `detectPhishingTicket` import, `triggerPhishingDetection` method, and a fire-and-forget call in the `ticket.created` handler alongside the existing workflow-engine trigger +- `lib/services/sync-scheduler.ts` - Extended `sync_type` union, added `phishing-sweep` `defaultSchedules` entry, added a dispatch branch dynamically importing `sweepPhishingTickets` +- `migrations/098_phishing_sweep_schedule.sql` - Seeds the `phishing-sweep` schedule row for existing installs (idempotent, disabled by default) + +## Decisions Made +- Mirrored `ticket-reconciliation-service.ts`'s structure for the sweep verbatim (module constants for window/limit, `createSyncLogger`, per-row try/catch with `logger.warn` and no rethrow) rather than introducing a different shape, since the plan explicitly called this out as the closest analog. +- Used the exact `createdByContactID` field name confirmed by the plan's interface notes and `lib/utils/entity-mapper.ts:211`; verified via grep that no `creatorContactID` typo was introduced (a typo would compile cleanly since `AutotaskWebhookPayload.entity` is `Record`, but would silently produce `undefined` at runtime). +- Kept the `phishing-sweep` schedule disabled by default (`is_enabled: false`), matching the `tickets-reconcile` precedent, so the sweep does nothing until an admin explicitly enables it via `/admin`. + +## Deviations from Plan +None — plan executed exactly as written. All three tasks matched their `` specs, and every acceptance criterion (grep checks + `npx tsc --noEmit --pretty`) passed on first attempt. + +## Issues Encountered +None. + +## User Setup Required +None for this plan. The `phishing-sweep` schedule is seeded disabled; an admin can enable it later at `/admin` once ready to run reconciliation sweeps in production. No new env vars or credentials introduced (reuses existing `AUTOTASK_*` config via the Plan 02 detector). + +## Next Phase Readiness +- Both DETECT-01 scan triggers (webhook + cron) are now wired to the same shared `detectPhishingTicket` core, so DETECT-02 idempotency holds identically on either path. +- The `phishing-sweep` schedule exists in the `defaultSchedules` seed path (fresh installs) and migration 098 (existing installs), both idempotent and disabled by default. +- No blockers for Phase 16 (message parsing) or Phase 17 (Mimecast blast-radius), which build on this detection/evidence foundation. + +--- +*Phase: 15-data-model-detection-ticket-evidence* +*Completed: 2026-07-15*