Skip to main content

tenant-service

Bounded context: Tenant & Org — Supporting (becomes Core when chain customers dominate revenue). Deep bundle: services/tenant-service/ · Companion: 02 Enterprise Architecture §3.1 · 05 API Design · 06 Data Models · 07 Security & Tenancy · Naming · Error Codes


1. Mission

tenant-service owns the operator — the hotel business that uses Ghasi Melmastoon. It is the membrane that contains every other domain aggregate on the platform. It answers three questions for every request:

  1. Whose data is this?Tenant lifecycle, plan reference, residency pin, suspension state.
  2. Which property?OrganizationUnit hierarchy: chain → region → property.
  3. Who can do what?Membership, Role, RoleAssignment, Invitation, ABAC scoping by property.

It does not own credentials or sessions (those are iam-service), and it does not own theme visuals (that is theme-config-service). It does own the policy + role catalog that those services rely on.

A tenant in Melmastoon is a hotel operator. A tenant may run one property (a single guesthouse) or many (a chain). All other services receive tenant_id on every request and event; this service is the authority that mints, validates, and enriches that identity.


2. Bounded Context

AttributeValue
ContextTenant & Org
Domain classSupporting (Core for chain customers)
TenancyShared schema + tenant_id + RLS for child aggregates; tenants table itself is platform-scoped (only platform.super_admin reads it directly)
StorageCloud SQL Postgres, schema tenant
CacheMemorystore (Redis) — tenant config, membership resolution, feature-flag overrides
EventingGCP Pub/Sub, subjects melmastoon.tenant.*
Upstreamiam-service (melmastoon.iam.user.registered.v1), billing-service (melmastoon.billing.subscription.cancelled.v1, …reactivated.v1)
Downstreamevery service (consumes tenant.created, tenant.suspended, tenant.config_updated, tenant.membership.*); pricing-service, reservation-service, theme-config-service, notification-service consume TenantConfig for per-tenant defaults

3. Aggregates Owned

AggregatePurpose
TenantHotel operator identity; plan reference; status (pending, active, suspended, closed); residency pin
TenantConfigCurrencies, locales (RTL/LTR), tax model, time zone, default cancellation policy, default check-in/out times, breakfast included default, smoking policy, child policy, business hours
OrganizationUnitChain → region → property hierarchy; ltree-stored path; max depth 5
MembershipA User (from iam) acting in this tenant; status pending, active, suspended, removed; propertyScope[] for per-property staff
RoleTenant-scoped role definition; system roles (e.g. tenant.owner) immutable; tenant-custom roles allowed
RoleAssignmentA MembershipRole link, optionally scoped to one or more OrganizationUnit IDs
InvitationOutbound invite envelope; signed token (hash stored); single-use; 14-day TTL
BillingContactTenant-side contact + tax identifiers used by billing-service and invoice generation
FeatureFlagOverridePer-tenant override on a platform-wide flag

4. Key Responsibilities

  • Tenant lifecycle: provision (pending → active on plan attach), suspend (manual or billing-driven), reactivate, close.
  • Tenant configuration: edit defaults consumed downstream; emit on every change so caches refresh.
  • Organization structure: chain operator manages a tree of properties + regions; properties move between chains under controlled saga.
  • Staff onboarding: invite by email; pre-register against iam-service; auto-link if the user is already on the platform.
  • RBAC: maintain the role catalog; assign roles per-membership, optionally scoped to a subset of properties.
  • Per-property staff scoping: a front desk clerk usually works at one property; a GM may oversee many; the model carries propertyScope[] on every RoleAssignment.
  • Tenant-level feature flags: aiEnabled, mfaRequiredForStaff, cashPaymentsAllowed, lockIntegrationEnabled, etc.
  • Compliance hooks: GDPR participation for guest erasure (forwarded to reservation-service and billing-service); tenant deletion cascade saga.

5. Service Interactions

5.1 Events Published

SubjectWhen
melmastoon.tenant.created.v1New tenant provisioned
melmastoon.tenant.suspended.v1Manual or billing-driven suspension
melmastoon.tenant.reactivated.v1Suspension cleared
melmastoon.tenant.deleted.v1Tenant closed; cascade saga begins
melmastoon.tenant.config_updated.v1Any TenantConfig field change
melmastoon.tenant.membership.created.v1Membership materialized (post-invite-accept or auto-link)
melmastoon.tenant.membership.role_changed.v1Roles or property scope on a membership change
melmastoon.tenant.membership.removed.v1Membership soft-removed
melmastoon.tenant.invitation.sent.v1Invite issued (notification-service sends the email)
melmastoon.tenant.invitation.accepted.v1Invitee completed acceptance
melmastoon.tenant.invitation.expired.v1TTL elapsed without acceptance
melmastoon.tenant.guest.erasure_requested.v1DSAR fan-out trigger
melmastoon.tenant.feature_flag.toggled.v1Per-tenant flag override changed
melmastoon.tenant.organization_unit.created.v1New chain / region / property added to the org tree

5.2 Events Consumed

SubjectSourceEffect
melmastoon.iam.user.registered.v1iam-serviceAuto-promote any matching pending invitations to active memberships
melmastoon.billing.subscription.cancelled.v1billing-serviceAuto-suspend tenant after grace period
melmastoon.billing.subscription.reactivated.v1billing-serviceLift suspension; emit tenant.reactivated

6. Surface Summary

  • REST under /api/v1/tenants/*, /api/v1/memberships/*, /api/v1/invitations/*, /api/v1/roles/*, /api/v1/organization-units/*, /api/v1/feature-flags/*. Full contracts: API_CONTRACTS.md.
  • Sync surface: read-only projection of TenantConfig, Membership (own user only), Role catalog, OrganizationUnit tree, FeatureFlagOverride. Write back disallowed for role/membership changes (require online).
  • AI: light consumer via ai-orchestrator-service (AIClient port) for invite-abuse classification and anomaly detection on bulk membership removals — advisory only, never auto-applied.

7. Edge Cases (handled in domain)

#CaseBehavior
1Last tenant.owner removalDomain rejects with MELMASTOON.TENANT.LAST_OWNER_REMOVAL
2Role escalation (member assigning a role they do not hold)Rejected by assignableRoles invariant + ABAC at controller
3Invitation token replaySingle-use; cleared on acceptance; constant-time hash compare
4Concurrent TenantConfig editsOptimistic concurrency (If-Match ETag); 412 on stale write
5Property moved between chainsSaga pauses writes on the source unit, copies, swaps, then unfreezes; emits organization_unit.created on target
6Tenant deletion cascadeLong-running saga; downstream services must ack within window or tenant.deleted event is held in the saga store
7Suspended tenant reads vs writesReads allowed for tenant.owner only (to manage billing); writes universally rejected at gateway with MELMASTOON.TENANT.SUSPENDED
8Orphaned membership when iam deletes a userBackground sweep + iam.user.deleted.v1 consumer flips membership to removed
9Cross-tenant role reuseRoles are tenant-scoped; system roles read-only across tenants but never assignable cross-tenant
10Mass invitation abusePer-tenant + per-IP rate limit; AI classifier flags suspicious domains

8. Non-Functional Targets

AttributeTarget
Availability99.95% (every request resolves a tenant)
tenant.resolve p95≤ 5 ms (cached) / ≤ 25 ms (DB)
REST mutation p95≤ 200 ms
Membership resolution throughput5 000 rps per Cloud Run instance
Tenant isolationRLS on every child table; two-tenant simulator green on every PR
RPO / RTO1 min / 5 min (Cloud SQL HA + PITR)

9. Hotel-Specific Notes

  • System roles (immutable, seeded per tenant on provision): tenant.owner, tenant.gm, tenant.front_desk, tenant.housekeeping_lead, tenant.housekeeping, tenant.maintenance, tenant.finance, tenant.marketing, chain.operator. Tenant may add custom roles from the canonical (resource, action) registry.
  • Org tree shape: chain → region → property. A single-property guesthouse has chain collapsed to the property root. A regional chain has chain → region (e.g. Kabul) → property (Hotel Asia).
  • Per-property staff scoping is the norm. Front-desk clerks have propertyScope = [ppt_…]; GMs typically have propertyScope = [] meaning the entire tenant; chain operators have propertyScope = [] across all tenants in the chain account (a separate construct in iam-service).
  • TenantConfig drives downstream defaults. When a hotel sets defaultCheckInTime = 14:00, reservation-service uses that value when materializing a reservation that does not specify a check-in time. Same for cancellation policy, currency, breakfast inclusion, smoking, and child policy.

10. Bundle Index

The deep bundle lives at services/tenant-service/:

#DocOwns
1SERVICE_OVERVIEWMission, context, dependencies, decisions
2DOMAIN_MODELAggregates, VOs, invariants, state machines
3APPLICATION_LOGICUse cases, ports, sagas
4API_CONTRACTSREST endpoints with schemas
5EVENT_SCHEMASPub/Sub event shapes (published + consumed)
6DATA_MODELPostgres schema, RLS, indexes
7SYNC_CONTRACTPer-aggregate conflict policy for the desktop
8AI_INTEGRATIONAnomaly + invite-abuse via AIClient
9SECURITY_MODELRBAC matrix, ABAC, last-owner protection, invite security
10OBSERVABILITYSLOs, dashboards, alerts
11TESTING_STRATEGYPyramid + tenant-isolation + role-escalation suites
12DEPLOYMENT_TOPOLOGYCloud Run topology, Postgres, Pub/Sub
13FAILURE_MODESCascade failure, invite delivery, suspension propagation
14LOCAL_DEV_SETUPdocker compose; seed: 1 super-admin + 1 chain + 1 single-property
15SERVICE_READINESSProduction gate checklist
16SERVICE_RISK_REGISTERTop risks + mitigations
17MIGRATION_PLANSpreadsheet → org-tree CSV importer