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:
- Whose data is this? —
Tenantlifecycle, plan reference, residency pin, suspension state. - Which property? —
OrganizationUnithierarchy: chain → region → property. - 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
| Attribute | Value |
|---|---|
| Context | Tenant & Org |
| Domain class | Supporting (Core for chain customers) |
| Tenancy | Shared schema + tenant_id + RLS for child aggregates; tenants table itself is platform-scoped (only platform.super_admin reads it directly) |
| Storage | Cloud SQL Postgres, schema tenant |
| Cache | Memorystore (Redis) — tenant config, membership resolution, feature-flag overrides |
| Eventing | GCP Pub/Sub, subjects melmastoon.tenant.* |
| Upstream | iam-service (melmastoon.iam.user.registered.v1), billing-service (melmastoon.billing.subscription.cancelled.v1, …reactivated.v1) |
| Downstream | every 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
| Aggregate | Purpose |
|---|---|
Tenant | Hotel operator identity; plan reference; status (pending, active, suspended, closed); residency pin |
TenantConfig | Currencies, locales (RTL/LTR), tax model, time zone, default cancellation policy, default check-in/out times, breakfast included default, smoking policy, child policy, business hours |
OrganizationUnit | Chain → region → property hierarchy; ltree-stored path; max depth 5 |
Membership | A User (from iam) acting in this tenant; status pending, active, suspended, removed; propertyScope[] for per-property staff |
Role | Tenant-scoped role definition; system roles (e.g. tenant.owner) immutable; tenant-custom roles allowed |
RoleAssignment | A Membership ↔ Role link, optionally scoped to one or more OrganizationUnit IDs |
Invitation | Outbound invite envelope; signed token (hash stored); single-use; 14-day TTL |
BillingContact | Tenant-side contact + tax identifiers used by billing-service and invoice generation |
FeatureFlagOverride | Per-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 everyRoleAssignment. - Tenant-level feature flags:
aiEnabled,mfaRequiredForStaff,cashPaymentsAllowed,lockIntegrationEnabled, etc. - Compliance hooks: GDPR participation for guest erasure (forwarded to
reservation-serviceandbilling-service); tenant deletion cascade saga.
5. Service Interactions
5.1 Events Published
| Subject | When |
|---|---|
melmastoon.tenant.created.v1 | New tenant provisioned |
melmastoon.tenant.suspended.v1 | Manual or billing-driven suspension |
melmastoon.tenant.reactivated.v1 | Suspension cleared |
melmastoon.tenant.deleted.v1 | Tenant closed; cascade saga begins |
melmastoon.tenant.config_updated.v1 | Any TenantConfig field change |
melmastoon.tenant.membership.created.v1 | Membership materialized (post-invite-accept or auto-link) |
melmastoon.tenant.membership.role_changed.v1 | Roles or property scope on a membership change |
melmastoon.tenant.membership.removed.v1 | Membership soft-removed |
melmastoon.tenant.invitation.sent.v1 | Invite issued (notification-service sends the email) |
melmastoon.tenant.invitation.accepted.v1 | Invitee completed acceptance |
melmastoon.tenant.invitation.expired.v1 | TTL elapsed without acceptance |
melmastoon.tenant.guest.erasure_requested.v1 | DSAR fan-out trigger |
melmastoon.tenant.feature_flag.toggled.v1 | Per-tenant flag override changed |
melmastoon.tenant.organization_unit.created.v1 | New chain / region / property added to the org tree |
5.2 Events Consumed
| Subject | Source | Effect |
|---|---|---|
melmastoon.iam.user.registered.v1 | iam-service | Auto-promote any matching pending invitations to active memberships |
melmastoon.billing.subscription.cancelled.v1 | billing-service | Auto-suspend tenant after grace period |
melmastoon.billing.subscription.reactivated.v1 | billing-service | Lift 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),Rolecatalog,OrganizationUnittree,FeatureFlagOverride. Write back disallowed for role/membership changes (require online). - AI: light consumer via
ai-orchestrator-service(AIClientport) for invite-abuse classification and anomaly detection on bulk membership removals — advisory only, never auto-applied.
7. Edge Cases (handled in domain)
| # | Case | Behavior |
|---|---|---|
| 1 | Last tenant.owner removal | Domain rejects with MELMASTOON.TENANT.LAST_OWNER_REMOVAL |
| 2 | Role escalation (member assigning a role they do not hold) | Rejected by assignableRoles invariant + ABAC at controller |
| 3 | Invitation token replay | Single-use; cleared on acceptance; constant-time hash compare |
| 4 | Concurrent TenantConfig edits | Optimistic concurrency (If-Match ETag); 412 on stale write |
| 5 | Property moved between chains | Saga pauses writes on the source unit, copies, swaps, then unfreezes; emits organization_unit.created on target |
| 6 | Tenant deletion cascade | Long-running saga; downstream services must ack within window or tenant.deleted event is held in the saga store |
| 7 | Suspended tenant reads vs writes | Reads allowed for tenant.owner only (to manage billing); writes universally rejected at gateway with MELMASTOON.TENANT.SUSPENDED |
| 8 | Orphaned membership when iam deletes a user | Background sweep + iam.user.deleted.v1 consumer flips membership to removed |
| 9 | Cross-tenant role reuse | Roles are tenant-scoped; system roles read-only across tenants but never assignable cross-tenant |
| 10 | Mass invitation abuse | Per-tenant + per-IP rate limit; AI classifier flags suspicious domains |
8. Non-Functional Targets
| Attribute | Target |
|---|---|
| Availability | 99.95% (every request resolves a tenant) |
tenant.resolve p95 | ≤ 5 ms (cached) / ≤ 25 ms (DB) |
| REST mutation p95 | ≤ 200 ms |
| Membership resolution throughput | 5 000 rps per Cloud Run instance |
| Tenant isolation | RLS on every child table; two-tenant simulator green on every PR |
| RPO / RTO | 1 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 haschaincollapsed to the property root. A regional chain haschain → region (e.g. Kabul) → property (Hotel Asia). - Per-property staff scoping is the norm. Front-desk clerks have
propertyScope = [ppt_…]; GMs typically havepropertyScope = []meaning the entire tenant; chain operators havepropertyScope = []across all tenants in the chain account (a separate construct iniam-service). TenantConfigdrives downstream defaults. When a hotel setsdefaultCheckInTime = 14:00,reservation-serviceuses 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/:
| # | Doc | Owns |
|---|---|---|
| 1 | SERVICE_OVERVIEW | Mission, context, dependencies, decisions |
| 2 | DOMAIN_MODEL | Aggregates, VOs, invariants, state machines |
| 3 | APPLICATION_LOGIC | Use cases, ports, sagas |
| 4 | API_CONTRACTS | REST endpoints with schemas |
| 5 | EVENT_SCHEMAS | Pub/Sub event shapes (published + consumed) |
| 6 | DATA_MODEL | Postgres schema, RLS, indexes |
| 7 | SYNC_CONTRACT | Per-aggregate conflict policy for the desktop |
| 8 | AI_INTEGRATION | Anomaly + invite-abuse via AIClient |
| 9 | SECURITY_MODEL | RBAC matrix, ABAC, last-owner protection, invite security |
| 10 | OBSERVABILITY | SLOs, dashboards, alerts |
| 11 | TESTING_STRATEGY | Pyramid + tenant-isolation + role-escalation suites |
| 12 | DEPLOYMENT_TOPOLOGY | Cloud Run topology, Postgres, Pub/Sub |
| 13 | FAILURE_MODES | Cascade failure, invite delivery, suspension propagation |
| 14 | LOCAL_DEV_SETUP | docker compose; seed: 1 super-admin + 1 chain + 1 single-property |
| 15 | SERVICE_READINESS | Production gate checklist |
| 16 | SERVICE_RISK_REGISTER | Top risks + mitigations |
| 17 | MIGRATION_PLAN | Spreadsheet → org-tree CSV importer |