---
phase: quick-260721-fy8
plan: 01
subsystem: sync-scheduler
tags: [scheduler, mimecast, qbo, migration, bugfix]
key-decisions:
  - mimecast-sync and qbo dispatch branches use incrementalSync (not fullSync) — matches the manual /api/qbo/sync non-full path and is correct for a twice-daily recurring job
  - Chose 45 4 * * * for mimecast-sync's new cron slot — minute 45 is unused anywhere in the live sync_schedules table, avoiding 0 4 (contract-services, pax8-daily), 30 4 (tickets-reconcile), the hourly :15 device-link job, and the veeam */30 :00/:30 jobs
  - Migration 101 guards the UPDATE on the stale '0 2 * * *' value so it's a no-op if already moved and won't clobber an admin's manual schedule change made via /admin after this ships
status: complete
---

# Quick Task 260721-fy8: Fix mimecast and qbo sync scheduler dispatch and reschedule mimecast cron

**One-liner:** Added missing `mimecast-sync` and `qbo` dispatch branches to `sync-scheduler.ts` (both previously fell through to a generic Autotask full sync, silently never running their real sync logic) and rescheduled `mimecast-sync` off the 2am three-way cron collision with `qbo-sync-2am` and `veeam-full`.

## What was done

### Task 1 — Dispatch branches (`lib/services/sync-scheduler.ts`, commit `9311f10`)
- Extended the `ScheduleConfig.sync_type` union to add `'mimecast-sync'` (`'qbo'` was already present).
- Added an `else if (config.sync_type === 'mimecast-sync')` branch: dynamically imports `isMimecastConfigured` from `mimecast-client.ts`; if not configured, logs a skip line matching the engagement/zoom wording. Otherwise dynamically imports and calls `runMimecastIncrementalSync()` from `mimecast-sync-service.ts`, logging a one-line summary using the real `MimecastSyncResult` fields (`messagesUpserted`, `threatsUpserted`, `bodiesFetched`, `purgedMessages`, `errors.length`, `durationMs`).
- Added an `else if (config.sync_type === 'qbo')` branch: queries `integration_settings` for `key = 'qbo'` (mirroring the `pax8-daily` disable-check pattern exactly); if disabled, logs a skip line; otherwise dynamically imports `getQboSyncService` and calls `.incrementalSync('scheduled')`.
- Both new branches sit before the final `'incremental'` branch and catch-all `else`, which are unchanged — the catch-all remains reachable only for `'full'`/legacy full-sync types.
- `npx tsc --noEmit --pretty` passes clean.

### Task 2 — Reschedule migration (`migrations/101_reschedule_mimecast_sync.sql`, commit `f447ac3`)
- New guarded migration: `UPDATE sync_schedules SET cron_expression = '45 4 * * *', updated_at = NOW() WHERE id = 'mimecast-sync' AND cron_expression = '0 2 * * *'` — a no-op if already moved, safe against clobbering a manual admin change.
- Applied the same UPDATE directly against the live `pulse-postgres` container (migrations only auto-apply on first volume boot per CLAUDE.md) — confirmed live: `mimecast-sync` now reads `45 4 * * *`; `qbo-sync-2am` and `veeam-full` unchanged at `0 2 * * *` (collision eliminated for mimecast-sync specifically, as intended — qbo-sync-2am and veeam-full no longer contend with mimecast-sync, though qbo-sync-2am and veeam-full still share 2am with each other, which was out of scope for this task).

## Verification

- `npx tsc --noEmit --pretty` — clean.
- Live DB: `SELECT cron_expression FROM sync_schedules WHERE id = 'mimecast-sync'` → `45 4 * * *`.
- No existing branch (`phishing-sweep`, `pax8-daily`, `veeam-*`, `incremental`, catch-all) modified.
- No test file exists for `sync-scheduler.ts` (only `mimecast-client.test.ts`), so no test changes were made, per plan constraint.

## Deviations from plan

None. Both tasks executed exactly as planned.

## Note on this SUMMARY.md

This file was originally written by the executor inside its isolated git worktree but was lost when the orchestrator removed the worktree (`git worktree remove --force`) without first running the standard rescue-before-remove step. Reconstructed immediately after from the executor's final report text and the actual `git show` output of both commits — content is accurate but not verbatim the original (same recovery situation documented previously in this repo for quick task 260717-a19).
