From 9311f1044bfdf91c0dfd04aa7ffd508bd6aa4772 Mon Sep 17 00:00:00 2001 From: lorentz Date: Tue, 21 Jul 2026 11:36:16 -0400 Subject: [PATCH 1/2] fix(quick-260721-fy8): add mimecast-sync and qbo dispatch branches to scheduler - Extend ScheduleConfig.sync_type union with 'mimecast-sync' - Add mimecast-sync branch calling runMimecastIncrementalSync() behind isMimecastConfigured(), mirroring the engagement/zoom configured-gate pattern - Add qbo branch calling getQboSyncService().incrementalSync('scheduled') behind an integration_settings disabled check, mirroring the pax8-daily disable-check pattern - Both branches previously fell through to the generic Autotask fullSync() catch-all, which also contended for the SyncService singleton mutex --- lib/services/sync-scheduler.ts | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/lib/services/sync-scheduler.ts b/lib/services/sync-scheduler.ts index 48a0048..b907094 100644 --- a/lib/services/sync-scheduler.ts +++ b/lib/services/sync-scheduler.ts @@ -22,7 +22,7 @@ export interface ScheduleConfig { name: string; description: string; cron_expression: string; - sync_type: 'incremental' | 'full' | 'veeam-incremental' | 'veeam-full' | 'veeam-rpo-check' | 'contract-services' | 'engagement-daily' | 'zoom-daily' | 'morning-summary' | 'ticket-digest-daily' | 'ticket-digest-weekly' | 'ticket-digest-monthly' | 'device-link-reconcile' | 'integration-health' | 'qbo' | 'appgate-sessions' | 'appgate-daily' | 'tickets-reconcile' | 'pax8-daily' | 'phishing-sweep'; + sync_type: 'incremental' | 'full' | 'veeam-incremental' | 'veeam-full' | 'veeam-rpo-check' | 'contract-services' | 'engagement-daily' | 'zoom-daily' | 'morning-summary' | 'ticket-digest-daily' | 'ticket-digest-weekly' | 'ticket-digest-monthly' | 'device-link-reconcile' | 'integration-health' | 'qbo' | 'mimecast-sync' | 'appgate-sessions' | 'appgate-daily' | 'tickets-reconcile' | 'pax8-daily' | 'phishing-sweep'; years_back?: number; is_enabled: boolean; last_run?: Date; @@ -491,6 +491,28 @@ class SyncScheduler { await getPax8SyncService().fullSync('scheduled'); } } + } else if (config.sync_type === 'mimecast-sync') { + const { isMimecastConfigured } = await import('@/lib/services/mimecast-client'); + if (!isMimecastConfigured()) { + console.log('[SCHEDULER] Skipping mimecast-sync — Mimecast not configured'); + } else { + const { runMimecastIncrementalSync } = await import('@/lib/services/mimecast-sync-service'); + const result = await runMimecastIncrementalSync(); + console.log( + `[SCHEDULER] mimecast-sync: messagesUpserted=${result.messagesUpserted} threatsUpserted=${result.threatsUpserted} bodiesFetched=${result.bodiesFetched} purgedMessages=${result.purgedMessages} errors=${result.errors.length} durationMs=${result.durationMs}` + ); + } + } else if (config.sync_type === 'qbo') { + const disabledRes = await postgresClient.query<{ disabled: boolean }>( + `SELECT disabled FROM integration_settings WHERE key = 'qbo'` + ); + const isDisabled = disabledRes.rows[0]?.disabled === true; + if (isDisabled) { + console.log('[SCHEDULER] Skipping qbo sync — QBO disabled via /admin/integrations'); + } else { + const { getQboSyncService } = await import('@/lib/services/qbo-sync-service'); + await getQboSyncService().incrementalSync('scheduled'); + } } else if (config.sync_type === 'incremental') { await this.syncService.incrementalSync('scheduled'); } else { From f447ac33c05dabf4b291340ebeedd0c77a9b657e Mon Sep 17 00:00:00 2001 From: lorentz Date: Tue, 21 Jul 2026 11:36:56 -0400 Subject: [PATCH 2/2] fix(quick-260721-fy8): reschedule mimecast-sync off the 2am cron collision - Add migration 101: guarded UPDATE moving mimecast-sync from 0 2 * * * to 45 4 * * * (minute 45 is unused by any other schedule row) - Guarded on the stale cron value so it's a no-op if already moved and won't clobber an admin's manual schedule change - Applied the same UPDATE directly to the live pulse-postgres container (migrations only auto-apply on first volume boot, per CLAUDE.md) - Eliminates the 3-way 2 AM collision with qbo-sync-2am and veeam-full --- migrations/101_reschedule_mimecast_sync.sql | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 migrations/101_reschedule_mimecast_sync.sql diff --git a/migrations/101_reschedule_mimecast_sync.sql b/migrations/101_reschedule_mimecast_sync.sql new file mode 100644 index 0000000..01786e0 --- /dev/null +++ b/migrations/101_reschedule_mimecast_sync.sql @@ -0,0 +1,22 @@ +-- Migration 101: Reschedule mimecast-sync off the 0 2 * * * cron collision. +-- +-- mimecast-sync was seeded at '0 2 * * *', the same slot as qbo-sync-2am and +-- veeam-full. Because the sync-scheduler dispatch table had no branches for +-- 'mimecast-sync' or 'qbo' (fixed alongside this migration), both jobs were +-- silently falling through to the generic Autotask fullSync() catch-all, +-- which meant they never ran their real sync logic AND contended with +-- veeam-full for the SyncService singleton mutex — producing +-- "A sync operation is already in progress" lock errors at 2 AM. +-- +-- Moves mimecast-sync to 4:45 AM (minute 45 is unused anywhere in the live +-- sync_schedules table — avoids 0 4 (contract-services, pax8-daily), 30 4 +-- (tickets-reconcile), the 15 * * * * hourly device-link job, and the +-- */30 :00/:30 veeam jobs). +-- +-- Guarded on the stale cron value so this is a no-op if already moved (safe +-- to re-run) and won't clobber an admin's manual schedule change made via +-- /admin after this ships. + +UPDATE sync_schedules +SET cron_expression = '45 4 * * *', updated_at = NOW() +WHERE id = 'mimecast-sync' AND cron_expression = '0 2 * * *';