From 98aed06f8ff4d33cd3cad9137c4f42812788f165 Mon Sep 17 00:00:00 2001 From: lorentz Date: Fri, 10 Jul 2026 12:18:05 -0400 Subject: [PATCH] docs(10): capture phase context --- .../10-CONTEXT.md | 163 ++++++++++++++++++ .../10-DISCUSSION-LOG.md | 75 ++++++++ 2 files changed, 238 insertions(+) create mode 100644 .planning/phases/10-pax8-client-auth-foundation/10-CONTEXT.md create mode 100644 .planning/phases/10-pax8-client-auth-foundation/10-DISCUSSION-LOG.md diff --git a/.planning/phases/10-pax8-client-auth-foundation/10-CONTEXT.md b/.planning/phases/10-pax8-client-auth-foundation/10-CONTEXT.md new file mode 100644 index 0000000..050c468 --- /dev/null +++ b/.planning/phases/10-pax8-client-auth-foundation/10-CONTEXT.md @@ -0,0 +1,163 @@ +# Phase 10: PAX8 Client & Auth Foundation - Context + +**Gathered:** 2026-07-10 +**Status:** Ready for planning + + +## 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. + + + + +## 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). + + + + +## 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/-client.ts` + `-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, `isConfigured()` + throw-if-missing). + PAX8's auth client should follow this shape, not the Veeam API-key shape. +- `lib/services/veeam-factory.ts` — canonical `isConfigured()` + + 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. + + + + +## 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 + `isConfigured()` + 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/.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). + + + + +## 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. + + + + +## 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.) + + + +--- + +*Phase: 10-pax8-client-auth-foundation* +*Context gathered: 2026-07-10* diff --git a/.planning/phases/10-pax8-client-auth-foundation/10-DISCUSSION-LOG.md b/.planning/phases/10-pax8-client-auth-foundation/10-DISCUSSION-LOG.md new file mode 100644 index 0000000..0e8f185 --- /dev/null +++ b/.planning/phases/10-pax8-client-auth-foundation/10-DISCUSSION-LOG.md @@ -0,0 +1,75 @@ +# Phase 10: PAX8 Client & Auth Foundation - Discussion Log + +> **Audit trail only.** Do not use as input to planning, research, or execution agents. +> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered. + +**Date:** 2026-07-10 +**Phase:** 10-pax8-client-auth-foundation +**Areas discussed:** Orders/invoices granularity + +--- + +## Gray areas presented (multiSelect) + +| Area | Description | Selected | +|------|-------------|----------| +| Match/review table shape | Model on `device_link_review` (candidate array + confidences) vs. simpler single-best-guess + status enum | | +| Product catalog scope | Full PAX8 catalog vs. lazily populated from referenced subscriptions only | | +| Orders/invoices granularity | Line-item grain with header+items tables vs. flat single table | ✓ | +| Raw payload retention | JSONB safety-net column per row vs. fully typed columns only | | + +**User selected:** Orders/invoices granularity only. The other three were left to the planner's discretion (see CONTEXT.md "Claude's Discretion"). + +--- + +## Orders/Invoices Granularity + +### Q1 — Grain + +| Option | Description | Selected | +|--------|-------------|----------| +| Two tables (header + line items) | `pax8_orders` (order id, company, order date, total, status) + `pax8_order_items` (order_id FK, product/SKU, quantity, unit price, line total). Matches PAX8's own order shape. | ✓ | +| One flat table | `pax8_order_items` only, order-level fields repeated per line. Simpler queries, no natural home for order status/totals. | | + +**User's choice:** Two tables (header + line items) + +### Q2 — Backfill window + +| Option | Description | Selected | +|--------|-------------|----------| +| Full history, no bound | Pull everything on first sync, no lookback-window column needed. | ✓ | +| Bounded lookback window | e.g. last 24 months; requires a config value and sync logic to respect it. | | + +**User's choice:** Full history, no bound + +### Q3 — Raw JSON safety net + +| Option | Description | Selected | +|--------|-------------|----------| +| Yes, keep raw_payload JSONB | Typed columns for used fields + raw_payload JSONB per row for anything not yet modeled. | ✓ | +| No, typed columns only | Fully committed to normalized columns per PROJECT.md's "avoid PAX8-API-shaped blobs" guidance. | | + +**User's choice:** Yes, keep raw_payload JSONB + +### Q4 — Currency handling + +| Option | Description | Selected | +|--------|-------------|----------| +| NUMERIC amount + currency code column | `NUMERIC(12,2)` + `CHAR(3) DEFAULT 'USD'` on orders and line items. | ✓ | +| USD-only, no currency column | `NUMERIC(12,2)` only, assume USD everywhere. | | + +**User's choice:** NUMERIC amount + currency code column + +**Notes:** No additional follow-up questions requested — user moved straight to wrap-up after the 4 questions. + +--- + +## Claude's Discretion + +- Company match/review table shape — follow `device_link_review` (migration 080) as the closest precedent, adapted for PAX8 company → Autotask company matching. +- Product catalog scope (full vs. lazy/referenced-only) — deferred to Phase 11's sync-service design; schema should not preclude either approach. +- Raw payload retention on `pax8_companies` / `pax8_subscriptions` / `pax8_products` — reasonable to apply the same JSONB safety-net pattern established for orders (D-03), for consistency, unless the planner has a specific reason not to. + +## Deferred Ideas + +None — discussion stayed within Phase 10's boundary. Company matching logic (Phase 12), product catalog population (Phase 11), the `/pax8` UI (Phase 14), and scheduler wiring (Phase 13) are already sequenced elsewhere in ROADMAP.md/REQUIREMENTS.md.