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
This commit is contained in:
parent
b638189cb0
commit
672f17b7f9
35 changed files with 2801 additions and 92 deletions
95
scripts/verify-blast-radius-held-bug.ts
Normal file
95
scripts/verify-blast-radius-held-bug.ts
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
/**
|
||||
* 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 Tomlinson’s 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);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue