Source document
docs/platform/TENANT-LIFECYCLE.md
Source version
0.0.0
Generated time
2026-07-06T14:59:20.943Z
Owner
Unassigned
Audience
Internal
Status
Approved

Tenant Lifecycle — Specification (Beta-5)

Đặc tả vòng đời Tenant của BNG Booking Platform — chứng minh mô hình multi-tenant / white-label hoạt động thật.
Nguồn sự thật code: provisioning/provisioning-service.ts (transition + audit), booking-service.ts (assertTenantActive — cổng booking),
prisma/schema.prisma (tenants.status). Kiểm chứng E2E: src/__tests__/tenant-provisioning-e2e.test.ts, src/__tests__/ops-tenant.test.ts.

Tenant là gì

Tenant = một tenants row (đơn vị thuê / thương hiệu white-label). Booking, Inventory, Reservation đều tenant-scoped. Beta-5 thêm cột tenants.status (migration 20260706000000_add_tenant_status, additive DEFAULT 'active').

Trạng thái

Trạng tháiÝ nghĩaBooking chạy?
provisionedĐã tạo, chưa kích hoạt (chưa mở bán).❌ (cổng chặn → 403)
activeĐang hoạt động — booking chạy bình thường.
suspendedTạm ngưng. Thuận nghịch.❌ (403 — inventory/key không bị xoá)
archivedĐóng vĩnh viễn. Terminal.❌ (403)
Cổng Tenant (booking): BookingService.assertTenantActive chặn availability/booking khi tenant != active
TENANT_INACTIVE (HTTP 403). Đặt ở BookingService (không ở auth) vì bảng tenantsRLS-FORCE:
auth chạy TRƯỚC khi có tenant context nên không đọc được tenants. Cổng chạy TRONG tenant context (đã `SET LOCAL
app.current_tenant`) ⇒ RLS-safe. Tương thích ngược: tenant không có row lifecycle (legacy/chưa provision qua Beta-5)
giữ hành vi cũ; chỉ chặn tenant đã có row và không active.

Sơ đồ chuyển trạng thái

   provision            activate                suspend
 (∅) ───────▶ provisioned ─────────▶ active ─────────────▶ suspended
                   │                   ▲  │                    │
                   │                   └──┘ reactivate ────────┘
                   │                   │
                   │ archive           │ archive               │ archive
                   ▼                   ▼                        ▼
                            archived  (TERMINAL)

Transition hợp lệ

TừThao tácĐếnAudit event
provisionTenantprovisionedtenant.created
provisionedactivateTenantactivetenant.activated
activesuspendTenantsuspendedtenant.suspended
suspendedreactivateTenantactivetenant.reactivated
provisioned / active / suspendedarchiveTenantarchivedtenant.archived

Transition KHÔNG hợp lệ (⇒ TENANT_INVALID_TRANSITION, exit 2)

Invariant

  1. Terminal: archived không có transition ra.
  2. Suspend thuận nghịch: suspend chỉ đổi tenants.statuskhông xoá inventory/key/reservation ⇒ reactivate khôi phục nguyên trạng, cùng API key booking lại được.
  3. Cổng booking = tenant status: nguồn sự thật cho "booking chạy không" là tenants.status = active. Không đường vòng.
  4. Độc lập với Partner (Beta-4): suspend Tenant không đụng Partner status/key; suspend Partner không đụng Tenant status. Hai cổng độc lập, xếp tầng (một request qua cả hai).
  5. Audit đầy đủ & bất biến: mỗi transition sinh đúng 1 AuditEvent (append-only), scope tenantId.
  6. Không secret trong audit: metadata sanitize ở domain.

Quan hệ Partner ↔ Tenant ↔ Inventory ↔ Booking

Partner (Customer, Beta-4)
  │ 1..1  (provisionTenant gắn customer.tenantId)
  ▼
Tenant (tenants, Beta-5)  ──status gate──▶ Booking API
  │ 1..N (seed-inventory / provision)
  ▼
Inventory (Listing → Unit → Availability + Price)
  │
  ▼
Reservation (Booking, Beta-1/2) — tenant-scoped, RLS
PartnerTenantKết quả request
activeactive✅ chạy
suspended/revoked(bất kỳ)❌ 401 (cổng Partner ở auth)
activeprovisioned/suspended/archived❌ 403 TENANT_INACTIVE (cổng Tenant ở booking)

Audit event (Deliverable #5)

Scope AuditEvent.tenantId = tenantId; entityRef = tenantId; actor {kind:"authenticated", source:"ops_cli", role:"ops"}. Kiểm chứng: TENANT-AUDIT-VERIFICATION.md.

Thao tácAudit action
Tenant createdtenant.created
Tenant activatedtenant.activated
Tenant suspendedtenant.suspended
Tenant re-activatedtenant.reactivated
Tenant archivedtenant.archived