docs(13): capture phase context
This commit is contained in:
parent
4b34109a65
commit
e6802d549a
2 changed files with 288 additions and 0 deletions
209
.planning/phases/13-scheduler-admin-toggle/13-CONTEXT.md
Normal file
209
.planning/phases/13-scheduler-admin-toggle/13-CONTEXT.md
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
# Phase 13: Scheduler & Admin Toggle - Context
|
||||
|
||||
**Gathered:** 2026-07-11
|
||||
**Status:** Ready for planning
|
||||
|
||||
<domain>
|
||||
## 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.
|
||||
|
||||
</domain>
|
||||
|
||||
<decisions>
|
||||
## 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.
|
||||
|
||||
</decisions>
|
||||
|
||||
<canonical_refs>
|
||||
## 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
|
||||
|
||||
</canonical_refs>
|
||||
|
||||
<code_context>
|
||||
## 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
|
||||
- `is<Name>Configured()` 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
|
||||
|
||||
</code_context>
|
||||
|
||||
<specifics>
|
||||
## 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.
|
||||
|
||||
</specifics>
|
||||
|
||||
<deferred>
|
||||
## 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.)
|
||||
|
||||
</deferred>
|
||||
|
||||
---
|
||||
|
||||
*Phase: 13-scheduler-admin-toggle*
|
||||
*Context gathered: 2026-07-11*
|
||||
|
|
@ -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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue