diff --git a/.windsurf/workflows/kiosk.md b/.devin/workflows/kiosk.md similarity index 100% rename from .windsurf/workflows/kiosk.md rename to .devin/workflows/kiosk.md diff --git a/.windsurf/workflows/plan.md b/.devin/workflows/plan.md similarity index 100% rename from .windsurf/workflows/plan.md rename to .devin/workflows/plan.md diff --git a/.planning/research/questions.md b/.planning/research/questions.md new file mode 100644 index 0000000..c73fd75 --- /dev/null +++ b/.planning/research/questions.md @@ -0,0 +1,20 @@ +# Research Questions + +Open questions surfaced during exploration, not yet investigated. Pull into the +relevant phase's research step when picked up. + +## RESEARCH-pax8-company-identifiers + +**Raised:** 2026-07-10, during exploration of [[SEED-002]] (PAX8 Integration) + +**Question:** Does the PAX8 API expose a stable identifier for companies beyond +name — a domain, external/tenant ID, or similar — that could serve as a more +reliable join key to Autotask companies than fuzzy name matching? + +**Why it matters:** The PAX8 integration scope defaulted to "fuzzy name match at +sync time, flag unmatched/ambiguous for manual review" for mapping PAX8 +companies to Autotask companies. That's a reasonable starting point, but if the +API exposes something like a primary domain, it would be materially more +reliable and worth using instead (or as a tiebreaker for ambiguous name +matches). Check the PAX8 API docs (`companies` endpoint) before committing to +fuzzy-name as the permanent strategy. diff --git a/.planning/seeds/SEED-002-pax8-integration.md b/.planning/seeds/SEED-002-pax8-integration.md new file mode 100644 index 0000000..b71bbed --- /dev/null +++ b/.planning/seeds/SEED-002-pax8-integration.md @@ -0,0 +1,92 @@ +--- +id: SEED-002 +status: dormant +planted: 2026-07-10 +planted_during: v1.0 milestone (Pulse Mobile Shell Redesign), Phase 9.1 in flight +trigger_when: Whenever the developer is ready to start this as its own milestone — surface during the next /gsd-new-milestone scan +scope: Medium +--- + +# SEED-002: PAX8 Integration + +A read-only integration syncing PAX8 (vendor licensing/distribution marketplace) +data into Pulse — companies, subscriptions, product catalog, and orders/invoices — +mapped to existing Autotask companies, surfaced on a new `/pax8` page. + +## Why This Matters + +Three overlapping needs, all satisfied by the same underlying data sync: +1. **Cost reconciliation** — compare what PAX8 bills per client/subscription + against what's actually provisioned or invoiced. +2. **License visibility** — see subscription/seat counts per company alongside + other Pulse company data. +3. **Autotask cost mapping** — tie PAX8 companies and subscription costs to + Autotask companies/contracts for margin/profitability reporting. + +The developer already has a PAX8 client ID + client secret provisioned, so auth +setup is not a blocker — this seed is purely about scope/sequencing, not +credential access. + +## When to Surface + +**Trigger:** Whenever the developer wants to start this — not gated on any other +milestone closing, but naturally lands after the current Mobile Shell Redesign +wraps. Also surface if a future milestone's scope mentions: "PAX8", "license +reconciliation", "vendor subscription costs", "seat count audit", or "Autotask +contract margin". + +## Scope Estimate + +**Medium** — roughly 2–4 phases. New external client integration (auth, 4 +entities), a sync service, a new migration, a company-matching pass with manual +review for exceptions, and one new UI page. Follows an existing, well-worn +pattern in this codebase (autotask-factory, veeam-sync-service, etc.) — no new +architectural concepts, just new surface area. + +## Breadcrumbs + +- Auth: PAX8 REST API at `https://api.pax8.com/v1`, OAuth2 client-credentials. + Client ID + secret already provisioned by the developer. +- Pattern to follow: `lib/services/-client.ts` + `-factory.ts` + (`isConfigured()`) — see `lib/services/veeam-client.ts` / + `veeam-factory.ts` or `lib/services/msgraph-client.ts` / `msgraph-factory.ts` + for the shape. +- Sync pattern: `lib/services/entity-sync.ts` (Autotask) and + `lib/services/engagement-sync-service.ts` — incremental-if-supported, else + full upsert via `postgresClient.bulkUpsert()`. +- Env vars: `PAX8_*` (add to the integrations table in `CLAUDE.md` / + `INTEGRATIONS.md` once built). +- Scheduler: new `pax8-daily` schedule via `lib/services/sync-scheduler.ts`, + matching cadence of other integrations (see `engagement-daily`). +- Admin disable: wire into `/admin/integrations` (`integration_settings` table, + migration 081) like other integrations, so it can be toggled without a + container restart. +- UI: new top-level page (like `/engagement`) — company list, subscriptions, + cost breakdown. Not folded into the existing company detail modal. +- This is explicitly the first planned data source for [[SEED-003]] (General + Pulse Data Assistant) — build the Postgres schema with that eventual consumer + in mind (clean, well-typed tables; avoid PAX8-API-shaped blobs). + +## Notes + +### Entities in scope (v1) +- Companies (join key to Autotask companies) +- Subscriptions (product, seat count, billing term — current state) +- Products/Catalog (SKUs, categories — needed to make subscriptions readable) +- Orders/Invoices (historical line items — needed for actual cost reconciliation, + not just current-state seats) + +### Key design forks (decided during exploration) +- **Read-only** — no write-back to PAX8 (no seat adjustments, no orders) in v1. +- **Company matching: fuzzy name match at sync time** — auto-match by name + similarity, flag unmatched/ambiguous companies for manual review. See + [[RESEARCH-pax8-company-identifiers]] — worth checking whether PAX8 exposes a + more stable identifier (domain, external ID) before committing to fuzzy + matching as the permanent strategy. +- **New dedicated `/pax8` page**, not folded into company detail — daily sync + via the scheduler, not on-demand-only. + +### Anti-goals +- Not building write access (seat changes, placing orders) in v1. +- Not building the chatbot/NL query layer here — that's [[SEED-003]], a + separate initiative that consumes this data once it exists. diff --git a/.planning/seeds/SEED-003-general-pulse-data-assistant.md b/.planning/seeds/SEED-003-general-pulse-data-assistant.md new file mode 100644 index 0000000..3b8d4d4 --- /dev/null +++ b/.planning/seeds/SEED-003-general-pulse-data-assistant.md @@ -0,0 +1,85 @@ +--- +id: SEED-003 +status: dormant +planted: 2026-07-10 +planted_during: v1.0 milestone (Pulse Mobile Shell Redesign), Phase 9.1 in flight +trigger_when: After PAX8 (SEED-002) and ideally other core data sources are mirrored into Postgres — surface during the next /gsd-new-milestone scan +scope: Large +--- + +# SEED-003: General Pulse Data Assistant + +A natural-language chatbot interface over Pulse's own data generally — tickets, +companies, engagement, finance, and PAX8 once it exists — not scoped to any +single integration. + +## Why This Matters + +Surfaced while scoping [[SEED-002]] (PAX8 integration): the developer wants +"the ability to ask questions through a chatbot style interface" as one of the +motivations for pulling PAX8 data in, but on reflection the real want is broader +than PAX8 — a general assistant that happens to need PAX8 (and other sources) +as data it can draw on, not a PAX8-specific bot. + +This is a meaningfully different initiative from any single data integration: +it needs a query/tool-calling layer that can span multiple Postgres tables +safely, decide what's answerable, and present results — not just sync data +into a table. + +## When to Surface + +**Trigger:** After PAX8 data (SEED-002) — and ideally other core sources +(tickets, engagement, finance) — are mirrored into Postgres and stable. Also +surface if a future milestone's scope mentions: "chatbot", "natural language +query", "ask Pulse", "data assistant", or "LLM interface over dashboard data". + +## Scope Estimate + +**Large** — likely its own multi-phase milestone. Needs: query/tool-calling +architecture (what can the LLM call — raw SQL? scoped read helpers per domain?), +guardrails against unsafe/expensive queries, a chat UI surface (new page, or a +persistent widget across pages?), conversation state/history, and an answer +format that handles tabular results well (not just prose). + +## Breadcrumbs + +- Existing LLM plumbing to reuse rather than rebuild: `@anthropic-ai/sdk` + already wired for the AI Ticket Analyzer pipeline + (`lib/services/analyzer/pipeline.ts`, `lib/services/analyzer/stages/*.ts`), + plus `ai-triage-service.ts`. Provider is per-request (`anthropic` | + `openrouter`) — same pattern likely applies here. +- Analyzer's cost-ceiling guard (Stage 4 skipped above $2.00 estimated cost) is + a pattern worth mirroring for a chat interface that could otherwise run + unbounded queries/tokens per question. +- IT Glue data has a hard redaction requirement before hitting an LLM + (`lib/services/analyzer/itglue-search.ts`) — any assistant that can see IT + Glue-sourced data must route through the same redaction, not raw client + output. +- `postgresClient` singleton (`lib/services/postgres-client.ts`) is the only + DB access path — any tool-calling layer built for this assistant should call + through it, not open a second connection path. +- Data sources this assistant should eventually reach: tickets (Autotask sync), + companies, engagement snapshots (`engagement_snapshots`), finance/QBO data, + and PAX8 once [[SEED-002]] lands. + +## Notes + +### Key design forks (decide during brainstorming, not now) +- **Tool-calling over scoped read helpers vs. raw SQL generation** — raw + text-to-SQL is riskier (injection, runaway queries, schema drift); scoped + helpers per domain (e.g. `getCompanySubscriptionCosts(companyId)`) are safer + but need maintenance as new questions come up. Current lean: scoped helpers, + mirroring the analyzer's structured-stage-output philosophy. +- **Where it surfaces** — dedicated page vs. a persistent chat widget + available across desktop and mobile. Not decided. +- **Read-only vs. can it trigger actions** — e.g. could it kick off a sync, or + strictly answer questions? Current lean: read-only, at least for v1. +- **Conversation memory** — single-turn Q&A vs. multi-turn session with + history. Affects whether this needs new schema (a `chat_sessions` / + `chat_messages` table) beyond the query layer itself. + +### Anti-goals +- Not a replacement for the existing dashboards/pages — a supplement for + ad-hoc questions those views don't answer directly. +- Not scoped to any single integration (started life as a "PAX8 chatbot" idea, + deliberately broadened).