Bundles several in-progress efforts that were sitting uncommitted: - User queue-preferences (migration 087, API route, popover component) - QBO invoice soft-delete (migration 088) and AR diagnostics route - Dashboard/mobile engagement route and page adjustments - Docker Compose log-rotation config - One-off ticket/RMM investigation scripts (scripts/) - Planning docs: phase verification/pattern notes, mobile shell design spec - .gitignore: exclude local scratch financial/inventory data and Claude Code worktree/local-settings runtime state (never meant for version control) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6RuWdiUiXrPK6FLBHjtpY
9.9 KiB
| phase | verified | status | score | overrides_applied |
|---|---|---|---|---|
| 17-mimecast-blast-radius-lookup | 2026-07-15T14:35:00Z | passed | 5/5 must-haves verified | 0 |
Phase 17: Mimecast Blast Radius Lookup Verification Report
Phase Goal: Pulse can ask "how far did this message spread" via a Mimecast blast-radius abstraction when Mimecast is configured, and gets a clean unavailable signal — never a crash or a block — when it isn't.
Verified: 2026-07-15T14:35:00Z
Status: passed
Re-verification: No — initial verification
Goal Achievement
Observable Truths
| # | Truth | Status | Evidence |
|---|---|---|---|
| 1 | isMimecastConfigured() returns true only when both MIMECAST_CLIENT_ID and MIMECAST_CLIENT_SECRET are set, false otherwise |
VERIFIED | lib/services/mimecast-client.ts:647-649 — !!(process.env.MIMECAST_CLIENT_ID && process.env.MIMECAST_CLIENT_SECRET). Tested in mimecast-client.test.ts (4 cases: neither/only-ID/only-secret/both), all pass. |
| 2 | getBlastRadius() returns status:'unavailable' reason:'not_configured' synchronously (no Mimecast call) when unconfigured |
VERIFIED | mimecast-blast-radius.ts:92-95 returns before any client construction. Test asserts getMimecastClientMock/all 3 fan-out mocks .not.toHaveBeenCalled(). |
| 3 | getBlastRadius() when configured returns normalized matched/delivered/held/rejected/clicked counts + per-recipient status array, built by fanning out to searchDeliveredMessages + getHeldMessages + getThreatEvents UNCONDITIONALLY (not gated behind a getMessageInfo miss — corrected D-01 / Pitfall 1) |
VERIFIED | mimecast-blast-radius.ts:104-128 — getMessageInfo (line 109-111) is called only for supplementary body/header evidence when messageId present, its result is discarded (not awaited into a variable used downstream), and is NOT inside any conditional that gates the Promise.all([...]) fan-out at line 118, which always runs. Merge logic at 130-184 builds real counts, not fallback zeros. Test "merges delivered/held/threat-event fixtures..." confirms counts (matched:2, delivered:1, held:1, rejected:1, clicked:1) and perRecipient array. |
| 4 | An unexpected error thrown during the fan-out degrades to status:'unavailable' reason:'lookup_failed' rather than propagating |
VERIFIED | mimecast-blast-radius.ts:188-194 catch block. Test "degrades to unavailable/lookup_failed (never throws) when a fan-out call rejects" — getHeldMessagesMock.mockRejectedValue(...), asserts resolved (not rejected) result equals {status:'unavailable', reason:'lookup_failed', error:'Mimecast API timeout'}. |
| 5 | A repeated lookup for the same message identity within the cache TTL returns the cached result without re-calling any MimecastClient method | VERIFIED | mimecast-blast-radius.ts:101-102 — getCachedData checked and returned BEFORE getMimecastClient() is called at line 105. Test "returns the cached result on a cache hit..." asserts all 3 fan-out mocks and setCachedDataMock not called. |
Score: 5/5 truths verified
Required Artifacts
| Artifact | Expected | Status | Details |
|---|---|---|---|
lib/services/mimecast-client.ts |
isMimecastConfigured() + _resetMimecastClient() config gate / test seam, existing exports untouched |
VERIFIED | Lines 647-649 (isMimecastConfigured), 651-655 (_resetMimecastClient) added directly above unmodified getMimecastClient() (657-672) and getMimecastClientForTenant() (674+). |
lib/services/mimecast-blast-radius.ts |
getBlastRadius() orchestration + BlastRadiusInput/BlastRadiusResult types |
VERIFIED | 195 lines; exports getBlastRadius, BlastRadiusInput, BlastRadiusResult (discriminated union) exactly as specced. |
lib/services/mimecast-client.test.ts |
Unit tests for isMimecastConfigured() + getMimecastClient() throw/cache behavior |
VERIFIED | 63 lines, 7 tests, all pass (npx vitest run confirmed). |
lib/services/mimecast-blast-radius.test.ts |
Unit tests for config gate, fan-out merge, never-throw, cache-hit | VERIFIED | 227 lines, 6 tests, all pass. |
Key Link Verification
| From | To | Via | Status | Details |
|---|---|---|---|---|
mimecast-blast-radius.ts |
mimecast-client.ts |
import { isMimecastConfigured, getMimecastClient, type MimecastDeliveredMessage, type MimecastHeldMessage } from './mimecast-client' |
WIRED | Import present (line 37-42); both interface types confirmed exported from mimecast-client.ts (lines 52, 76, 92 — MimecastThreatEvent, MimecastHeldMessage, MimecastDeliveredMessage). |
mimecast-blast-radius.ts |
redis-client.ts |
import { getCachedData, setCachedData } from './redis-client' |
WIRED | Import present (line 43); both functions called and awaited correctly (cache-check before client construction, cache-write after successful merge only). |
D-05 Multi-Tenant Comment Verification
grep -qi "D-05" lib/services/mimecast-blast-radius.ts → present. The module doc-comment (lines 25-34) explicitly states: "KNOWN LIMITATION — MULTI-TENANT GAP (D-05): this module uses only the single global env-var-configured getMimecastClient(), NOT the per-company mimecast_tenants table / getMimecastClientForTenant(). Reports belonging to companies with their own registered Mimecast tenant ... will return status: 'unavailable'..." This is a genuine code comment in the shipped file, not just a plan/summary claim.
Pitfall-Avoidance Verification (17-RESEARCH.md's 3 documented pitfalls)
| Pitfall | Research Concern | Shipped-Code Verification |
|---|---|---|
#1 — getMessageInfo() has no status/counts; fan-out must be unconditional, not gated on a miss |
Implementer might skip fan-out when getMessageInfo hits |
Confirmed avoided: getMessageInfo call (line 110) is a bare await whose return value is discarded; the Promise.all fan-out (lines 118-128) is unconditional — no if branch separates "exact match" from "fallback." Code comment at line 107-108 explicitly documents why. |
#2 — getThreatEvents() click derivation is best-effort, not confirmed-zero |
Risk of overstating confidence in clicked: 0 |
Confirmed avoided: doc-comment (b) at lines 17-23 states clicked: 0 means "no click-type threat event found... NOT confirmed zero clicks." isClickEvent() helper (line 88-90) comment references D-02 explicitly. |
| #3 — Multi-tenant gap silently missed | Risk of a silent single-tenant assumption | Confirmed avoided: D-05 comment present and specific (see above); T-17-03 in the plan's threat model documents the accepted risk; not touched by this phase per explicit scope. |
Requirements Coverage
| Requirement | Source Plan | Description | Status | Evidence |
|---|---|---|---|---|
| BLAST-01 | 17-01-PLAN.md | Query blast-radius abstraction for delivery data when configured, keyed on message ID/sender/recipient/subject/date-window | SATISFIED | getBlastRadius() implemented and tested per Truth #3 above; ROADMAP.md and REQUIREMENTS.md both mark BLAST-01 [x]/Complete. |
| BLAST-02 | 17-01-PLAN.md | Not configured → status: unavailable, never blocks; unexpected error also degrades |
SATISFIED | Truths #2 and #4 above; both paths tested with explicit call-count/rejection assertions. |
No orphaned requirements found for Phase 17 in REQUIREMENTS.md (only BLAST-01/BLAST-02 map to this phase).
Anti-Patterns Found
None. Scanned all 4 phase-modified/created files (mimecast-client.ts diff region, mimecast-blast-radius.ts, mimecast-blast-radius.test.ts, mimecast-client.test.ts) for TBD|FIXME|XXX|TODO|HACK|PLACEHOLDER|placeholder|not yet implemented — zero matches.
Behavioral Spot-Checks
| Behavior | Command | Result | Status |
|---|---|---|---|
| New unit tests pass in isolation | npx vitest run lib/services/mimecast-blast-radius.test.ts lib/services/mimecast-client.test.ts |
2 files, 13 tests, all passed | PASS |
| Type-check clean | npx tsc --noEmit --pretty |
No output / exit 0 | PASS |
| Full suite has no new regressions | npm test |
27 passed / 1 failed file (itglue-search.test.ts, 2 tests) — confirmed pre-existing via git log (a0a6e7f, predates this phase's commits 8b032c3/efbc437) and unrelated to any file this phase touched |
PASS (pre-existing failure correctly excluded per task instructions) |
Test Isolation / Mocking Verification
Both test files declare vi.mock('./mimecast-client', ...) / vi.mock('./redis-client', ...) (blast-radius test) and import the real mimecast-client.ts module only for the config-gate/factory tests (which intentionally exercise the real singleton with env-var manipulation and _resetMimecastClient() — no network egress since getMimecastClient() only constructs the object, doesn't call out). No MIMECAST_CLIENT_ID/MIMECAST_CLIENT_SECRET real credentials are referenced; no live HTTP calls are made in either test file (confirmed by reading both files in full — no fetch/request/network imports present). Redis is fully mocked (getCachedDataMock/setCachedDataMock).
Human Verification Required
None. This phase produces no UI, no HTTP route, and no externally-observable runtime behavior beyond the unit-testable function contract — all Success Criteria are objectively verifiable via code + tests.
Gaps Summary
No gaps found. All 5 derived truths verified, both roadmap Success Criteria requirements (BLAST-01, BLAST-02) satisfied, all 3 documented research pitfalls confirmed avoided in the shipped code (not just claimed in SUMMARY.md), D-05 comment confirmed present in the actual file, cache-short-circuit-before-client-call confirmed via call-count assertions, and the full test suite has zero new regressions (the 2 failing itglue-search.test.ts tests are confirmed pre-existing and unrelated).
Verified: 2026-07-15T14:35:00Z Verifier: Claude (gsd-verifier)