Tenant types
A tenant is the top-level isolation boundary in Constellation — one customer's fully isolated space, enforced by row-level security on every tenant-scoped table. Each tenant carries a type that reflects which side of the collaboration the customer sits on.
The four types are defined in packages/contracts/src/entities/tenant.ts:
export const TenantType = z.enum(['AGENCY', 'SUPPLIER', 'PROGRAMME_OFFICE', 'PLATFORM_OPERATOR']);
The four types
AGENCY
A buying organisation — a government agency or enterprise procurement function.
- Primary activities: searching the catalogue, building shortlists, running delivery projects with stage gates, raising and tracking issues against suppliers.
- Typical members: procurement officers, project managers, technical evaluators.
SUPPLIER
A selling organisation offering products, services, or capabilities.
- Primary activities: publishing and maintaining catalogue entries, participating in delivery projects, responding to issues, keeping certifications current in Directory.
- Typical members: account managers, bid teams, delivery engineers.
PROGRAMME_OFFICE
An oversight body coordinating work that spans multiple organisations and projects.
- Primary activities: tracking initiatives and cross-project progress, reviewing gates, consuming the audit trail.
- Typical members: programme managers, governance and assurance staff.
- Not to be confused with the organisation type of the same name — see below.
PLATFORM_OPERATOR
The organisation operating the Constellation deployment itself (in the SaaS tier, the platform vendor; in a dedicated or on-prem deployment, the customer's own platform team).
- Primary activities: tenant administration, platform configuration, operational monitoring.
- Elevated posture: this is the only tenant type permitted to hold the wildcard (
*) permission — see below.
Permission posture
All tenant types share the same access-control machinery. The shared evaluator — checkPermissionDetailed() in @constellation-platform/auth-core — runs tenant isolation → classification → permission evaluation against the caller's role-derived permissions (with matchPermission() as the underlying permission-string matcher; it performs no tenant or classification checks on its own). A fuller RBAC/ABAC sequence with a separate attribute-evaluation step is the platform's target architecture for later phases, not a distinct step today. Roles and their permission sets are defined per tenant, so an agency and a supplier can shape their own role models independently.
The type-specific rule is at the top of the ladder: wildcard permission assignment is restricted to PLATFORM_OPERATOR tenants. The permission guard (apps/directory/src/server/policies/permission-guard.ts) rejects any attempt to assign the * permission inside an AGENCY, SUPPLIER, or PROGRAMME_OFFICE tenant. Those tenants can still hold powerful tenant-scoped admin roles — the default role seeding gives every tenant an Admin role composed of all catalogued permissions — but always as enumerated permissions, never the unrestricted * wildcard.
Deployments in the defence tier extend the classification step with a clearance JWT claim (the hasSufficientClearance helper), which combines with per-record classification labels for clearance-gated visibility. National-caveat enforcement (a caveats claim combined with resource-level caveat metadata) is planned for a later phase — the passesNationalCaveats() helper exists as a seam but currently always passes, and no current deployment carries caveat claims.
Interaction patterns
Tenants do not read each other's data — isolation is absolute at the database layer. Collaboration happens along two paths:
- Within a tenant. A tenant hosts member organisations (its own departments plus counterpart organisations it works with), and users collaborate under that tenant's roles and permissions. This is where day-to-day agency ↔ supplier collaboration lives — see How the pieces fit together.
- Through published contracts. Modules — and external systems — integrate via domain events and APIs rather than shared tables, keeping every cross-boundary interaction explicit and auditable.
Tenant type vs organisation type
Two different enums that share some names — easy to conflate:
| Tenant type | Organisation type | |
|---|---|---|
| Applies to | The whole isolated customer space | A member organisation inside a tenant |
| Values | AGENCY, SUPPLIER, PROGRAMME_OFFICE, PLATFORM_OPERATOR | AGENCY, PRIME_CONTRACTOR, SUB_TIER_SUPPLIER, SME, PROGRAMME_OFFICE |
| Governs | Security posture (e.g. the wildcard-permission rule) | Matching, verification, and display within the tenant |
| Defined in | packages/contracts/src/entities/tenant.ts | apps/directory/src/lib/entity-enums.ts |
So an AGENCY tenant will typically contain an organisation of type AGENCY (the buyer itself) alongside PRIME_CONTRACTOR, SUB_TIER_SUPPLIER, and SME organisations it collaborates with.