wulf-pulse/scripts/verify-blast-radius-held-bug.ts
lorentz 672f17b7f9 chore: check in pending work — queue preferences, QBO AR diagnostics, mobile engagement fixes, ops scripts
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
2026-07-18 06:34:57 -04:00

95 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* verify-blast-radius-held-bug.ts
*
* Ticket 699687 (Tomlinson, Seubert) shows Blast Radius: Matched 16,
* Delivered 1, Held 15, with btomlinson@seubert.com's per-recipient status
* shown as "held" — despite this being the accidental Guardian Protection
* report we already confirmed was cleanly delivered (spamScore 0, no
* detection) via Mimecast's own trace. Hypothesis: getHeldMessages() in
* lib/services/mimecast-blast-radius.ts is scoped ONLY by recipient (Mimecast's
* get-hold-message-list API has no sender/subject/date filter), so it pulls
* in EVERY message currently sitting in btomlinson's hold queue — unrelated
* to Guardian Protection — and the per-recipient merge logic lets any held
* row silently overwrite a same-recipient delivered row. This script
* independently verifies by calling the same two Mimecast calls the blast
* radius module makes and inspecting what's actually in the held queue.
*
* Usage:
* POSTGRES_HOST=localhost npx tsx scripts/verify-blast-radius-held-bug.ts
*/
import { config } from 'dotenv';
import { resolve } from 'path';
config({ path: resolve('/opt/stacks/pulse/.env.local') });
import postgresClient from '../lib/services/postgres-client';
import { getMimecastClientForTenant } from '../lib/services/mimecast-client';
const SEUBERT_COMPANY_ID = 29683407;
const SENDER = 'guardian_protection_do_not_reply@guardianprotection.com';
const RECIPIENT = 'btomlinson@seubert.com';
const SUBJECT = "Unexpected Activity for Tomlinsons Home: The Front Door was left unlocked at 1:39 pm";
async function main() {
const tenantRow = await postgresClient.query<{
client_id: string;
client_secret: string;
base_url: string | null;
account_name: string | null;
}>(
`SELECT client_id, client_secret, base_url, account_name FROM mimecast_tenants WHERE company_id = $1 AND enabled = true`,
[SEUBERT_COMPANY_ID]
);
const tenant = tenantRow.rows[0];
if (!tenant) {
console.log('No enabled Mimecast tenant for Seubert — cannot verify.');
process.exit(1);
}
console.log(`Using Mimecast tenant: ${tenant.account_name}`);
const client = getMimecastClientForTenant({
client_id: tenant.client_id,
client_secret: tenant.client_secret,
base_url: tenant.base_url ?? undefined,
});
const now = new Date();
const start = new Date(now.getTime() - 24 * 60 * 60 * 1000);
const startStr = start.toISOString().replace(/\.\d{3}Z$/, '+0000');
const endStr = now.toISOString().replace(/\.\d{3}Z$/, '+0000');
console.log(`\n=== 1. searchDeliveredMessages (scoped: sender+recipient+subject+24h window) ===`);
const delivered = await client.searchDeliveredMessages({
from: SENDER,
to: RECIPIENT,
subject: SUBJECT,
start: startStr,
end: endStr,
});
console.log(`Delivered rows found: ${delivered.messages?.length ?? 0}`);
console.log(JSON.stringify(delivered.messages, null, 2));
console.log(`\n=== 2. getHeldMessages (scoped ONLY by recipient — no sender/subject/date filter possible) ===`);
const held = await client.getHeldMessages({ recipient: RECIPIENT });
console.log(`Held rows found: ${held.messages.length} (totalCount reported: ${held.totalCount})`);
console.log(
JSON.stringify(
held.messages.map((m) => ({ from: m.from, subject: m.subject, dateReceived: m.dateReceived, reason: m.reason })),
null,
2
)
);
const heldFromGuardianProtection = held.messages.filter((m) =>
(m.from ?? '').toLowerCase().includes('guardianprotection.com')
);
console.log(`\n=== 3. Of those held messages, how many are actually from Guardian Protection? ===`);
console.log(`${heldFromGuardianProtection.length} of ${held.messages.length}`);
process.exit(0);
}
main().catch((err) => {
console.error(err);
process.exit(1);
});