Skip to content

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

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”
Auth = identity + memberships + module grants + permissions + delegation
Core = company commercial entitlements
Backends = business execution
Frontend = UI consumer only

Auth computes final access as:

effective_access = company_entitlements ∩ membership_grants

Core only provides the left-hand side:

company_entitlements

Platform Core is consumed by:

  1. Auth Backend
    • to resolve company entitlements during /auth/me/access
  2. Platform Admin Backend
    • to manage packages, add-ons, modules, company subscription state
  3. 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

Platform Core owns the following domains.

  • modules
  • packages
  • add-ons
  • mappings between packages/add-ons and modules
  • 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
  • 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”
  • users
  • passwords
  • sessions
  • JWT
  • refresh token lifecycle
  • company memberships
  • business unit memberships
  • tenant roles
  • membership module grants
  • membership permissions
  • delegation policies
  • invitations
  • invitation tokens
  • invitation acceptance
  • onboarding flows
  • membership onboarding
  • events
  • vendors
  • venue business records
  • AI outputs
  • finance transactions
  • market/touring documents

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.


Invitations do not belong to Platform Core.

Invitations are part of:

  • user onboarding
  • identity lifecycle
  • company membership creation
  • tenant role assignment

Those responsibilities belong to Auth Backend.

  • invitation creation
  • invitation storage
  • invitation email sending
  • invitation acceptance
  • invitation token validation
  • membership creation through invitation flow
  • Auth Backend → invitations, onboarding, membership creation
  • Platform Core Backend → packages, add-ons, modules, subscriptions, entitlements

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.

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.


Commercial product that enables:

  • Core App UI
  • Basic module
  • Basic business experience

Commercial add-ons:

  • finance
  • touring
  • market
  • venue
  • ai

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

Core must be able to return:

  • hasBasic
  • basePackage
  • active add-ons
  • enabled modules
  • entitlement version

4. Service location and deployment identity

Section titled “4. Service location and deployment identity”
core.kisum.io

Optional internal alias:

platform-core.kisum.io

Use one canonical service name in logs/metrics:

platform-core-backend

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

  • Auth Backend
  • Platform Admin Backend
  • approved internal jobs/services
  • browser frontend
  • public clients
  • direct mobile apps
  • arbitrary third-party clients

Every internal route must require service-to-service authentication.

Allowed patterns:

  • internal API key
  • signed internal service JWT
  • mTLS
  • private network + internal header validation

For first version:

  • private network
  • internal API key or signed internal service token
  • clear allowlist of callers
X-Internal-API-Key: <secret>

Or, if using service JWT:

Authorization: Bearer <internal-service-token>

Can call:

  • entitlement reads
  • catalog reads if needed

Can call:

  • catalog reads
  • catalog writes
  • company subscription writes
  • company add-on writes
  • company subscription summary reads

Can call:

  • read routes
  • write routes only if explicitly allowed

platform_core_db

This 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.


Recommended:

  • pgcrypto if using gen_random_uuid()
  • timezone support as standard
  • JSONB support (native in Postgres)

Recommended schema:

public

You can later split into:

  • catalog
  • entitlements
  • billing

But first version can stay in public.


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:

  • basic
  • finance
  • market
  • touring
  • venue
  • ai

type:

  • base → module delivered by Basic subscription
  • addon → module delivered by add-on

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_plus
  • enterprise
  • etc.

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()
);
  • finance
  • market
  • touring
  • venue
  • ai

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 → module basic

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 → module finance
  • addon market → module market
  • addon touring → module touring
  • addon venue → module venue
  • addon ai → module ai

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_id is 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
  • source examples:
    • platform_admin
    • self_service
    • billing_sync
  • external_reference may store Stripe subscription id or invoice ref later

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

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
);

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.


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()
);

change_type:

  • basic_activated
  • basic_deactivated
  • addon_activated
  • addon_deactivated
  • catalog_updated
  • entitlement_repaired

entity_type:

  • package
  • addon
  • mapping
  • company

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.


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);

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;
insert into packages (key, name, description) values
('basic', 'Basic', 'Basic subscription that enables Core App')
on conflict (key) do nothing;
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;
insert into package_modules (package_id, module_id)
select p.id, m.id
from packages p
join modules m on m.key = 'basic'
where p.key = 'basic'
on conflict do nothing;
insert into addon_modules (addon_id, module_id)
select a.id, m.id
from addons a
join modules m on m.key = a.key
on conflict do nothing;

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.

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.

Core computes:

enabledModules =
package modules from active Basic subscription
+
add-on modules from active company add-ons
  • Basic inactive
  • Add-ons active: finance, market

Result:

