LumiBaseDocs

POST-GA Walkthrough — Completed

Overview

All 9/9 main tasks in the POST-GA phase have been delivered, covering the backend, database schema, AI Copilot, frontend UI, and automated testing.


Task 1: [AI] A real LLM provider ✅

Replaced the mock analyzeIntent() with a real LLM provider abstraction.

New files

  • apps/cms/src/services/llm-provider.ts — the provider abstraction, with 4 implementations:
    • OpenAIProvider — calls the OpenAI Chat Completions API (gpt-4o-mini by default), tool-calling format
    • AnthropicProvider — calls the Anthropic Messages API (claude-sonnet-4-20250514 by default), native tool_use
    • WorkersAIProvider — calls the Cloudflare Workers AI REST API, OpenAI-compatible tool format
    • EchoProvider — the backward-compatible keyword matcher (legacy behaviour), used when no API key is present
    • A createLLMProvider(env) factory — picks the provider from the LLM_PROVIDER env var
    • A system prompt combining CMS context with safety rules

Modified files

  • apps/cms/src/env.ts — added 7 env vars: LLM_PROVIDER, OPENAI_API_KEY, ANTHROPIC_API_KEY, WORKERS_AI_ACCOUNT_ID, WORKERS_AI_API_TOKEN, WORKERS_AI_GATEWAY
  • apps/cms/src/routes/ai.ts — fully rewritten: analyzeIntent() replaced with llmProvider.chat() → parse tool_calls → execute via the harness

Task 2: [AI] Context memory ✅

Added conversation history to the AI Copilot — messages now persist across sessions.

New schema

  • packages/database/src/schema/ai.ts — 2 new tables:
    • ai_conversations (id, siteId, userId, title, createdAt, updatedAt)
    • ai_messages (id, conversationId, role, content, toolCalls jsonb, metadata jsonb, createdAt)

New routes

  • apps/cms/src/routes/ai.ts — added:
    • POST /chat now accepts an optional conversationId, creates a conversation when absent, persists messages, and loads the last 20 messages as LLM context
    • GET /conversations — list conversations (sorted by updatedAt desc)
    • GET /conversations/:id/messages — get the messages in a conversation
    • DELETE /conversations/:id — delete a conversation and cascade its messages

Frontend

  • apps/studio/src/components/ai-assistant.tsx — added:
    • A conversationId state, passed in the request body
    • A dropdown to pick a previous conversation (loaded from GET /conversations)
    • A "New conversation" button
    • A delete-conversation button
    • Auto-loading messages when switching conversation

Task 3: [AI] RAG skills ✅

Added the aiSuggestField and aiContentAssist skills plus an embedding service.

New files

  • apps/cms/src/services/embedding-service.ts — a provider abstraction:
    • OpenAIEmbeddingProvider — text-embedding-3-small (1536 dims)
    • WorkersAIEmbeddingProvider — @cf/baai/bge-base-en-v1.5 (768 dims)
    • EchoEmbeddingProvider — deterministic pseudo-embeddings for testing
    • A cosineSimilarity() helper
    • A createEmbeddingProvider(env) factory

New schema

  • packages/database/src/schema/ai.ts — the ai_embeddings table:
    • JSONB vector storage (note: migrate to pgvector when ANN search is needed)
    • Indexed by siteId + collection, itemId

Skills registry

  • packages/ai-skills/src/skills.ts — 2 new skill definitions:
    • aiSuggestField — suggest a field from a description plus the existing schema
    • aiContentAssist — generate/edit field content using RAG context

Harness wiring

  • apps/cms/src/services/ai-harness.ts — added:
    • A generateFieldSuggestions() helper — pattern matching across 16 field types
    • An aiSuggestField handler — wired to SchemaService
    • An aiContentAssist handler — a placeholder for the full LLM integration
    • Widened the service type: 'schema' | 'items' | 'ai'

Task 4: [BE] Materialized collection writes ✅

Upgraded from physical DDL to automatic synchronisation on every item change.

New files

  • apps/cms/src/services/materialize-service.ts — the DDL operations:
    • createPhysicalTable() — initialise the mat_{target} table.
    • refreshPhysicalTable() — synchronise via TRUNCATE + INSERT INTO ... SELECT from items.
    • dropPhysicalTable() — drop the physical table when the configuration is removed.
    • installAutoRefreshTrigger() — a PG trigger that automates the refresh.
    • queryPhysicalTable() — read straight from the physical table for the Delivery API.

Modified files

  • apps/cms/src/routes/materialize.ts — mount the endpoints and wire table creation in.
  • apps/cms/src/services/item-service.ts — added the auto-refresh mechanism:
    • After every write (create, patch, softDelete) the system checks and automatically triggers/enqueues a refresh for the matching materialized collections (completing Sub-task A).

Task 5: [BE] Multi-region DO sharding ✅

New files

  • apps/cms/src/realtime/shard-config.ts — region mapping:
    • 60+ IATA colo codes → 5 regions (wnam, enam, weur, eeur, apac)
    • getRegionFromColo(), getShardKey(), getLocationHint(), isShardingSupported()
    • Docker fallback: an undefined location hint → a single instance

Modified routes

  • apps/cms/src/routes/realtime.ts — added:
    • Region detection from the cf.colo request property
    • Shard key format {siteId}:{region} for DO naming
    • locationHint passed to siteRoom.get(id, { locationHint })
    • The region param forwarded to the DO via query string

Task 6: [FE] Marketplace browser UI ✅ (Sub-task B)

Built the extension search-and-install page inside Studio.

New files

  • apps/studio/src/modules/settings/marketplace-page.tsx — the Marketplace UI:
    • A card grid showing each extension's details, publisher, version and type.
    • Keyword search plus live filtering by category (Module, Layout, Display, …).
    • A detail modal showing the description, capabilities, and the list of requested permissions.
    • Display of the publisher's cryptographic signature (Verified Signature).
    • An Install button calling the install API, switching to an "Installed" badge on success.

Modified files

  • apps/studio/src/router.tsx — lazy-loaded routing for /settings/marketplace.
  • apps/studio/src/components/app-shell.tsx — added the Marketplace link to the Settings menu.

Task 7: [FE] Flows visual editor ✅ (Sub-task C)

Built a drag-and-drop automation graph editor.

Library added

  • @xyflow/react (React Flow) for graph rendering and connection events.

New files

  • apps/studio/src/modules/automation/flow-editor.tsx — the visual graph canvas:
    • A left palette of operation blocks (Condition, Transform, HTTP, Mail, Log, Sleep, Database CRUD).
    • A drag-and-drop canvas where handles connect into success (Next) or failure (OnError) edges.
    • A right-hand Config Panel that changes with the selected node type.
    • Graph persistence as JSON via PATCH /api/v1/flows/:id, plus a Test Run.
  • apps/studio/src/modules/automation/flow-node-types.tsx — registers the custom nodes with their icons and handles.

Modified files

  • apps/studio/src/modules/automation/flows-page.tsx — added create and edit buttons leading into the editor.
  • apps/studio/src/router.tsx — added the /automation/flows/$id and /automation/flows/new routes.

Task 8: [BE] SCIM token rotation + audit ✅ (Sub-task D)

Hardened SCIM security: authentication via database-hashed tokens, with rotation and activity logging.

New schema

  • packages/database/src/schema/access.ts — the scim_tokens table:
    • id, siteId, tokenHash (SHA-256), label, createdBy, expiresAt, revokedAt, lastUsedAt, createdAt.
    • Indexed on siteId + tokenHash.

New files

  • apps/cms/src/routes/scim-admin.ts — the token management surface (requires a Logto JWT):
    • POST / — mint a new token (returns the plaintext once only).
    • GET / — list the metadata of existing tokens.
    • DELETE /:id — revoke a token.
    • POST /:id/rotate — mint a new token and expire the old one after a 24-hour grace period.

Modified files

  • apps/cms/src/routes/scim.ts — changed the auth middleware:
    • Hash the bearer token with SHA-256 before looking it up in scim_tokens.
    • Take siteId straight from the token, so an X-Lumi-Site header cannot spoof the tenant.
    • Write SCIM activity (scim.user.create, scim.group.patch, …) to the activity table.
  • apps/cms/src/index.ts — mount the /scim-tokens router.

Task 9: [OPS] Multi-tenant isolation testing (k6) ✅ (Sub-task E)

Measure data isolation and probe for tenant-leak holes.

New files

  • apps/cms/k6/helpers/setup-tenants.js — provisions two independent test sites with tokens/collections and tears them down automatically.
  • apps/cms/k6/cross-site-leak.js — a k6 script running 5 isolation scenarios:
    • Scenario 1 — Data: write on site A, check whether site B can see it (expected: no).
    • Scenario 2 — Auth: use site A's token against site B's API (expected: rejected, 403).
    • Scenario 3 — SCIM: use site A's SCIM token with a spoofed site-B header to create a user (expected: forced back to site A).
    • Scenario 4 — Search: a search on site A returns none of site B's results.
    • Scenario 5 — Realtime: a WebSocket subscriber on site A receives none of site B's change events.

Modified files

  • apps/cms/src/routes/admin.ts — added APIs letting an admin create and delete a site, to support the k6 script.

🛠️ Test & build results ✅ (Sub-task F)

1. TypeScript typecheck

  • Fixed every cast issue around Durable Objects, destructuring returned rows, missing import dependencies, and React Flow's Node<any> generic.
  • pnpm typecheck passes across all 11 packages in the monorepo.

2. Unit/integration/property testing

  • The full test suite (pnpm test) passes, 62/62 tests green, with no regressions.
Last modified: 24/08/2026