docs(quick-260718-7v8): Fix Mimecast blast-radius held-message false-positive fix

This commit is contained in:
lorentz 2026-07-18 05:48:23 -04:00
parent b7d6be47c6
commit a30d72e9ba
2 changed files with 112 additions and 1 deletions

View file

@ -28,7 +28,7 @@ See: .planning/PROJECT.md (updated 2026-07-14)
Phase: Milestone v3.0 complete Phase: Milestone v3.0 complete
Plan: — Plan: —
Status: Awaiting next milestone Status: Awaiting next milestone
Last activity: 2026-07-18 — Completed quick task 260717-v6c: Mark as accidental report action Last activity: 2026-07-18 — Completed quick task 260718-7v8: Mimecast blast-radius held-message false-positive fix
## Performance Metrics ## Performance Metrics
@ -128,6 +128,7 @@ None yet.
| 260716-pgr | Fix confidence display bug (0-1 scale rendered as raw percent, e.g. "1%" instead of "100%") | 2026-07-16 | 3f16268 | [260716-pgr-fix-confidence-display-bug-in-classifica](./quick/260716-pgr-fix-confidence-display-bug-in-classifica/) | | 260716-pgr | Fix confidence display bug (0-1 scale rendered as raw percent, e.g. "1%" instead of "100%") | 2026-07-16 | 3f16268 | [260716-pgr-fix-confidence-display-bug-in-classifica](./quick/260716-pgr-fix-confidence-display-bug-in-classifica/) |
| 260717-a19 | Fix phishing simulation-vendor allowlist gaps (3 missing KnowBe4 domains), auto-parse timing race (retry on ticket.update), and parseAndStoreMessage idempotency; reclassified 6 stale Seubert campaigns (all flipped UNWANTED → USER_AWARENESS) | 2026-07-17 | cf04f07 | [260717-a19-fix-phishing-simulation-vendor-allowlist](./quick/260717-a19-fix-phishing-simulation-vendor-allowlist/) | | 260717-a19 | Fix phishing simulation-vendor allowlist gaps (3 missing KnowBe4 domains), auto-parse timing race (retry on ticket.update), and parseAndStoreMessage idempotency; reclassified 6 stale Seubert campaigns (all flipped UNWANTED → USER_AWARENESS) | 2026-07-17 | cf04f07 | [260717-a19-fix-phishing-simulation-vendor-allowlist](./quick/260717-a19-fix-phishing-simulation-vendor-allowlist/) |
| 260717-v6c | Add "Mark as accidental report" action to the phishing Action Area — closes out a campaign and posts a fixed customer-facing note to the reporter (distinct from the silent "Mark as false positive" action) | 2026-07-18 | 565a0c1 | [260717-v6c-add-a-mark-as-accidental-report-action-t](./quick/260717-v6c-add-a-mark-as-accidental-report-action-t/) | | 260717-v6c | Add "Mark as accidental report" action to the phishing Action Area — closes out a campaign and posts a fixed customer-facing note to the reporter (distinct from the silent "Mark as false positive" action) | 2026-07-18 | 565a0c1 | [260717-v6c-add-a-mark-as-accidental-report-action-t](./quick/260717-v6c-add-a-mark-as-accidental-report-action-t/) |
| 260718-7v8 | Fix Mimecast blast-radius false positives — date-scope `getHeldMessages()` and add a sender-domain relevance guard so unrelated held mail in a recipient's queue no longer inflates held/matched counts or overwrites a genuinely delivered recipient's status | 2026-07-18 | b7d6be4 | [260718-7v8-fix-mimecast-blast-radius-held-message-f](./quick/260718-7v8-fix-mimecast-blast-radius-held-message-f/) |
## Deferred Items ## Deferred Items

View file

