Skip to content

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”

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

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-negotiablecompanies, 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 = company & commercial truth
Auth = identity & access truth
Promoters Module Backend = promoter-facing/shared business data
Modules = domain-specific business data


DataSource of TruthDatabaseTableWhy
Company tenant / commercial identityCoreCore DBcompaniesCompany 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.
UsersAuthAuth DBusersIdentity, authentication, sessions, and user lifecycle
Company profile/contact metadataCoreCore DBcompanies, company_addresses, company_profiles, company_social_links, company_documentsCompany contact, address, profile, social links, and documents belong with the company master domain, not Auth.
Billing catalog/provider mappingCoreCore DBbilling_productsExternal billing/provider product mapping belongs to Core commercial ownership.
PackagesCoreCore DBpackagesCommercial 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).
ModulesCoreCore DBmodulesDefines platform capabilities and feature gates
Add-onsCoreCore DBaddonsCommercial 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 subscriptionCoreCore DBcompany_subscriptionsWhich package (e.g. Basic) the company has; keyed by company_id
Company add-on stateCoreCore DBcompany_addonsActive add-ons per company; keyed by company_id
Entitlement version / historyCoreCore DBcompany_entitlement_versions, entitlement_historyVersion 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
PermissionsAuthAuth DBpermissionsDefines what actions exist in system
RolesAuthAuth DBrolesDefines delegation and authority levels
User MembershipsAuthAuth DBmembershipsDefines user ↔ company relationship
Business Unit MembershipsAuthAuth DBbusiness_unit_membershipsDefines user ↔ business-unit relationship inside a company; mandatory for Finance-scoped access.
Membership ModulesAuthAuth DBmembership_modulesDefines module access per user
Membership PermissionsAuthAuth DBmembership_permissionsDefines granular permissions
Effective AccessAuthRedisaccess:*Computed access for enforcement
Shared EntitiesBaseBase DBevents, artistsShared 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 DataFinanceFinance DBinvoices, 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 DataMarketMarket DBcontracts, dealsDomain-specific
Venue DataVenueVenue DBvenues, venue_spaces, venue_bookings, venue_contracts, venue_deposits, venue_events (references/placeholders only), venue_operational_checklist_templates, venue_operational_checklists, venue_operational_tasksDomain-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 DataAIAI DBpredictionsDomain-specific
Ticketing DataTicketing platform (Backend-Nextkt, standalone)Own Postgresorganizations, 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)CheckoutShared Redischeckout: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 idsCheckout (passthrough)Core DBcompanies.metadata.xenditStamped 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).

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.

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 Postgresorganizations (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).


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.


Stored in: Auth DB → users

Why:

  • identity and authentication
  • session lifecycle
  • token management

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

Stored in: Auth DB → permissions

Defines:

  • system actions
  • API access control

Stored in: Auth DB → roles

Defines:

  • delegation capability
  • authority levels

Stored in:

  • membership_modules
  • membership_permissions

Defines:

  • what modules user can access
  • what actions user can perform

Stored in:

  • packages (incl. audience, trial_enabled, trial_days)
  • modules (type is now constrained to base | 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:

keypersonaconsumer frontend
promoterEvent promoter / organizer (renamed from legacy basic)Frontend-Kisum
venueVenue management (promoted from addon)Frontend-Kisum-Venues
artistArtist / Agentnot built yet — artist.* permission keys seeded ahead
vendorVendornot built yet — empty permission catalog

Four addon modules — any base can attach any subset:

keywhat it adds
financeFinance backoffice (Backend-Kisum-Finance / Frontend-Kisum-Finance)
touringTouring / routing (not built yet — empty permission catalog)
aiAI Copilot, AI reports
venue_marketplacePromoter → Venue booking flow (not built yet — empty permission catalog)

Not a Kisum addon: ticketing (repo Backend-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.


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)

Computed:

effective_access = entitlements ∩ membership_grants

Cached in Redis for performance.


Stored in:

  • events
  • artists (see Artist ownership split below — directory master is no longer Mongo-only)

Used by all modules.


Artist-related data is intentionally split across three stores. Full per-endpoint registry: Artist Source of Truth Registry.

ConcernSource of truthAccess from Promoters browser
Directory / profile — list, search, identity, bio, genres (display), platform account links, team, roster, booking graphBackend-Kisum-Artists PostgresGET /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 UUIDsMongo 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, availsMongo Promoters collections/api/events, /api/offer, /api/avails (artist refs may still be Mongo _id in places)

Rules:

  • Do not treat Mongo artists_v2 as directory master for new reads — use Artists Postgres via Promoters machine BFF or /api/artists-network/*.
  • Do not route stats/analytics through Artists upstream — getArtistFromMongo paths are deliberate.
  • Frontend-Kisum-Artists (agency app) may call Artists /api/v1 directly; Frontend-Kisum-Promoters must 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 referencepromoter_event_id set to Promoter publicEventId, 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 promoteris_placeholder = true, promoter_event_id null, plus placeholder_promoter_name (and optionally placeholder_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 mutates promoter_event_id or is_placeholder.

Booking ↔ event linkage:

  • venue_bookings.event_id and venue_bookings.promoter_event_id carry the linkage from booking creation through confirmation.
  • On confirm, SyncBookingEvent resolves the venue event in order: explicit event_idpromoter_event_id (upsert by (company_id, promoter_event_id)) → placeholder shadow keyed on booking_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-Promoters exposes GET /internal/events/:eventId (machine key) so Venue can resolve promoterEventId before 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.


Each module stores its own data.

Example: Finance → expenses, settlements


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


Core defines what exists
Auth defines who can use it
Promoters Module Backend shares data
Modules execute business logic