Multi-Tenancy

Causet uses a three-level hierarchy — platform, application, fork — to isolate tenants. Each fork gets a dedicated PostgreSQL schema in the projections database, and can run an independent active IR version.


The Tenancy Hierarchy

Platform
  └── Application
        └── Fork (e.g. production, staging, demo)
LevelDescription
PlatformTop-level owner — a company or team. Has API keys for server-to-server access.
ApplicationA deployed Causet application (a set of .causet DSL files).
ForkAn isolated instance of an application — typically production, staging, or named demo forks. Each fork has its own event data, active IR version, and projection schema.

Per-Fork PostgreSQL Schema

Each fork is assigned a PostgreSQL schema in the projections database:

schema name = {platformId}_{applicationId}_{forkId}

For example:

Platform IDApp IDFork IDSchema Name
acmeconcertappproductionacme_concertapp_production
acmeconcertappstagingacme_concertapp_staging
acmeconcertappdemo-2024-06acme_concertapp_demo_2024_06

Schema name sanitization

The schema name is sanitized before use:

  • Non-alphanumeric characters are replaced with _
  • The result is truncated to ≤ 63 characters (PostgreSQL identifier limit)
  • Names are lowercased

If a sanitized name collides with another fork’s schema, a suffix is appended to disambiguate.


Schema Isolation in Practice

Both projection-worker and query-service issue SET LOCAL search_path before any SQL operation:

SET LOCAL search_path = acme_concertapp_production;

This scopes all table references to the fork’s schema. Two forks of the same application never read or write each other’s projection tables, even though they share the same PostgreSQL database instance.

The causet event store uses fork_id as a column filter — events for different forks are stored in the same tables but filtered by fork_id on all queries.


DDL at Deploy Time

Projection table DDL is generated from the IR at deploy time. When a new IR version is activated for a fork:

  1. causet-saas-cloud applies DDL changes to the fork’s schema (creates new tables, adds new columns).
  2. The new IR version becomes active for the fork.
  3. The projection worker and query service pick up the new IR version on their next cache refresh.

Table creation is CREATE TABLE IF NOT EXISTS — safe to run repeatedly.

Warning: DDL is not applied destructively. Columns are not dropped automatically when removed from the DSL. Removing a projection field requires a manual migration step.


Independent IR Versions Per Fork

Different forks can run different active IR versions simultaneously:

acme_concertapp_production  → IR version v41 (stable)
acme_concertapp_staging     → IR version v42 (new release under test)
acme_concertapp_demo-june   → IR version v39 (frozen demo)

This allows safe canary deployments: activate a new IR version on staging, validate, then promote to production.

The active IR version per fork is resolved via GetActiveRelease(forkId) from causet-saas-cloud. The result is used by all downstream services (runtime, projection worker, query service) to load the correct IR artifact.


Authentication and Authorization

ContextMechanism
Control plane (UI)Clerk JWT — carries platform/application context
SaaS API (server-to-server)API key per platform
Direct runtime accessAPI key or service-to-service credential
Demo / anonymous sessionsAnonymous session token — scoped to a specific demo fork

Clerk JWTs carry the platformId and applicationId claims. The fork is resolved from the request context or URL parameter.

Anonymous sessions are scoped to a specific fork and have no write access by default. They are used for interactive demos where a visitor can query a pre-populated fork without authentication.


Fork Isolation Guarantees

PropertyGuarantee
Projection dataFully isolated — different PostgreSQL schema per fork
Ledger eventsLogically isolated — same tables, filtered by fork_id
IR versionIndependent per fork
Kafka eventsNot isolated by fork — all forks share the same topics (roadmap item)
Redis cacheKeyed by irVersion — different versions cached independently

Fork Lifecycle

  1. Create fork — creates a schema in the projections DB and registers the fork in causet_control_plane.
  2. Deploy IR — activates an IR version for the fork; applies DDL.
  3. Write — intents are submitted with forkId; ledger events and snapshots are scoped to forkId.
  4. Query — queries are executed in the fork’s schema.
  5. Promote — activate a new IR version on the fork.
  6. Archive/delete fork — drop schema, remove fork registration (manual operation).