docs(17): fix plan-checker warnings (merge formula clarity + resolved open questions)

This commit is contained in:
lorentz 2026-07-15 13:47:48 -04:00
parent 7303b16cbb
commit 3424812d60
2 changed files with 3 additions and 3 deletions

View file

@ -179,7 +179,7 @@ Factory-convention analogs to mirror:
2. Build the cache key `<service>:<resource>:<discriminators>` per addigy-devices precedent: with messageId → `mimecast:blast-radius:msgid:${input.messageId}`; else → `mimecast:blast-radius:composite:${input.sender}:${input.subject}:${input.dateWindow.start.toISOString()}:${input.dateWindow.end.toISOString()}`.
3. `const cached = await getCachedData<BlastRadiusResult>(cacheKey); if (cached) return cached;` — a cache hit must short-circuit BEFORE any MimecastClient call (D-04).
4. Inside a `try`: `const client = getMimecastClient();`. If `input.messageId` is present, optionally `await client.getMessageInfo(input.messageId)` for body/header evidence ONLY — never use its return to derive counts and never let it gate the fan-out (Pitfall 1). Then ALWAYS run the fan-out via `Promise.all([...])` (D-01 corrected — fan-out is unconditional, not a fallback): `searchDeliveredMessages({ to: input.recipient, from: input.sender, subject: input.subject, start, end })` where `start`/`end` are `input.dateWindow.start/end.toISOString().replace(/\.\d{3}Z$/, '+0000')`; `getHeldMessages({ recipient: input.recipient })`; `getThreatEvents()`.
5. Merge per 17-RESEARCH Pattern 3: build `perRecipient[]` by grouping delivered + held rows on their single-string `to` field (delivered → 'delivered', held → 'held', neither → 'unknown'); `delivered` = count of delivered rows, `held` = count of held rows, `rejected` = delivered rows whose `.status` string indicates rejection, `matched` = total delivered + held rows. Because the exact `.status` rejection enum is UNCONFIRMED (17-RESEARCH A3), add a code comment noting this and treat unrecognized status strings conservatively as 'delivered' rather than hardcoding a guessed rejected value as confirmed; log the raw status values seen at debug level for first-real-tenant validation. Derive `clicked` best-effort by counting threat-event items whose `analysis[]` includes a click-type value, else `0` (comment the best-effort limitation).
5. Merge per 17-RESEARCH Pattern 3: build `perRecipient[]` by grouping delivered + held rows on their single-string `to` field (delivered → 'delivered', held → 'held', neither → 'unknown'); first partition `searchDeliveredMessages` rows into non-rejected vs. rejected using `.status` (rejected = row's `.status` string indicates rejection); `delivered` = count of NON-rejected delivered rows (mutually exclusive with `rejected`), `rejected` = count of rejected delivered rows, `held` = count of held rows, `matched` = `delivered + held` (does not include `rejected` — a rejected recipient was never actually delivered). Because the exact `.status` rejection enum is UNCONFIRMED (17-RESEARCH A3), add a code comment noting this and treat unrecognized status strings conservatively as 'delivered' (i.e. not rejected) rather than hardcoding a guessed rejected value as confirmed; log the raw status values seen at debug level for first-real-tenant validation. Derive `clicked` best-effort by counting threat-event items whose `analysis[]` includes a click-type value, else `0` (comment the best-effort limitation).
6. `await setCachedData(cacheKey, result, 300);` then return the result.
7. `catch (err)`: `console.error('[MIMECAST-BLAST-RADIUS] lookup failed', err instanceof Error ? err.message : err)` — log err.message ONLY, never full Mimecast response bodies which may contain other recipients' subjects/content (T-17-02) — and return `{ status:'unavailable', reason:'lookup_failed', error: err instanceof Error ? err.message : String(err) }`. This satisfies BLAST-02: the public entry point never throws.

View file

@ -535,7 +535,7 @@ actually call, not `docs/mimecast-api-guide.md`.
## Open Questions
1. **Should this phase's abstraction resolve a per-company Mimecast tenant, or only use the
1. **(RESOLVED: see CONTEXT.md D-05 — single global client for v1, multi-tenant gap documented in code)** Should this phase's abstraction resolve a per-company Mimecast tenant, or only use the
single global env-var client?**
- What we know: `mimecast_tenants` (migration 062) exists and is actively used by
`/api/mimecast/held` and `/api/mimecast/delivered` (the two existing routes closest in
@ -552,7 +552,7 @@ actually call, not `docs/mimecast-api-guide.md`.
addition later (swap `getMimecastClient()` for a tenant lookup + `getMimecastClientForTenant()`,
using the same fan-out logic).
2. **What are the real string values Mimecast returns for `status` in message-tracking search
2. **(RESOLVED: deferred to runtime observation — 17-01-PLAN.md Task 2 treats unrecognized status strings conservatively as non-rejected and logs raw values at debug level rather than hardcoding a guessed enum)** What are the real string values Mimecast returns for `status` in message-tracking search
results (delivered vs. rejected classification)?**
- What we know: The TypeScript field exists (`MimecastDeliveredMessage.status: string`) and
is populated from `e.status` in the raw API response, per `searchDeliveredMessages()`'s