From 9dbc1e7510173e62af359e2138fb7d87e9c344f1 Mon Sep 17 00:00:00 2001 From: lorentz Date: Wed, 15 Jul 2026 13:35:21 -0400 Subject: [PATCH] docs(17): add research findings + tenant-scope decision --- .../17-CONTEXT.md | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/.planning/phases/17-mimecast-blast-radius-lookup/17-CONTEXT.md b/.planning/phases/17-mimecast-blast-radius-lookup/17-CONTEXT.md index dc16d48..f974d5a 100644 --- a/.planning/phases/17-mimecast-blast-radius-lookup/17-CONTEXT.md +++ b/.planning/phases/17-mimecast-blast-radius-lookup/17-CONTEXT.md @@ -22,12 +22,15 @@ whether Mimecast is present. ## Implementation Decisions ### Query Strategy -- **D-01:** Try `getMessageInfo(messageId)` first for an exact Message-ID - match. If that misses (no match, or Message-ID unknown/altered by - intermediate relays), fan out to `searchDeliveredMessages` + - `getHeldMessages` + `getThreatEvents` keyed on sender/subject/date-window - and merge results into one normalized blast-radius shape (matched/ - delivered/held/rejected/clicked counts + per-recipient status). This is the +- **D-01 (corrected by research):** `getMessageInfo(messageId)` returns only + `{ messageId, bodyText, bodyHtml, headers }` — no status, no counts, no + per-recipient data. It is a supplementary body/header fetch only, never a + substitute for the counts. The fan-out — `searchDeliveredMessages` + + `getHeldMessages` + `getThreatEvents` keyed on sender/subject/date-window, + merged into one normalized blast-radius shape (matched/delivered/held/ + rejected/clicked counts + per-recipient status) — runs **unconditionally**, + not as a fallback path. `getMessageInfo` may still be called opportunistically + for body/header evidence but must never gate whether the fan-out runs. This is the most complete picture available from the existing `MimecastClient` methods, at the cost of more API calls on the fallback path. @@ -57,6 +60,22 @@ whether Mimecast is present. succession, while staying short enough that data doesn't go stale across a single classification session. +### Tenant Scope +- **D-05 (added post-research):** Research surfaced an existing per-company + Mimecast multi-tenant setup (`mimecast_tenants` table, migration 062, + consumed via `getMimecastClientForTenant()` in the existing `/api/mimecast/ + held` and `/api/mimecast/delivered` routes) that this phase's original + scope did not address. Decision: use **only the single global env-var + client** (`getMimecastClient()`) for v1 — do not resolve `reports.company_id` + to a per-tenant client in this phase. This must be documented explicitly in + code comments as a known limitation: reports from companies with their own + `mimecast_tenants` row (not covered by the global `MIMECAST_CLIENT_ID`) will + return `unavailable` even though Mimecast is technically configured for + that company. Per-tenant resolution is a small, mechanical follow-up later + (swap `getMimecastClient()` for a tenant lookup + `getMimecastClientForTenant()`, + same fan-out logic) if it turns out to be needed in practice — not blocking + for this phase. + ### Claude's Discretion (explicitly deferred to research + planner) - **`isMimecastConfigured()` helper.** `lib/services/mimecast-client.ts` currently has `getMimecastClient()` (throws if `MIMECAST_CLIENT_ID`/