POS Adapter Architecture Plan¶
Overview¶
POS Adapters transform data between external POS systems (Deliverect, Revel, Atlas Kitchen, Lightspeed) and Cata's Standard API. Each adapter is a set of JavaScript transformation scripts — one per integration topic — stored centrally and executed at runtime via a JS engine (goja).
Key Principles¶
- One script per POS per topic — shared across all tenants using that POS
- Scripts stored in
centraldb— not per-tenant (a Deliverect transform is the same for everyone) - AI-generated, human-reviewed — Claude generates candidate scripts, humans approve before activation
- Runtime execution in Go — goja (pure Go JS engine), no CGo dependency
The 7 Adapter Topics¶
| # | Topic | Direction | Cata Endpoint | Description |
|---|---|---|---|---|
| 1 | test_connection |
Cata → POS | POST .../provider-connections/{slug}/test |
Verify credentials work |
| 2 | list_remote_outlets |
Cata → POS | GET .../provider-connections/{slug}/remote-outlets |
Fetch POS locations for dropdown |
| 3 | sync_outlet |
POS → Cata | POST /api/v1/outlets/sync |
Import or map POS locations to Cata outlets |
| 4 | sync_products |
POS → Cata | POST /api/v1/products/sync |
Flat product sync (products + modifier groups + options) |
| 5 | order_dispatch |
Cata → POS | External POS order API | Transform Cata order → POS order format on OrderPaid event |
| 6 | order_status_update |
POS → Cata | Cata webhook endpoint (POST /api/v1/inbound/{provider}/order-status) |
Transform POS status webhook → Cata order status update |
| 7 | snooze_items |
Cata → POS | External POS API | Temporarily disable/enable items on the POS system |
Topic Details¶
1. SyncOutlet — Two modes: - Import: POS location → create new Cata outlet - Map: POS location → link to existing outlet (match by name)
2. SyncProducts — Flat only: - Pull all products from POS as flat list - Map: Product → Modifier Groups → Modifier Options (one level) - Ignore POS menu tree / categories / sections — Cata owns menu composition via Menu V2
3. OrderDispatch — Outbound: - Triggered by OrderPaid event from kds-management-service - Cata order format → adapter script → external POS createOrder API - Prices in Cata are decimals (15.50) — script converts to cents if POS requires
4. OrderStatusUpdate — Inbound:
- External POS sends webhook to Cata
- Adapter transforms POS status values → Cata order status (PENDING/ACCEPTED/PREPARING/READY/COMPLETED/CANCELLED)
- Must follow Status Transition Diagram (see pos-adapter-guidelines.md)
- Special case: COMPLETED → COMPLETED = ACK only (Cata auto-completes after X min)
Database Schema¶
-- centraldb (shared, not per-tenant)
CREATE TABLE centraldb.`adapter_scripts` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`provider` varchar(50) NOT NULL,
`topic` enum('test_connection','list_remote_outlets','sync_outlet','sync_products','order_dispatch','order_status_update') NOT NULL,
`version` varchar(20) NOT NULL,
`status` enum('candidate','active','deprecated','archived') NOT NULL DEFAULT 'candidate',
`script` mediumtext NOT NULL,
`metadata` json DEFAULT NULL,
`created_by` varchar(100) DEFAULT NULL,
`reviewed_by` varchar(100) DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_active_script` (`provider`, `topic`, `status`),
KEY `idx_provider_topic` (`provider`, `topic`)
);
Constraints: Only one active script per provider + topic at any time.
Status: Table already created in centraldb.
TODO — Revisions: Add revision column (int, auto-increment per provider+topic) to support version history, diff between revisions, and rollback. Current table uses version string — migrate to numeric revision tracking when building the UI.
Example data:
| provider | topic | version | status |
|------------|---------------------|---------|-----------|
| deliverect | sync_outlet | 1.0.0 | active |
| deliverect | sync_products | 1.0.0 | active |
| deliverect | order_dispatch | 1.0.0 | active |
| deliverect | order_status_update | 1.0.0 | active |
| revel | sync_outlet | 1.0.0 | candidate |
| revel | sync_products | 1.0.0 | candidate |
Script Lifecycle¶
candidate → (human review in UI) → active
candidate → (rejected) → archived
active → (new version generated) → deprecated → archived
AI Agent Generation Pipeline¶
┌─────────────────────────────────────────────────┐
│ POS Adapter Manager UI │
│ │
│ [Select Provider: Deliverect ▼] │
│ [Select Topic: SyncProducts ▼] │
│ [Generate Script] │
│ │ │
│ ▼ │
│ n8n Workflow │
│ ┌─────────────────────────────────────────┐ │
│ │ 1. Fetch latest Cata openapi.yml │ │
│ │ 2. Load POS knowledge for this provider │ │
│ │ 3. Load Guideline Principles │ │
│ │ 4. Call Claude API │ │
│ │ 5. Save script as "candidate" │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ JS Code Editor (Monaco/CodeMirror) │
│ ┌─────────────────────────────────────────┐ │
│ │ // @provider deliverect │ │
│ │ // @topic sync_products │ │
│ │ function transform(input) { │ │
│ │ return { products: ... }; │ │
│ │ } │ │
│ └─────────────────────────────────────────┘ │
│ │
│ [Test with Sample] [Save Draft] [Activate] │
└─────────────────────────────────────────────────┘
Claude Inputs¶
| Input | Source | Description |
|---|---|---|
| Cata Standard API | openapi.yml (auto-fetched from repo) |
Target format — what the transform must produce |
| Guideline Principles | docs/guides/pos-adapter-guidelines.md |
Rules for field mapping, price format, null handling, etc. |
| POS Knowledge | Per-provider context store (TBD) | API docs, sample payloads, field mappings for the specific POS |
POS Knowledge Management (Decided — see ADR 0005)¶
How to maintain the context Claude needs for each POS adapter was the original open question here. It is now decided:
| Option | Description | Pros | Cons |
|---|---|---|---|
| API doc links | Claude fetches POS docs at generation time | Always up-to-date | Docs may be behind auth |
| Uploaded context docs | PDF/markdown stored in DB or GCS per provider | Full control | Manual maintenance |
| Sample payload pairs | Input (POS) → expected output (Cata) examples | Best accuracy, testable | Need to collect per POS |
| Hybrid | Docs + samples + links | Best coverage | More complexity |
Decision (Hybrid): registered knowledge bases (central.knowledge_bases —
GitHub repos of API docs) plus human clarifications captured via the
generation pipeline's needs_clarification loop plus sample payload pairs,
with samples treated as authoritative when they conflict with prose. Samples are
a hard gate: no script is generated for a topic before a sample exists. See
ADR 0005 — onboarding framework
and the onboarding playbook.
Runtime Execution¶
Inbound (POS → Cata):
HTTP request from POS
→ identify provider (from outlet_providers for per-outlet flows,
provider_connections for tenant-wide flows)
→ load active script from centraldb.adapter_scripts
→ goja.RunString(script) with POS payload as input
→ validate output against Cata Standard API
→ call internal Cata endpoint (SyncOutlet / SyncProducts / OrderStatusUpdate)
Outbound (Cata → POS):
OrderPaid event
→ load Cata order
→ identify provider (from outlet_providers — per-outlet mapping)
→ load active script for order_dispatch
→ goja.RunString(script) with Cata order as input
→ POST transformed payload to external POS API
Provider Authority Model¶
A tenant on pos-integration-service can have multiple providers connected at the same time (e.g. Deliverect for some outlets, Revel for others). Three tables collaborate:
| Table | Scope | Owner | Role |
|---|---|---|---|
tenantdb.provider_connections |
Per-tenant, unique on provider_slug |
Provider settings UI | "This tenant has credentials for provider X." Required for any adapter sync from X. |
tenantdb.outlet_providers |
Per-outlet, unique on store_id |
Outlet-mapping UI | "This outlet is served by provider X with external id Y." Required before any per-outlet adapter sync targets the outlet. |
tenantdb.settings.nameOf3rdPartyKDS |
Per-tenant | Legacy | Single-provider switch used only by kds-management-service. pos-integration-service ignores it. |
Which guard runs where¶
| Call site | Guard | Source of truth |
|---|---|---|
POST /api/v1/products/sync (tenant-wide) |
validateTenantProviderConnection |
provider_connections — reject if no row for provider_slug |
POST /api/v1/outlets/sync (tenant-wide; creates the store) |
validateTenantProviderConnection |
provider_connections — UI mapping creates outlet_providers separately |
POST /api/v1/outlets/{outletId}/menu (per-outlet) |
validateOutletProvider |
outlet_providers for the outlet — must exist and provider_slug must match |
POST /api/v1/outlets/{outletId}/items/snooze (per-outlet) |
(TODO) | Should adopt validateOutletProvider |
Helpers live in internal/service/provider.go. The Cata provider (domain.ProviderCata) is always allowed for the tenant-wide guard because Cata has no provider_connections row by design.
Bootstrap order¶
For a new provider on an existing tenant:
- Admin connects credentials → inserts
provider_connectionsrow (Provider Settings page). - Admin maps outlets in the UI → inserts/updates
outlet_providersrows (Outlet Mapping page). - Adapter sync (
sync_products,sync_menu_v1,order_dispatch, etc.) runs through the standard guards above.
If an adapter call arrives before steps 1–2 are complete it is rejected with provider mismatch. This is intentional — populate the mapping tables first.
Multi-provider catalog hazard (TODO)¶
SoftDeleteOrphanItems in database/queries/items.sql does not filter by provider. If a multi-provider tenant ever calls SyncProducts with deleteOrphans=true, the call will soft-delete items belonging to other providers in the tenant. The adapter execution path (adapter_execute_service.go:170) passes deleteOrphans=false, so production traffic is safe — but the legacy POST /api/v1/products/sync REST endpoint can trigger it. Scope the delete by provider before relying on multi-provider full-replace flows.
Script Interface¶
Every transform script must export a transform function:
/**
* @param {object} input - Source payload (POS format for inbound, Cata format for outbound)
* @param {object} context - Runtime context (provider, tenantId, storeId, etc.)
* @returns {object} - Transformed payload
*/
function transform(input, context) {
// ... transformation logic
return output;
}
UI Features (POS Adapter Manager)¶
| Feature | Description |
|---|---|
| Provider list | List all POS providers with script status per topic (4 indicators) |
| Script editor | Monaco/CodeMirror JS editor with syntax highlighting |
| Generate | Trigger n8n → Claude to generate candidate script |
| Test | Run script against sample payload, show input/output side-by-side |
| Diff view | Compare candidate vs current active version |
| Activate | Promote candidate → active (deprecates previous active) |
| Version history | List all versions per provider + topic with status |
| Execution logs | View recent transform executions with success/failure |
Target POS Systems¶
| Provider | Status | Priority |
|---|---|---|
| Deliverect | First target — sample payloads already available | High |
| Revel Systems | API-pull based | Medium |
| Atlas Kitchen | Webhook with payload | Medium |
| Lightspeed | Webhook + pull | Low |
Implementation Phases¶
Phase 1: Foundation¶
- [ ] Create
centraldb.adapter_scriptstable - [ ] Build goja runtime wrapper (load script, execute transform, validate output)
- [ ] CRUD API for adapter scripts (
/api/v1/adapter-scripts/*) - [ ] Wire into existing SyncOutlet / SyncProducts / OrderDispatch flows
Phase 2: AI Generation¶
- [ ] n8n workflow: Claude generates transform scripts
- [ ] POS knowledge store (start with uploaded context docs)
- [ ] Test runner: validate script against sample payloads
Phase 3: UI¶
- [ ] POS Adapter Manager page (provider list, script status)
- [ ] JS code editor (Monaco)
- [ ] Test panel (input/output side-by-side)
- [ ] Activate/archive workflow
- [ ] Version history and diff view
Phase 4: Production Rollout¶
- [ ] Deliverect adapter (all 4 topics)
- [ ] Execution logging and monitoring
- [ ] Error alerting (failed transforms)
- [ ] Revel / Atlas / Lightspeed adapters
Related Documents¶
- Adapter Script Contracts — universal input/output shapes per topic (test_connection, list_remote_outlets, etc.)
- POS Adapter Transformation Guidelines — field mapping rules, DO/DON'T for AI generation
Per-Provider Guides¶
- Revel Systems — API key auth, test_connection + list_remote_outlets scripts ready
- Deliverect — API key auth, TODO
- Atlas Kitchen — webhook-only, TODO
- Lightspeed — OAuth, TODO