> ## Documentation Index
> Fetch the complete documentation index at: https://docs.emergence.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Multi-Tenancy

> Tenant isolation architecture with Keycloak realm separation, org_id scoping, dual authorization, and per-service database partitioning.

# Multi-Tenancy

CRAFT implements multi-tenancy at every layer of the stack. Each organization is fully isolated through a combination of **identity separation** (Keycloak realms), **authorization scoping** (OpenFGA relationship tuples), and **data partitioning** (org\_id filtering on every query). This page describes the architecture that makes this work.

## Isolation Layers

```mermaid theme={null}
%%{init: {'theme': 'base', 'themeVariables': {'lineColor': '#555555', 'fontFamily': 'sans-serif', 'edgeLabelBackground': '#ffffff'}}}%%
flowchart TB
    REQ["Inbound Request\n(user or service account)"]
    KC["Keycloak Realm\n(identity isolation)"]
    JWT["JWT Validation\n(org_id extracted)"]
    OF["OpenFGA\n(authorization tuples)"]
    FILT["org_id Filter\n(data isolation)"]
    DB[("Per-Service DB\n(service isolation)")]

    REQ --> KC
    KC -->|"issues JWT\nwith org_id claim"| JWT
    JWT --> OF
    JWT --> FILT
    OF -->|"permission check\n(resource-scoped)"| FILT
    FILT --> DB

    classDef entry fill:#56B4E9,stroke:#555555,color:#000
    classDef identity fill:#56B4E9,stroke:#555555,color:#000
    classDef authz fill:#E69F00,stroke:#555555,color:#000
    classDef data fill:#009E73,stroke:#555555,color:#000
    classDef infra fill:#0072B2,stroke:#555555,color:#fff
    class REQ entry
    class KC,JWT identity
    class OF authz
    class FILT data
    class DB infra
```

The platform enforces tenant isolation through four complementary mechanisms:

<CardGroup cols={2}>
  <Card title="Identity Isolation" icon="fingerprint">
    Each organization is a separate Keycloak realm. Users, groups, sessions, and identity provider configurations are completely isolated between realms. A user in `acme-corp` cannot see or interact with users in `globex`.
  </Card>

  <Card title="Authorization Isolation" icon="shield-halved">
    OpenFGA relationship tuples are scoped to specific resources. Permission checks always include the resource ID, ensuring a user in one organization cannot access resources in another even if they somehow obtain a valid token.
  </Card>

  <Card title="Data Isolation" icon="database">
    Every database query includes an `organization_id` filter derived from the JWT token. This is enforced at the application layer -- queries without `org_id` filters are treated as security vulnerabilities.
  </Card>

  <Card title="Service Isolation" icon="server">
    Each platform service (Governance, Assets, Utils) owns its own PostgreSQL database. There are no cross-service foreign keys, preventing accidental data leakage through join queries.
  </Card>
</CardGroup>

## How org\_id Flows Through the System

The organization ID is the foundation of tenant isolation. It flows through every request:

<Steps>
  <Step title="Authentication">
    The user authenticates against their organization's Keycloak realm. The JWT token's `iss` (issuer) claim contains the realm name, which becomes the `org_id`.

    ```text theme={null}
    iss: "https://keycloak.example.com/realms/acme-corp"
                                               ^^^^^^^^^ org_id
    ```
  </Step>

  <Step title="Token Validation">
    The Governance service validates the JWT using the realm's JWKS public keys. The `org_id` is extracted from the verified issuer claim and stored in the `Auth` object. Users in the `master` realm (platform developers) have `org_id = null`.
  </Step>

  <Step title="Request Processing">
    Every API endpoint receives the `Auth` object via dependency injection. The `org_id` is used to:

    * **Filter list queries** to return only the organization's resources
    * **Scope write operations** to associate new resources with the correct organization
    * **Validate access** via OpenFGA permission checks
  </Step>

  <Step title="Cross-Service Calls">
    When Assets or Utils need to check permissions, they forward the user's JWT token to the Governance service via auto-generated SDK calls. The Governance service re-validates the token and checks OpenFGA -- the `org_id` is never passed as a parameter that could be spoofed.
  </Step>
</Steps>

## Dual Authorization Pattern

The platform uses a dual authorization pattern that combines database-level filtering with authorization checks:

