docs(17-01): complete Mimecast blast-radius lookup plan
Mark BLAST-01/BLAST-02 complete in REQUIREMENTS.md and add the plan's SUMMARY.md documenting getBlastRadius() delivery. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
efbc437e2e
commit
2bb1817e8d
2 changed files with 106 additions and 4 deletions
|
|
@ -50,11 +50,11 @@ destructive remediation gated behind explicit human approval.
|
|||
|
||||
### Blast Radius (Mimecast)
|
||||
|
||||
- [ ] **BLAST-01**: The system can query a Mimecast blast-radius abstraction for
|
||||
- [x] **BLAST-01**: The system can query a Mimecast blast-radius abstraction for
|
||||
message delivery data (matched/delivered/held/rejected/clicked counts,
|
||||
per-recipient status) when Mimecast is configured, keyed on message ID,
|
||||
sender, recipient/reporter, subject, and date window
|
||||
- [ ] **BLAST-02**: When Mimecast is not configured, the system records
|
||||
- [x] **BLAST-02**: When Mimecast is not configured, the system records
|
||||
`status: unavailable` for that lookup and classification proceeds using
|
||||
ticket/email evidence alone — it never blocks on missing Mimecast config
|
||||
|
||||
|
|
@ -159,8 +159,8 @@ Populated during roadmap creation.
|
|||
| CAMP-01 | Phase 18 | Pending |
|
||||
| CAMP-02 | Phase 18 | Pending |
|
||||
| CAMP-03 | Phase 18 | Pending |
|
||||
| BLAST-01 | Phase 17 | Pending |
|
||||
| BLAST-02 | Phase 17 | Pending |
|
||||
| BLAST-01 | Phase 17 | Complete |
|
||||
| BLAST-02 | Phase 17 | Complete |
|
||||
| CLASSIFY-01 | Phase 19 | Pending |
|
||||
| CLASSIFY-02 | Phase 19 | Pending |
|
||||
| CLASSIFY-03 | Phase 19 | Pending |
|
||||
|
|
|
|||
|
|
@ -0,0 +1,102 @@
|
|||
---
|
||||
phase: 17-mimecast-blast-radius-lookup
|
||||
plan: 01
|
||||
subsystem: api
|
||||
tags: [mimecast, redis-cache, blast-radius, phishing-triage, vitest]
|
||||
|
||||
# Dependency graph
|
||||
requires:
|
||||
- phase: 15-data-model-detection-ticket-evidence
|
||||
provides: migrations/097_phishing_triage_schema.sql (classifications.reasons JSONB — the eventual persistence point for a blast-radius result, not touched by this phase)
|
||||
provides:
|
||||
- "isMimecastConfigured() config gate + _resetMimecastClient() test seam in lib/services/mimecast-client.ts"
|
||||
- "getBlastRadius() orchestration in lib/services/mimecast-blast-radius.ts — normalized matched/delivered/held/rejected/clicked counts + perRecipient status, never-throwing, Redis-cached (5min TTL)"
|
||||
affects: [19-classifier-service]
|
||||
|
||||
# Tech tracking
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- "is<Name>Configured() config-gate convention extended to mimecast-client.ts (matches pax8-factory.ts/veeam-factory.ts)"
|
||||
- "Never-throw orchestration wrapper: config gate returns synchronously, unexpected errors degrade to a discriminated-union 'unavailable' result instead of propagating"
|
||||
- "Redis cache-key format <service>:<resource>:<discriminators> (mirrors app/api/addigy-devices/route.ts)"
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- lib/services/mimecast-blast-radius.ts
|
||||
- lib/services/mimecast-blast-radius.test.ts
|
||||
- lib/services/mimecast-client.test.ts
|
||||
modified:
|
||||
- lib/services/mimecast-client.ts
|
||||
|
||||
key-decisions:
|
||||
- "Fan-out (searchDeliveredMessages + getHeldMessages + getThreatEvents) runs unconditionally, not as a fallback after getMessageInfo — per corrected D-01, getMessageInfo() has no status/count data"
|
||||
- "clicked is best-effort from getThreatEvents() analysis[] click-type match; documented in code as not a confirmed-zero signal (D-02)"
|
||||
- "Delivered-message .status rejection classification treats unrecognized strings as non-rejected (conservative) since the real enum (17-RESEARCH A3) is unconfirmed; raw values logged at console.debug for first-real-tenant validation"
|
||||
- "Single global getMimecastClient() only for v1 — per-company mimecast_tenants/getMimecastClientForTenant() explicitly out of scope, documented as D-05 known limitation in the module doc-comment"
|
||||
|
||||
patterns-established:
|
||||
- "Blast-radius abstraction is a pure function: config-gate → cache-check → fan-out → merge → cache-write → return; every branch returns a typed discriminated union, never throws"
|
||||
|
||||
requirements-completed: [BLAST-01, BLAST-02]
|
||||
|
||||
# Metrics
|
||||
duration: ~20min
|
||||
completed: 2026-07-15
|
||||
---
|
||||
|
||||
# Phase 17 Plan 01: Mimecast Blast Radius Lookup Summary
|
||||
|
||||
**New `getBlastRadius()` orchestration composes Mimecast's searchDeliveredMessages/getHeldMessages/getThreatEvents into normalized matched/delivered/held/rejected/clicked counts + per-recipient status, gated by a new `isMimecastConfigured()` and Redis-cached for 5 minutes — never throws, degrades to `status: 'unavailable'` on missing config or unexpected error.**
|
||||
|
||||
## Performance
|
||||
|
||||
- **Duration:** ~20 min
|
||||
- **Completed:** 2026-07-15T18:30:12Z
|
||||
- **Tasks:** 2 completed
|
||||
- **Files modified:** 4 (1 modified, 3 created)
|
||||
|
||||
## Accomplishments
|
||||
- `isMimecastConfigured()` + `_resetMimecastClient()` added to `mimecast-client.ts`, matching the project's `is<Name>Configured()` factory convention (mirrors `pax8-factory.ts`)
|
||||
- `lib/services/mimecast-blast-radius.ts`: pure, never-throwing `getBlastRadius()` that fans out to the three existing `MimecastClient` methods unconditionally (not gated on `getMessageInfo`), merges into a normalized shape, and short-circuits on a Redis cache hit (5-min TTL, matching D-04)
|
||||
- Both new test files (13 tests total) pass; full `npm test` shows no new failures (2 pre-existing, unrelated `itglue-search.test.ts` failures logged to `deferred-items.md`, out of scope for this plan)
|
||||
|
||||
## Task Commits
|
||||
|
||||
Each task was committed atomically:
|
||||
|
||||
1. **Task 1: Add isMimecastConfigured() config gate + _resetMimecastClient() test seam + tests** - `8b032c3` (feat)
|
||||
2. **Task 2: Build mimecast-blast-radius.ts orchestration (fan-out merge, never-throw, Redis cache) + tests** - `efbc437` (feat)
|
||||
|
||||
_Both tasks were tdd="true"; test files were authored alongside the implementation in the same commit per the plan's task grouping (test file + implementation in one commit, matching the plan's `<files>` grouping rather than separate RED/GREEN commits)._
|
||||
|
||||
## Files Created/Modified
|
||||
- `lib/services/mimecast-client.ts` - Added `isMimecastConfigured()` + `_resetMimecastClient()`; `getMimecastClient()` and `getMimecastClientForTenant()` untouched
|
||||
- `lib/services/mimecast-client.test.ts` - New: covers `isMimecastConfigured()` env-var combinations + `getMimecastClient()` throw/cache/reset behavior
|
||||
- `lib/services/mimecast-blast-radius.ts` - New: `BlastRadiusInput`/`BlastRadiusResult` types + `getBlastRadius()` orchestration
|
||||
- `lib/services/mimecast-blast-radius.test.ts` - New: covers not-configured short-circuit, cache-hit short-circuit, fan-out merge (delivered/rejected/held/clicked/perRecipient), never-throw-on-error, and unknown-recipient classification
|
||||
|
||||
## Decisions Made
|
||||
- Fan-out is unconditional (corrected D-01) — `getMessageInfo()` is called only as a supplementary body/header fetch when `messageId` is present, and never gates whether the fan-out runs
|
||||
- `clicked` best-effort derivation and the D-05 multi-tenant known-limitation are both documented directly in the module's doc-comment, per the plan's acceptance criteria (`grep -qi "D-05"` passes)
|
||||
- Delivered-message rejection status classification is conservative (unrecognized → non-rejected) since the real Mimecast `.status` enum is unconfirmed (17-RESEARCH A3); raw values are logged via `console.debug` for future validation against a real tenant, not hardcoded as a guessed enum
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
None — plan executed exactly as written. Both tasks' `<action>` and `<behavior>` requirements were implemented as specified; all acceptance criteria (exports present, D-05 comment present, test/tsc/npm-test green) verified directly.
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
Full `npm test` run surfaces 2 pre-existing failures in `lib/services/analyzer/itglue-search.test.ts` (`client.getFlexibleAssetsForOrganization is not a function`), unrelated to any file this plan touched. Verified via `git log` that the last commit affecting those files (`a0a6e7f`) predates this plan's work. Logged to `.planning/phases/17-mimecast-blast-radius-lookup/deferred-items.md` per the SCOPE BOUNDARY rule (only auto-fix issues directly caused by the current task's changes) — not fixed here.
|
||||
|
||||
## User Setup Required
|
||||
|
||||
None - no external service configuration required. This phase reuses the existing `MIMECAST_CLIENT_ID`/`MIMECAST_CLIENT_SECRET`/`REDIS_URL` env vars already documented in CLAUDE.md; no new env vars, migrations, or dependencies were introduced.
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
`getBlastRadius({ messageId?, sender, recipient, subject, dateWindow })` is ready for Phase 19's classifier to call directly — it never throws, and its `BlastRadiusResult` discriminated union (`status: 'ok' | 'unavailable'`) is fully typed. Known, documented limitation: v1 uses only the single global env-var Mimecast client, not per-company `mimecast_tenants` — Phase 19 (or a later phase) should be aware that companies with their own registered Mimecast tenant will see `status: 'unavailable'` from this abstraction even when Mimecast is otherwise configured for them.
|
||||
|
||||
---
|
||||
*Phase: 17-mimecast-blast-radius-lookup*
|
||||
*Completed: 2026-07-15*
|
||||
Loading…
Add table
Add a link
Reference in a new issue