diff --git a/.planning/phases/13-scheduler-admin-toggle/13-CONTEXT.md b/.planning/phases/13-scheduler-admin-toggle/13-CONTEXT.md
new file mode 100644
index 0000000..0a5e1c0
--- /dev/null
+++ b/.planning/phases/13-scheduler-admin-toggle/13-CONTEXT.md
@@ -0,0 +1,209 @@
+# Phase 13: Scheduler & Admin Toggle - Context
+
+**Gathered:** 2026-07-11
+**Status:** Ready for planning
+
+
+## Phase Boundary
+
+PAX8 sync runs automatically once a day like every other Pulse integration
+(`pax8-daily` cron entry in `sync-scheduler.ts`), and can be turned on/off from
+`/admin/integrations` without a container restart. Unlike every existing
+integration's toggle today, disabling PAX8 must actually stop future sync
+runs (not just suppress health-check display) — this phase introduces that
+enforcement, scoped to PAX8 only. No `/pax8` UI, no manual company-match
+resolution (Phase 14). `Pax8SyncService.fullSync()` already performs the
+complete companies + subscriptions + products + orders + company-matching
+sequence (built in Phases 11-12) — this phase only wires it into the
+scheduler and the admin toggle, it does not change sync logic itself.
+
+
+
+
+## Implementation Decisions
+
+### Toggle Enforcement Scope
+- **D-01:** The disabled-check is a PAX8-only inline check inside
+ `executeScheduledSync`'s `pax8-daily` case — query `integration_settings`
+ for `key='pax8'` before calling `fullSync()`, skip + log if disabled.
+ Mirrors the `isAppgateConfigured()`/`isMsgraphConfigured()` guard style
+ already used for `appgate-daily`/`engagement-daily`, just checking the DB
+ toggle instead of env vars. Explicitly **not** a general mechanism applied
+ to all integrations — that would change behavior for ~10 existing
+ integrations in a phase whose requirements (PAX8-07, PAX8-09) only ask for
+ PAX8. Do not touch the shared `executeScheduledSync` switch's other cases.
+- **D-02:** The manual trigger route (`POST /api/pax8/sync`) must also
+ refuse to run while PAX8 is disabled — return 403 with a clear message.
+ "Disabled" means fully off; no side door via the manual route while the
+ toggle says off.
+- **D-03:** If PAX8 gets disabled while a sync is already in progress, let
+ it finish normally — no mid-flight cancellation. The disabled flag only
+ prevents the *next* scheduled tick or *next* manual trigger from
+ starting. `isSyncInProgress()`'s existing 409 guard already treats a
+ running sync as atomic; don't add cancellation logic to `fullSync()`'s
+ per-entity loop for this rare edge case.
+
+### Daily Sync Timing
+- **D-04:** `pax8-daily` cron fires at **4:00 AM** (`0 4 * * *`), grouping
+ with the earlier "backend reconciliation" cluster (`contract-services` at
+ 4am, `tickets-reconcile` at 4:30am) rather than the 6-7:30am
+ "reporting/digest" cluster (`engagement-daily`, `zoom-daily`,
+ `ticket-digest-daily`, `integration-health`).
+
+### Failure Visibility
+- **D-05:** No dedicated alert/notification on sync failure. A failed
+ `pax8-daily` run sets `sync_schedules.last_status = 'failed'` /
+ `last_error` — visible on `/admin` only, same silent-failure pattern
+ already used by Veeam full sync, contract-services, and most other
+ integrations. Do not add a new Teams webhook alert path for this phase —
+ that's a bigger cross-cutting concern than PAX8-07/PAX8-09 ask for.
+
+### Claude's Discretion
+- **Migration for seeding the `sync_schedules` row** — follow the exact
+ precedent in `migrations/089_appgate_tables.sql`: seed via
+ `INSERT INTO sync_schedules (...) SELECT ... WHERE NOT EXISTS (SELECT 1
+ FROM sync_schedules WHERE name = '...')` inside the phase's own numbered
+ migration (next number after `095`). `is_enabled` defaults to `false` —
+ every existing precedent except one legacy row (`contract-services`)
+ ships new schedules disabled by default; an admin must opt in via the
+ schedule editor (`/api/sync/schedules`) separately from the
+ `integration_settings` toggle. Not discussed explicitly this session
+ since it's a clear, unambiguous precedent match — flagging here so the
+ planner doesn't re-derive it from scratch.
+- **`/admin/integrations` row wiring** — add PAX8 to the
+ `checkConfigOnly()` list in `checkIntegrationHealth()`
+ (`lib/services/integration-health.ts`), following the `qbo`/`appgate`
+ entries exactly (key, display name, category, required env vars). This
+ is what makes PAX8 appear as a toggleable row per SC#2 — mechanical, not
+ discussed as it has no meaningful alternative given the established
+ pattern.
+- **Env var names for the `checkConfigOnly()` call** — confirm the exact
+ `PAX8_*` env var names against `lib/services/pax8-factory.ts`'s
+ `isPax8Configured()` implementation (established in Phase 10) rather than
+ guessing from convention.
+
+
+
+
+## Canonical References
+
+**Downstream agents MUST read these before planning or implementing.**
+
+### Project scope & requirements
+- `.planning/PROJECT.md` — Current Milestone: v2.0 PAX8 Integration section
+- `.planning/REQUIREMENTS.md` — PAX8-07, PAX8-09 (this phase's requirement
+ IDs); traceability table confirms both map to Phase 13
+- `.planning/ROADMAP.md` — Phase 13 section (goal, 4 success criteria,
+ depends on Phase 12)
+
+### Prior phase foundation this phase builds on
+- `lib/services/pax8-sync-service.ts` — `Pax8SyncService.fullSync()`
+ (line ~74) already runs companies → subscriptions → products → orders →
+ company-matching in one call (Phases 11-12 built this); this phase's
+ scheduler entry calls `getPax8SyncService().fullSync('scheduled')`
+ directly, no new orchestration needed
+- `app/api/pax8/sync/route.ts` — existing fire-and-forget manual trigger
+ (`POST`) and status (`GET`); D-02 requires adding a disabled-check to the
+ `POST` handler
+- `lib/services/pax8-factory.ts` — `isPax8Configured()` from Phase 10; the
+ scheduler case should check this too (like `appgate-daily` does with
+ `isAppgateConfigured()`), in addition to the new `integration_settings`
+ disabled check from D-01
+- `migrations/091_pax8_tables.sql` through `095_...` — existing PAX8
+ schema; next migration number for this phase's `sync_schedules` seed is
+ 096
+
+### Existing patterns to follow
+- `lib/services/sync-scheduler.ts` — `executeScheduledSync` (line ~385):
+ the `appgate-sessions`/`appgate-daily` and `engagement-daily` branches are
+ the direct template for the new `pax8-daily` branch, including the
+ `isXConfigured()` skip-with-log style; `defaultSchedules` array (line
+ ~180) only seeds on a virgin table (`createDefaultSchedules()` — "already
+ exist, skipping defaults") so the actual seed for an existing DB must go
+ through the migration, not this array
+- `migrations/089_appgate_tables.sql` (lines 137-150) — exact precedent for
+ seeding a new `sync_schedules` row via `INSERT ... WHERE NOT EXISTS`
+ inside a feature migration, `is_enabled: false`
+- `lib/services/integration-health.ts` — `checkConfigOnly()` (line ~239)
+ and the `checkConfigOnly('qbo', ...)`/`checkConfigOnly('appgate', ...)`
+ call sites (line ~344-347) in `checkIntegrationHealth()` — template for
+ adding the PAX8 row; `getDbDisabledKeys()` (line ~295) and
+ `applyDisableOverlay()` (line ~310) show the exact `integration_settings`
+ query shape D-01's scheduler check should reuse
+- `app/api/admin/integrations/route.ts` — existing `integration_settings`
+ CRUD (list, toggle with `disabled_by`/`disabled_at`/`disabled_reason`);
+ no changes needed here, PAX8 just needs a row like any other integration
+- `CLAUDE.md` "Operator config" section — documents the current two-source
+ disable model (env var + DB) and that "live auth checks still run" for
+ disabled integrations; note D-01 makes PAX8 the **first** integration
+ where the DB toggle actually gates something beyond display — call this
+ out explicitly if updating this doc during/after the phase, since it's a
+ behavior precedent, not just a new integration following an old one
+
+
+
+
+## Existing Code Insights
+
+### Reusable Assets
+- `getPax8SyncService().fullSync('scheduled')` — the entire scheduled-run
+ entry point; no new sync orchestration code needed in this phase
+- `getDbDisabledKeys()` / `applyDisableOverlay()` pattern in
+ `integration-health.ts` — the exact SQL shape (`SELECT key FROM
+ integration_settings WHERE disabled = true`) to reuse for D-01's
+ scheduler-side check (querying for `key = 'pax8'` specifically, or
+ reusing the existing helper if it's exported/exportable)
+
+### Established Patterns
+- New integration schedules always ship `is_enabled: false`, seeded via
+ `INSERT ... WHERE NOT EXISTS` in that feature's own migration (see
+ `089_appgate_tables.sql`) — never rely on the in-code `defaultSchedules`
+ array to reach an already-provisioned Postgres volume
+- `isConfigured()` env-var guard + `integration_settings.disabled`
+ DB guard are two independent checks; both should gate `pax8-daily`
+ (config missing → skip + log distinctly from disabled → skip + log)
+- `/admin/integrations` rows come from `checkIntegrationHealth()`'s
+ `checkConfigOnly()` calls, not a separate registry — adding PAX8 there
+ is the entire SC#2 requirement
+
+### Integration Points
+- `lib/services/sync-scheduler.ts` — new `pax8-daily` branch in
+ `executeScheduledSync`
+- `app/api/pax8/sync/route.ts` — `POST` handler gains a disabled check
+- `lib/services/integration-health.ts` — new `checkConfigOnly('pax8', ...)`
+ call site in `checkIntegrationHealth()`
+- New migration `096_pax8_daily_schedule.sql` (or similar name) — seeds the
+ `sync_schedules` row per D-04's cron expression
+
+
+
+
+## Specific Ideas
+
+No UI mockups or specific behavioral scripts — this is scheduler + toggle
+wiring, no new page. The five numbered decisions (D-01 through D-05) plus
+the three Claude's Discretion items are the concrete specifics: PAX8-scoped
+(not general) toggle enforcement covering both the cron and the manual
+route, let-it-finish semantics on mid-run disable, 4am cron slot, and
+silent (dashboard-only) failure visibility matching existing precedent.
+
+
+
+
+## Deferred Ideas
+
+None — discussion stayed within Phase 13's scope. (The `/pax8` UI and
+manual company-match resolution are already sequenced into Phase 14 per
+ROADMAP.md/REQUIREMENTS.md, not deferred from this discussion. Generalizing
+the toggle-gates-scheduler mechanism to other integrations, discussed as
+an explicit alternative under Toggle Enforcement Scope, was deliberately
+rejected as out of scope for this phase — not deferred to a specific future
+phase, just noted as a possible future direction if another integration
+needs the same guarantee.)
+
+
+
+---
+
+*Phase: 13-scheduler-admin-toggle*
+*Context gathered: 2026-07-11*
diff --git a/.planning/phases/13-scheduler-admin-toggle/13-DISCUSSION-LOG.md b/.planning/phases/13-scheduler-admin-toggle/13-DISCUSSION-LOG.md
new file mode 100644
index 0000000..f32d110
--- /dev/null
+++ b/.planning/phases/13-scheduler-admin-toggle/13-DISCUSSION-LOG.md
@@ -0,0 +1,79 @@
+# Phase 13: Scheduler & Admin Toggle - Discussion Log
+
+> **Audit trail only.** Do not use as input to planning, research, or execution agents.
+> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.
+
+**Date:** 2026-07-11
+**Phase:** 13-scheduler-admin-toggle
+**Areas discussed:** Toggle enforcement scope, Daily sync timing, Failure visibility
+
+---
+
+## Toggle Enforcement Scope
+
+| Option | Description | Selected |
+|--------|-------------|----------|
+| PAX8-only inline check | Check inside executeScheduledSync's pax8-daily case, mirrors isAppgateConfigured()/isMsgraphConfigured() guard style | ✓ |
+| General scheduler mechanism | Shared isIntegrationEnabled(key) helper applied to every sync_type | |
+
+**User's choice:** PAX8-only inline check
+**Notes:** Rejecting the general mechanism explicitly to avoid changing behavior for ~10 existing integrations in a phase scoped only to PAX8-07/PAX8-09.
+
+| Option | Description | Selected |
+|--------|-------------|----------|
+| Yes, block it too | POST /api/pax8/sync returns 403 when PAX8 is disabled | ✓ |
+| No, manual trigger still works | Only the scheduled cron respects the toggle | |
+
+**User's choice:** Yes, block it too
+**Notes:** "Disabled" should mean fully off, no side door via the manual route.
+
+| Option | Description | Selected |
+|--------|-------------|----------|
+| Let it finish | In-progress fullSync() completes normally; disabled flag only blocks the next tick/trigger | ✓ |
+| Abort immediately | Requires threading cancellation into fullSync()'s per-entity loop | |
+
+**User's choice:** Let it finish
+**Notes:** Rare edge case, not worth the added complexity.
+
+---
+
+## Daily Sync Timing
+
+| Option | Description | Selected |
+|--------|-------------|----------|
+| 6:00 AM | Matches engagement-daily/zoom-daily | |
+| 4:00 AM | Groups with contract-services (4am) and tickets-reconcile (4:30am) | ✓ |
+| You decide | No strong preference | |
+
+**User's choice:** 4:00 AM
+**Notes:** None beyond the selection.
+
+---
+
+## Failure Visibility
+
+| Option | Description | Selected |
+|--------|-------------|----------|
+| Silent, admin-dashboard only | Matches Veeam full sync / contract-services precedent — last_status/last_error only | ✓ |
+| Post a Teams alert on failure | Follows integration-health/ticket-digest precedent of pushing an Adaptive Card | |
+
+**User's choice:** Silent, admin-dashboard only
+**Notes:** Keeps phase scoped to scheduling + toggle, not a new alerting feature.
+
+---
+
+## Claude's Discretion
+
+- Migration mechanics for seeding the `sync_schedules` row — follow
+ `migrations/089_appgate_tables.sql`'s `INSERT ... WHERE NOT EXISTS`
+ precedent exactly, `is_enabled: false` by default. Not asked as a
+ question — unambiguous precedent match.
+- `/admin/integrations` row wiring — add PAX8 to `checkConfigOnly()` in
+ `integration-health.ts` following the `qbo`/`appgate` call sites.
+ Mechanical, no real alternative.
+- Exact `PAX8_*` env var names for that call — confirm against
+ `pax8-factory.ts`'s `isPax8Configured()` rather than guessing.
+
+## Deferred Ideas
+
+None — discussion stayed within Phase 13's scope.