Commit graph

419 commits

Author SHA1 Message Date
e557e7f4a6 docs(11): capture phase context 2026-07-10 18:42:56 -04:00
72ffcc230c docs(phase-10): add/update validation strategy 2026-07-10 18:27:09 -04:00
148e6c1a83 docs(phase-10): add/update security threat verification 2026-07-10 18:21:50 -04:00
253e37e1e2 docs(phase-10): evolve PROJECT.md after phase completion 2026-07-10 18:11:49 -04:00
5cec100def docs(phase-10): complete phase execution 2026-07-10 18:11:02 -04:00
d1e5a522c4 docs(10): add code review report 2026-07-10 18:07:33 -04:00
70f23c6b13 docs(phase-10): update tracking after wave 2 2026-07-10 18:02:13 -04:00
120e55b9ed chore: merge executor worktree (worktree-agent-a6d8ca4687c637b52) 2026-07-10 18:01:51 -04:00
59a8152075 docs(10-03): complete PAX8 live auth-proof plan
- Task 2 checkpoint resolved: developer ran scripts/verify-pax8-auth.ts with real credentials, confirmed live OAuth2 token exchange + /companies read (118 total companies)
- Phase 10 Success Criterion #2 verified end-to-end (not mocked)
2026-07-10 18:01:18 -04:00
72bba30007 docs(10-03): log pre-existing tsc errors as deferred (unrelated to plan) 2026-07-10 17:45:00 -04:00
076c8629b3 feat(10-03): add live PAX8 auth-proof script + document PAX8 in CLAUDE.md
- scripts/verify-pax8-auth.ts loads .env.local, calls getPax8Client().listCompanies(0,1), logs only counts/status (never token/secret)
- CLAUDE.md External integrations table gains a PAX8 row (PAX8_* prefix)
- INTEGRATIONS.md does not exist at repo root; edit skipped per plan instructions (noted in SUMMARY)
2026-07-10 17:44:56 -04:00
2c7d059a0f docs(phase-10): update tracking after wave 1 2026-07-10 17:41:44 -04:00
4614e287bf chore: merge executor worktree (worktree-agent-aa3a717ced7b6de6f) 2026-07-10 17:36:09 -04:00
4a6f3e496a chore: merge executor worktree (worktree-agent-a2bb09637c4029677) 2026-07-10 17:36:09 -04:00
5fd3a3b20b docs(10-01): append self-check results to SUMMARY.md 2026-07-10 17:35:15 -04:00
472612cca9 docs(10-01): complete PAX8 client auth foundation plan
- SUMMARY.md documents Task 1-3 commits, decisions, and the git-stash recovery incident
- REQUIREMENTS.md marks PAX8-01/PAX8-02 complete
- deferred-items.md logs two pre-existing, unrelated failures (out of scope)
2026-07-10 17:35:02 -04:00
e531c5f6ea docs(10-02): append self-check results to plan summary
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LHRgZqkzBHBbAbc3KHneuR
2026-07-10 17:34:40 -04:00
bf1eb35855 docs(10-02): add plan summary for PAX8 schema migration
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LHRgZqkzBHBbAbc3KHneuR
2026-07-10 17:34:28 -04:00
532ca96dd0 feat(10-01): implement pax8-factory config check + singleton
- isPax8Configured(): both PAX8_CLIENT_ID and PAX8_CLIENT_SECRET required
- getPax8Client(): throws exact error naming both env vars when missing;
  caches singleton Pax8Client instance
- _resetPax8Client(): test seam to clear the cached singleton
- follows appgate-factory.ts / 10-RESEARCH.md Pattern 2 verbatim
2026-07-10 17:33:56 -04:00
a07fe4574a test(10-01): add failing tests for pax8-factory config check + singleton 2026-07-10 17:33:52 -04:00
4d20e45ce8 feat(10-02): create PAX8 schema migration 091
- Six tables: pax8_companies, pax8_products, pax8_subscriptions,
  pax8_orders, pax8_order_items, pax8_company_match_review
- Header/line-item design (D-01) with hard FK order_items->orders CASCADE
- Monetary NUMERIC(12,2) + currency CHAR(3) DEFAULT 'USD' (D-04)
- raw_payload JSONB safety net on all five non-review tables (D-03)
- pax8_company_match_review modeled field-for-field on device_link_review
  (migration 080), with soft-ref BIGINT[] candidates and hard FK to
  pax8_companies + nullable FK to companies(id)
