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:
- Where are you? — Tenant identity, organizational hierarchy, data residency.
- What can you do? — RBAC roles, ABAC predicates, feature flags, plan entitlements.
- 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
| Attribute | Value |
|---|---|
| Context name | Tenant |
| Domain class | Generic (commodity — could be replaced by SaaS, kept in-house for isolation + cost) |
| Shared Kernel | TenantId value object shared with every context |
| Upstream | identity-service (user registration events) |
| Downstream | Every service (tenant resolution, RBAC/ABAC decisions) |
| Context map pattern | SK on TenantId VO only — no shared entities |
3. Responsibilities
| Responsibility | Description |
|---|---|
| Tenant lifecycle | Provision, configure, suspend, close tenants |
| Organizational structure | Hierarchical org-units via ltree (departments, campuses, teams) |
| Membership management | Invite users, activate, suspend, remove; link to roles and org-units |
| Role & permission management | System roles + tenant-custom roles; ABAC predicate DSL |
| Authorization decisions | POST /api/v1/authz/check — the platform's policy decision point |
| Feature flags | Per-tenant overrides on platform-wide feature flags |
| Dynamic groups | Query-based cohorts evaluated on demand (consumed by assignment-service) |
| SSO configuration | Per-tenant SAML/OIDC provider setup and secret rotation |
| Data residency | Home region assignment; migration saga orchestration (M5) |
| Plan routing | Link tenant to billing plan; route feature entitlements |
| GDPR orchestration | Handle subject requests that span this service's data |
4. Aggregate Roots
| Aggregate | Key invariants |
|---|---|
Tenant | Slug globally unique; status transitions are one-way (trial → active → suspended → closed); homeRegion immutable after provisioning (changed only via migration saga) |
OrgUnit | ltreePath consistent with parentId chain; no cycles; max depth 10 |
Membership | One active membership per (tenantId, userId) pair; status transitions: invited → active → suspended |
Role | System roles (isSystem: true) are immutable by tenants; permissions array validated against known resource/action registry |
DynamicGroup | query validated against ABAC DSL schema; evaluation is idempotent |
5. Service Interactions
5.1 Synchronous (NATS request/reply, max 1 hop)
| Subject | Direction | Purpose |
|---|---|---|
tenant.resolve_for_request | Inbound | API gateway resolves tenant context from JWT tid claim |
tenant.membership.resolve | Inbound | Other services resolve membership + roles for authorization |
5.2 Events Published
| Subject | Trigger |
|---|---|
tenant.org.provisioned.v1 | New tenant created |
tenant.org.settings_updated.v1 | Settings patched |
tenant.org.suspended.v1 | Tenant suspended |
tenant.org.closed.v1 | Tenant closed |
tenant.org.user_invited.v1 | User invited to tenant |
tenant.org.membership_activated.v1 | Invite accepted / user joined |
tenant.org.membership_suspended.v1 | Membership suspended |
tenant.role.created.v1 | Role created |
tenant.role.updated.v1 | Role permissions changed |
tenant.role.deleted.v1 | Role removed |
tenant.org_unit.created.v1 | Org unit added |
tenant.org_unit.moved.v1 | Org unit re-parented |
tenant.org_unit.deleted.v1 | Org unit removed |
tenant.dynamic_group.evaluated.v1 | Dynamic group membership resolved |
tenant.data_residency.changed.v1 | Home region migration completed |
5.3 Events Consumed
| Subject | Source | Action |
|---|---|---|
identity.user.registered.v1 | identity-service | Auto-activate matching pending invitations |
billing.subscription.changed.v1 | billing-service | Update tenant plan reference + entitlements |
gdpr.subject_request.received.v1 | platform | Orchestrate erasure of tenant-owned PII |
6. Technology Stack
| Layer | Technology |
|---|---|
| Runtime | Node.js 22 LTS |
| Framework | NestJS (Clean / Hexagonal architecture) |
| Language | TypeScript 5.x, strict mode |
| Database | PostgreSQL 16 with ltree + RLS |
| Cache | Redis 7 (authz decisions, feature flags, dynamic group snapshots) |
| Messaging | NATS JetStream |
| Observability | OpenTelemetry SDK → @ghasi/telemetry wrapper |
| Schema validation | Zod (API) + Ajv (events) |
| Testing | Vitest (unit) + Testcontainers (integration) + Pact (contract) + Playwright (E2E) |
7. Service Readiness Targets
| Milestone | Target Level | Key Gates |
|---|---|---|
| M0 | L2 (internal MVP) | G1 Domain, G2 API, G3 Events, G6 Observability, G8 Security (RLS + two-tenant isolation) |
| M1 | L3 (customer-facing MVP) | + org-unit hierarchy usable; pen-test #1 closed |
| M2 | L3 | + feature flags + plan routing |
| M3 | L4 (feature-complete) | + custom roles + ABAC + DynamicGroup at scale |
| M5 | L4 | + data residency migration saga complete |
8. Freeze Points
| Freeze | Contract | Frozen by |
|---|---|---|
| F03 | TenantId, OrgUnitId, RoleId branded VOs | M0 end |
| F29 | Role + ABAC predicate DSL | M3 start |
| F30 | DynamicGroup query DSL | M3 start |
9. Slices
| Slice | Milestone | Scope |
|---|---|---|
| S0 | M0 | Provisioning, org units, roles, memberships |
| S4 | M3 | SAML SSO config, ABAC depth, dynamic groups, custom roles |
| S6 | M5 | Data residency migration saga |
10. Non-Functional Requirements
| Attribute | Target |
|---|---|
| Availability | 99.95% (tenant resolution is on the critical path of every request) |
| Latency p95 | tenant.resolve_for_request ≤ 5 ms (cached) |
| Latency p95 | REST API mutations ≤ 200 ms |
| Throughput | 10,000 authz checks/sec per region |
| Data isolation | Postgres RLS on every table; two-tenant isolation suite in CI |
| Recovery | RPO 1 min, RTO 5 min (hot standby) |