212 lines
14 KiB
Markdown
212 lines
14 KiB
Markdown
|
|
# Architecture
|
||
|
|
|
||
|
|
**Analysis Date:** 2026-05-03
|
||
|
|
|
||
|
|
## Pattern Overview
|
||
|
|
|
||
|
|
**Overall:** Single-instance Next.js 16 backend with in-process background workers (no external job queue).
|
||
|
|
|
||
|
|
**Key Characteristics:**
|
||
|
|
- App Router pages (`'use client'`) fetch data via API routes using `fetch()`
|
||
|
|
- Three auto-starting background workers via side-effect imports (sync-scheduler, analyzer, RMM overshell)
|
||
|
|
- Service layer in `lib/services/` with factories + configuration helpers
|
||
|
|
- Postgres 16 as system of record (snake_case columns), Redis for caching only
|
||
|
|
- Manual transformation from snake_case DB columns to camelCase API responses
|
||
|
|
- Better Auth 1.4 for sessions + magic link + TOTP 2FA + Microsoft OAuth
|
||
|
|
|
||
|
|
## Layers
|
||
|
|
|
||
|
|
**Route Layer (HTTP entry):**
|
||
|
|
- Purpose: Accept HTTP requests, validate auth, delegate to services, return JSON responses
|
||
|
|
- Location: `app/api/*/route.ts`, `app/*/page.tsx`
|
||
|
|
- Contains: Next.js route handlers (GET/POST/PATCH/DELETE), page components
|
||
|
|
- Depends on: Auth via `lib/auth-utils.ts`, services via `lib/services/`
|
||
|
|
- Used by: Client-side fetch calls from UI components
|
||
|
|
|
||
|
|
**Service Layer (business logic):**
|
||
|
|
- Purpose: Sync data from external APIs, run background jobs, execute workflows, store results in Postgres
|
||
|
|
- Location: `lib/services/*.ts` (~50 files) plus subdirectories (`analyzer/`, `rmm/`, `llm/`, `b2/`)
|
||
|
|
- Contains: Integration clients (autotask, datto-rmm, itglue, veeam, msgraph, etc.), sync logic, job executors, pipeline orchestration
|
||
|
|
- Depends on: Postgres client, external API clients, environment configuration
|
||
|
|
- Used by: API routes (sync endpoints, webhook handlers) and background workers
|
||
|
|
|
||
|
|
**Data Access Layer (Postgres):**
|
||
|
|
- Purpose: Query, upsert, and manage state in Postgres
|
||
|
|
- Location: `lib/services/postgres-client.ts` (singleton) + `migrations/NNN_*.sql`
|
||
|
|
- Contains: Connection pool, query builder methods (`query()`, `transaction()`, `upsert()`, `bulkUpsert()`), migration definitions
|
||
|
|
- Depends on: PostgreSQL 16 connection string from env
|
||
|
|
- Used by: All services that read/write data
|
||
|
|
|
||
|
|
**Background Workers (long-running processes):**
|
||
|
|
- Purpose: Poll for work and execute sync/analysis/RMM tasks without blocking HTTP requests
|
||
|
|
- Location: `lib/services/sync-scheduler.ts`, `lib/services/analyzer/worker.ts`, `lib/services/rmm/worker.ts`
|
||
|
|
- Contains: node-cron scheduler, polling loops with exponential backoff, state machine handlers
|
||
|
|
- Depends on: Postgres client, service layer integrations
|
||
|
|
- Used by: Auto-start on module import (side effects); HTTP routes that need them running will import the module to start them
|
||
|
|
|
||
|
|
**Component Layer (UI):**
|
||
|
|
- Purpose: Render pages, dialogs, tables, charts, and handle client-side state
|
||
|
|
- Location: `components/ui/` (shadcn primitives), `components/*/` (feature-specific), `app/*/page.tsx`
|
||
|
|
- Contains: React components, hooks for fetch + state, sonner toasts, recharts visualizations
|
||
|
|
- Depends on: API routes via `fetch()`, client auth via Better Auth SDK
|
||
|
|
- Used by: Next.js pages and other components
|
||
|
|
|
||
|
|
**Types & Schema (contracts):**
|
||
|
|
- Purpose: Define TypeScript interfaces and database schema
|
||
|
|
- Location: `lib/types/<domain>.ts`, `migrations/NNN_*.sql`
|
||
|
|
- Contains: Entity types (ticket, company, analysis, rmm_execution, etc.), sync request shapes, API response envelopes
|
||
|
|
- Depends on: (none — they define contracts)
|
||
|
|
- Used by: Services, API routes, components
|
||
|
|
|
||
|
|
## Data Flow
|
||
|
|
|
||
|
|
**Autotask Webhook → Analyzer Pipeline:**
|
||
|
|
|
||
|
|
1. **Webhook ingress** (`POST /api/webhooks/autotask`) — Public endpoint (no auth), HMAC-verified inside handler via `lib/services/webhook-service.ts`
|
||
|
|
2. **Enqueue job** — If ticket.created, fires webhook handler in `lib/services/webhook-service.ts` which inserts `analyzer_jobs` row with `status='queued'`
|
||
|
|
3. **Worker poll** — `lib/services/analyzer/worker.ts` auto-starts in production; every 2s polls for `analyzer_jobs.status='queued'`, claims one with `FOR UPDATE SKIP LOCKED`
|
||
|
|
4. **Pipeline orchestration** — `lib/services/analyzer/pipeline.ts` runs 7 stages:
|
||
|
|
- Stage 0: Preprocess (filter noise, compute `content_hash` for idempotency)
|
||
|
|
- Stage 1: Triage (Haiku — categorize, extract entities)
|
||
|
|
- Stage 2: IT Glue retrieval (if configured, redacted docs only via `itglue-search.ts`)
|
||
|
|
- Stage 3: Deep analysis (Sonnet — summary, gaps, root cause)
|
||
|
|
- Stage 4: Deep reasoning (Opus — optional, skipped above $2.00 cost ceiling)
|
||
|
|
- Stage 5: Persist to `analyzer_analyses` + stage execution rows
|
||
|
|
- Stage 6: Fingerprint (Haiku — cross-ticket aggregation data)
|
||
|
|
5. **Link-aware bundles** — `lib/services/analyzer/link-discovery.ts` resolves related tickets; members get `pending_analyses` rows
|
||
|
|
6. **Aggregate reports** (optional) — Once all bundle members analyzed, `stages/aggregate-reduce.ts` fires
|
||
|
|
|
||
|
|
**Periodic Sync Cadence:**
|
||
|
|
|
||
|
|
1. **Cron trigger** — `lib/services/sync-scheduler.ts` (node-cron singleton) reads `sync_schedules` table; fires at configured times
|
||
|
|
2. **Entity sync** — `lib/services/entity-sync.ts` per entity type (tickets, companies, resources, etc.), incremental via `lastTrackedModificationDateTime` when supported
|
||
|
|
3. **Postgres upsert** — `postgresClient.bulkUpsert()` writes batches to DB tables (`tickets`, `companies`, etc.)
|
||
|
|
4. **Integration-specific syncs** — Datto RMM devices/alerts, IT Glue configs/contacts, Veeam agents/alarms, Engagement data, Zoom, Duo, etc.
|
||
|
|
|
||
|
|
**RMM Overshell Execution:**
|
||
|
|
|
||
|
|
1. **User trigger** (`POST /api/rmm/execute`) — Admin user picks registered script + device
|
||
|
|
2. **Validation & rate limit** — `lib/services/rmm/executor.ts` validates script ID, resolves device, enforces per-user limit (50 / 24h)
|
||
|
|
3. **Queue insertion** — Insert `pending` row in `rmm_executions` table, call Datto `client.runQuickJob()`
|
||
|
|
4. **Worker poll** — `lib/services/rmm/worker.ts` (5s cadence) polls in-flight executions, queries Datto for result status
|
||
|
|
5. **Output parsing** — Script's `parseOutput()` method transforms Datto output; result stored in `rmm_executions`
|
||
|
|
|
||
|
|
**LogLift Evidence Ingest:**
|
||
|
|
|
||
|
|
1. **Webhook** (`POST /api/rmm/loglift/upload`) — Public, `x-openclaw-key` header auth
|
||
|
|
2. **Decompression** — Download gzipped JSON from B2, decompress, cap at 100 MB (zip-bomb guard)
|
||
|
|
3. **Storage** — Slim summary to `loglift_uploads` table, full payload to B2 via `lib/services/b2/client.ts`
|
||
|
|
4. **Auto-audit** — Resolve device → Autotask company → IT Glue config; if unique match, fire asset-first audit
|
||
|
|
|
||
|
|
**IT Glue Write-back:**
|
||
|
|
|
||
|
|
1. **Audit runner** (`lib/services/analyzer/asset-audit/runner.ts`) — Post-analysis, runs LLM audit against IT Glue config/flexible asset
|
||
|
|
2. **Results** — Persisted to `itglue_audit_logs`, linked via `itglue_ticket_xrefs`
|
||
|
|
3. **Revert** — Patch `/api/analyzer/itglue/configurations/[id]/revert/[writeId]` rolls back changes
|
||
|
|
|
||
|
|
**State Management:**
|
||
|
|
|
||
|
|
- **HTTP requests:** Stateless; session from Better Auth cookie
|
||
|
|
- **Background jobs:** State in Postgres (status columns: `queued` → `in_flight` → `complete` / `failed`)
|
||
|
|
- **Caching:** Redis (optional, used for integration health checks, cache duration varies)
|
||
|
|
- **Authorization:** Checked in route handlers via `requireAuth()` / `requireAdmin()` / `requirePermission()` from `lib/auth-utils.ts`
|
||
|
|
|
||
|
|
## Key Abstractions
|
||
|
|
|
||
|
|
**Factory Pattern (Integration Clients):**
|
||
|
|
- Purpose: Lazy-load integration clients with configured credentials; provide `is<Name>Configured()` helper to check env vars
|
||
|
|
- Examples: `lib/services/autotask-factory.ts`, `lib/services/datto-rmm-factory.ts`, `lib/services/msgraph-factory.ts`
|
||
|
|
- Pattern: Export `getAutotaskClient()`, `getDattoRmmClient()`, `getMsgraphClient()` with caching; throw if credentials missing; bundled with `is<Name>Configured()` for upstream checks
|
||
|
|
- Why: Decouples client initialization from route handlers; allows conditional feature gates per env
|
||
|
|
|
||
|
|
**PostgresClient Singleton:**
|
||
|
|
- Purpose: Single connection pool for all Postgres queries; auto-lazy-initializes; provides ORM-like query builder
|
||
|
|
- Examples: `postgresClient.query()`, `postgresClient.transaction()`, `postgresClient.upsert()`, `postgresClient.bulkUpsert()`
|
||
|
|
- Pattern: Private constructor, static `getInstance()`, pool initialized on first use
|
||
|
|
- Why: Prevents connection leaks; provides consistent interface across ~50 services
|
||
|
|
|
||
|
|
**Sync Service (Entity-Agnostic):**
|
||
|
|
- Purpose: Incremental/full sync of any entity type from external API → Postgres
|
||
|
|
- Examples: `lib/services/entity-sync.ts` (Autotask), `lib/services/itglue-sync-service.ts`, `lib/services/veeam-sync-service.ts`
|
||
|
|
- Pattern: Reads `lastTrackedModificationDateTime` from last_sync table; queries external API; batches upsert via `bulkUpsert()`
|
||
|
|
- Why: Codifies the "last sync timestamp + incremental pull + batch insert" pattern across integrations
|
||
|
|
|
||
|
|
**Analyzer Pipeline (7 Stages):**
|
||
|
|
- Purpose: Orchestrate multi-stage LLM analysis with fallbacks, cost guards, and persistence
|
||
|
|
- Examples: `lib/services/analyzer/pipeline.ts`, `lib/services/analyzer/stages/*.ts`
|
||
|
|
- Pattern: Each stage returns structured output (gap analysis, category, cost estimate); cost guard checks ceiling before Stage 4; all stages persisted to `analyzer_stage_executions`
|
||
|
|
- Why: Allows cost control (skip expensive Opus above $2.00), idempotency (content_hash), and debugging (inspect each stage's output)
|
||
|
|
|
||
|
|
**Background Worker Polling Loop:**
|
||
|
|
- Purpose: Auto-start in production, poll for work, claim rows with `FOR UPDATE SKIP LOCKED`, execute, persist result
|
||
|
|
- Examples: `lib/services/analyzer/worker.ts`, `lib/services/rmm/worker.ts`, `lib/services/sync-scheduler.ts`
|
||
|
|
- Pattern: Side-effect import auto-starts on module load; exponential backoff if no work; row locking for multi-instance safety (analyzer), or single-instance gate (sync scheduler)
|
||
|
|
- Why: Keeps background work out of HTTP request path; analyzer is safe to scale (row locking); sync scheduler should run on one instance only
|
||
|
|
|
||
|
|
**Better Auth Roles (RBAC):**
|
||
|
|
- Purpose: Define three roles (`user`, `admin`, `super-admin`) with per-resource permissions
|
||
|
|
- Examples: `lib/auth.ts` (auth config), `lib/permissions.ts` (permission matrix), `lib/auth-utils.ts` (runtime checks)
|
||
|
|
- Pattern: Route handler calls `requireAdmin()` / `requirePermission()`, which decode session and check role; middleware only verifies session cookie exists
|
||
|
|
- Why: Separates auth (middleware) from authorization (route handler); role-based gates are checked at the point of use
|
||
|
|
|
||
|
|
## Entry Points
|
||
|
|
|
||
|
|
**HTTP Pages (Authenticated):**
|
||
|
|
- Location: `app/*/page.tsx`
|
||
|
|
- Triggers: Browser navigation to any route except public paths
|
||
|
|
- Responsibilities: Render page shell with `AppNavigation` + `PageHeader`, fetch data from API, render client-side components, handle toast/dialog interactions
|
||
|
|
|
||
|
|
**HTTP API Routes (Public & Authenticated):**
|
||
|
|
- Location: `app/api/*/route.ts`
|
||
|
|
- Triggers: `fetch()` from client, external webhooks (Autotask, Zabbix, RMM), scheduler HTTP calls
|
||
|
|
- Responsibilities: Validate auth/webhook signature, delegate to service layer, return JSON response with appropriate status (200, 401, 403, 503, 500)
|
||
|
|
|
||
|
|
**Webhook Handlers (Public):**
|
||
|
|
- Location: `app/api/webhooks/autotask`, `app/api/zabbix/webhook`, `app/api/rmm/loglift`
|
||
|
|
- Triggers: External systems (Autotask, Zabbix, OpenClaw) POST events
|
||
|
|
- Responsibilities: Verify HMAC or custom header, parse event, enqueue jobs or upsert data, return 200 (even on error so Autotask doesn't deactivate)
|
||
|
|
|
||
|
|
**Sync Endpoints (Public, called by scheduler):**
|
||
|
|
- Location: `app/api/sync/*`, `app/api/datto-rmm/sync`, `app/api/itglue/sync`, `app/api/veeam/sync`, etc.
|
||
|
|
- Triggers: `lib/services/sync-scheduler.ts` fires HTTP POST at configured times
|
||
|
|
- Responsibilities: Call sync service, update `last_sync` timestamp, return 200 on success or error message
|
||
|
|
|
||
|
|
**Background Workers (Auto-starting, in-process):**
|
||
|
|
- Location: `lib/services/sync-scheduler.ts` (cron), `lib/services/analyzer/worker.ts` (2s poll), `lib/services/rmm/worker.ts` (5s poll)
|
||
|
|
- Triggers: Auto-starts as side effect of module import; runs indefinitely in production
|
||
|
|
- Responsibilities: Poll for work from DB, claim row, execute, persist result, handle errors + logging
|
||
|
|
|
||
|
|
## Error Handling
|
||
|
|
|
||
|
|
**Strategy:** Defensive; assume external APIs can fail, return 200 on webhook failures (so Autotask doesn't deactivate), log all errors, flag analyses for human review if cost ceiling exceeded.
|
||
|
|
|
||
|
|
**Patterns:**
|
||
|
|
|
||
|
|
- **Webhook handlers** — Return 200 even if processing fails; log error so ops can investigate via audit log
|
||
|
|
- **API route handlers** — `try/catch`, return `NextResponse.json({ error, message }, { status })` with conventions:
|
||
|
|
- 401: Session missing or invalid
|
||
|
|
- 403: Authenticated but lacks permission
|
||
|
|
- 503: Missing/bad integration config (e.g., Autotask API key not set)
|
||
|
|
- 500: Runtime error (query failed, external API timeout, etc.)
|
||
|
|
- **Analyzer pipeline** — Cost ceiling guard at $2.00; above that, Stage 4 (Opus) skipped, analysis flagged with `needs_review=true`
|
||
|
|
- **Sync services** — Incremental sync errors log + re-trigger on next schedule; full sync errors persist `last_error` to `sync_schedules` table
|
||
|
|
- **Background workers** — Stale jobs reset on worker boot (no recovery); exponential backoff on empty polls; errors logged with job ID for manual inspection
|
||
|
|
|
||
|
|
## Cross-Cutting Concerns
|
||
|
|
|
||
|
|
**Logging:** Console (stdout) in all services; elevated to syslog or Datadog in production. Analyzer logs all LLM calls + cost to `analyzer_cost_audit` table for billing reconciliation.
|
||
|
|
|
||
|
|
**Validation:** Explicit checks in route handlers where it matters (e.g., device ID exists before RMM execute); no centralized validation framework. Zod used for auth/admin forms only.
|
||
|
|
|
||
|
|
**Authentication:** Better Auth session stored in Postgres; cookie `Auth` + `Auth.Secure` sent on all requests. `middleware.ts` verifies session cookie exists for authenticated pages. Role checks happen in route handlers.
|
||
|
|
|
||
|
|
**Authorization:** Per-resource permissions defined in `lib/permissions.ts` (tickets, configItems, admin, users, roles, auditLog, settings, itglue, rmm). API routes call `requirePermission(resource, action)` to enforce. UI hides links based on `session.user.role`.
|
||
|
|
|
||
|
|
**Rate Limiting:** RMM execute endpoint has per-user limit (50 scripts / 24h via `lib/services/rate-limiter.ts`). No global rate limiter.
|
||
|
|
|
||
|
|
**Integration Health:** `lib/services/integration-health.ts` polls each integration's health (e.g., Autotask token expiry, last sync age); stores status in `integration_health` table; fires alerts if integration is down or sync is stale.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
*Architecture analysis: 2026-05-03*
|