2026-07-10 17:33:27 -04:00
1da08093eb feat(10-01): implement Pax8Client token exchange + auth-proof call
- getToken() JSON-body OAuth2 client-credentials exchange with audience field
  (deviates from msgraph-client.ts's form-encoded body per 10-RESEARCH.md Pitfall 3)
- 60s expiry-buffer token cache, reused across calls
- fetchJson<T>() with 429/Retry-After retry copied from msgraph-client.ts
- listCompanies() auth-proof call parsing the {content,page} envelope
- secret never interpolated into any throw/console call
2026-07-10 17:32:25 -04:00
ed485d8bde test(10-01): add failing tests for Pax8Client token exchange + auth-proof call 2026-07-10 17:32:20 -04:00
5c9cee02af feat(10-01): add PAX8 typed entity barrel
- Pax8PageEnvelope<T> generic pagination envelope
- Pax8Company, Pax8Subscription, Pax8Product entity interfaces
- Pax8Order/Pax8OrderItem modeled on PAX8 Invoice/InvoiceItem fields
  (not bare Order/LineItem, which lack pricing) per 10-RESEARCH.md Pitfall 1
- escape-hatch [key: string]: unknown on each entity, matching appgate.ts convention
2026-07-10 17:31:38 -04:00
8b975be700 docs(10): record planning completion in STATE.md, add pattern map 2026-07-10 13:28:00 -04:00
40e17aa4b6 docs(10): cite D-01/D-02/D-03 explicitly in 10-02-PLAN.md truths
Decision coverage gate requires an explicit D-NN citation inside a
plan's must_haves/truths block, not just body prose. D-04 was already
cited; D-01/D-02/D-03 were only mentioned in read_first/action text.
2026-07-10 13:27:35 -04:00
741b2425c1 docs(10): resolve plan-checker blockers on validation strategy and research
Populate 10-VALIDATION.md from RESEARCH.md's Validation Architecture
section (was an unfilled template), add RESOLVED/DEFERRED markers to
RESEARCH.md's Open Questions, and align pax8_subscriptions/order_items
product_id to UUID (matching pax8_products.id) to avoid a Phase 11 cast.
2026-07-10 13:23:56 -04:00
26f41a7011 docs(10): create phase plan
3 plans across 2 waves for PAX8 Client & Auth Foundation (PAX8-01, PAX8-02).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LHRgZqkzBHBbAbc3KHneuR
2026-07-10 13:16:23 -04:00
90ed8ba893 docs(phase-10): add validation strategy 2026-07-10 13:01:52 -04:00
8f814e2d94 docs(10): research PAX8 client auth foundation 2026-07-10 12:59:06 -04:00
0166a8583d docs(state): record phase 10 context session 2026-07-10 12:18:11 -04:00
98aed06f8f docs(10): capture phase context 2026-07-10 12:18:05 -04:00
25edff0462 docs: create milestone v2.0 roadmap (5 phases) 2026-07-10 12:05:48 -04:00
e0a41da051 docs: define milestone v2.0 requirements 2026-07-10 12:01:02 -04:00
46a775e81f chore: clear v1.0 phase artifacts for v2.0 milestone start
Phase directories (01-09.1) are fully preserved in git history at
prior commits; this just clears the working tree so v2.0 roadmapping
starts clean, per gsd-new-milestone's phases.clear step.
2026-07-10 11:54:22 -04:00
bb737bdf65 docs: start milestone v2.0 PAX8 Integration 2026-07-10 11:54:06 -04:00
4f7e9be059 docs: capture exploration — PAX8 integration scope
Plant two seeds (PAX8 sync + Autotask mapping, general Pulse data
assistant) and one research question (PAX8 company identifier
alternatives to fuzzy name matching), surfaced while scoping a
future PAX8 integration.
2026-07-10 11:45:11 -04:00
758b7e7f15 feat(engagement): replace Graph email counts with real-time mimecast data
- Broaden mimecast retention from 30 days to 18 months rolling
- Re-enable mimecast-sync schedule (was disabled since March 17)
- Full sync triggered: 35,559 messages loaded for last 30 days
- Users list API: LATERAL join on mimecast_messages for emails_sent/received
- User detail API: add emails{d7,d30,d90} field from mimecast
- Engagement page: prefer mimecast email counts in detail panel sub-label

Graph API has 48-72hr reporting lag; mimecast is same-day
2026-06-02 20:25:14 -04:00
078e087d5f docs(quick-260521-foj): Fix weekly-full FK error - widen Companies filter + defensive ticket company_id validation 2026-05-21 11:24:34 -04:00
62c529fb91 fix(quick-260521-foj-02): defensively nullify ticket company_id for missing companies
- Add getValidCompanyIds() helper mirroring getValidResourceIds()
- Add a new TICKETS validation block that nullifies ticket.company_id when
  the referenced company is not present (is_deleted=false) in the Pulse
  mirror, instead of letting tickets_company_id_fkey roll back the
  bulkUpsert transaction
- Block runs after the existing recordsWithoutCompany filter and before
  the existing resource-FK nullification block (correct ordering)
- Belt-and-suspenders on top of Task 1: covers hard-deleted-in-Autotask
  companies that Task 1's widening still won't fetch
2026-05-21 11:22:33 -04:00
1ecaefe85a fix(quick-260521-foj-01): widen Companies full sync to fetch all companies
- Add buildCompaniesFilter() returning id > 0 in lib/utils/sync-helpers.ts
- Route COMPANIES through buildCompaniesFilter on full sync instead of
  the generic buildActiveFilter (which applied isActive=true and missed
  inactive companies with tickets, causing tickets_company_id_fkey on
  weekly-full and full syncs since 2026-05-15)
- hasAppliedFilters stays true (filter is non-empty), so soft-delete of
  companies is not triggered
2026-05-21 11:21:48 -04:00
d02796e863 docs(quick-260521-fci): Stopgap nightly reconciliation for stale open tickets in postgres mirror 2026-05-21 11:11:33 -04:00
badd718194 feat(260521-fci-02): wire tickets-reconcile schedule + migration 090
- sync-scheduler.ts: extend sync_type union with 'tickets-reconcile', add a
  default schedule entry (disabled, 30 4 * * *), and a dispatch case using
  the device-link-reconcile / integration-health dynamic-import pattern.
- migrations/090_ticket_reconcile_schedule.sql: idempotent INSERT (ON CONFLICT
  DO NOTHING) so existing installs pick up the row without disturbing the
  fresh-DB default-seed path.
2026-05-21 11:09:36 -04:00
51f0b32cb3 feat(260521-fci-01): add ticket reconciliation service + API route
- New lib/services/ticket-reconciliation-service.ts: reconcileStaleTickets()
  scans tickets where is_deleted=false AND status<>5 AND synced_at older than
  7 days (capped at 500), re-fetches each from Autotask, and either upserts
  via the webhook SQL pattern or soft-deletes when Autotask returns null.
- Returns { scanned, updated, statusFlippedToComplete, softDeleted, errors }.
- New POST /api/sync/reconcile-tickets — fire-and-forget trigger mirroring
  /api/sync/incremental (public per existing middleware allowlist).
2026-05-21 11:08:24 -04:00
1a488e5d1d docs(quick-260519-0oz): Add QBO createPayment + createDeposit + .FH reconciliation script 2026-05-19 00:42:32 -04:00
54974584b1 fix(260519-0oz-02): defer QBO client construction in dry-run to avoid credential requirement
In dry-run mode the script makes zero QBO writes, so QboClient should not
be instantiated (which would throw if QBO_CLIENT_ID etc are unset).
Replace eager getQboClient() call with a lazy getClient() wrapper that
constructs QboClient only when the first live POST is about to be made.
2026-05-19 00:38:47 -04:00
4745de1bce feat(260519-0oz-02): add apply-fh-deposit.ts CLI script and 5/18 batch mapping JSON
- scripts/apply-fh-deposit.ts: parse .FH, validate per-check + total against mapping,
  resolve invoices + deposit account from Postgres, post Payments + Deposit to QBO
- Idempotency via sibling .applied.json written after each successful QBO post
- --dry-run flag prints all payloads and makes zero QBO writes
- Check number normalization (strip leading zeros) handles bank-vs-remittance mismatch
- dev/fin/4183_mapping_20260518.json: 11 checks, $18,962.12 total, 2026-05-18 batch
2026-05-19 00:37:31 -04:00
ef9b31e7c2 feat(260519-0oz-01): add QboPaymentCreatePayload, QboDepositCreatePayload types and createPayment/createDeposit methods to QboClient
- Add QboPaymentCreatePayload + QboDepositCreatePayload interfaces to lib/types/qbo.ts
- Add createPayment(payload) and createDeposit(payload) public methods to QboClient
- Both methods use existing private this.request<T>() with POST + minorversion=65
- Both methods throw descriptively if QBO returns no Id in response
2026-05-19 00:36:01 -04:00
5f4ccb9c56 fix(dashboard): correct NOW() timezone conversion for KPI/trend queries
NOW() returns TIMESTAMPTZ. The pattern
  (NOW() AT TIME ZONE 'UTC' AT TIME ZONE $userTz)::date
double-converts: first strips the tz designation (keeping UTC wall-clock as
naive TIMESTAMP), then re-interprets that wall-clock as user-local
(pushing UTC into the user-tz's UTC equivalent). For non-UTC users this
gives the WRONG date — e.g. NY user at 9pm sees "today = tomorrow's UTC
date", so opened-today returns 0.

The column-side pattern ((col AT TIME ZONE 'UTC') AT TIME ZONE $userTz)
is correct because the columns are TIMESTAMP without TZ (stored as UTC) —
only the NOW() side was buggy. Replace with (NOW() AT TIME ZONE $userTz)
everywhere.

Affects: dashboard overview/trends, mobile dashboard/engagement/finance.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 21:58:35 -04:00
f32403ee35 fix(theme): guard ThemeSessionBridge sync with hasSynced ref
The dep-only fix wasn't enough: better-auth's useSession (nanostores) can
re-emit on focus / store refresh, transitioning session.user.id through
undefined and back. Each transition re-fires the effect, which then calls
setTheme(session.user.theme) with the stale cached value and reverts the
user's selection.

Track "have we synced this tab session" with a ref. After the first
successful sync, no subsequent effect fire can revert.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 21:58:35 -04:00