chore: merge executor worktree (worktree-agent-a77fd74cb36676519)
This commit is contained in:
commit
db36c1afe6
3 changed files with 120 additions and 0 deletions
108
.planning/phases/13-scheduler-admin-toggle/13-02-SUMMARY.md
Normal file
108
.planning/phases/13-scheduler-admin-toggle/13-02-SUMMARY.md
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
---
|
||||||
|
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 `<threat_model>` (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.
|
||||||
|
|
||||||
|
## 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*
|
||||||
|
|
@ -3,6 +3,16 @@ import { getPax8SyncService } from '@/lib/services/pax8-sync-service';
|
||||||
import postgresClient from '@/lib/services/postgres-client';
|
import postgresClient from '@/lib/services/postgres-client';
|
||||||
|
|
||||||
export async function POST(req: NextRequest) {
|
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 body = await req.json().catch(() => ({}));
|
||||||
const triggeredBy = body.triggeredBy || 'manual';
|
const triggeredBy = body.triggeredBy || 'manual';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -345,6 +345,8 @@ export async function checkIntegrationHealth(opts?: { skipCache?: boolean }): Pr
|
||||||
['QBO_CLIENT_ID', 'QBO_CLIENT_SECRET'])),
|
['QBO_CLIENT_ID', 'QBO_CLIENT_SECRET'])),
|
||||||
Promise.resolve(checkConfigOnly('appgate', 'AppGate SDP', 'security',
|
Promise.resolve(checkConfigOnly('appgate', 'AppGate SDP', 'security',
|
||||||
['APPGATE_URL', 'APPGATE_USERNAME', 'APPGATE_PASSWORD', 'APPGATE_DEVICE_ID'])),
|
['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',
|
Promise.resolve(checkConfigOnly('anthropic', 'Anthropic', 'llm',
|
||||||
['ANTHROPIC_API_KEY'])),
|
['ANTHROPIC_API_KEY'])),
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue