Skip to main content

Overview

:::info Source Sourced from services/tenant-service/SERVICE_OVERVIEW.md in the documentation repo. :::

Blueprint doc 1 of 17. Companion: 03 tenant-service | 02 DDD | 13 Security


1. Mission Statement

The tenant-service is the membrane that contains every other aggregate on the Ghasi-edTech platform. It owns the answer to three questions:

  1. Where are you? — Tenant identity, organizational hierarchy, data residency.
  2. What can you do? — RBAC roles, ABAC predicates, feature flags, plan entitlements.
  3. Who belongs? — Memberships, invitations, dynamic groups.

Every request on the platform carries a TenantId claim. The tenant-service is the authoritative source that mints, validates, and enriches that claim.


2. Bounded Context

AttributeValue
Context nameTenant
Domain classGeneric (commodity — could be replaced by SaaS, kept in-house for isolation + cost)
Shared KernelTenantId value object shared with every context
Upstreamidentity-service (user registration events)
DownstreamEvery service (tenant resolution, RBAC/ABAC decisions)
Context map patternSK on TenantId VO only — no shared entities

3. Responsibilities

ResponsibilityDescription
Tenant lifecycleProvision, configure, suspend, close tenants
Organizational structureHierarchical org-units via ltree (departments, campuses, teams)
Membership managementInvite users, activate, suspend, remove; link to roles and org-units
Role & permission managementSystem roles + tenant-custom roles; ABAC predicate DSL
Authorization decisionsPOST /api/v1/authz/check — the platform's policy decision point
Feature flagsPer-tenant overrides on platform-wide feature flags
Dynamic groupsQuery-based cohorts evaluated on demand (consumed by assignment-service)
SSO configurationPer-tenant SAML/OIDC provider setup and secret rotation
Data residencyHome region assignment; migration saga orchestration (M5)
Plan routingLink tenant to billing plan; route feature entitlements
GDPR orchestrationHandle subject requests that span this service's data

4. Aggregate Roots

AggregateKey invariants
TenantSlug globally unique; status transitions are one-way (trial → active → suspended → closed); homeRegion immutable after provisioning (changed only via migration saga)
OrgUnitltreePath consistent with parentId chain; no cycles; max depth 10
MembershipOne active membership per (tenantId, userId) pair; status transitions: invited → active → suspended
RoleSystem roles (isSystem: true) are immutable by tenants; permissions array validated against known resource/action registry
DynamicGroupquery validated against ABAC DSL schema; evaluation is idempotent

5. Service Interactions

5.1 Synchronous (NATS request/reply, max 1 hop)

SubjectDirectionPurpose
tenant.resolve_for_requestInboundAPI gateway resolves tenant context from JWT tid claim
tenant.membership.resolveInboundOther services resolve membership + roles for authorization

5.2 Events Published

SubjectTrigger
tenant.org.provisioned.v1New tenant created
tenant.org.settings_updated.v1Settings patched
tenant.org.suspended.v1Tenant suspended
tenant.org.closed.v1Tenant closed
tenant.org.user_invited.v1User invited to tenant
tenant.org.membership_activated.v1Invite accepted / user joined
tenant.org.membership_suspended.v1Membership suspended
tenant.role.created.v1Role created
tenant.role.updated.v1Role permissions changed
tenant.role.deleted.v1Role removed
tenant.org_unit.created.v1Org unit added
tenant.org_unit.moved.v1Org unit re-parented
tenant.org_unit.deleted.v1Org unit removed
tenant.dynamic_group.evaluated.v1Dynamic group membership resolved
tenant.data_residency.changed.v1Home region migration completed

5.3 Events Consumed

SubjectSourceAction
identity.user.registered.v1identity-serviceAuto-activate matching pending invitations
billing.subscription.changed.v1billing-serviceUpdate tenant plan reference + entitlements
gdpr.subject_request.received.v1platformOrchestrate erasure of tenant-owned PII

6. Technology Stack

LayerTechnology
RuntimeNode.js 22 LTS
FrameworkNestJS (Clean / Hexagonal architecture)
LanguageTypeScript 5.x, strict mode
DatabasePostgreSQL 16 with ltree + RLS
CacheRedis 7 (authz decisions, feature flags, dynamic group snapshots)
MessagingNATS JetStream
ObservabilityOpenTelemetry SDK → @ghasi/telemetry wrapper
Schema validationZod (API) + Ajv (events)
TestingVitest (unit) + Testcontainers (integration) + Pact (contract) + Playwright (E2E)

7. Service Readiness Targets

MilestoneTarget LevelKey Gates
M0L2 (internal MVP)G1 Domain, G2 API, G3 Events, G6 Observability, G8 Security (RLS + two-tenant isolation)
M1L3 (customer-facing MVP)+ org-unit hierarchy usable; pen-test #1 closed
M2L3+ feature flags + plan routing
M3L4 (feature-complete)+ custom roles + ABAC + DynamicGroup at scale
M5L4+ data residency migration saga complete

8. Freeze Points

FreezeContractFrozen by
F03TenantId, OrgUnitId, RoleId branded VOsM0 end
F29Role + ABAC predicate DSLM3 start
F30DynamicGroup query DSLM3 start

9. Slices

SliceMilestoneScope
S0M0Provisioning, org units, roles, memberships
S4M3SAML SSO config, ABAC depth, dynamic groups, custom roles
S6M5Data residency migration saga

10. Non-Functional Requirements

AttributeTarget
Availability99.95% (tenant resolution is on the critical path of every request)
Latency p95tenant.resolve_for_request ≤ 5 ms (cached)
Latency p95REST API mutations ≤ 200 ms
Throughput10,000 authz checks/sec per region
Data isolationPostgres RLS on every table; two-tenant isolation suite in CI
RecoveryRPO 1 min, RTO 5 min (hot standby)