# Plan: Backup Scheduling MVP (032) **Date**: 2026-01-05 **Input**: spec.md ## Architecture / Reuse - Reuse existing services: - `PolicySyncService::syncPoliciesWithReport()` for selected policy types - `BackupService::createBackupSet()` to create immutable snapshots + items (include_foundations supported) - Store selection as `policy_types` (config keys), not free-form categories. - Use tenant scoping (`tenant_id`) consistent with existing tables (`backup_sets`, `backup_items`). ## Scheduling Mechanism - Add Artisan command: `tenantpilot:schedules:dispatch`. - Scheduler integration (Laravel 12): schedule the command every minute via `routes/console.php` + ops configuration (Dokploy cron `schedule:run` or long-running `schedule:work`). - Dispatcher algorithm: 1) load enabled schedules 2) compute whether due for the current minute in schedule timezone 3) create run with `scheduled_for` slot (minute precision) using DB unique constraint 4) dispatch `RunBackupScheduleJob(schedule_id, run_id)` - Concurrency: - Cache lock per schedule (`lock:backup_schedule:{id}`) plus DB unique slot constraint for idempotency. ## Run Execution - `RunBackupScheduleJob`: 1) load schedule + tenant 2) preflight: tenant active; Graph/auth errors mapped to error_code 3) sync policies for selected types (collect report) 4) select policy IDs from local DB for those types (exclude ignored) 5) create backup set: - name: `{schedule_name} - {Y-m-d H:i}` - includeFoundations: schedule flag 6) set run status: - success if backup_set.status == completed - partial if backup_set.status == partial OR sync had failures but backup succeeded - failed if nothing backed up / hard error 7) update schedule last_run_* and compute/persist next_run_at 8) dispatch retention job ## Retention - `ApplyBackupScheduleRetentionJob(schedule_id)`: - identify runs ordered newest→oldest - keep last N runs that created a backup_set_id - for older ones: soft-delete referenced BackupSets (and cascade soft-delete items) ## Filament UX - Tenant-scoped resources: - `BackupScheduleResource` - Runs UI via RelationManager under schedule (or a dedicated resource if needed) - Actions: enable/disable, run now, retry - Notifications: persist via `->sendToDatabase($user)` for the DB info panel. ## Ops / Deployment Notes - Requires queue worker. - Requires scheduler running. - Missed runs policy (MVP): no catch-up.