Data Ownership & Storage
Related documentation: Backend implementation · Backend Core · Backend Auth · Promoters Module Backend
Data Ownership & Storage — Kisum Platform
Section titled “Data Ownership & Storage — Kisum Platform”Purpose
Section titled “Purpose”This document defines where every critical piece of data is stored,
which system owns it, and why.
This is one of the most important documents in the platform because:
- it prevents duplicated logic
- it prevents inconsistent data
- it defines clear system boundaries
- it guides migration from legacy systems
Mandatory persistence (MUST)
Section titled “Mandatory persistence (MUST)”Where this document assigns data to Core DB with a concrete table name, that table MUST exist in the Platform Core PostgreSQL database. The authoritative DDL is Backend Core (sections 6–9): migrations are non-negotiable—companies, company_addresses, company_profiles, company_social_links, company_documents, modules, packages, addons, package_modules, addon_modules, company_subscriptions, company_addons, company_entitlement_versions, entitlement_history, and billing_products must be created as specified.
Rows marked computed (enabled modules for API output) are not a physical entitlements table. Everything else in the Core column below is mandatory, not optional.
Core Principle
Section titled “Core Principle”Core = company & commercial truth
Auth = identity & access truth
Promoters Module Backend = promoter-facing/shared business data
Modules = domain-specific business data
Master Data Ownership Table
Section titled “Master Data Ownership Table”| Data | Source of Truth | Database | Table | Why |
|---|---|---|---|---|
| Company tenant / commercial identity | Core | Core DB | companies | Company master record, tenant lifecycle, creation source, and commercial identity live in one authoritative place. Includes had_trial boolean (lifetime “this company has consumed its free trial”) — auto-flipped to true by UpsertBasicSubscription / UpsertCompanyAddon whenever a status='trial' row lands; never reverts. |
| Users | Auth | Auth DB | users | Identity, authentication, sessions, and user lifecycle |
| Company profile/contact metadata | Core | Core DB | companies, company_addresses, company_profiles, company_social_links, company_documents | Company contact, address, profile, social links, and documents belong with the company master domain, not Auth. |
| Billing catalog/provider mapping | Core | Core DB | billing_products | External billing/provider product mapping belongs to Core commercial ownership. |
| Packages | Core | Core DB | packages | Commercial product structure. Per-row audience text CHECK IN ('promoter','venue') decides which marketing-site /pricing persona tab the row appears under. Per-row trial_enabled boolean + trial_days int decides whether self-serve signup provisions the row as status='trial' (no card) or status='active' (card up front via Xendit). |
| Modules | Core | Core DB | modules | Defines platform capabilities and feature gates |
| Add-ons | Core | Core DB | addons | Commercial extensions mapped to modules. Same audience + trial_enabled + trial_days columns as packages; addon-level trial_enabled only matters for the standalone “add a single trialable addon” flow (existing customer, companies.had_trial=false). At new-signup time, addons inherit the package’s trial window. |
| Company Basic subscription | Core | Core DB | company_subscriptions | Which package (e.g. Basic) the company has; keyed by company_id |
| Company add-on state | Core | Core DB | company_addons | Active add-ons per company; keyed by company_id |
| Entitlement version / history | Core | Core DB | company_entitlement_versions, entitlement_history | Version bumps and audit for cache invalidation |
| Enabled modules (commercial) | Core | (computed) | (API + joins; not one table named entitlements) | Derived from catalog mappings + active company_subscriptions / company_addons |
| Permissions | Auth | Auth DB | permissions | Defines what actions exist in system |
| Roles | Auth | Auth DB | roles | Defines delegation and authority levels |
| User Memberships | Auth | Auth DB | memberships | Defines user ↔ company relationship |
| Business Unit Memberships | Auth | Auth DB | business_unit_memberships | Defines user ↔ business-unit relationship inside a company; mandatory for Finance-scoped access. |
| Membership Modules | Auth | Auth DB | membership_modules | Defines module access per user |
| Membership Permissions | Auth | Auth DB | membership_permissions | Defines granular permissions |
| Effective Access | Auth | Redis | access:* | Computed access for enforcement |
| Shared Entities | Base | Base DB | events, artists | Shared business data. Events: canonical event truth lives with the Promoter module (Base). Venue stores only a reference row (venue_events.promoter_event_id) or a placeholder for off-platform promoters that can be reconciled later — see Events ownership below. |
| Finance Data | Finance | Finance DB | invoices, income, vendors, xero connections, etc. (see Finance module) | Domain-specific financial persistence. companyId / businessUnitId on Finance rows are foreign references to Core canonical ids; company/BU master truth stays in Core, not Finance. |
| Market Data | Market | Market DB | contracts, deals | Domain-specific |
| Venue Data | Venue | Venue DB | venues, venue_spaces, venue_bookings, venue_contracts, venue_deposits, venue_events (references/placeholders only), venue_operational_checklist_templates, venue_operational_checklists, venue_operational_tasks | Domain-specific venue operations. Canonical shared-party master data should still come from Market where possible. Canonical event master truth comes from the Promoter module — Venue stores either a reference (promoter_event_id) or a placeholder (is_placeholder = true) that can be reconciled via POST /api/v1/events/:id/link-promoter. Finance-grade obligations/settlements remain Finance-owned; Venue reports (utilization / revenue / profitability / customers) read aggregations from Venue tables and, when needed, pull Finance rows over HTTP (GET /api/income, GET /api/bills) without persisting them. |
| AI Data | AI | AI DB | predictions | Domain-specific |
| Ticketing Data | Ticketing platform (Backend-Nextkt, standalone) | Own Postgres | organizations, users, organization_members, api_keys, ticketing_events, event_inventory, reservations, ticket_issuances, payment_records, settlements, etc. | A standalone platform that owns its own tenancy + auth (organizations = company_id, operator users/roles, global buyers, per-org api_keys) plus all ticketing state. References the Promoter event by promoter_event_id only — never owns events. Per-ticket fee pushed to Finance (POST /api/income). Not a Kisum Core/Auth tenant — Kisum integrates as a white-label partner; see Ticketing Addon Backend. |
| Self-serve signup draft (between session + finalize) | Checkout | Shared Redis | checkout:pending:{referenceId} | Short-lived (~30 min) buffer between POST /api/checkout/session and finalize. Wiped on success; not a tenant store. See Checkout. |
| Xendit Customer / Recurring Plan ids | Checkout (passthrough) | Core DB | companies.metadata.xendit | Stamped at company creation as a reference for the recurring webhook. Xendit itself is the source of truth for the customer + plan rows. |
Note on Core: Table names and DDL are normative in Backend Core. Any row in this table that lists a Core DB table name MUST be implemented in the database. Use company_subscriptions, not a generic subscriptions name alone.
Adjunct AI / MCP tooling (Chat-Kisum-MCP-Node)
Section titled “Adjunct AI / MCP tooling (Chat-Kisum-MCP-Node)”The Chat-Kisum-MCP-Node Compose stack may host optional conversation/memory Postgres and stateless MCP HTTP tool services beside a Fastify agent. That Postgres is operational/auxiliary for chat threads only—not users, memberships, companies, catalogs, subscriptions, Finance/Artist master data. Do not treat it as a second identity or tenant store. MCP tools must consume Kisum/external APIs according to repo configuration without becoming a parallel SoT for platform master data. Canonical doc: Chat-Kisum-MCP-Node (AI agent + MCP).
MCP tooling (Backend-MCP-Kisum)
Section titled “MCP tooling (Backend-MCP-Kisum)”Backend-MCP-Kisum is also adjunct MCP infrastructure, but in a separate Cloudflare Workers form. It exposes tools to compatible clients and must not be treated as a second source of truth for companies, memberships, entitlements, or business-domain master records.
RAG retrieval layer (Backend-Kisum-RAG-KB-LLM)
Section titled “RAG retrieval layer (Backend-Kisum-RAG-KB-LLM)”Backend-Kisum-RAG-KB-LLM stores embeddings, vectors, and retrieval payloads in Qdrant and reads source data from PostgreSQL plus some legacy Mongo ingest paths. Those artifacts are retrieval indexes only. They do not replace the underlying source tables as master truth.
Payments BFF (System-Kisum-Checkout)
Section titled “Payments BFF (System-Kisum-Checkout)”The System-Kisum-Checkout Next.js app at checkout.kisum.io owns the marketing self-serve signup + Xendit payment flow. It is not a master-data owner. The only persistent state it touches is a short-lived Redis “signup draft” under checkout:pending:{referenceId} (TTL ~30 min). All user / company / membership / permission / subscription writes go through the existing Auth and Core internal APIs — Checkout never writes Auth or Core tables directly. Canonical doc: Checkout (self-serve + Xendit).
Adjunct AI prototype (AI-Event-Intelligence-Copilot-Prototype)
Section titled “Adjunct AI prototype (AI-Event-Intelligence-Copilot-Prototype)”The AI-Event-Intelligence-Copilot-Prototype repository currently stores operational copilot data such as conversation sessions, cached artist metrics, computed artist scores, and related lookup material in its own runtime data stores. Those records are local to the prototype’s AI workflow and must not be treated as canonical platform ownership for companies, memberships, commercial entitlements, finance truth, or shared-party master data.
Current docs position:
- prototype-local sessions/AI caches belong to the prototype runtime only
- platform master identity/access still belongs to Auth
- commercial tenant truth still belongs to Core
- venue/market/finance truth remains with their documented domain owners
Canonical doc: AI Event Intelligence Copilot Prototype.
Ticketing platform (Backend-Nextkt) — standalone
Section titled “Ticketing platform (Backend-Nextkt) — standalone”The Backend-Nextkt repository is an independent ticketing platform (no longer a Kisum-auth module). It owns its own tenancy + auth in its own Postgres — organizations (id = company_id), operator users + organization_members (roles), global buyers, per-org api_keys — plus all ticketing state, with company_id on every domain table and every query carrying WHERE company_id = $1 (deliberate exceptions: global buyers, system sweeps, and public storefront discovery).
- Does NOT own the Event. Canonical event truth stays with the Promoter module; ticketing references it by
promoter_event_id(=publicEventId) only. - Owns its own identity/access — operators are local accounts with role→permission resolution; no Kisum JWT/JWKS, no
/auth/me/access, no Core entitlement. Kisum integrates as a white-label partner (per-org API key) and as a read-only venue/artist data source (proxies). - Money flow: consumer payments via the host’s Xendit gateway; per-ticket platform fee + settlement recorded locally then pushed to Finance (
POST /api/income). Finance owns the payout math.
Canonical doc: Ticketing Addon Backend (Backend-Nextkt).
Key Explanations
Section titled “Key Explanations”Company identity vs commercial state in Core
Section titled “Company identity vs commercial state in Core”Tenant UUID: The platform uses a stable company_id (UUID) everywhere (Auth memberships, Core commercial rows, Base data, etc.).
What Core MUST persist (all required): A companies master table, companion tables company_addresses, company_profiles, company_social_links, company_documents, catalog tables modules, packages, addons, package_modules, addon_modules, plus company_subscriptions, company_addons, company_entitlement_versions, entitlement_history, and billing_products — each MUST exist per Backend Core. These are not optional stubs.
What Core owns: company existence, lifecycle status, creation source, basic contact/billing linkage fields, and commercial state keyed by the same company_id.
What Auth owns instead: memberships, roles, invitations, module grants, permission grants, and effective access computation against that same company_id.
Relational rule: company_subscriptions, company_addons, company_entitlement_versions, and entitlement_history should all reference companies.id inside Core DB.
Users → Auth
Section titled “Users → Auth”Stored in: Auth DB → users
Why:
- identity and authentication
- session lifecycle
- token management
Memberships → Auth
Section titled “Memberships → Auth”Stored in: Auth DB → memberships
Defines:
- which companies user belongs to
- role within company
Business-unit memberships are also required and stored in: Auth DB → business_unit_memberships
Defines:
- which business units inside a company the user belongs to
- BU-scoped role within that company
Permissions → Auth
Section titled “Permissions → Auth”Stored in: Auth DB → permissions
Defines:
- system actions
- API access control
Roles → Auth
Section titled “Roles → Auth”Stored in: Auth DB → roles
Defines:
- delegation capability
- authority levels
Membership Grants → Auth
Section titled “Membership Grants → Auth”Stored in:
- membership_modules
- membership_permissions
Defines:
- what modules user can access
- what actions user can perform
Packages / Modules / Add-ons → Core
Section titled “Packages / Modules / Add-ons → Core”Stored in:
- packages (incl.
audience,trial_enabled,trial_days) - modules (
typeis now constrained tobase|addon) - addons (incl.
audience,trial_enabled,trial_days)
Module taxonomy (after 2026-05-21 Base/Addon Modules Rework)
Section titled “Module taxonomy (after 2026-05-21 Base/Addon Modules Rework)”Four base modules — exactly one per company, chosen at signup, determines which product app the user is handed off to:
| key | persona | consumer frontend |
|---|---|---|
promoter | Event promoter / organizer (renamed from legacy basic) | Frontend-Kisum |
venue | Venue management (promoted from addon) | Frontend-Kisum-Venues |
artist | Artist / Agent | not built yet — artist.* permission keys seeded ahead |
vendor | Vendor | not built yet — empty permission catalog |
Four addon modules — any base can attach any subset:
| key | what it adds |
|---|---|
finance | Finance backoffice (Backend-Kisum-Finance / Frontend-Kisum-Finance) |
touring | Touring / routing (not built yet — empty permission catalog) |
ai | AI Copilot, AI reports |
venue_marketplace | Promoter → Venue booking flow (not built yet — empty permission catalog) |
Not a Kisum addon:
ticketing(repoBackend-Nextkt) became a standalone platform with its own auth + tenancy (organizations, operator users + roles, buyers, per-org API keys). It is not a Core module/addon row and seeds no rows in Auth — by design. Kisum integrates as a white-label partner (per-org API key) and as a read-only venue/artist data source. See Ticketing Addon Backend and Modules Roadmap.
The legacy standalone market addon/module was removed from the taxonomy — its artist/agency marketplace and booking-network role is now documented under the artist base module, and the current runtime repository is Backend-Kisum-Artists.
Defines:
- product structure
- feature availability
- per-persona catalog (
audience IN ('promoter','venue','artist','vendor')) - trial eligibility at signup
Public surface: Core exposes a small browser-facing catalog read at:
GET /public/packages?audience=promoter|venue[&key=…]GET /public/addons?audience=promoter|venue[&keys=ai,finance]
Both are CORS-allow-listed via CORS_PUBLIC_ORIGINS (comma-separated; empty means fail-closed). They ship a slimmed shape (no tax_code, no internal flags) — anything sensitive is stripped at the boundary. Used by Frontend-Kisum-Website /pricing + /create to render persona tabs and decide trial-vs-paid. Every other Core route remains internal-key-gated.
One trial per company, lifetime: companies.had_trial is flipped to true automatically inside UpsertBasicSubscription / UpsertCompanyAddon whenever a status='trial' row lands; never reverts. Read back via GET /internal/companies/{id}/trial-eligibility. Used by Checkout when an existing customer asks to add a single trialable addon.
Commercial entitlements → Core
Section titled “Commercial entitlements → Core”Persisted: companies, company_subscriptions, company_addons, company_entitlement_versions, entitlement_history (and catalog/mapping tables). See Backend Core.
Computed “enabled modules” for API responses: derived from active Basic + active add-ons and catalog mappings—not a single table literally named entitlements.
Defines:
- what the company has purchased commercially (normalized in Core APIs)
Effective Access → Auth
Section titled “Effective Access → Auth”Computed:
effective_access = entitlements ∩ membership_grants
Cached in Redis for performance.
Base Data → Base
Section titled “Base Data → Base”Stored in:
- events
- artists (see Artist ownership split below — directory master is no longer Mongo-only)
Used by all modules.
Artist ownership split (2026-06-07)
Section titled “Artist ownership split (2026-06-07)”Artist-related data is intentionally split across three stores. Full per-endpoint registry: Artist Source of Truth Registry.
| Concern | Source of truth | Access from Promoters browser |
|---|---|---|
| Directory / profile — list, search, identity, bio, genres (display), platform account links, team, roster, booking graph | Backend-Kisum-Artists Postgres | GET /api/artists (list/search/detail migrated), /api/artists-network/*, /api/v2/artists* |
| Analytics / enrichment — stats, demographics, discography, chart presence, predictions, trend, Soundcharts/Viberate/Songstats/Spotify UUIDs | Mongo kisum_data.artists_v2 + data_analyst.artist_data + Kworb charts.* | Legacy GET /api/artists/:id/stats, /demographics, /discography, etc. |
| Promoter tenant CRM — events lineup refs, offers, avails | Mongo Promoters collections | /api/events, /api/offer, /api/avails (artist refs may still be Mongo _id in places) |
Rules:
- Do not treat Mongo
artists_v2as directory master for new reads — use Artists Postgres via Promoters machine BFF or/api/artists-network/*. - Do not route stats/analytics through Artists upstream —
getArtistFromMongopaths are deliberate. Frontend-Kisum-Artists(agency app) may call Artists/api/v1directly;Frontend-Kisum-Promotersmust use Promoters BFF only.
Events ownership: Promoter (Base) owns, Venue references or shadows
Section titled “Events ownership: Promoter (Base) owns, Venue references or shadows”Canonical event truth lives in the Promoter module (Base — Mongo events collection). The canonical cross-persona identifier is publicEventId (UUID v4). API responses also expose promoterEventId (same value) for Venue contract alignment. Mongo _id remains for legacy Promoter FE routes and internal joins.
Other modules that need to operate around an event keep references, not copies.
Venue side (Backend-Kisum-Venues). The venue_events table is a reference / shadow store, not a canonical event store. Every venue_events row is exactly one of:
- Promoter reference —
promoter_event_idset to PromoterpublicEventId,is_placeholder = false. The Venue row mirrors a canonical promoter event so finance-status, operations checklists, and reporting can hang off something local without round-tripping through Promoter. - Placeholder for off-platform promoter —
is_placeholder = true,promoter_event_idnull, plusplaceholder_promoter_name(and optionallyplaceholder_promoter_email,placeholder_notes). The promoter is not on Kisum yet; the row holds venue-side operational state until it can be reconciled.
Reconciliation:
POST /api/v1/events/{id}/link-promoter(Venue) promotes a placeholder to a canonical promoter reference once the previously off-platform promoter joins Kisum and gets a canonical promoter event id. Mirrors the sleeping-venue takeover pattern.PATCH /api/v1/events/{id}may update venue-side operational fields but never mutatespromoter_event_idoris_placeholder.
Booking ↔ event linkage:
venue_bookings.event_idandvenue_bookings.promoter_event_idcarry the linkage from booking creation through confirmation.- On confirm,
SyncBookingEventresolves the venue event in order: explicitevent_id→promoter_event_id(upsert by(company_id, promoter_event_id)) → placeholder shadow keyed onbooking_id. The resolved id is back-linked onto the booking row.
Rule:
- Venue must not invent event identity. Other modules wanting “the event” must read the Promoter-owned canonical record.
- Validation (Phase 2):
Backend-Kisum-PromotersexposesGET /internal/events/:eventId(machine key) so Venue can resolvepromoterEventIdbefore persist. Venue HTTP client wiring is Phase 3.
Canonical doc: Venue Module Backend (see Promoter ↔ Venue event linkage) and Venue Module Backend API (see Events and Bookings). Promoter contract: Backend-Kisum-Promoters/docs/PROMOTER_EVENT_ID.md.
Module Data → Modules
Section titled “Module Data → Modules”Each module stores its own data.
Example: Finance → expenses, settlements
Final Rules
Section titled “Final Rules”Core MUST store the company master row
Core NEVER stores user access
Auth NEVER stores company entitlements
Base NEVER stores identity or access
Modules NEVER compute access
One-line Summary
Section titled “One-line Summary”Core defines what exists
Auth defines who can use it
Promoters Module Backend shares data
Modules execute business logic