Skip to content

Data Ownership & Storage

Organization access and Finance workflow ownership

Section titled “Organization access and Finance workflow ownership”
  • Core owns company and business-unit master rows.
  • Auth owns users, company/BU memberships, module/permission grants, Finance roles, limits, and primary flags.
  • Persona apps administer organization access through their BFFs.
  • Finance reads Core business units and may update Finance workflow fields on existing Auth memberships only; it cannot create organization membership or BU assignment.

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


The table below is the canonical answer to “who owns this, and which app edits it”. The storage table further down answers “which database row holds it” — these are different questions, and conflating them is what produced the contradictions this page exists to prevent.

Owner = the service that holds the truth. Edited in = the only user-facing surface allowed to change it.

ConcernOwnerEdited inNotes
Company master record, commercial identityCoreAdmin (platform staff)PUT /api/companies/:id from a persona app stays 410. Only logoUrl + countryIso2 are self-serve, via narrow sub-paths.
Company persona (base package)CoreAdmin / CheckoutExactly one per company — enforced by migration 00022. Exposed as audience; becomes packageAppUrl in Auth. This is the platform’s “which app owns this company”.
Users (identity, login, sessions)AuthAuth / the persona appFinance’s /api/admin/users returns 410 on every method.
Organization invitationsAuthPersona app (Promoters / Artists / Venues)Finance cannot invite.
Company membership (user ↔ company)AuthPersona appFinance cannot create or remove membership.
Module access grantsAuthPersona appEach persona app edits only its own namespace and must preserve the others’ grants on save.
Permission grants (allow/deny rules)AuthPersona appChoices come from the Auth permission catalog (/api/access/catalog), never from frontend navigation constants.
Business-unit structure (create, rename, archive)CorePersona appFinance POST|PUT|DELETE /api/admin/business-units returns 410 org_master_write_removed.
Business-unit assignment (user ↔ BU)AuthPersona appGranting Finance access creates the assignment at Submitter level.
Department head flagAuthPersona appis_department_head on the BU membership. Not the same as primary approver.
Finance workflow role (Submitter / Approver)AuthFinanceSet on an existing BU membership only. Finance may not create the membership.
Approval limit, primary approverAuthFinanceSame rule — role metadata on an existing assignment.
Primary Finance AdminAuthFinanceis_primary_finance_admin on the company membership.
Finance domain data (invoices, bills, vendors, Xero)FinanceFinanceKeyed by canonical Core UUIDs; Finance holds no org master tables.

The rule in one line: persona apps own who is in the organization and what they can reach; Finance owns what they may do inside a Finance approval workflow. Anything that creates a person, a membership, or a business unit belongs to the persona app.

Why the split exists: Finance is an add-on. A company can drop it and must not lose its organization structure. If Finance could create users or business units, cancelling the add-on would orphan real org 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
Passkey credentialsAuthAuth DBpasskey_credentialsPublic WebAuthn credential records, counters, labels, and revocation; private keys and biometrics remain in platform credential providers
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. company_profiles.logo_url (S3-backed, migration-independent) and company_profiles.country_iso2 (migration 00021) are the two fields with a narrow self-serve write path — see Promoters directory disclosure (2026-07-15) below.
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).

For non-checkout test submissions, Backend Admin is the retry-safe orchestrator, not a new data owner: Auth still owns the user, membership, and permission rows; Core still owns company status, package/add-on subscriptions, and entitlements. A repeated approval reconciles each owner through its internal API.

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.

Company identity rule (2026-07-14): Core stores only the canonical UUID. It has no Mongo connection or legacy-company alias. Finance is PostgreSQL-only and stores that UUID directly on financial rows. Promoters may still use private legacy persistence for its event/CRM domain, but those storage identifiers must never enter Core, Finance, browser contracts, or operator rollout commands.


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, offers, availsMongo Promoters collections/api/events, /api/offer, /api/avails; new Event lineup writes store numeric Artists PostgreSQL artists.id (older records may still contain legacy ObjectId refs until re-saved)

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 cast a public Artist id to ObjectId. Marketplace officialArtistId and current Event lineup references are numeric PostgreSQL artists.id and are validated through Backend-Kisum-Artists.
  • 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.
  • Marketplace offer representation: Artists owns the listing truth and validates whether an offer is exclusive or middle_agent. Promoters carries the explicit per-target channel but cannot override the resolved listing type. Official Exclusive records belong to normal Artists booking_offers and approved agency/Core ownership; secondary records belong to middle_agent_offers and Middle Agent operator ownership. These ownership models must never be substituted for each other.

Artist directory identity & URL slugs (2026-07-10)