@ -0,0 +1,110 @@
---
phase: quick-260718-7v8
plan: 01
subsystem: api
tags: [mimecast, blast-radius, phishing, false-positive-fix]
# Dependency graph
requires:
- phase: 17-mimecast-blast-radius-lookup
provides: getBlastRadius() fan-out orchestration and MimecastClient
provides:
- Date-scoped getHeldMessages() (optional start/end threaded into request body)
- Sender-domain relevance guard on held rows before counting/merging
affects: [17-mimecast-blast-radius-lookup, 22-approval-ui-livelink-addressable-campaign-review-and-approve]
# Tech tracking
tech-stack:
added: []
patterns:
- "Sender-relevance guard: filter external-API rows by domain match against campaign input.sender BEFORE they feed counts or a per-recipient merge — mirrors campaign-classifier's exact-or-proper-subdomain comparison without reusing its allowlist-specific function."
key-files:
created: []
modified:
- lib/services/mimecast-client.ts
- lib/services/mimecast-blast-radius.ts
- lib/services/mimecast-blast-radius.test.ts
- lib/services/mimecast-client.test.ts
key-decisions:
- "getHeldMessages() start/end are pre-formatted strings supplied by the caller (mirrors searchDeliveredMessages), never formatted inside the client method."
- "domainsMatch() is a new local helper in mimecast-blast-radius.ts, not a reuse of campaign-classifier's domainMatchesAllowlist (that helper is allowlist-specific)."
- "Held-row counts (held/matched) and the perRecipient merge both use the same relevantHeldRows array so a filtered-out row can never leak into either output."
requirements-completed: [BUG-BLAST-RADIUS-HELD]
# Metrics
duration: 15min
completed: 2026-07-18
---
# Quick Task 260718-7v8: Fix Mimecast Blast-Radius Held-Message False Positives Summary
**Date-scoped `getHeldMessages()` plus a sender-domain relevance guard eliminate unrelated held-message false positives in the Blast Radius panel.**
## Performance
- **Duration:** ~15 min
- **Started:** 2026-07-18T09:35:00Z (approx.)
- **Completed:** 2026-07-18T09:46:50Z
- **Tasks:** 2
- **Files modified:** 4
## Accomplishments
- `MimecastClient.getHeldMessages()` now accepts optional `start`/`end` and threads them into `data[0]` as siblings of `admin`/`searchBy` (backward compatible when omitted; the existing 403 fallback body inherits them automatically via its `{ ...reqBody }` spread).
- `getBlastRadius()` passes the same `startStr`/`endStr` window already computed for `searchDeliveredMessages` into `getHeldMessages()`, so the held-queue lookup is scoped to the reported campaign's window instead of the recipient's entire hold queue.
- Added a `domainsMatch()` sender-relevance guard in `mimecast-blast-radius.ts`: held rows whose sender domain does not match `input.sender`'s domain (exact-or-proper-subdomain, case-insensitive) are filtered out of `relevantHeldRows` before they can contribute to `held`/`matched` counts or the `perRecipient` merge.
- Extended test coverage in both files: date-window threading assertion, unrelated-sender-domain exclusion test, matching-sender-domain override-still-works test, and client-level POST-body-shape tests for `start`/`end` presence/absence.
## Task Commits
Each task was committed atomically:
1. **Task 1: Date-scope getHeldMessages and add sender-relevance guard in getBlastRadius** - `94f7dad` (fix)
2. **Task 2: Extend test coverage for date-scoping and sender-relevance filtering** - `67ee680` (test)
**Plan metadata:** committed separately by the orchestrator after this summary.
## Files Created/Modified
- `lib/services/mimecast-client.ts` - `getHeldMessages()` gains optional `start`/`end` params, threaded into the request body's `data[0]` alongside `admin`/`searchBy`.
- `lib/services/mimecast-blast-radius.ts` - `getBlastRadius()` passes the shared date window into `getHeldMessages()`; new `domainsMatch()` helper filters held rows by sender-domain relevance before they feed `held`/`matched`/`perRecipient`.
- `lib/services/mimecast-blast-radius.test.ts` - new tests: shared-window assertion, unrelated-sender exclusion, matching-sender override still works.
- `lib/services/mimecast-client.test.ts` - new `describe('getHeldMessages date-scoping')` block: asserts `start`/`end` presence/absence in the POST body via a `request` spy.
## Decisions Made
- Followed the plan's interface precedent exactly: caller pre-formats `start`/`end` strings (same pattern as `searchDeliveredMessages`); the client method does no date formatting itself.
- `domainsMatch()` intentionally duplicates (rather than imports) the exact-or-proper-subdomain comparison logic from `campaign-classifier.ts`'s `domainMatchesAllowlist`, per the plan's explicit instruction not to reuse that allowlist-specific function.
- Kept the existing "merges delivered/held/threat-event fixtures" test's held row using `from: BASE_INPUT.sender` (a domain match) so it continues to pass unchanged — confirmed via the test run (25/25 passing).
## Deviations from Plan
None — plan executed exactly as written. No architectural changes, no scope additions beyond the two locked fixes and their specified test coverage.
## Issues Encountered
Two TypeScript errors surfaced in the new client test (`'body' is of type 'unknown'` from destructuring `vi.spyOn(...).mock.calls[0]` directly) — fixed inline by typing the extracted body as `any` off the correct tuple index (`requestSpy.mock.calls[0][2]`) before asserting on `body.data[0]`. This is an in-scope Rule 1 fix (blocking type error in code written as part of this same task), not a deviation from the plan's intent.
## Known Stubs
None.
## Threat Flags
None — no new network endpoints, auth paths, or trust-boundary changes. This plan only tightens an existing data-filtering boundary (held-message relevance) inside an already-reviewed lookup module.
## User Setup Required
None - no external service configuration required.
## Next Phase Readiness
The Blast Radius panel (Phase 17/22 consumers) now returns a correctly date-scoped and sender-relevance-filtered held count for any campaign lookup. No follow-up work is required; this was a standalone bug fix confirmed against two live verification scripts referenced in the plan (date-scoping alone: 15→1 false held rows; sender-relevance guard: 1→0).
---
*Quick task: 260718-7v8*
*Completed: 2026-07-18*
## Self-Check: PASSED
All modified/created files confirmed present on disk; both task commits (94f7dad, 67ee680) confirmed present in git log.