docs(m4): define multi-tenant schema and phased rbac rollout
This commit is contained in:
390
docs/MILESTONES/M4/planning/m4-target-schema-blueprint.md
Normal file
390
docs/MILESTONES/M4/planning/m4-target-schema-blueprint.md
Normal file
@@ -0,0 +1,390 @@
|
||||
# M4 Target Schema Blueprint (Command-Ready)
|
||||
|
||||
Status: Draft for team alignment
|
||||
Date: 2026-02-24
|
||||
Owner: Technical Lead
|
||||
|
||||
## 1) Goal
|
||||
Define the target database shape we want **before** command-backend implementation, so critical flows are atomic, secure, and scalable.
|
||||
|
||||
## 1.1 Stakeholder and tenancy model
|
||||
This product should be designed as a **multi-tenant platform**.
|
||||
|
||||
1. Tenant:
|
||||
- One staffing company account (example: Legendary Event Staffing and Entertainment).
|
||||
2. Business:
|
||||
- A customer/client account owned by a tenant.
|
||||
3. User:
|
||||
- A human identity (auth account) that can belong to one or more tenants.
|
||||
4. Staff:
|
||||
- A workforce profile linked to a user identity and tenant-scoped operations.
|
||||
|
||||
Practical meaning:
|
||||
1. The same platform can serve multiple staffing companies safely.
|
||||
2. Data isolation is by `tenant_id`, not only by business/vendor IDs.
|
||||
3. Not every record starts as a full active user:
|
||||
- invite-first or pending onboarding records are valid,
|
||||
- then bound to `user_id` when activation is completed.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
U["User identity"] --> M["Tenant membership"]
|
||||
M --> T["Tenant staffing company"]
|
||||
T --> B["Business client"]
|
||||
T --> V["Vendor partner"]
|
||||
B --> O["Orders and shifts"]
|
||||
V --> O
|
||||
```
|
||||
|
||||
## 2) First-principles rules
|
||||
1. Every critical write must be server-mediated and transactional.
|
||||
2. Tenant boundaries must be explicit in data and queries.
|
||||
3. Money and rates must use exact numeric types, not floating point.
|
||||
4. Data needed for constraints should be relational, not hidden in JSON blobs.
|
||||
5. Every high-risk state transition must be auditable and replayable.
|
||||
|
||||
## 3) Current anti-patterns we are removing
|
||||
1. Direct client mutation of core entities.
|
||||
2. Broad `USER`-auth CRUD without strict tenant scoping.
|
||||
3. Financial values as `Float`.
|
||||
4. Core workflow state embedded in generic `Any/jsonb` fields.
|
||||
5. Missing uniqueness/index constraints on high-traffic paths.
|
||||
|
||||
## 4) Target modular schema
|
||||
|
||||
## 4.1 Identity and Access
|
||||
Tables:
|
||||
1. `users` (source identity, profile, auth linkage)
|
||||
2. `tenant_memberships` (new; membership + base access per tenant)
|
||||
3. `team_members` (membership + scope per team)
|
||||
4. `roles` (new)
|
||||
5. `permissions` (new)
|
||||
6. `role_bindings` (new; who has which role in which scope)
|
||||
|
||||
Rules:
|
||||
1. Unique tenant membership: `(tenant_id, user_id)`.
|
||||
2. Unique team membership: `(team_id, user_id)`.
|
||||
3. Access checks resolve through tenant membership first, then optional team/hub scope.
|
||||
|
||||
## 4.2 Organization and Tenant
|
||||
Tables:
|
||||
1. `tenants` (new canonical boundary: business/vendor ownership root)
|
||||
2. `businesses`
|
||||
3. `vendors`
|
||||
4. `teams`
|
||||
5. `team_hubs`
|
||||
6. `hubs`
|
||||
|
||||
Rules:
|
||||
1. Every command-critical row references `tenant_id`.
|
||||
2. All list queries must include tenant predicate.
|
||||
|
||||
## 4.8 RBAC rollout strategy (deferred enforcement)
|
||||
RBAC should be introduced in phases and **not enforced everywhere immediately**.
|
||||
|
||||
Phase A: Auth-first (now)
|
||||
1. Require valid auth token.
|
||||
2. Resolve tenant context.
|
||||
3. Allow current work to continue while logging actor + tenant + action.
|
||||
|
||||
Phase B: Shadow RBAC
|
||||
1. Evaluate permissions (`allow`/`deny`) in backend.
|
||||
2. Log decisions but do not block most requests yet.
|
||||
3. Start with warnings and dashboards for denied actions.
|
||||
|
||||
Phase C: Enforced RBAC on command writes
|
||||
1. Enforce RBAC on `/commands/*` only.
|
||||
2. Keep low-risk read flows in transition mode.
|
||||
|
||||
Phase D: Enforced RBAC on high-risk reads
|
||||
1. Enforce tenant and role checks on sensitive read connectors.
|
||||
2. Remove remaining broad user-level access.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["Auth only"] --> B["Shadow RBAC logging"]
|
||||
B --> C["Enforce RBAC on command writes"]
|
||||
C --> D["Enforce RBAC on sensitive reads"]
|
||||
```
|
||||
|
||||
## 4.3 Scheduling and Orders
|
||||
Tables:
|
||||
1. `orders`
|
||||
2. `order_schedule_rules` (new; replaces schedule JSON fields)
|
||||
3. `shifts`
|
||||
4. `shift_roles`
|
||||
5. `shift_role_requirements` (optional extension for policy rules)
|
||||
6. `shift_managers` (new; replaces `managers: [Any!]`)
|
||||
|
||||
Rules:
|
||||
1. No denormalized `assignedStaff` or `shifts` JSON in `orders`.
|
||||
2. Time constraints: `start_time < end_time`.
|
||||
3. Capacity constraints: `assigned <= count`, `filled <= workers_needed`.
|
||||
4. Canonical status names (single spelling across schema).
|
||||
|
||||
## 4.4 Staffing and Matching
|
||||
Tables:
|
||||
1. `staffs`
|
||||
2. `staff_roles`
|
||||
3. `workforce`
|
||||
4. `applications`
|
||||
5. `assignments`
|
||||
|
||||
Rules:
|
||||
1. One active workforce relation per `(vendor_id, staff_id)`.
|
||||
2. One application per `(shift_id, role_id, staff_id)` unless versioned intentionally.
|
||||
3. Assignment state transitions only through command APIs.
|
||||
|
||||
## 4.5 Compliance and Verification
|
||||
Tables:
|
||||
1. `documents`
|
||||
2. `staff_documents`
|
||||
3. `certificates`
|
||||
4. `verification_jobs`
|
||||
5. `verification_reviews`
|
||||
6. `verification_events`
|
||||
|
||||
Rules:
|
||||
1. Verification is asynchronous and append-only for events.
|
||||
2. Manual review is explicit and tracked.
|
||||
3. Government ID and certification provider references are persisted.
|
||||
|
||||
## 4.6 Financial and Payout
|
||||
Tables:
|
||||
1. `invoices`
|
||||
2. `invoice_templates`
|
||||
3. `recent_payments`
|
||||
4. `accounts` (refactor to tokenized provider references)
|
||||
|
||||
Rules:
|
||||
1. Replace monetary `Float` with exact numeric (`DECIMAL(12,2)` or integer cents).
|
||||
2. Do not expose raw account/routing values in query connectors.
|
||||
3. Add one-primary-account constraint per owner.
|
||||
|
||||
## 4.7 Audit and Reliability
|
||||
Tables:
|
||||
1. `domain_events` (new)
|
||||
2. `idempotency_keys` (already started in command API SQL)
|
||||
3. `activity_logs`
|
||||
|
||||
Rules:
|
||||
1. Every command write emits a domain event.
|
||||
2. Idempotency scope: `(actor_uid, route, idempotency_key)`.
|
||||
|
||||
## 5) Target core model (conceptual)
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
TENANT ||--o{ BUSINESS : owns
|
||||
TENANT ||--o{ VENDOR : owns
|
||||
TENANT ||--o{ TEAM : owns
|
||||
TEAM ||--o{ TEAM_MEMBER : has
|
||||
USER ||--o{ TEAM_MEMBER : belongs_to
|
||||
|
||||
BUSINESS ||--o{ ORDER : requests
|
||||
VENDOR ||--o{ ORDER : fulfills
|
||||
ORDER ||--o{ ORDER_SCHEDULE_RULE : has
|
||||
ORDER ||--o{ SHIFT : expands_to
|
||||
SHIFT ||--o{ SHIFT_ROLE : requires
|
||||
SHIFT ||--o{ SHIFT_MANAGER : has
|
||||
|
||||
USER ||--o{ STAFF : identity
|
||||
STAFF ||--o{ STAFF_ROLE : skills
|
||||
VENDOR ||--o{ WORKFORCE : contracts
|
||||
STAFF ||--o{ WORKFORCE : linked
|
||||
SHIFT_ROLE ||--o{ APPLICATION : receives
|
||||
STAFF ||--o{ APPLICATION : applies
|
||||
SHIFT_ROLE ||--o{ ASSIGNMENT : allocates
|
||||
WORKFORCE ||--o{ ASSIGNMENT : executes
|
||||
|
||||
STAFF ||--o{ CERTIFICATE : has
|
||||
STAFF ||--o{ STAFF_DOCUMENT : uploads
|
||||
DOCUMENT ||--o{ STAFF_DOCUMENT : references
|
||||
STAFF ||--o{ VERIFICATION_JOB : subject
|
||||
VERIFICATION_JOB ||--o{ VERIFICATION_REVIEW : reviewed_by
|
||||
VERIFICATION_JOB ||--o{ VERIFICATION_EVENT : logs
|
||||
|
||||
ORDER ||--o{ INVOICE : billed_by
|
||||
INVOICE ||--o{ RECENT_PAYMENT : settles
|
||||
TENANT ||--o{ ACCOUNT_TOKEN_REF : payout_method
|
||||
|
||||
ORDER ||--o{ DOMAIN_EVENT : emits
|
||||
SHIFT ||--o{ DOMAIN_EVENT : emits
|
||||
ASSIGNMENT ||--o{ DOMAIN_EVENT : emits
|
||||
```
|
||||
|
||||
## 6) Command write boundary on this schema
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["Frontend app"] --> B["Command API"]
|
||||
B --> C["Policy + validation"]
|
||||
C --> D["Single database transaction"]
|
||||
D --> E["orders, shifts, shift_roles, applications, assignments"]
|
||||
D --> F["domain_events + idempotency_keys"]
|
||||
E --> G["Read models and reports"]
|
||||
```
|
||||
|
||||
## 7) Minimum constraints and indexes to add before command build
|
||||
|
||||
## 7.1 Constraints
|
||||
1. `shift_roles`: check `assigned >= 0 AND assigned <= count`.
|
||||
2. `shifts`: check `start_time < end_time`.
|
||||
3. `applications`: unique `(shift_id, role_id, staff_id)`.
|
||||
4. `workforce`: unique active `(vendor_id, staff_id)`.
|
||||
5. `team_members`: unique `(team_id, user_id)`.
|
||||
6. `accounts` (or token ref table): unique primary per owner.
|
||||
|
||||
## 7.2 Indexes
|
||||
1. `orders (tenant_id, status, date)`.
|
||||
2. `shifts (order_id, date, status)`.
|
||||
3. `shift_roles (shift_id, role_id, start_time)`.
|
||||
4. `applications (shift_id, role_id, status, created_at)`.
|
||||
5. `assignments (workforce_id, shift_id, role_id, status)`.
|
||||
6. `verification_jobs (subject_id, type, status, created_at)`.
|
||||
7. `invoices (business_id, vendor_id, status, due_date)`.
|
||||
|
||||
## 8) Data type normalization
|
||||
1. Monetary: `Float -> DECIMAL(12,2)` (or integer cents).
|
||||
2. Generic JSON fields in core scheduling: split into relational tables.
|
||||
3. Timestamps: store UTC and enforce server-generated creation/update fields.
|
||||
|
||||
## 9) Security boundary in schema/connectors
|
||||
1. Remove broad list queries for sensitive entities unless tenant-scoped.
|
||||
2. Strip sensitive fields from connector query payloads (bank/routing).
|
||||
3. Keep high-risk mutations behind command API; Data Connect remains read-first for client.
|
||||
|
||||
## 10) Migration phases (schema-first)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
P0["Phase 0: Safety patch
|
||||
- lock sensitive fields
|
||||
- enforce tenant-scoped queries
|
||||
- freeze new direct write connectors"] --> P1["Phase 1: Core constraints
|
||||
- add unique/check constraints
|
||||
- add indexes
|
||||
- normalize money types"]
|
||||
P1 --> P2["Phase 2: Tenant and RBAC base tables
|
||||
- add tenants and tenant_memberships
|
||||
- add roles permissions role_bindings
|
||||
- run RBAC in shadow mode"]
|
||||
P2 --> P3["Phase 3: Scheduling normalization
|
||||
- remove order JSON workflow fields
|
||||
- add order_schedule_rules and shift_managers"]
|
||||
P3 --> P4["Phase 4: Command rollout
|
||||
- command writes on hardened schema
|
||||
- emit domain events + idempotency
|
||||
- enforce RBAC for command routes"]
|
||||
P4 --> P5["Phase 5: Read migration + cleanup
|
||||
- migrate frontend reads as needed
|
||||
- enforce RBAC for sensitive reads
|
||||
- retire deprecated connectors"]
|
||||
```
|
||||
|
||||
## 11) Definition of ready for command backend
|
||||
1. P0 and P1 complete in `dev`.
|
||||
2. Tenant scoping verified in connector tests.
|
||||
3. Sensitive field exposure removed.
|
||||
4. Core transaction invariants enforced by schema constraints.
|
||||
5. Command API contracts mapped to new normalized tables.
|
||||
6. RBAC is in shadow mode with decision logs in place (not hard-blocking yet).
|
||||
|
||||
## 12) Full current model relationship map (all models)
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Account["Account"]
|
||||
ActivityLog["ActivityLog"]
|
||||
Application["Application"]
|
||||
Assignment["Assignment"]
|
||||
AttireOption["AttireOption"]
|
||||
BenefitsData["BenefitsData"]
|
||||
Business["Business"]
|
||||
Category["Category"]
|
||||
Certificate["Certificate"]
|
||||
ClientFeedback["ClientFeedback"]
|
||||
Conversation["Conversation"]
|
||||
Course["Course"]
|
||||
CustomRateCard["CustomRateCard"]
|
||||
Document["Document"]
|
||||
EmergencyContact["EmergencyContact"]
|
||||
FaqData["FaqData"]
|
||||
Hub["Hub"]
|
||||
Invoice["Invoice"]
|
||||
InvoiceTemplate["InvoiceTemplate"]
|
||||
Level["Level"]
|
||||
MemberTask["MemberTask"]
|
||||
Message["Message"]
|
||||
Order["Order"]
|
||||
RecentPayment["RecentPayment"]
|
||||
Role["Role"]
|
||||
RoleCategory["RoleCategory"]
|
||||
Shift["Shift"]
|
||||
ShiftRole["ShiftRole"]
|
||||
Staff["Staff"]
|
||||
StaffAvailability["StaffAvailability"]
|
||||
StaffAvailabilityStats["StaffAvailabilityStats"]
|
||||
StaffCourse["StaffCourse"]
|
||||
StaffDocument["StaffDocument"]
|
||||
StaffRole["StaffRole"]
|
||||
Task["Task"]
|
||||
TaskComment["TaskComment"]
|
||||
TaxForm["TaxForm"]
|
||||
Team["Team"]
|
||||
TeamHub["TeamHub"]
|
||||
TeamHudDepartment["TeamHudDepartment"]
|
||||
TeamMember["TeamMember"]
|
||||
User["User"]
|
||||
UserConversation["UserConversation"]
|
||||
Vendor["Vendor"]
|
||||
VendorBenefitPlan["VendorBenefitPlan"]
|
||||
VendorRate["VendorRate"]
|
||||
Workforce["Workforce"]
|
||||
|
||||
Application --> Shift
|
||||
Application --> ShiftRole
|
||||
Application --> Staff
|
||||
Assignment --> ShiftRole
|
||||
Assignment --> Workforce
|
||||
BenefitsData --> Staff
|
||||
BenefitsData --> VendorBenefitPlan
|
||||
Certificate --> Staff
|
||||
ClientFeedback --> Business
|
||||
ClientFeedback --> Vendor
|
||||
Course --> Category
|
||||
Invoice --> Business
|
||||
Invoice --> Order
|
||||
Invoice --> Vendor
|
||||
InvoiceTemplate --> Business
|
||||
InvoiceTemplate --> Order
|
||||
InvoiceTemplate --> Vendor
|
||||
MemberTask --> Task
|
||||
MemberTask --> TeamMember
|
||||
Message --> User
|
||||
Order --> Business
|
||||
Order --> TeamHub
|
||||
Order --> Vendor
|
||||
RecentPayment --> Application
|
||||
RecentPayment --> Invoice
|
||||
Shift --> Order
|
||||
ShiftRole --> Role
|
||||
ShiftRole --> Shift
|
||||
StaffAvailability --> Staff
|
||||
StaffAvailabilityStats --> Staff
|
||||
StaffDocument --> Document
|
||||
StaffRole --> Role
|
||||
StaffRole --> Staff
|
||||
TaskComment --> TeamMember
|
||||
TeamHub --> Team
|
||||
TeamHudDepartment --> TeamHub
|
||||
TeamMember --> Team
|
||||
TeamMember --> TeamHub
|
||||
TeamMember --> User
|
||||
UserConversation --> Conversation
|
||||
UserConversation --> User
|
||||
VendorBenefitPlan --> Vendor
|
||||
VendorRate --> Vendor
|
||||
Workforce --> Staff
|
||||
Workforce --> Vendor
|
||||
```
|
||||
Reference in New Issue
Block a user