Section titled “Artist directory identity & URL slugs (2026-07-10)”
FieldRole
artists.idCanonical bigint primary key
artists.uuidStable UUID for network/booking filters
artists.slugUnique URL slug (migration 000032; backfilled from name or slug-shaped legacy_object_id; auto-set on insert/rename)
artists.legacy_object_idLegacy Mongo ObjectId import — still accepted in path :id as 24-hex only

Public path resolution: pkg/artistref — id → uuid → slug → Mongo hex.

Agency representation & company roster (2026-07-09, breaking)

Section titled “Agency representation & company roster (2026-07-09, breaking)”

Migration 000031 removed company_rosters. Representation truth is artist_team_assignments with required person_id (staff agent). Company context is derived from the agent’s active company_people employment — not stored on the assignment row.

StateTableMeaning
Representedartist_team_assignmentsArtist linked to a staff person + structured territories JSONB (regions/subregions/countries; legacy free-text in territories_legacy)
Needs agentcompany_needs_agent_artistsImport/holding until staff + territory assigned (promote → assignment)
Company roster (read)DerivedDistinct artists represented by agents currently at the company plus needs-agent rows
companies.roster_sizeDenormalized cacheMaintained by triggers; counts derived roster

Admin Contacts (Frontend-Kisum-AdminBackend-Kisum-Admin market-directory) is the control plane for team, locations, assignments, and needs-agent promote. PUT companies/:id/roster is no longer valid — use assignment CRUD or needs-agent promote.


Promoters directory in the Artists app — first cross-tenant disclosure (2026-07-15)

Section titled “Promoters directory in the Artists app — first cross-tenant disclosure (2026-07-15)”

Frontend-Kisum-Artists gained a Promoters directory (/promoters, /promoters/[id]) so an artist agency can see which Kisum promoter tenants exist, who works there, and what they have actually delivered. This is a data-ownership event worth calling out explicitly, not just a new screen:

This is the platform’s first cross-tenant data disclosure. Every row in Auth and Core up to this point was written on the assumption that only internal services would read it. This feature deliberately shows one tenant’s staff (name, company role, department, work email) and show history to a different tenant’s users. No consent record exists anywhere in Auth or Core for this — there is no is_public / visibility / opt_in column on any relevant table. The substitute is a visible notice in the Promoters app (Company profile), not a stored consent row. Revisit an explicit opt-out if promoters object; until then, this is the whole safeguard.

Identity ownership is unchanged — Core is the sole runtime source. The list is audience = 'promoter' Core tenants only (never the Artists industry directory, and never a promoter with no Kisum presence). Core gained a real S3-backed logo upload and a real country_iso2 (migration 00021) specifically so it could stop being blank for this feed. The legacy Promoters Mongo companies.logo / address.country_iso2 fields are read exactly once, by a one-time idempotent backfill script, and never again at runtime — Core owns both fields going forward, the same as every other profile field.

Staff exposure is deliberately narrow: name + company role + department + work email — never phone. Only active + approved users are shown (Auth’s ListUsersInCompany join gap on user state, not just membership state, is filtered on the Promoters aggregation side, not fixed at the Auth query level). “Department head” is a new, real Auth field (business_unit_memberships.is_department_head, migration 000019) — it is not an inference from MANAGER or from Finance’s is_primary_approver (see Backend Auth §5.2b).

Aggregation, not a new source of truth: Backend-Kisum-Promoters aggregates Core (identity + list filters) + Auth (staff/roles) + its own events (past shows) + Artists/Venues (name resolution) behind GET /internal/directory/promoters*; Backend-Kisum-Artists proxies that feed read-only at GET /api/v1/promoters* and never writes promoter, company, or user data. See Promoters API and Frontend Artist.

Company self-serve write got narrower, not wider. Core also exposes a narrow self-serve profile write — logo + country_iso2 only — consumed by a new Promoters BFF route pair. This deliberately re-opens a door that PUT /api/companies/:id closed with an intentional 410 in May 2026; that 410 targeted the full 849-line company-master form and stays dead. Only these two fields became self-serve. This is recorded as an explicit, scoped decision, not a silent reversal.


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

  • Core owns promoter business-unit master records and the six default templates.
  • Auth owns policy mode, teams, memberships, rules, scopes, direct denies, Finance roles/limits, primary approval assignments, invitation policy snapshots, and access audit history.
  • Promoters owns the management surface, not the master data: it drives Core to create/rename/archive business units and drives Auth to assign users to them, and it enforces scoped domain access. The rows themselves stay in Core (structure) and Auth (assignments) — Promoters stores no BU or membership truth of its own.
  • Finance owns financial records and approval execution while consuming the same Auth/Core identity and organization scope.
  • Promoters and Finance must not restore local team or business-unit permission truth.