{
"hasBasic": false,
"enabledModules": ["finance", "market"]
}
  • Basic active
  • Add-ons active: finance

Result:

{
"hasBasic": true,
"enabledModules": ["basic", "finance"]
}

There are 3 groups of endpoints:

  1. Catalog read
  2. Entitlement read
  3. Catalog/company write
  4. Admin/audit/support read

All are internal-only.

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.


{
"success": true,
"data": {}
}
{
"success": false,
"error": {
"code": "string_code",
"message": "Human readable message"
}
}
  • unauthorized
  • forbidden
  • validation_error
  • not_found
  • conflict
  • internal_error
  • service_unavailable

Purpose:

  • simple liveness probe
{
"success": true,
"data": {
"status": "ok"
}
}

Purpose:

  • readiness check

Must verify:

  • Core DB reachable
  • internal config loaded
{
"success": false,
"error": {
"code": "not_ready",
"message": "core database unavailable"
}
}

  • Platform Admin Backend
  • Auth Backend (optional)
  • internal jobs

Return full module catalog.

{
"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
}
]
}
}

  • Platform Admin Backend
  • internal jobs
{
"success": true,
"data": {
"packages": [
{
"id": "uuid",
"key": "basic",
"name": "Basic",
"description": "Basic subscription that enables Core App",
"isActive": true,
"modules": ["basic"]
}
]
}
}

  • Platform Admin Backend
  • internal jobs
{
"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.1 GET /internal/companies/{companyId}/entitlements

Section titled “15.1 GET /internal/companies/{companyId}/entitlements”
  • Auth Backend (main consumer)
  • Platform Admin Backend
  • internal jobs

Return the current commercial entitlement state for a company.

  • companyId: canonical UUID
{
"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
  • basePackage may be null
  • enabledModules still 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”
  • Platform Admin Backend
  • internal jobs
  • Auth Backend (optional)

Admin-facing summary of commercial state.

{
"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”
  • Platform Admin Backend
  • internal audit/reporting jobs

Return entitlement history.

{
"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"
}
]
}
}

These are mainly consumed through the Platform Admin Backend.

  • Platform Admin Backend only
{
"key": "finance",
"name": "Finance",
"type": "addon",
"description": "Finance module",
"isActive": true
}
{
"success": true,
"data": {
"id": "uuid",
"key": "finance",
"name": "Finance",
"type": "addon",
"description": "Finance module",
"isActive": true
}
}
  • key unique
  • type must be base or addon

16.2 PATCH /internal/catalog/modules/{moduleId}

Section titled “16.2 PATCH /internal/catalog/modules/{moduleId}”
{
"name": "Finance",
"description": "Finance module updated",
"isActive": true
}
  • key should generally be immutable once used in production

{
"key": "basic",
"name": "Basic",
"description": "Basic subscription that enables Core App",
"isActive": true,
"moduleKeys": ["basic"]
}
  • 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}”
{
"name": "Basic",
"description": "Updated Basic description",
"isActive": true,
"moduleKeys": ["basic"]
}
  • update package
  • replace mapping set if moduleKeys present
  • if mapping change impacts active companies, entitlement version repair may be needed

