docs(18): add research findings + EML-parsing scope decision

This commit is contained in:
lorentz 2026-07-15 16:33:49 -04:00
parent 4ccd379dc3
commit e05d34640c
2 changed files with 32 additions and 2 deletions

View file

@ -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)

View file

@ -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