# Phase 10: PAX8 Client & Auth Foundation - Context

**Gathered:** 2026-07-10
**Status:** Ready for planning

<domain>
## Phase Boundary

Pulse can authenticate to the PAX8 REST API (`api.pax8.com/v1`) via OAuth2
client-credentials, and the Postgres schema for all PAX8 entities exists —
companies, subscriptions, product catalog, orders/order-line-items, and a
company-match review queue. No sync logic, no company-matching logic, no
`/pax8` UI, and no scheduler wiring in this phase — those are Phase 11-14.
This phase proves the auth + schema foundation only.

</domain>

<decisions>
## Implementation Decisions

### Orders / Invoices Schema
- **D-01:** Two-table design — `pax8_orders` (header: order id, company,
  order date, total, status) + `pax8_order_items` (line-item detail:
  `order_id` FK, product/SKU, quantity, unit price, line total). Matches
  PAX8's own order shape (an order has N line items) and avoids repeating
  order-level fields on every line.
- **D-02:** No lookback-window bound. Sync (built in a later phase) pulls
  full order history PAX8's API returns on first sync — no "earliest
  synced" or window-config column needed in this phase's migration.
- **D-03:** Both `pax8_orders` and `pax8_order_items` carry a `raw_payload
  JSONB` column alongside typed columns, as a safety net for PAX8 fields
  not yet modeled. Mirrors the `itglue-search.ts` precedent of not
  discarding API data even when only a subset is used today.
- **D-04:** Monetary amounts use `NUMERIC(12,2)` + a `currency CHAR(3)
  DEFAULT 'USD'` column on both orders and line items — cheap insurance
  against a future non-USD client without requiring a schema migration
  later.

### Claude's Discretion
The user chose to discuss only Orders/Invoices granularity. The following
gray areas were surfaced but explicitly left to the planner/executor,
default to the closest existing codebase pattern:

- **Company match/review table shape** — model on `device_link_review`
  (migration `080_device_xref_company_id.sql`): a conflicts/review queue
  with candidate match(es), confidence, `resolved_at`/`resolved_by_user_id`/
  `resolution_note`. Adapt field names for PAX8 companies →
  Autotask companies instead of device → CI matching. This table is
  created in this phase's migration but populated by Phase 12's matching
  logic — schema should anticipate that consumer without over-designing it.
- **Product catalog scope** — no explicit decision; planner may choose
  full-catalog sync or lazy/referenced-only population. This is a Phase
  11 sync-service decision more than a Phase 10 schema decision — the
  `pax8_products` table shape should accommodate either without requiring
  a redesign (e.g., don't add a "referenced only" constraint at the schema
  level).
- **Raw payload retention on other tables** — whether `pax8_companies`,
  `pax8_subscriptions`, and `pax8_products` also get a `raw_payload`
  JSONB column. Given D-03 established this pattern for orders, applying
  it consistently across all four PAX8 tables is a reasonable default
  unless the planner has a specific reason not to (e.g., a table is fully
  and confidently typed).

</decisions>

<canonical_refs>
## Canonical References

**Downstream agents MUST read these before planning or implementing.**

### Project scope & requirements
- `.planning/PROJECT.md` — Current Milestone: v2.0 PAX8 Integration section;
  states read-only scope, out-of-scope write access, and the SEED-003
  future-consumer design note ("build the Postgres schema with that eventual
  consumer in mind - clean, well-typed tables, avoid PAX8-API-shaped blobs")
- `.planning/REQUIREMENTS.md` — PAX8-01, PAX8-02 (this phase's requirement
  IDs); also PAX8-03..14 for downstream-phase awareness of what this schema
  must support later
- `.planning/seeds/SEED-002-pax8-integration.md` — original exploration:
  auth breadcrumbs (client ID/secret already provisioned), pattern
  breadcrumbs (`lib/services/<name>-client.ts` + `<name>-factory.ts`), entity
  list (companies/subscriptions/catalog/orders), and the "fuzzy name match
  at sync time, flag ambiguous for manual review" design fork
- `.planning/research/questions.md` — `RESEARCH-pax8-company-identifiers`:
  open question on whether PAX8 exposes a stable identifier (domain,
  external ID) beyond company name — worth checking PAX8's `companies`
  endpoint response shape before Phase 12 commits fully to fuzzy name
  matching. Not blocking for this phase (schema should keep the door open:
  don't build the match table assuming name is the only comparable field).

### Existing patterns to follow
- `lib/services/msgraph-client.ts` + `lib/services/msgraph-factory.ts` —
  closest existing OAuth2 client-credentials pattern (token endpoint POST,
  in-memory token + expiry cache, `is<Name>Configured()` + throw-if-missing).
  PAX8's auth client should follow this shape, not the Veeam API-key shape.
- `lib/services/veeam-factory.ts` — canonical `is<Name>Configured()` +
  singleton + throw-with-clear-message-if-missing-creds pattern (same shape
  msgraph-factory.ts follows; both are valid reference points).
- `migrations/080_device_xref_company_id.sql` (`device_link_review` table) —
  reference shape for the PAX8 company-match/review table (see Claude's
  Discretion above).
- `migrations/081_integration_settings.sql` — integration toggle table;
  PAX8's toggle row is added in Phase 13, not this phase — do not seed it
  here.
- `CLAUDE.md` "External integrations" table — env var prefix convention
  (`PAX8_*`); add PAX8 to this table and to `INTEGRATIONS.md` once the
  client exists.

</canonical_refs>

<code_context>
## Existing Code Insights

### Reusable Assets
- `lib/services/msgraph-client.ts` — direct template for OAuth2
  client-credentials token exchange (`getToken()` with expiry-aware
  in-memory caching), including the exact `fetch` + `URLSearchParams` shape
  to adapt for PAX8's token endpoint.
- `lib/services/msgraph-factory.ts` — direct template for
  `isPax8Configured()` / `getPax8Client()` / `resetPax8Client()`.

### Established Patterns
- Factory + `is<Name>Configured()` + throw-if-missing-creds is used
  uniformly across all ~15 existing integrations — no deviation expected
  here.
- Migration numbering is sequential regardless of gaps; next number after
  `090_ticket_reconcile_schedule.sql` is `091`.
- Match/review queues for ambiguous cross-system links use a dedicated
  table (`device_link_review`) rather than a status column on the primary
  entity table, keeping the "needs human review" concern separate from
  the synced data itself.

### Integration Points
- New files: `lib/services/pax8-client.ts`, `lib/services/pax8-factory.ts`,
  `lib/types/pax8.ts` (types barrel, per `lib/types/<domain>.ts` convention),
  new migration `091_pax8_tables.sql` (or similar name).
- No route/nav/UI integration in this phase (`UI hint: no` per ROADMAP.md).

</code_context>

<specifics>
## Specific Ideas

No specific UI or behavioral references were given — this phase is schema
and client plumbing only. The concrete decisions are the four Orders/
Invoices schema points (D-01 through D-04) above.

</specifics>

<deferred>
## Deferred Ideas

None — discussion stayed within phase scope. (Company matching logic,
product catalog population strategy, the `/pax8` UI, and scheduler wiring
are already sequenced into Phases 11-14 per ROADMAP.md and REQUIREMENTS.md
— not deferred from this discussion, just out of this phase's boundary.)

</deferred>

---

*Phase: 10-pax8-client-auth-foundation*
*Context gathered: 2026-07-10*