{
"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}”
{
"name": "Finance",
"description": "Finance updated",
"isActive": true,
"moduleKeys": ["finance"]
}

These are the most important write endpoints.

17.1 POST /internal/companies/{companyId}/basic

Section titled “17.1 POST /internal/companies/{companyId}/basic”
  • Platform Admin Backend
  • optional internal billing/reconciliation jobs

Activate, update, pause, or deactivate Basic subscription.

{
"status": "active",
"startsAt": "2026-04-16T00:00:00Z",
"endsAt": "2026-05-16T00:00:00Z",
"source": "platform_admin",
"externalReference": "sub_123"
}
  • find package basic
  • upsert company subscription row
  • update status/dates/source/external ref
  • bump entitlement version
  • write entitlement history
{
"success": true,
"data": {
"companyId": "cmp_001",
"hasBasic": true,
"basePackage": "basic",
"entitlementVersion": 8
}
}
  • active
  • inactive
  • cancelled
  • expired
  • trial
  • paused

17.2 POST /internal/companies/{companyId}/addons

Section titled “17.2 POST /internal/companies/{companyId}/addons”
  • Platform Admin Backend
  • optional internal billing jobs

Activate or update one add-on for a company.

{
"addonKey": "finance",
"status": "active",
"startsAt": "2026-04-16T00:00:00Z",
"endsAt": "2026-05-16T00:00:00Z",
"source": "platform_admin",
"externalReference": "addon_sub_123"
}
  • resolve addon by addonKey
  • upsert company_addons
  • bump entitlement version
  • write history
{
"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”

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.

Use:

POST /internal/companies/{companyId}/addons

for both activate and deactivate.


Auth caches merged access.
When Core changes company entitlements, Auth must know access is stale.

So Core must bump:

entitlementVersion

on 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

Preferred:

  • company_entitlement_versions table

Fallback:

  • version stored on company_subscriptions

Preferred is cleaner.


Auth receives:

  • entitlementVersion from Core response and uses it in cache key or stale-check logic.

Example cache key:

access:{companyId}:{membershipId}:{accessVersion}:{entitlementVersion}

Auth calls:

GET /internal/companies/{companyId}/entitlements
  • hasBasic
  • basePackage
  • addons
  • enabledModules
  • entitlementVersion
  • membership grants
  • permissions
  • delegation

Core does not perform the merge.


If Core is unavailable:

  • Auth should fail /auth/me/access
  • return 503
  • do not guess entitlements
  • do not fallback to stale business assumptions

Frontend admin UI should not call Core directly.
It calls:

  • Platform Admin Backend

Platform Admin Backend then calls Core internal routes.


  1. Platform Admin UI → Admin Backend
  2. Admin Backend validates platform-admin access
  3. Admin Backend calls:
    • POST /internal/catalog/addons
  4. Core writes addons
  5. Core returns created addon

  1. Platform Admin UI → Admin Backend
  2. Admin Backend validates admin permissions
  3. Admin Backend calls:
    • POST /internal/companies/{companyId}/basic
  4. Core upserts subscription
  5. Core bumps entitlement version
  6. Core returns updated state
  7. Auth access cache must become stale

  1. Platform Admin UI → Admin Backend
  2. Admin Backend validates platform admin
  3. Admin Backend calls:
    • POST /internal/companies/{companyId}/addons
  4. Core upserts addon status
  5. Core bumps entitlement version
  6. Core writes history
  7. Auth invalidates / rebuilds access next time

  • companyId, moduleId, packageId, addonId must be valid UUIDs where path expects UUID
  • module.key, package.key, addon.key should be lowercase slug format
  • no spaces
  • immutable after production use if possible

Allowed status values:

  • active
  • inactive
  • cancelled
  • expired
  • trial
  • paused
  • startsAt <= endsAt if both present
  • dates optional for manually controlled states
  • server should normalize timezone to UTC

{
"success": false,
"error": {
"code": "validation_error",
"message": "addonKey is required"
}
}
{
"success": false,
"error": {
"code": "conflict",
"message": "addon key already exists"
}
}
{
"success": false,
"error": {
"code": "unauthorized",
"message": "missing or invalid internal credentials"
}
}
{
"success": false,
"error": {
"code": "not_found",
"message": "company not found"
}
}

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
/migrations
  • modules CRUD
  • packages CRUD
  • addons CRUD
  • package/addon mappings
  • company Basic state
  • company add-on state
  • enabled modules computation
  • entitlement version bumping
  • write/read entitlement history
  • all SQL access

Must log:

  • entitlement reads
  • catalog writes
  • company subscription writes
  • add-on writes
  • entitlement version bumps
  • invalid internal auth attempts

Track:

  • entitlement lookup count
  • entitlement lookup latency
  • catalog mutation count
  • company mutation count
  • 4xx/5xx rate
  • history insert failures

/ready must verify:

  • DB connectivity
  • config loaded

Core DB must be backed up independently.


  • 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

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

  • 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

  • create platform_core_db
  • create all tables
  • create indexes
  • seed catalog
  • bootstrap service
  • add health/ready
  • add internal auth middleware
  • add structured logging
  • 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}
  • 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
  • entitlement version table or equivalent
  • version bump logic
  • history writing
  • invalidation strategy documented

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.


Defines actions inside modules.

Core MUST NOT store or evaluate this.


Core returns entitlements ONLY
Core never computes user access

Platform Core is a dependency of Auth.

Auth → Core → Auth → Frontend

Core:

  • returns company entitlements
  • returns enabled modules
  • returns entitlement version

Auth:

  • merges entitlements with user grants
  • computes effective access
  • returns final access model

Core must NEVER:

  • return user-level access
  • validate permissions
  • validate roles
  • decide if a request is allowed

Core MUST NOT implement:

effective_access = entitlements ∩ grants

This 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.


Frontend must NOT call Platform Core directly for access.

Allowed frontend calls:

  • Auth
  • Base backend
  • module backends
  • admin backend

Business backends must NOT:

  • call Core for user access decisions
  • use Core as authorization service

They must rely on:

Auth → /auth/me/access

Core = commercial truth
Auth = access truth
Backend = enforcement
Frontend = UX only