diff --git a/.planning/phases/18-campaign-grouping-phishing-analysis-api/18-CONTEXT.md b/.planning/phases/18-campaign-grouping-phishing-analysis-api/18-CONTEXT.md index 6cf91e8..79a5d1e 100644 --- a/.planning/phases/18-campaign-grouping-phishing-analysis-api/18-CONTEXT.md +++ b/.planning/phases/18-campaign-grouping-phishing-analysis-api/18-CONTEXT.md @@ -84,6 +84,36 @@ phishing endpoint (Phases 19-21) must also follow. browsing — it wires the fine-grained check through now rather than retrofitting it later. +### Automatic-Path EML Parsing Scope +- **D-07 (added post-research):** Research found that `parseAndStoreMessage()` + (Phase 16's `.eml` parser — the only writer of `messages`/`indicators` rows, + i.e. the only source of Message-ID and attachment-hash/URL data) has zero + callers anywhere in the codebase; Phase 16's own plan doc explicitly + earmarked this phase's `/analyze` endpoint as its first real caller. + Decision: do **NOT** wire `parseAndStoreMessage()` into the automatic + webhook/cron path in this phase. The automatic path (webhook `ticket.created` + + cron sweep) calls `groupReportIntoCampaign()` using only whatever + `reports`/`contacts` data already exists — which means Tier 1 (Message-ID) + and Tier 2 (attachment-hash/URL-domain) can only ever match for a ticket + that has already been through an explicit `POST /analyze` call at least + once; the automatic path effectively only reaches **Tier 3** + (sender+normalized-subject+client+time-window) until then. This must be + documented explicitly in code comments (not silently built around) since it + changes what "grouped automatically" means in practice versus D-01's + original framing. Rationale: matches Phase 16's explicit design intent and + keeps the automatic webhook/cron path cheap — no extra Autotask + attachment-content fetch + optional B2 upload firing on every single + `ticket.created` event, most of which aren't reviewed by anyone. +- **D-08 (added post-research):** `groupReportIntoCampaign()` short-circuits + immediately (does nothing) if `reports.campaign_id IS NOT NULL` when called + from the automatic webhook/cron path — avoids redundant tier-query work on + every 7-day cron sweep pass for reports already grouped. The `/analyze` + route calls the function WITHOUT this short-circuit (always re-runs full + tiered matching) — an explicit operator request may follow a fresh + `/analyze` call that just populated `messages`/`indicators` for the first + time, and deserves the chance to upgrade an existing Tier-3-only grouping + to a Tier-1 match if a shared Message-ID is now available. + ### Claude's Discretion (explicitly deferred to research + planner) - **Exact SQL/query shape for `GET /api/phishing/campaigns/{id}`'s nested response** (linked reports, messages, indicators, classification history) diff --git a/.planning/phases/18-campaign-grouping-phishing-analysis-api/18-RESEARCH.md b/.planning/phases/18-campaign-grouping-phishing-analysis-api/18-RESEARCH.md index 5e9f36a..65cafb4 100644 --- a/.planning/phases/18-campaign-grouping-phishing-analysis-api/18-RESEARCH.md +++ b/.planning/phases/18-campaign-grouping-phishing-analysis-api/18-RESEARCH.md @@ -538,7 +538,7 @@ assumptions about how this codebase "probably" works. ## Open Questions -1. **Should this phase also wire `parseAndStoreMessage()` into the automatic +1. **(RESOLVED: see CONTEXT.md D-07 — do NOT auto-wire, document Tier-3-only automatic-path limitation)** Should this phase also wire `parseAndStoreMessage()` into the automatic webhook/cron path, or leave it reachable only via the on-demand `/analyze` endpoint?** - What we know: `parseAndStoreMessage` has zero callers today outside its test file. Phase 16's own plan doc explicitly earmarked "the live on-demand trigger" (this @@ -560,7 +560,7 @@ assumptions about how this codebase "probably" works. around it — it changes what "automatically grouped" means in practice for success criterion #1. -2. **Does `groupReportIntoCampaign` re-run for a report already linked to a campaign?** +2. **(RESOLVED: see CONTEXT.md D-08 — short-circuit on automatic path, always re-run on /analyze)** Does `groupReportIntoCampaign` re-run for a report already linked to a campaign?** - What we know: `detectPhishingTicket`'s idempotency guard (content-hash unchanged) still refreshes the `evidence` JSONB on every call — so it's called repeatedly over a ticket's lifetime (every webhook re-fire, every cron sweep pass within the 7-day