Skip to content

Tenant Isolation & Data Residency

Consystence is multi-tenant by construction: many customer organisations share the same cloud codebase and Orleans cluster, but no tenant can read or touch another tenant’s operational data. This page describes the isolation boundaries — how membership is keyed, how a request’s subdomain and grain key bind it to one org, and how every resource is authorised against the org that owns it — and where customer data physically lives.

A tenant is the routed, cell-hosted workspace this page’s boundaries protect. Each tenant is minted by the account control plane with a stable UUID (org_*), addressed by a human-readable slug under its own subdomain — {slug}.consystence.com for enterprise-family tenants, {slug}.consystence.dev for integrator-family tenants and individual sandboxes — and hosted on exactly one cell. It is distinct from an account-plane organisation: a free members-and-invites container with no slug, subdomain, or cell — creating an organisation does not create a tenant or an isolation boundary. A paid tenant is owned by an organisation; an organisation may also hold its one free trial (simulation) tenant, convertible in place to an integrator-family paid tier; an individual’s sandbox tenant has no owning organisation at all. Within a cell, this page uses “org” for the tenant that owns a resource. A site is a path within a tenant (/sites/{code}), never a top-level domain. See the org and site model for the full hierarchy.

Membership in an org is the unit of access, and it resolves to exactly one of three roles — owner, admin, member — described in roles.

Identity comes from Entra External ID (CIAM). The stable membership key is the Entra oid — the object identifier that is constant for a user across sessions. Email is a verified attribute used for display and platform-admin detection; it is not identity, and it can change. The pairwise per-relying-party sub claim is not used as the membership key.

When a request arrives, the caller’s oid is checked against the membership directory for the org being addressed. Active members pass; pending or revoked members fail closed; non-members resolve to zero access. See authentication for how each scheme establishes the caller’s identity (a site-PIN principal carries no oid — it proves site presence, not org membership).

Three boundaries stack on top of each other. Each is independent — a request must clear all of them — so a defect in one does not silently open the tenant boundary.

flowchart TD
  A["Request to<br/>acme.consystence.com"] --> B["Subdomain boundary<br/>subdomain &rarr; org slug 'acme'<br/>caller oid validated vs org"]
  B --> C["Grain-key scoping<br/>grain keys prefixed 'acme/...'<br/>resources site-scoped under org"]
  C --> D["Resource-to-org authz<br/>derive owning org from resource id<br/>validate vs caller membership"]
  D --> E["Tenant-scoped resource grain"]

Every org is a distinct subdomain. At the request boundary, org-resolution middleware maps the subdomain to the org slug, validates the caller’s membership against that org, and rejects mismatches before any business logic runs. A user holding a valid token for one org cannot reach another simply by changing the host.

Orleans grains that hold tenant state are keyed with the org slug as a prefix (acme/...), and resources beneath an org are site-scoped ({siteId}). The grain key itself therefore carries the tenant, so two tenants’ resources are distinct grain activations with no shared state. Sites are registered into the org → site map at provisioning time, which is what makes a site id resolvable back to its owning org (used by the authorisation layer below).

Subdomain and grain keying are necessary but not sufficient. Role policies answer who you are (are you an admin or member of some org?) — they do not, on their own, answer which tenant owns the specific resource this request touches. A route that carries a client-supplied {siteId} can decouple the addressed resource from the subdomain, so a cross-org member could otherwise reach another tenant’s site resource while still passing a role check.

The resource-to-org authorisation layer closes this. Every caller-facing surface — HTTP endpoint, SignalR hub method, or gRPC edge service — that reads or streams a tenant-scoped resource must, before touching the resource grain:

  1. Derive the resource’s owning org from the resource identifier. Resource ids are site-scoped; membership is org-scoped. A directory grain maps siteId → owning org using the map populated at site provisioning.
  2. Validate that org against the caller through a single shared membership chokepoint, keyed by the caller’s Entra oid against the derived org (active members only). Denial throws.

The invariant, in one sentence: the org validated is the org of the requested resource, derived from the resource id — never the caller’s session or home org, and never merely “has some org context.”

Cross-tenant isolation is enforced structurally in CI, not by review alone.

Verified consystence.com users (or seed-listed accounts) carry a platform_admin claim that bypasses the resource-org check for support, diagnostics, and onboarding — exactly as the render path and the admin API already do. This bypass is deliberate and logged; durable audit is forthcoming. It is the only path that legitimately crosses the tenant boundary.

Today, all customer data lives in Azure Australia East (Sydney). This is the contractual commitment, not just a default.

CategoryStored where
Tenant configuration (grain state)Azure Australia East (PostgreSQL)
Historian samples, tags, events, lineage, registryAzure Australia East (PostgreSQL)
Auth tables (site users, sessions, signing keys)Azure Australia East (PostgreSQL)
Audit log streams (command audit, event log)Azure Australia East (Orleans persistence)
Container imagesAzure Australia East (ACR)
BackupsPaid tiers: geo-redundant to the paired region (Australia Southeast), with high availability. Sandbox / Basic: no geo-redundancy or HA. Data does not leave Australia.
Telemetry (traces, metrics)Azure Australia East (Application Insights)

Identity is the expected exception in kind: Entra External ID is a global Microsoft service, so user identity records live in Microsoft’s tenant store. Only the identity claims propagated via JWT (oid, verified email, name) are seen by the platform, and they are persisted only in audit logs.

AI inference is the one flow that leaves a tenant’s data cell: advisory requests are handled by the regional advisory plane rather than inside the cell. Advisory inference for the Australian deployment runs on AWS in Australia (Sydney, with Australia-pinned inference profiles), so prompt content stays within Australia — it is processed outside the Azure data cell but never leaves the country. Prompt and response content is not logged or retained by the inference layer and is not used for model training. A tenant that requires no external inference at all can run with the AI tier disabled.

No additional region is stood up speculatively — US, EU, or on-prem-only residency is added only on a signed customer ask, each with its own routing partition.

  • Roles — owner / admin / member, the membership that the resource-org gate validates against.
  • Cell topology — how tenant orgs are provisioned into cells above the four-tier stack.
  • Authentication — the MultiAuth schemes that establish the caller’s oid.
  • Licensing — tenant tier and seat pools, which degrade but never gate control writes.