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)