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”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
Company Access Ownership Matrix
Section titled “Company Access Ownership Matrix”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.
| Concern | Owner | Edited in | Notes |
|---|---|---|---|
| Company master record, commercial identity | Core | Admin (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) | Core | Admin / Checkout | Exactly 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) | Auth | Auth / the persona app | Finance’s /api/admin/users returns 410 on every method. |
| Organization invitations | Auth | Persona app (Promoters / Artists / Venues) | Finance cannot invite. |
| Company membership (user ↔ company) | Auth | Persona app | Finance cannot create or remove membership. |
| Module access grants | Auth | Persona app | Each persona app edits only its own namespace and must preserve the others’ grants on save. |
| Permission grants (allow/deny rules) | Auth | Persona app | Choices come from the Auth permission catalog (/api/access/catalog), never from frontend navigation constants. |
| Business-unit structure (create, rename, archive) | Core | Persona app | Finance POST|PUT|DELETE /api/admin/business-units returns 410 org_master_write_removed. |
| Business-unit assignment (user ↔ BU) | Auth | Persona app | Granting Finance access creates the assignment at Submitter level. |
| Department head flag | Auth | Persona app | is_department_head on the BU membership. Not the same as primary approver. |
| Finance workflow role (Submitter / Approver) | Auth | Finance | Set on an existing BU membership only. Finance may not create the membership. |
| Approval limit, primary approver | Auth | Finance | Same rule — role metadata on an existing assignment. |
| Primary Finance Admin | Auth | Finance | is_primary_finance_admin on the company membership. |
| Finance domain data (invoices, bills, vendors, Xero) | Finance | Finance | Keyed 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.
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 |
| Passkey credentials | Auth | Auth DB | passkey_credentials | Public WebAuthn credential records, counters, labels, and revocation; private keys and biometrics remain in platform credential providers |
| 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. 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 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).
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 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.
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.
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, offers, avails | Mongo 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_v2as 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
officialArtistIdand current Event lineup references are numeric PostgreSQLartists.idand are validated through Backend-Kisum-Artists. - 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.- Marketplace offer representation: Artists owns the listing truth and validates whether an offer is
exclusiveormiddle_agent. Promoters carries the explicit per-target channel but cannot override the resolved listing type. Official Exclusive records belong to normal Artistsbooking_offersand approved agency/Core ownership; secondary records belong tomiddle_agent_offersand 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)”| Field | Role |
|---|---|
artists.id | Canonical bigint primary key |
artists.uuid | Stable UUID for network/booking filters |
artists.slug | Unique URL slug (migration 000032; backfilled from name or slug-shaped legacy_object_id; auto-set on insert/rename) |
artists.legacy_object_id | Legacy 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.
| State | Table | Meaning |
|---|---|---|
| Represented | artist_team_assignments | Artist linked to a staff person + structured territories JSONB (regions/subregions/countries; legacy free-text in territories_legacy) |
| Needs agent | company_needs_agent_artists | Import/holding until staff + territory assigned (promote → assignment) |
| Company roster (read) | Derived | Distinct artists represented by agents currently at the company plus needs-agent rows |
companies.roster_size | Denormalized cache | Maintained by triggers; counts derived roster |
Admin Contacts (Frontend-Kisum-Admin → Backend-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 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
Promoters Permissions V2 ownership
Section titled “Promoters Permissions V2 ownership”- 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.