---
phase: 13-scheduler-admin-toggle
plan: 01
subsystem: infra
tags: [sync-scheduler, cron, postgres, pax8, integration-toggle]

# Dependency graph
requires:
  - phase: 11-company-catalog-subscription-sync
    provides: Pax8SyncService.fullSync() companies/subscriptions/products orchestration
  - phase: 12-orders-invoices-company-matching
    provides: Pax8SyncService.fullSync() extended with orders/invoices + company matching
provides:
  - Idempotent pax8-daily sync_schedules seed row (migration 096)
  - executeScheduledSync dual-guarded pax8-daily dispatch branch
  - CLAUDE.md precedent note for DB-toggle-gates-action behavior
affects: [13-02-admin-toggle-route, 13-03-live-verification]

# Tech tracking
tech-stack:
  added: []
  patterns: [dual-guard scheduler branch (config + DB toggle), idempotent sync_schedules seed migration]

key-files:
  created: [migrations/096_pax8_daily_schedule.sql]
  modified: [lib/services/sync-scheduler.ts, CLAUDE.md]

key-decisions:
  - "pax8-daily seeded only via migration 096, never added to the in-code defaultSchedules array, per existing precedent for existing installs"
  - "PAX8 disabled-check is inline in the scheduler branch only (D-01) — no shared helper extracted, no changes to other integrations' branches"
  - "cron 0 4 * * * groups pax8-daily with the backend-reconciliation cluster (contract-services, tickets-reconcile) per D-04"

patterns-established:
  - "Dual-guard scheduler branch: isXConfigured() env check first, then integration_settings.disabled DB check, each with a distinct skip log, before calling the sync service"

requirements-completed: [PAX8-07, PAX8-09]

# Metrics
duration: ~15min
completed: 2026-07-11
---

# Phase 13 Plan 01: Scheduler pax8-daily Wiring Summary

**Wired the existing `Pax8SyncService.fullSync()` into the daily cron scheduler via a new idempotent migration seed and a dual-guarded `pax8-daily` dispatch branch that respects both the env-config check and the `integration_settings` DB disable toggle.**

## Performance

- **Duration:** ~15 min
- **Tasks:** 3
- **Files modified:** 3 (1 created, 2 modified)

## Accomplishments
- `migrations/096_pax8_daily_schedule.sql` idempotently seeds a `pax8-daily` row (cron `0 4 * * *`, `is_enabled=false`) using the `WHERE NOT EXISTS` style from migration 089; applied to the running dev DB and verified idempotent on a second run.
- `executeScheduledSync` in `lib/services/sync-scheduler.ts` now dispatches `pax8-daily` to `getPax8SyncService().fullSync('scheduled')`, gated by two independent checks: `isPax8Configured()` (env vars) and `integration_settings.key='pax8'` disabled flag (DB toggle), each with a distinct skip log line.
- `CLAUDE.md`'s "Operator config" section now documents PAX8 as the first integration where the DB toggle gates an action (scheduler skip + 403 on the manual route), not just health-check display.

## Task Commits

Each task was committed atomically:

1. **Task 1: Seed the pax8-daily schedule via migration 096** - `230296c` (feat)
2. **Task 2: Add dual-guarded pax8-daily dispatch branch to executeScheduledSync** - `d07c0b7` (feat)
3. **Task 3: Record the DB-toggle-gates-action precedent in CLAUDE.md** - `d5456bb` (docs)

_No plan metadata commit yet — orchestrator handles that after wave completion (worktree mode)._

## Files Created/Modified
- `migrations/096_pax8_daily_schedule.sql` - Idempotent seed of the pax8-daily sync_schedules row
- `lib/services/sync-scheduler.ts` - Extended `sync_type` union with `'pax8-daily'`; added dual-guarded dispatch branch after the appgate branch in `executeScheduledSync`
- `CLAUDE.md` - Added a note in Operator config / Integration disable documenting PAX8 as the first DB-toggle-gates-action precedent

## Decisions Made
- Followed the exact 089-style `WHERE NOT EXISTS` seed pattern (not 090's `ON CONFLICT`), per explicit CONTEXT.md/PATTERNS.md direction.
- Kept the disabled-check PAX8-only and inline in the scheduler branch (D-01) rather than extracting a shared helper, since generalizing to all integrations was explicitly out of scope for this phase.
- Placed the new branch immediately after the appgate branch to keep integration-toggle blocks together in the switch, per PATTERNS.md guidance.

## Deviations from Plan

None - plan executed exactly as written.

## Issues Encountered

None. `npx tsc --noEmit --pretty` passed with no output after the sync-scheduler.ts edit. The migration was applied to the running `pulse-postgres` container using the default `pulse_user`/`pulse_autotask` credentials confirmed from `docker-compose.yml` (no `.env` overrides present).

## User Setup Required

None - no external service configuration required. The `pax8-daily` schedule ships `is_enabled=false`; an admin must opt in via the schedule editor separately, as documented in the plan.

## Next Phase Readiness

- The `pax8-daily` schedule row exists and the scheduler branch is wired and type-checked; ready for the manual-trigger route toggle (D-02, likely 13-02) and live cron/disable-skip verification (13-03).
- No changes were made to `app/api/pax8/sync/route.ts` or `lib/services/integration-health.ts` in this plan — those remain for subsequent plans in this phase per the plan's `files_modified` scope.

## Self-Check: PASSED

- FOUND: migrations/096_pax8_daily_schedule.sql
- FOUND: .planning/phases/13-scheduler-admin-toggle/13-01-SUMMARY.md
- FOUND commit: 230296c (Task 1)
- FOUND commit: d07c0b7 (Task 2)
- FOUND commit: d5456bb (Task 3)
- FOUND commit: 73b33f7 (SUMMARY.md)

---
*Phase: 13-scheduler-admin-toggle*
*Completed: 2026-07-11*
