Backend Core Specification
Detailed Implementation Specification for Platform Core Backend
Section titled “Detailed Implementation Specification for Platform Core Backend”Audience: backend engineers, platform engineers, DevOps, architecture leads, platform-admin feature owners
Status: implementation specification
Scope: this document defines the Platform Core backend in full detail:
- what the Core service owns
- what it does not own
- PostgreSQL structure
- internal authentication
- request/response contracts
- how Auth calls it
- how Platform Admin writes to it
- how subscription/add-on/catalog changes are stored
- how entitlement versions are computed and invalidated
- how other services should consume it
- operational and security rules
0. Why this service exists
Section titled “0. Why this service exists”Platform Core exists to answer one question:
What does this company commercially own right now?
That means Platform Core is the commercial entitlement authority for:
- Basic subscription
- add-on modules
- package catalog
- add-on catalog
- module catalog
- package-to-module mappings
- add-on-to-module mappings
- company subscription state
- company add-on state
- entitlement versioning
Platform Core does not answer:
- who the user is
- what permissions the user has
- what tenant/company role the user has
- what a user can grant to another user
- whether a specific route is allowed for a specific user
Those belong to Auth.
1. Core service role in the full architecture
Section titled “1. Core service role in the full architecture”1.1 Final architecture rule
Section titled “1.1 Final architecture rule”Auth = identity + memberships + module grants + permissions + delegationCore = company commercial entitlementsBackends = business executionFrontend = UI consumer only1.2 Effective access rule
Section titled “1.2 Effective access rule”Auth computes final access as:
effective_access = company_entitlements ∩ membership_grantsCore only provides the left-hand side:
company_entitlements1.3 Main consumers of Platform Core
Section titled “1.3 Main consumers of Platform Core”Platform Core is consumed by:
- Auth Backend
- to resolve company entitlements during
/auth/me/access
- to resolve company entitlements during
- Platform Admin Backend
- to manage packages, add-ons, modules, company subscription state
- Optional internal jobs
- if later you introduce reconciliation, billing sync, catalog sync, or entitlement repair jobs
1.4 Platform Core is not public browser API
Section titled “1.4 Platform Core is not public browser API”Platform Core should be internal-only.
It should not be called directly by:
- browser frontend
- public clients
- mobile clients
Public flows should go through:
- Auth
- Platform Admin Backend
- business backends where appropriate
2. Service ownership
Section titled “2. Service ownership”2.1 What Platform Core owns
Section titled “2.1 What Platform Core owns”Platform Core owns the following domains.
Catalog
Section titled “Catalog”- modules
- packages
- add-ons
- mappings between packages/add-ons and modules
Company commercial state
Section titled “Company commercial state”- whether Basic subscription is active
- whether add-ons are active
- add-on start/end dates
- subscription start/end dates
- subscription status
- company entitlement version
- entitlement history / audit trail
Optional future commercial domain
Section titled “Optional future commercial domain”- Stripe product references
- Stripe price ids
- plan migrations
- trials
- renewals
- grace periods
- cancellations
- invoicing references
- self-service billing flags
2.2 What Platform Core explicitly does NOT own
Section titled “2.2 What Platform Core explicitly does NOT own”Not identity
Section titled “Not identity”- users
- passwords
- sessions
- JWT
- refresh token lifecycle
Not tenant-user access
Section titled “Not tenant-user access”- company memberships
- business unit memberships
- tenant roles
- membership module grants
- membership permissions
- delegation policies
- invitations
- invitation tokens
- invitation acceptance
- onboarding flows
- membership onboarding
Not business data
Section titled “Not business data”- events
- vendors
- venue business records
- AI outputs
- finance transactions
- market/touring documents
2.3 Source-of-truth rule
Section titled “2.3 Source-of-truth rule”Platform Core is the source of truth for:
- whether the company has Basic
- which add-ons are active
- which modules are enabled by those products
Platform Core is not allowed to become a second source of truth for:
- user permissions
- user roles
- user module grants
That remains in Auth.
2.4 Invitations are not in Core
Section titled “2.4 Invitations are not in Core”Invitations do not belong to Platform Core.
Reason
Section titled “Reason”Invitations are part of:
- user onboarding
- identity lifecycle
- company membership creation
- tenant role assignment
Those responsibilities belong to Auth Backend.
Platform Core must NOT handle
Section titled “Platform Core must NOT handle”- invitation creation
- invitation storage
- invitation email sending
- invitation acceptance
- invitation token validation
- membership creation through invitation flow
Correct ownership
Section titled “Correct ownership”- Auth Backend → invitations, onboarding, membership creation
- Platform Core Backend → packages, add-ons, modules, subscriptions, entitlements
Important rule
Section titled “Important rule”If a company is invited, approved, or onboarded through invitation flow, the invitation logic still belongs to Auth.
Core only becomes relevant after that for commercial entitlement state.
Interaction with Core
Section titled “Interaction with Core”Invitations may result in a user joining a company that already has commercial entitlements.
However:
- Core does not participate in invitation flow
- Core does not validate invitations
- Core does not create memberships
After invitation acceptance:
- Auth creates membership
- Auth later calls Core (via
/auth/me/access) to resolve entitlements
Core remains unaware of invitation lifecycle.
3. Commercial model handled by Core
Section titled “3. Commercial model handled by Core”3.1 Products supported
Section titled “3.1 Products supported”A. Basic subscription
Section titled “A. Basic subscription”Commercial product that enables:
- Core App UI
- Basic module
- Basic business experience
B. Add-on modules
Section titled “B. Add-on modules”Commercial add-ons:
- finance
- touring
- market
- venue
- ai
3.2 Important business rule
Section titled “3.2 Important business rule”A company can have add-ons without Basic.
So these are all valid:
- Basic only
- Finance only
- Market only
- Basic + Finance
- Basic + Finance + Market
- Venue + AI only
- no active Basic, but active add-ons
3.3 What Core must return
Section titled “3.3 What Core must return”Core must be able to return:
hasBasicbasePackage- active add-ons
- enabled modules
- entitlement version
4. Service location and deployment identity
Section titled “4. Service location and deployment identity”4.1 Recommended service hostname
Section titled “4.1 Recommended service hostname”core.kisum.ioOptional internal alias:
platform-core.kisum.io4.2 Recommended service name
Section titled “4.2 Recommended service name”Use one canonical service name in logs/metrics:
platform-core-backend4.3 Runtime assumptions
Section titled “4.3 Runtime assumptions”The service should:
- run as independent backend
- have its own PostgreSQL database
- be private/internal
- have health and readiness checks
- use structured logs
- use request IDs
- use strict config validation
5. Internal authentication model
Section titled “5. Internal authentication model”5.1 Who can call Platform Core
Section titled “5.1 Who can call Platform Core”Allowed callers
Section titled “Allowed callers”- Auth Backend
- Platform Admin Backend
- approved internal jobs/services
Not allowed
Section titled “Not allowed”- browser frontend
- public clients
- direct mobile apps
- arbitrary third-party clients
5.2 Required internal auth
Section titled “5.2 Required internal auth”Every internal route must require service-to-service authentication.
Allowed patterns:
- internal API key
- signed internal service JWT
- mTLS
- private network + internal header validation
Recommendation
Section titled “Recommendation”For first version:
- private network
- internal API key or signed internal service token
- clear allowlist of callers
Required headers example
Section titled “Required headers example”X-Internal-API-Key: <secret>Or, if using service JWT:
Authorization: Bearer <internal-service-token>5.3 Authorization model for callers
Section titled “5.3 Authorization model for callers”Auth Backend
Section titled “Auth Backend”Can call:
- entitlement reads
- catalog reads if needed
Platform Admin Backend
Section titled “Platform Admin Backend”Can call:
- catalog reads
- catalog writes
- company subscription writes
- company add-on writes
- company subscription summary reads
Internal jobs
Section titled “Internal jobs”Can call:
- read routes
- write routes only if explicitly allowed
6. PostgreSQL database structure
Section titled “6. PostgreSQL database structure”6.1 Database name
Section titled “6.1 Database name”platform_core_dbThis database must be separate from:
auth_db- finance DB
- market/touring DB
- main/basic DB
Same PostgreSQL cluster is acceptable, but separate database is preferred.
6.2 PostgreSQL extensions
Section titled “6.2 PostgreSQL extensions”Recommended:
pgcryptoif usinggen_random_uuid()- timezone support as standard
- JSONB support (native in Postgres)
6.3 Schema layout
Section titled “6.3 Schema layout”Recommended schema:
publicYou can later split into:
catalogentitlementsbilling
But first version can stay in public.
7. Core tables — exact structure
Section titled “7. Core tables — exact structure”7.1 modules
Section titled “7.1 modules”Purpose:
- canonical list of modules the platform understands
create table if not exists modules ( id uuid primary key default gen_random_uuid(), key text not null unique, name text not null, type text not null check (type in ('base', 'addon')), description text, is_active boolean not null default true, created_at timestamptz not null default now(), updated_at timestamptz not null default now());key values:
basicfinancemarkettouringvenueai
type:
base→ module delivered by Basic subscriptionaddon→ module delivered by add-on
7.2 packages
Section titled “7.2 packages”Purpose:
- commercial package catalog
create table if not exists packages ( id uuid primary key default gen_random_uuid(), key text not null unique, name text not null, description text, is_active boolean not null default true, created_at timestamptz not null default now(), updated_at timestamptz not null default now());Initially expected:
basic
Future expansion possible:
basic_plusenterprise- etc.
7.3 addons
Section titled “7.3 addons”Purpose:
- commercial add-on catalog
create table if not exists addons ( id uuid primary key default gen_random_uuid(), key text not null unique, name text not null, description text, is_active boolean not null default true, created_at timestamptz not null default now(), updated_at timestamptz not null default now());Expected rows
Section titled “Expected rows”- finance
- market
- touring
- venue
- ai
7.4 package_modules
Section titled “7.4 package_modules”Purpose:
- maps package → modules
create table if not exists package_modules ( package_id uuid not null references packages(id) on delete cascade, module_id uuid not null references modules(id) on delete cascade, created_at timestamptz not null default now(), primary key (package_id, module_id));Initially:
- package
basic→ modulebasic
7.5 addon_modules
Section titled “7.5 addon_modules”Purpose:
- maps addon → modules
create table if not exists addon_modules ( addon_id uuid not null references addons(id) on delete cascade, module_id uuid not null references modules(id) on delete cascade, created_at timestamptz not null default now(), primary key (addon_id, module_id));Initially:
- addon
finance→ modulefinance - addon
market→ modulemarket - addon
touring→ moduletouring - addon
venue→ modulevenue - addon
ai→ moduleai
7.6 company_subscriptions
Section titled “7.6 company_subscriptions”Purpose:
- stores company Basic subscription state
create table if not exists company_subscriptions ( id uuid primary key default gen_random_uuid(), company_id uuid not null, package_id uuid not null references packages(id), status text not null check (status in ('active', 'inactive', 'cancelled', 'expired', 'trial', 'paused')), starts_at timestamptz, ends_at timestamptz, source text, external_reference text, entitlement_version integer not null default 1, created_by text, updated_by text, created_at timestamptz not null default now(), updated_at timestamptz not null default now(), unique (company_id, package_id));company_idis the canonical company UUID- one company may have 0 or 1 active Basic package in first version
- if future multi-package model is needed, schema already supports it
sourceexamples:platform_adminself_servicebilling_sync
external_referencemay store Stripe subscription id or invoice ref later
7.7 company_addons
Section titled “7.7 company_addons”Purpose:
- stores company add-on state
create table if not exists company_addons ( id uuid primary key default gen_random_uuid(), company_id uuid not null, addon_id uuid not null references addons(id), status text not null check (status in ('active', 'inactive', 'cancelled', 'expired', 'trial', 'paused')), starts_at timestamptz, ends_at timestamptz, source text, external_reference text, created_by text, updated_by text, created_at timestamptz not null default now(), updated_at timestamptz not null default now(), unique (company_id, addon_id));Supports:
- one company with many add-ons
- add-ons active even without Basic
7.8 company_entitlement_versions
Section titled “7.8 company_entitlement_versions”Purpose:
- explicit version tracker for entitlement invalidation
This is optional because company_subscriptions.entitlement_version can be used.
But a dedicated table is cleaner when changes come from multiple rows.
create table if not exists company_entitlement_versions ( company_id uuid primary key, entitlement_version integer not null default 1, updated_at timestamptz not null default now(), updated_by text);Why recommended
Section titled “Why recommended”Because any of the following must bump the version:
- Basic activated/deactivated
- add-on activated/deactivated
- catalog mapping changes that affect a company’s enabled modules
- manual repair of entitlement state
Using a dedicated table simplifies invalidation and response generation.
7.9 entitlement_history
Section titled “7.9 entitlement_history”Purpose:
- audit trail of entitlement changes
create table if not exists entitlement_history ( id uuid primary key default gen_random_uuid(), company_id uuid not null, change_type text not null, entity_type text not null, entity_key text, previous_status text, new_status text, payload_json jsonb not null default '{}'::jsonb, source text, changed_by text, created_at timestamptz not null default now());Example values
Section titled “Example values”change_type:
basic_activatedbasic_deactivatedaddon_activatedaddon_deactivatedcatalog_updatedentitlement_repaired
entity_type:
packageaddonmappingcompany
7.10 Optional future table: billing_products
Section titled “7.10 Optional future table: billing_products”Purpose:
- hold external billing references
create table if not exists billing_products ( id uuid primary key default gen_random_uuid(), entity_type text not null check (entity_type in ('package', 'addon')), entity_id uuid not null, provider text not null, provider_product_id text, provider_price_id text, created_at timestamptz not null default now(), updated_at timestamptz not null default now());This is optional and can wait.
8. Required indexes
Section titled “8. Required indexes”Apply these indexes.
create index if not exists idx_modules_key on modules(key);create index if not exists idx_packages_key on packages(key);create index if not exists idx_addons_key on addons(key);
create index if not exists idx_package_modules_package_id on package_modules(package_id);create index if not exists idx_package_modules_module_id on package_modules(module_id);
create index if not exists idx_addon_modules_addon_id on addon_modules(addon_id);create index if not exists idx_addon_modules_module_id on addon_modules(module_id);
create index if not exists idx_company_subscriptions_company_id on company_subscriptions(company_id);create index if not exists idx_company_subscriptions_status on company_subscriptions(status);
create index if not exists idx_company_addons_company_id on company_addons(company_id);create index if not exists idx_company_addons_status on company_addons(status);
create index if not exists idx_entitlement_history_company_id on entitlement_history(company_id);create index if not exists idx_entitlement_history_created_at on entitlement_history(created_at);9. Seed data to insert
Section titled “9. Seed data to insert”9.1 Modules
Section titled “9.1 Modules”insert into modules (key, name, type, description) values('basic', 'Core App', 'base', 'Core App / Basic product module'),('finance', 'Finance', 'addon', 'Finance module'),('market', 'Market', 'addon', 'Market module'),('touring', 'Touring', 'addon', 'Touring module'),('venue', 'Venue', 'addon', 'Venue module'),('ai', 'AI', 'addon', 'AI module')on conflict (key) do nothing;9.2 Packages
Section titled “9.2 Packages”insert into packages (key, name, description) values('basic', 'Basic', 'Basic subscription that enables Core App')on conflict (key) do nothing;9.3 Add-ons
Section titled “9.3 Add-ons”insert into addons (key, name, description) values('finance', 'Finance', 'Finance add-on'),('market', 'Market', 'Market add-on'),('touring', 'Touring', 'Touring add-on'),('venue', 'Venue', 'Venue add-on'),('ai', 'AI', 'AI add-on')on conflict (key) do nothing;9.4 Package mappings
Section titled “9.4 Package mappings”insert into package_modules (package_id, module_id)select p.id, m.idfrom packages pjoin modules m on m.key = 'basic'where p.key = 'basic'on conflict do nothing;9.5 Add-on mappings
Section titled “9.5 Add-on mappings”insert into addon_modules (addon_id, module_id)select a.id, m.idfrom addons ajoin modules m on m.key = a.keyon conflict do nothing;10. Core data model rules
Section titled “10. Core data model rules”10.1 Company can have Basic without add-ons
Section titled “10.1 Company can have Basic without add-ons”Valid.
10.2 Company can have add-ons without Basic
Section titled “10.2 Company can have add-ons without Basic”Valid.
10.3 Company can have Basic plus add-ons
Section titled “10.3 Company can have Basic plus add-ons”Valid.
10.4 Company may have no active Basic and still have active add-ons
Section titled “10.4 Company may have no active Basic and still have active add-ons”Valid and must be supported everywhere.
10.5 Enabled modules algorithm
Section titled “10.5 Enabled modules algorithm”Core computes:
enabledModules = package modules from active Basic subscription + add-on modules from active company add-ons10.6 Example
Section titled “10.6 Example”Example A
Section titled “Example A”- Basic inactive
- Add-ons active: finance, market
Result:
{ "hasBasic": false, "enabledModules": ["finance", "market"]}Example B
Section titled “Example B”- Basic active
- Add-ons active: finance
Result:
{ "hasBasic": true, "enabledModules": ["basic", "finance"]}11. Endpoints — full list
Section titled “11. Endpoints — full list”There are 3 groups of endpoints:
- Catalog read
- Entitlement read
- Catalog/company write
- Admin/audit/support read
All are internal-only.
Important restriction
Section titled “Important restriction”Platform Core must not expose any endpoints related to:
- invitations
- onboarding
- user creation
- membership creation
If such endpoints are required, they must be implemented in Auth.
12. Response envelope standard
Section titled “12. Response envelope standard”12.1 Success
Section titled “12.1 Success”{ "success": true, "data": {}}12.2 Error
Section titled “12.2 Error”{ "success": false, "error": { "code": "string_code", "message": "Human readable message" }}12.3 Common error codes
Section titled “12.3 Common error codes”unauthorizedforbiddenvalidation_errornot_foundconflictinternal_errorservice_unavailable
13. Health and discovery endpoints
Section titled “13. Health and discovery endpoints”13.1 GET /health
Section titled “13.1 GET /health”Purpose:
- simple liveness probe
Response
Section titled “Response”{ "success": true, "data": { "status": "ok" }}13.2 GET /ready
Section titled “13.2 GET /ready”Purpose:
- readiness check
Must verify:
- Core DB reachable
- internal config loaded
Failure response
Section titled “Failure response”{ "success": false, "error": { "code": "not_ready", "message": "core database unavailable" }}14. Catalog read endpoints
Section titled “14. Catalog read endpoints”14.1 GET /internal/catalog/modules
Section titled “14.1 GET /internal/catalog/modules”Who can call
Section titled “Who can call”- Platform Admin Backend
- Auth Backend (optional)
- internal jobs
Purpose
Section titled “Purpose”Return full module catalog.
Response
Section titled “Response”{ "success": true, "data": { "modules": [ { "id": "uuid", "key": "basic", "name": "Core App", "type": "base", "description": "Core App / Basic product module", "isActive": true }, { "id": "uuid", "key": "finance", "name": "Finance", "type": "addon", "description": "Finance module", "isActive": true } ] }}14.2 GET /internal/catalog/packages
Section titled “14.2 GET /internal/catalog/packages”Who can call
Section titled “Who can call”- Platform Admin Backend
- internal jobs
Response
Section titled “Response”{ "success": true, "data": { "packages": [ { "id": "uuid", "key": "basic", "name": "Basic", "description": "Basic subscription that enables Core App", "isActive": true, "modules": ["basic"] } ] }}14.3 GET /internal/catalog/addons
Section titled “14.3 GET /internal/catalog/addons”Who can call
Section titled “Who can call”- Platform Admin Backend
- internal jobs
Response
Section titled “Response”{ "success": true, "data": { "addons": [ { "id": "uuid", "key": "finance", "name": "Finance", "description": "Finance add-on", "isActive": true, "modules": ["finance"] }, { "id": "uuid", "key": "market", "name": "Market", "description": "Market add-on", "isActive": true, "modules": ["market"] } ] }}15. Entitlement read endpoints
Section titled “15. Entitlement read endpoints”15.1 GET /internal/companies/{companyId}/entitlements
Section titled “15.1 GET /internal/companies/{companyId}/entitlements”Who can call
Section titled “Who can call”- Auth Backend (main consumer)
- Platform Admin Backend
- internal jobs
Purpose
Section titled “Purpose”Return the current commercial entitlement state for a company.
Path params
Section titled “Path params”companyId: canonical UUID
Response
Section titled “Response”{ "success": true, "data": { "companyId": "cmp_001", "hasBasic": false, "basePackage": null, "addons": [ { "key": "finance", "status": "active", "startsAt": "2026-04-16T00:00:00Z", "endsAt": "2026-05-16T00:00:00Z" }, { "key": "market", "status": "active", "startsAt": "2026-04-16T00:00:00Z", "endsAt": "2026-05-16T00:00:00Z" } ], "enabledModules": ["finance", "market"], "entitlementVersion": 7, "updatedAt": "2026-04-16T05:00:00Z" }}- if Basic inactive,
hasBasic = false basePackagemay benullenabledModulesstill may include add-ons- output must always be normalized
15.2 GET /internal/companies/{companyId}/subscription-summary
Section titled “15.2 GET /internal/companies/{companyId}/subscription-summary”Who can call
Section titled “Who can call”- Platform Admin Backend
- internal jobs
- Auth Backend (optional)
Purpose
Section titled “Purpose”Admin-facing summary of commercial state.
Response
Section titled “Response”{ "success": true, "data": { "companyId": "cmp_001", "hasBasic": true, "basePackage": "basic", "addons": ["finance", "market"], "status": "active", "entitlementVersion": 7 }}15.3 GET /internal/companies/{companyId}/history
Section titled “15.3 GET /internal/companies/{companyId}/history”Who can call
Section titled “Who can call”- Platform Admin Backend
- internal audit/reporting jobs
Purpose
Section titled “Purpose”Return entitlement history.
Response
Section titled “Response”{ "success": true, "data": { "companyId": "cmp_001", "history": [ { "id": "uuid", "changeType": "addon_activated", "entityType": "addon", "entityKey": "finance", "previousStatus": "inactive", "newStatus": "active", "source": "platform_admin", "changedBy": "admin_user_uuid", "createdAt": "2026-04-16T05:00:00Z" } ] }}16. Catalog write endpoints
Section titled “16. Catalog write endpoints”These are mainly consumed through the Platform Admin Backend.
16.1 POST /internal/catalog/modules
Section titled “16.1 POST /internal/catalog/modules”Who can call
Section titled “Who can call”- Platform Admin Backend only
Request
Section titled “Request”{ "key": "finance", "name": "Finance", "type": "addon", "description": "Finance module", "isActive": true}Response
Section titled “Response”{ "success": true, "data": { "id": "uuid", "key": "finance", "name": "Finance", "type": "addon", "description": "Finance module", "isActive": true }}Validation
Section titled “Validation”keyuniquetypemust bebaseoraddon
16.2 PATCH /internal/catalog/modules/{moduleId}
Section titled “16.2 PATCH /internal/catalog/modules/{moduleId}”Request
Section titled “Request”{ "name": "Finance", "description": "Finance module updated", "isActive": true}keyshould generally be immutable once used in production
16.3 POST /internal/catalog/packages
Section titled “16.3 POST /internal/catalog/packages”Request
Section titled “Request”{ "key": "basic", "name": "Basic", "description": "Basic subscription that enables Core App", "isActive": true, "moduleKeys": ["basic"]}Behavior
Section titled “Behavior”- create package row
- map module keys in
package_modules
16.4 PATCH /internal/catalog/packages/{packageId}
Section titled “16.4 PATCH /internal/catalog/packages/{packageId}”Request
Section titled “Request”{ "name": "Basic", "description": "Updated Basic description", "isActive": true, "moduleKeys": ["basic"]}Behavior
Section titled “Behavior”- update package
- replace mapping set if
moduleKeyspresent - if mapping change impacts active companies, entitlement version repair may be needed
16.5 POST /internal/catalog/addons
Section titled “16.5 POST /internal/catalog/addons”Request
Section titled “Request”{ "key": "finance", "name": "Finance", "description": "Finance add-on", "isActive": true, "moduleKeys": ["finance"]}16.6 PATCH /internal/catalog/addons/{addonId}
Section titled “16.6 PATCH /internal/catalog/addons/{addonId}”Request
Section titled “Request”{ "name": "Finance", "description": "Finance updated", "isActive": true, "moduleKeys": ["finance"]}17. Company entitlement write endpoints
Section titled “17. Company entitlement write endpoints”These are the most important write endpoints.
17.1 POST /internal/companies/{companyId}/basic
Section titled “17.1 POST /internal/companies/{companyId}/basic”Who can call
Section titled “Who can call”- Platform Admin Backend
- optional internal billing/reconciliation jobs
Purpose
Section titled “Purpose”Activate, update, pause, or deactivate Basic subscription.
Request
Section titled “Request”{ "status": "active", "startsAt": "2026-04-16T00:00:00Z", "endsAt": "2026-05-16T00:00:00Z", "source": "platform_admin", "externalReference": "sub_123"}Behavior
Section titled “Behavior”- find package
basic - upsert company subscription row
- update status/dates/source/external ref
- bump entitlement version
- write entitlement history
Response
Section titled “Response”{ "success": true, "data": { "companyId": "cmp_001", "hasBasic": true, "basePackage": "basic", "entitlementVersion": 8 }}Valid statuses
Section titled “Valid statuses”activeinactivecancelledexpiredtrialpaused
17.2 POST /internal/companies/{companyId}/addons
Section titled “17.2 POST /internal/companies/{companyId}/addons”Who can call
Section titled “Who can call”- Platform Admin Backend
- optional internal billing jobs
Purpose
Section titled “Purpose”Activate or update one add-on for a company.
Request
Section titled “Request”{ "addonKey": "finance", "status": "active", "startsAt": "2026-04-16T00:00:00Z", "endsAt": "2026-05-16T00:00:00Z", "source": "platform_admin", "externalReference": "addon_sub_123"}Behavior
Section titled “Behavior”- resolve addon by
addonKey - upsert
company_addons - bump entitlement version
- write history
Response
Section titled “Response”{ "success": true, "data": { "companyId": "cmp_001", "addonKey": "finance", "status": "active", "entitlementVersion": 9 }}17.3 POST /internal/companies/{companyId}/deactivate-addon
Section titled “17.3 POST /internal/companies/{companyId}/deactivate-addon”Optional alternative
Section titled “Optional alternative”You may choose not to create a separate endpoint and instead use the same POST /addons route with status: inactive.
That is actually preferred for simplicity.
Recommendation
Section titled “Recommendation”Use:
POST /internal/companies/{companyId}/addonsfor both activate and deactivate.
18. Versioning and cache invalidation
Section titled “18. Versioning and cache invalidation”18.1 Why entitlement version exists
Section titled “18.1 Why entitlement version exists”Auth caches merged access.
When Core changes company entitlements, Auth must know access is stale.
So Core must bump:
entitlementVersionon every meaningful entitlement change.
18.2 Events that MUST bump entitlement version
Section titled “18.2 Events that MUST bump entitlement version”- Basic activated
- Basic deactivated
- Basic expired
- add-on activated
- add-on deactivated
- add-on expired
- package mapping changed in a way that affects active companies
- add-on mapping changed in a way that affects active companies
- manual entitlement repair
18.3 How to store version
Section titled “18.3 How to store version”Preferred:
company_entitlement_versionstable
Fallback:
- version stored on
company_subscriptions
Preferred is cleaner.
18.4 How Auth consumes it
Section titled “18.4 How Auth consumes it”Auth receives:
entitlementVersionfrom Core response and uses it in cache key or stale-check logic.
Example cache key:
access:{companyId}:{membershipId}:{accessVersion}:{entitlementVersion}19. How Auth calls Core
Section titled “19. How Auth calls Core”19.1 Main runtime call
Section titled “19.1 Main runtime call”Auth calls:
GET /internal/companies/{companyId}/entitlementsAuth needs from Core:
Section titled “Auth needs from Core:”hasBasicbasePackageaddonsenabledModulesentitlementVersion
Auth then merges with:
Section titled “Auth then merges with:”- membership grants
- permissions
- delegation
Core does not perform the merge.
19.2 Failure behavior
Section titled “19.2 Failure behavior”If Core is unavailable:
- Auth should fail
/auth/me/access - return 503
- do not guess entitlements
- do not fallback to stale business assumptions
20. How Platform Admin writes to Core
Section titled “20. How Platform Admin writes to Core”20.1 General rule
Section titled “20.1 General rule”Frontend admin UI should not call Core directly.
It calls:
- Platform Admin Backend
Platform Admin Backend then calls Core internal routes.
20.2 Catalog creation flow
Section titled “20.2 Catalog creation flow”Example: create addon
Section titled “Example: create addon”- Platform Admin UI → Admin Backend
- Admin Backend validates platform-admin access
- Admin Backend calls:
POST /internal/catalog/addons
- Core writes
addons - Core returns created addon
20.3 Company activation flow
Section titled “20.3 Company activation flow”Example: activate Basic
Section titled “Example: activate Basic”- Platform Admin UI → Admin Backend
- Admin Backend validates admin permissions
- Admin Backend calls:
POST /internal/companies/{companyId}/basic
- Core upserts subscription
- Core bumps entitlement version
- Core returns updated state
- Auth access cache must become stale
20.4 Add-on activation flow
Section titled “20.4 Add-on activation flow”Example: activate Finance addon
Section titled “Example: activate Finance addon”- Platform Admin UI → Admin Backend
- Admin Backend validates platform admin
- Admin Backend calls:
POST /internal/companies/{companyId}/addons
- Core upserts addon status
- Core bumps entitlement version
- Core writes history
- Auth invalidates / rebuilds access next time
21. Request validation rules
Section titled “21. Request validation rules”21.1 UUID rules
Section titled “21.1 UUID rules”companyId,moduleId,packageId,addonIdmust be valid UUIDs where path expects UUID
21.2 Key rules
Section titled “21.2 Key rules”module.key,package.key,addon.keyshould be lowercase slug format- no spaces
- immutable after production use if possible
21.3 Status rules
Section titled “21.3 Status rules”Allowed status values:
activeinactivecancelledexpiredtrialpaused
21.4 Date rules
Section titled “21.4 Date rules”startsAt <= endsAtif both present- dates optional for manually controlled states
- server should normalize timezone to UTC
22. Error model
Section titled “22. Error model”22.1 Example validation error
Section titled “22.1 Example validation error”{ "success": false, "error": { "code": "validation_error", "message": "addonKey is required" }}22.2 Example conflict
Section titled “22.2 Example conflict”{ "success": false, "error": { "code": "conflict", "message": "addon key already exists" }}22.3 Example unauthorized
Section titled “22.3 Example unauthorized”{ "success": false, "error": { "code": "unauthorized", "message": "missing or invalid internal credentials" }}22.4 Example not found
Section titled “22.4 Example not found”{ "success": false, "error": { "code": "not_found", "message": "company not found" }}23. Service structure recommendation
Section titled “23. Service structure recommendation”Recommended Go project structure:
/backend-kisum-core /cmd/api /internal/config /internal/http /handlers /middleware /internal/catalog /internal/entitlements /internal/history /internal/repository /postgres /internal/types /internal/errors /migrations23.1 Responsibilities by package
Section titled “23.1 Responsibilities by package”/internal/catalog
Section titled “/internal/catalog”- modules CRUD
- packages CRUD
- addons CRUD
- package/addon mappings
/internal/entitlements
Section titled “/internal/entitlements”- company Basic state
- company add-on state
- enabled modules computation
- entitlement version bumping
/internal/history
Section titled “/internal/history”- write/read entitlement history
/internal/repository/postgres
Section titled “/internal/repository/postgres”- all SQL access
24. Operational requirements
Section titled “24. Operational requirements”24.1 Logging
Section titled “24.1 Logging”Must log:
- entitlement reads
- catalog writes
- company subscription writes
- add-on writes
- entitlement version bumps
- invalid internal auth attempts
24.2 Metrics
Section titled “24.2 Metrics”Track:
- entitlement lookup count
- entitlement lookup latency
- catalog mutation count
- company mutation count
- 4xx/5xx rate
- history insert failures
24.3 Readiness
Section titled “24.3 Readiness”/ready must verify:
- DB connectivity
- config loaded
24.4 Backup
Section titled “24.4 Backup”Core DB must be backed up independently.
25. Security requirements
Section titled “25. Security requirements”- Core must not be public browser API
- internal routes must require service auth
- DB credentials must be unique to Core
- no cross-service DB writes from Auth to Core DB
- no user permissions stored in Core
- no JWT generation in Core
- no tenant role interpretation in Core
26. What Auth must NOT expect from Core
Section titled “26. What Auth must NOT expect from Core”Auth must not expect Core to return:
- permissions
- delegation
- tenant roles
- user grants
- membership objects
Core returns only:
- company commercial state
- enabled modules
- entitlement version
- product catalog state if asked
27. What Platform Core must NOT do
Section titled “27. What Platform Core must NOT do”- do not read Auth DB directly
- do not validate frontend user JWTs as if it were public API
- do not compute user-level access
- do not duplicate user grants
- do not become tenant-authorization service
- do not manage invitations or onboarding flows
28. Minimum implementation checklist
Section titled “28. Minimum implementation checklist”Database
Section titled “Database”- create
platform_core_db - create all tables
- create indexes
- seed catalog
Service
Section titled “Service”- bootstrap service
- add health/ready
- add internal auth middleware
- add structured logging
Catalog APIs
Section titled “Catalog APIs”-
GET /internal/catalog/modules -
GET /internal/catalog/packages -
GET /internal/catalog/addons -
POST /internal/catalog/modules -
PATCH /internal/catalog/modules/{id} -
POST /internal/catalog/packages -
PATCH /internal/catalog/packages/{id} -
POST /internal/catalog/addons -
PATCH /internal/catalog/addons/{id}
Entitlement APIs
Section titled “Entitlement APIs”-
GET /internal/companies/{companyId}/entitlements -
GET /internal/companies/{companyId}/subscription-summary -
GET /internal/companies/{companyId}/history -
POST /internal/companies/{companyId}/basic -
POST /internal/companies/{companyId}/addons
Invalidation
Section titled “Invalidation”- entitlement version table or equivalent
- version bump logic
- history writing
- invalidation strategy documented
29. Final summary
Section titled “29. Final summary”Platform Core is the commercial entitlement backend.It owns packages, add-ons, modules, and company product state.It does not own users, permissions, or membership access.Auth calls Core to get company entitlements.Platform Admin writes to Core to change commercial state.Core returns normalized enabled modules and entitlementVersion.30. Access model layers (REFERENCE — DO NOT IMPLEMENT HERE)
Section titled “30. Access model layers (REFERENCE — DO NOT IMPLEMENT HERE)”Platform Core participates in a 3-layer access model but only owns Level 1.
Level 1 — Company entitlements (OWNED BY CORE)
Section titled “Level 1 — Company entitlements (OWNED BY CORE)”Defines what the company has purchased:
- Basic
- Finance
- Market
- Touring
- Venue
- AI
Source of truth: Platform Core DB
Level 2 — Membership module grants (OWNED BY AUTH)
Section titled “Level 2 — Membership module grants (OWNED BY AUTH)”Defines which modules a user can access.
Core MUST NOT store or evaluate this.
Level 3 — Permissions (OWNED BY AUTH)
Section titled “Level 3 — Permissions (OWNED BY AUTH)”Defines actions inside modules.
Core MUST NOT store or evaluate this.
Critical rule
Section titled “Critical rule”Core returns entitlements ONLYCore never computes user access31. Relationship with Auth (STRICT)
Section titled “31. Relationship with Auth (STRICT)”Platform Core is a dependency of Auth.
Auth → Core → Auth → FrontendResponsibilities
Section titled “Responsibilities”Core:
- returns company entitlements
- returns enabled modules
- returns entitlement version
Auth:
- merges entitlements with user grants
- computes effective access
- returns final access model
Forbidden usage
Section titled “Forbidden usage”Core must NEVER:
- return user-level access
- validate permissions
- validate roles
- decide if a request is allowed
32. Effective access boundary
Section titled “32. Effective access boundary”Core MUST NOT implement:
effective_access = entitlements ∩ grantsThis logic belongs ONLY to Auth.
33. Delegation awareness (READ-ONLY CONTEXT)
Section titled “33. Delegation awareness (READ-ONLY CONTEXT)”Core is aware that delegation exists, but:
- does not enforce delegation
- does not validate delegation
- does not store delegation rules
Delegation is owned by Auth.
34. Frontend interaction rule
Section titled “34. Frontend interaction rule”Frontend must NOT call Platform Core directly for access.
Allowed frontend calls:
- Auth
- Base backend
- module backends
- admin backend
35. Backend interaction rule
Section titled “35. Backend interaction rule”Business backends must NOT:
- call Core for user access decisions
- use Core as authorization service
They must rely on:
Auth → /auth/me/access36. Final enforcement rule
Section titled “36. Final enforcement rule”Core = commercial truthAuth = access truthBackend = enforcementFrontend = UX only