<Tabs>
  <Tab title="List Operations">
    List endpoints apply **both** database filtering and permission checks:

    ```python theme={null}
    @router.get("/data")
    async def list_data_connections(
        ctx: RequestContextDep,
        # Permission check: user can read resources in this project
        _: None = Depends(require_permission(
            Permissions.PROJECT,
            Permissions.PROJECT.actions.CAN_READ,
            HEADER_PROJECT_ID
        ))
    ):
        # Database filter: always scope by org_id from JWT
        connections = await service.list_data_connections(
            organization_id=ctx.org_id,    # From JWT, never from request body
            project_id=ctx.project_id,      # From X-Project-ID header
        )
    ```

    This defense-in-depth approach means that even if a permission check has a bug, the database query will not return another organization's data.
  </Tab>

  <Tab title="Get-by-ID Operations">
    Single-resource endpoints rely on **OpenFGA permission checks only**:

    ```python theme={null}
    @router.get("/data/{resource_uri}")
    async def get_data_connection(
        resource_uri: str,
        # Permission check: user can read this specific resource
        _: None = Depends(require_permission(
            Permissions.DATA_CONNECTION,
            Permissions.DATA_CONNECTION.actions.CAN_READ,
            "resource_uri"
        ))
    ):
        # OpenFGA already verified the user has access
        connection = await service.get_data_connection(
            organization_id=ctx.org_id,
            resource_uri=resource_uri,
        )
    ```

    Even here, the `organization_id` filter is included as an additional safety measure.
  </Tab>
</Tabs>

<Warning>
  **Security rules enforced across all services:**

  * Never trust user-supplied `org_id` from request body or parameters. Always use `auth.org_id` from the JWT.
  * Never list resources without an `org_id` filter. Omitting the filter returns all organizations' data.
  * Always use `auth.org_id` when creating new resources. This ensures the resource is correctly scoped to the authenticated user's organization.
</Warning>

## Database Architecture

Each service owns an independent PostgreSQL database with no cross-service foreign keys:

```text theme={null}
PostgreSQL Server
  keycloak_db          -- Keycloak identity data
  openfga_db           -- OpenFGA authorization tuples
  infisical_db         -- Infisical secrets data (present by default; unused if using ESO backend)
  governance_db        -- Organizations, projects
  assets_db            -- Artifacts, data connections, files, models
  utils_db             -- Data catalog, scheduling, context packs, memories
```

Every resource table includes an `organization_id` column:

| Column            | Type          | Purpose                                            |
| ----------------- | ------------- | -------------------------------------------------- |
| `organization_id` | `VARCHAR`     | Tenant partition key, always populated from JWT    |
| `project_id`      | `VARCHAR`     | Project scope within the organization              |
| `created_at`      | `TIMESTAMPTZ` | Record creation timestamp (via `TimestampMixin`)   |
| `updated_at`      | `TIMESTAMPTZ` | Last modification timestamp (via `TimestampMixin`) |

<Tip>
  Each service runs its own Alembic migrations independently. There are no cross-service migration dependencies, which means services can be upgraded independently without coordinating database schema changes.
</Tip>

## Keycloak Realm Structure

When an organization is created, the Governance service provisions a complete Keycloak realm:

```text theme={null}
acme-corp (realm)
  Groups:
    org-owners       -- Organization owners
    org-admins       -- Organization administrators
    org-members      -- Standard members
    project-owners   -- Project-level owners
    project-admins   -- Project-level administrators
    project-developers  -- Project-level developers
    project-operators   -- Project-level operators
    project-viewers     -- Project-level viewers

  Mappers:
    groups-mapper    -- Ensures group memberships appear in JWT "groups" claim

  Clients:
    em-runtime-ui    -- Frontend application client (PKCE)

  Identity Providers:
    (configurable per realm: SAML, OIDC, LDAP, social login)
```

## Service Account Cross-Tenant Access

Service accounts (background workers, automated processes) authenticate via the `master` realm and specify the target organization via headers:

| Header           | Purpose                               |
| ---------------- | ------------------------------------- |
| `X-Org-Id`       | Target organization for the operation |
| `X-On-Behalf-Of` | Original user ID for audit trails     |

These headers are only trusted when the caller passes all three service account checks:

1. Token issued by `master` realm
2. Client ID starts with `svc-`
3. `serviceAccount` role in `realm_access.roles`

Regular user tokens cannot use these headers -- the platform silently ignores them for non-service-account callers.

## Service Startup and Dependencies

The multi-tenant infrastructure requires a specific startup order:

```text theme={null}
PostgreSQL -> Redis -> Keycloak -> OpenFGA -> [Secrets backend] -> Governance -> Assets/Utils
```

The Governance service must be running before Assets or Utils can validate permissions. If Governance is down, downstream services return 403 for all requests because permission checks fail.

<Note>
  `make docker-run` handles the startup order automatically via Docker Compose health checks. For manual local development, start `make run-deps` first, wait for readiness, then start Governance before Assets or Utils.
</Note>

## Next Steps

<CardGroup cols={2}>
  <Card title="Organizations" icon="building" href="/platform/organizations">
    Learn how organizations are provisioned with Keycloak realms.
  </Card>

  <Card title="Authentication" icon="lock" href="/platform/authentication">
    Understand the multi-realm JWT authentication flow.
  </Card>

  <Card title="Authorization" icon="shield-halved" href="/platform/authorization">
    Explore OpenFGA permission inheritance across tenants.
  </Card>

  <Card title="Projects" icon="folder" href="/platform/projects">
    See how projects provide the second level of scoping within tenants.
  </Card>
</CardGroup>
