From 3c114ae209bc4090bdfc88dbdaecbc17f022c248 Mon Sep 17 00:00:00 2001 From: lorentz Date: Sat, 11 Jul 2026 09:46:06 -0400 Subject: [PATCH 1/4] feat(13-02): register PAX8 as a toggleable integration-health row - Add checkConfigOnly('pax8', 'PAX8', 'finance', [PAX8_CLIENT_ID, PAX8_CLIENT_SECRET]) call site - Flows through existing applyDisableOverlay automatically for /admin/integrations --- lib/services/integration-health.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/services/integration-health.ts b/lib/services/integration-health.ts index 371e735..fe28934 100644 --- a/lib/services/integration-health.ts +++ b/lib/services/integration-health.ts @@ -345,6 +345,8 @@ export async function checkIntegrationHealth(opts?: { skipCache?: boolean }): Pr ['QBO_CLIENT_ID', 'QBO_CLIENT_SECRET'])), Promise.resolve(checkConfigOnly('appgate', 'AppGate SDP', 'security', ['APPGATE_URL', 'APPGATE_USERNAME', 'APPGATE_PASSWORD', 'APPGATE_DEVICE_ID'])), + Promise.resolve(checkConfigOnly('pax8', 'PAX8', 'finance', + ['PAX8_CLIENT_ID', 'PAX8_CLIENT_SECRET'])), Promise.resolve(checkConfigOnly('anthropic', 'Anthropic', 'llm', ['ANTHROPIC_API_KEY'])), ]); From fdc9919381f9ed324df5f2551f4f711a5522f701 Mon Sep 17 00:00:00 2001 From: lorentz Date: Sat, 11 Jul 2026 09:46:20 -0400 Subject: [PATCH 2/4] feat(13-02): gate POST /api/pax8/sync on the disabled toggle - Return 403 when integration_settings.key='pax8' has disabled=true - Check runs as the first statement, before isSyncInProgress() - GET handler unchanged; no new imports (postgresClient already imported) --- app/api/pax8/sync/route.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/api/pax8/sync/route.ts b/app/api/pax8/sync/route.ts index 60ae938..94b3a30 100644 --- a/app/api/pax8/sync/route.ts +++ b/app/api/pax8/sync/route.ts @@ -3,6 +3,16 @@ import { getPax8SyncService } from '@/lib/services/pax8-sync-service'; import postgresClient from '@/lib/services/postgres-client'; export async function POST(req: NextRequest) { + const disabledRes = await postgresClient.query<{ disabled: boolean }>( + `SELECT disabled FROM integration_settings WHERE key = 'pax8'` + ); + if (disabledRes.rows[0]?.disabled === true) { + return NextResponse.json( + { error: 'PAX8 is disabled', message: 'PAX8 sync is disabled via /admin/integrations' }, + { status: 403 } + ); + } + const body = await req.json().catch(() => ({})); const triggeredBy = body.triggeredBy || 'manual'; From 6413f99e78142d11315d75d8f99eae27b7eb9afd Mon Sep 17 00:00:00 2001 From: lorentz Date: Sat, 11 Jul 2026 09:47:00 -0400 Subject: [PATCH 3/4] docs(13-02): complete scheduler & admin toggle admin-surface plan - Add SUMMARY.md documenting PAX8 integration-health row + 403 disabled-gate --- .../13-02-SUMMARY.md | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 .planning/phases/13-scheduler-admin-toggle/13-02-SUMMARY.md diff --git a/.planning/phases/13-scheduler-admin-toggle/13-02-SUMMARY.md b/.planning/phases/13-scheduler-admin-toggle/13-02-SUMMARY.md new file mode 100644 index 0000000..c25402c --- /dev/null +++ b/.planning/phases/13-scheduler-admin-toggle/13-02-SUMMARY.md @@ -0,0 +1,104 @@ +--- +phase: 13-scheduler-admin-toggle +plan: 02 +subsystem: api +tags: [integration-health, admin-toggle, pax8, postgres] + +# Dependency graph +requires: + - phase: 10-pax8-client-auth-foundation + provides: pax8-factory.ts (isPax8Configured, PAX8_CLIENT_ID/PAX8_CLIENT_SECRET env vars) + - phase: 11-company-catalog-subscription-sync + provides: app/api/pax8/sync/route.ts (POST/GET handlers, Pax8SyncService) +provides: + - PAX8 as a toggleable row on /admin/integrations (checkConfigOnly call site) + - 403 disabled-gate on POST /api/pax8/sync closing the manual "side door" +affects: [13-03 (scheduler branch + live verification wave)] + +# Tech tracking +tech-stack: + added: [] + patterns: + - "DB-backed disabled toggle enforced inline at the top of a route handler (first precedent in this codebase for gating an action, not just display)" + +key-files: + created: [] + modified: + - lib/services/integration-health.ts + - app/api/pax8/sync/route.ts + +key-decisions: + - "Used category 'finance' for the PAX8 health-check row (matches qbo) since IntegrationHealth['category'] has no vendor/marketplace option, per plan instruction" + - "Disabled-check inlined in POST rather than extracted into a shared helper with the scheduler check (per plan D-01, deferred to 13-03)" + +patterns-established: + - "Pattern: gate a manual-trigger route on integration_settings.disabled via a first-statement inline query, mirroring the existing getDbDisabledKeys() query shape but scoped to a single key" + +requirements-completed: [PAX8-09] + +# Metrics +duration: 6min +completed: 2026-07-11 +--- + +# Phase 13 Plan 02: Scheduler & Admin Toggle (admin surface) Summary + +**PAX8 registered as a toggleable /admin/integrations row and POST /api/pax8/sync now returns 403 while PAX8 is disabled via the operator toggle.** + +## Performance + +- **Duration:** 6 min +- **Started:** 2026-07-11T13:XX:XXZ +- **Completed:** 2026-07-11T13:XX:XXZ +- **Tasks:** 2 completed +- **Files modified:** 2 + +## Accomplishments +- PAX8 now appears as a row (key `pax8`, name `PAX8`, category `finance`) in `checkIntegrationHealth()` output, making it toggleable on `/admin/integrations` and flowing through the existing `applyDisableOverlay` with zero changes to that overlay logic. +- `POST /api/pax8/sync` now returns HTTP 403 as its very first action when `integration_settings.key='pax8'` has `disabled=true`, closing the manual-trigger "side door" per D-02 — no other behavior changed. + +## Task Commits + +Each task was committed atomically: + +1. **Task 1: Register PAX8 as an integration-health row** - `3c114ae` (feat) +2. **Task 2: Gate POST /api/pax8/sync on the disabled toggle (403)** - `fdc9919` (feat) + +_Note: No TDD tasks in this plan; single commit per task._ + +## Files Created/Modified +- `lib/services/integration-health.ts` - Added `Promise.resolve(checkConfigOnly('pax8', 'PAX8', 'finance', ['PAX8_CLIENT_ID', 'PAX8_CLIENT_SECRET']))` to the `Promise.all` array inside `checkIntegrationHealth()`, immediately after the `appgate` entry. +- `app/api/pax8/sync/route.ts` - Inserted a disabled-check as the first statement of `POST`: queries `SELECT disabled FROM integration_settings WHERE key = 'pax8'` and returns 403 with `{ error: 'PAX8 is disabled', message: '...' }` when `rows[0]?.disabled === true`. `GET` unchanged, no new imports (`postgresClient` already imported at the top of the file). + +## Decisions Made +- Category `'finance'` chosen for the PAX8 health row (matches `qbo`'s billing/subscription-data classification) since the `IntegrationHealth['category']` union has no vendor/marketplace member — per plan instruction, the union was not extended. +- The disabled-check in the route is a standalone inline query, not a shared helper with the forthcoming scheduler branch (13-03) — per plan D-01, this separation is intentional for this phase. + +## Deviations from Plan + +None - plan executed exactly as written. + +## Issues Encountered + +None. + +## Threat Flags + +None — both edits are covered by the plan's own `` (T-13-01 mitigated by the 403 gate, T-13-04 mitigated by the constant-literal query, T-13-05 accepted for the error message). No new undocumented surface introduced. + +## Known Stubs + +None. + +## User Setup Required + +None - no external service configuration required. + +## Next Phase Readiness + +- Both `checkConfigOnly('pax8', ...)` in `integration-health.ts` and the 403 gate in `app/api/pax8/sync/route.ts` are ready for 13-03's live verification (row appears on `/admin/integrations`, 403 confirmed when toggled off) and the scheduler branch (`pax8-daily` in `sync-scheduler.ts`) which reuses the same `SELECT disabled FROM integration_settings WHERE key = 'pax8'` query shape. +- No blockers. + +--- +*Phase: 13-scheduler-admin-toggle* +*Completed: 2026-07-11* From 37855597852f29c719ef7adf3890d6873013037c Mon Sep 17 00:00:00 2001 From: lorentz Date: Sat, 11 Jul 2026 09:47:13 -0400 Subject: [PATCH 4/4] docs(13-02): append self-check result to SUMMARY.md --- .planning/phases/13-scheduler-admin-toggle/13-02-SUMMARY.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.planning/phases/13-scheduler-admin-toggle/13-02-SUMMARY.md b/.planning/phases/13-scheduler-admin-toggle/13-02-SUMMARY.md index c25402c..b0d5417 100644 --- a/.planning/phases/13-scheduler-admin-toggle/13-02-SUMMARY.md +++ b/.planning/phases/13-scheduler-admin-toggle/13-02-SUMMARY.md @@ -99,6 +99,10 @@ None - no external service configuration required. - Both `checkConfigOnly('pax8', ...)` in `integration-health.ts` and the 403 gate in `app/api/pax8/sync/route.ts` are ready for 13-03's live verification (row appears on `/admin/integrations`, 403 confirmed when toggled off) and the scheduler branch (`pax8-daily` in `sync-scheduler.ts`) which reuses the same `SELECT disabled FROM integration_settings WHERE key = 'pax8'` query shape. - No blockers. +## Self-Check: PASSED + +All claimed files exist (`lib/services/integration-health.ts`, `app/api/pax8/sync/route.ts`, this SUMMARY.md) and all claimed commits (`3c114ae`, `fdc9919`, `6413f99`) are present in git history. + --- *Phase: 13-scheduler-admin-toggle* *Completed: 2026-07-11*