- listAllCompanies/listAllSubscriptions/listAllProducts multi-page concat
- size=200 param assertion, single-page no-infinite-loop case
- Assert every request is GET with Authorization: Bearer header
- ALTER TABLE pax8_subscriptions to add price/partner_cost/currency
- Additive, idempotent (IF NOT EXISTS), matches currency convention from 091
- Applied to dev DB via docker exec (Postgres init won't re-run on existing volume)
Pattern mapper output for Phase 11 was missing from the initial plan
commit. Decision coverage gate also flagged D-02/D-07/D-08 as
implemented-but-uncited in 11-02-PLAN.md's must_haves.truths block —
added explicit citations so the translation gate passes cleanly.
- 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)
- 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
- 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)
- 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
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.
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.
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.
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.
- 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
- 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
- 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