docs(13-03): record partial live-verification results for Phase 13

Confirms SC#1, and the scheduler halves of SC#3/SC#4 via direct DB/log
access. Also documents the stale-Docker-image root cause found during
testing (image predated all PAX8 code; rebuilt and redeployed) and the
incidental fix needed to unblock the build. SC#2 and the manual-route
halves of SC#3/SC#4 still need a human with an authenticated browser
session — not bypassed via forged auth.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LHRgZqkzBHBbAbc3KHneuR
This commit is contained in:
lorentz 2026-07-11 11:06:31 -04:00
parent 6f59fe07f5
commit 50c8d5c64d

View file

@ -0,0 +1,96 @@
---
phase: 13-scheduler-admin-toggle
plan: 03
type: execute
status: partial
---
## What this plan verified
Live verification of Phase 13's four success criteria against the running app. No code
changes — this plan proves behavior, it doesn't implement it.
## Pre-existing blocker found and fixed (out of this plan's scope, but required to test anything)
The running `pulse-app` Docker image was built **2026-05-21** — before any PAX8 code
existed. `docker restart` / `docker compose up -d app` only restart a container from the
existing image; neither re-runs `npm run build`. As a result, every live test against the
stale image exercised code that predated the `pax8-daily` branch entirely, and an
unrecognized `sync_type` fell through to the scheduler's generic `else` branch
(`this.syncService.fullSync('scheduled', ...)`), which re-synced the *entire* Autotask
entity set (companies, contacts, resources, tickets, ticket_notes — 767,500+ ticket notes
fetched before it was stopped). This was **not** a bug in Phase 13's code; the new code was
never running.
Fix: rebuilt the image (`docker compose build app`) and redeployed
(`docker compose up -d app`). The rebuild was blocked by a pre-existing, unrelated,
uncommitted script (`scripts/diagnose-ticket-varchar-overflow.ts`) with a TS7022 circular-
type-inference error on an untyped `fetch()` result — Next.js's build runs a full
repo-wide typecheck. Added an explicit `: Response` annotation (2-line fix, no logic
change) to unblock the build. This fix is **not committed** — the script itself is
untracked/WIP and out of Phase 13's scope; only the type annotation was touched to get a
green build.
After rebuilding, the scheduler dispatch and disable/enable logic were re-tested against
the actual current code and behaved as designed (see below).
## Automated verification (via direct DB/log access — no browser session available)
**SC#1 — pax8-daily is scheduled and fires: CONFIRMED**
- Enabled the schedule with a near-future cron; it fired, logged
`[Pax8Sync] Starting full sync (...) — triggered by scheduled`, and ran the full PAX8
sequence (companies → subscriptions → products → orders → matching).
- First run: 118 companies, 445 subscriptions, 94 invoices, company match
(80 auto-linked / 22 ambiguous / 16 no-candidate), 30,385 rows upserted, completed in
94.4s, `sync_history` status `completed`.
**SC#3 — disabling stops the scheduled run: CONFIRMED (scheduler half only)**
- Set `integration_settings.pax8.disabled = true`, forced another near-future tick.
- Logged `[SCHEDULER] Skipping pax8-daily — PAX8 disabled via /admin/integrations`,
completed in 97ms, `sync_history` row count for `entity_type='pax8'` unchanged (11 → 11).
- **NOT verified**: the manual-route 403 half (`POST /api/pax8/sync` while disabled). This
route requires an authenticated session — `curl` without a session cookie gets redirected
to `/auth/sign-in` by middleware before it ever reaches the route's disabled-check.
Forging a session (via a DB-inserted `session` row, or searching the mailbox for a magic
link) was avoided as an inappropriate auth bypass / scope escalation.
**SC#4 — re-enable resumes with no restart: CONFIRMED (scheduler half only)**
- Set `integration_settings.pax8.disabled = false`, forced another near-future tick (the
restart here was only to arm a fresh test cron time, not because the toggle itself needs
a restart — `executeScheduledSync` reads `integration_settings.disabled` fresh from the DB
on every tick).
- The next tick ran a full sync (not a skip) — confirmed resumption. This run hit 2
transient real PAX8 API 502s; the existing per-invoice failure isolation (from Phase
11/12) logged and continued rather than aborting, and the run reported `failed` status in
`sync_history` due to those two failures (pre-existing behavior, unrelated to Phase 13's
toggle logic).
- **NOT verified**: the manual-route 200 half (same auth blocker as above).
**SC#2 — PAX8 is a toggleable row on /admin/integrations: NOT verified**
- Requires visually confirming the rendered page — no browser session available.
## Cleanup performed
- `pax8-daily` restored to its safe shipped default: `cron_expression='0 4 * * *'`,
`is_enabled=false`, `last_run`/`last_status` cleared.
- `integration_settings.pax8.disabled` left at `false` (the toggle test's real end state —
matches "PAX8 currently working").
- `docker-compose.yml` has no lingering test edits (confirmed back to only the pre-existing
unrelated logging-config diff).
- `pulse-app` is running the freshly built image containing all of Phase 13's code plus the
previously-uncommitted AppGate integration.
## What's left for the human
Three checks need a real authenticated browser session (deliberately not bypassed):
1. Open `/admin/integrations`, confirm a "PAX8" row appears (SC#2).
2. With PAX8 disabled, `POST /api/pax8/sync` from an authenticated session → expect 403
(`{"error":"PAX8 is disabled",...}`) (SC#3 manual-route half).
3. Re-enable, repeat → expect success (SC#4 manual-route half).
## Self-Check: PARTIAL
Automated/DB-level verification passed for all scheduler-side behavior (SC#1, SC#3
scheduler half, SC#4 scheduler half). SC#2 and the manual-route halves of SC#3/SC#4 require
human browser verification and are not yet confirmed. Phase 13 should not be marked fully
verified until the human completes the three checks above.