Files
Krow-workspace/docs/MILESTONES/M4/planning/m4-target-schema-models-and-keys.md

12 KiB

M4 Target Schema Models, Keys, and Relationships

Status: Draft for architecture workshop
Date: 2026-02-25
Owner: Technical Lead

1) Purpose

This document is the model-level view for slide creation:

  1. Key fields per model (PK, FK, unique keys).
  2. How models relate to each other.
  3. A first-principles structure for scale across tenant, business, vendor, and workforce flows.

2) Identity and access models

2.1 Model keys

Model Primary key Foreign keys Important unique keys
users id - email (optional unique)
tenants id - slug
tenant_memberships id tenant_id -> tenants.id, user_id -> users.id (tenant_id, user_id)
business_memberships id tenant_id -> tenants.id, business_id -> businesses.id, user_id -> users.id (business_id, user_id)
vendor_memberships id tenant_id -> tenants.id, vendor_id -> vendors.id, user_id -> users.id (vendor_id, user_id)
roles id tenant_id -> tenants.id (tenant_id, name)
permissions id - code
role_bindings id tenant_id -> tenants.id, role_id -> roles.id, user_id -> users.id (tenant_id, role_id, user_id, scope_type, scope_id)

2.2 Diagram

erDiagram
  USERS ||--o{ TENANT_MEMBERSHIPS : has
  TENANTS ||--o{ TENANT_MEMBERSHIPS : has

  USERS ||--o{ BUSINESS_MEMBERSHIPS : has
  BUSINESSES ||--o{ BUSINESS_MEMBERSHIPS : has
  TENANTS ||--o{ BUSINESS_MEMBERSHIPS : scopes

  USERS ||--o{ VENDOR_MEMBERSHIPS : has
  VENDORS ||--o{ VENDOR_MEMBERSHIPS : has
  TENANTS ||--o{ VENDOR_MEMBERSHIPS : scopes

  TENANTS ||--o{ ROLES : defines
  ROLES ||--o{ ROLE_BINDINGS : used_by
  USERS ||--o{ ROLE_BINDINGS : receives
  TENANTS ||--o{ ROLE_BINDINGS : scopes

## 3) Organization and stakeholder models

### 3.1 Model keys

| Model | Primary key | Foreign keys | Important unique keys |
|---|---|---|---|
| `businesses` | `id` | `tenant_id -> tenants.id` | `(tenant_id, business_name)` |
| `vendors` | `id` | `tenant_id -> tenants.id` | `(tenant_id, company_name)` |
| `teams` | `id` | `tenant_id -> tenants.id` | `(tenant_id, team_name)` |
| `team_hubs` | `id` | `team_id -> teams.id` | `(team_id, hub_name)` |
| `team_hud_departments` | `id` | `team_hub_id -> team_hubs.id` | `(team_hub_id, name)` |
| `stakeholder_types` | `id` | - | `code` |
| `stakeholder_profiles` | `id` | `tenant_id -> tenants.id`, `stakeholder_type_id -> stakeholder_types.id` | `(tenant_id, stakeholder_type_id, name)` |
| `stakeholder_links` | `id` | `tenant_id -> tenants.id`, `from_profile_id -> stakeholder_profiles.id`, `to_profile_id -> stakeholder_profiles.id` | `(tenant_id, from_profile_id, to_profile_id, relation_type)` |

### 3.2 Diagram

```mermaid
erDiagram
  TENANTS ||--o{ BUSINESSES : owns
  TENANTS ||--o{ VENDORS : owns
  TENANTS ||--o{ TEAMS : owns
  TEAMS ||--o{ TEAM_HUBS : has
  TEAM_HUBS ||--o{ TEAM_HUD_DEPARTMENTS : has

  STAKEHOLDER_TYPES ||--o{ STAKEHOLDER_PROFILES : classifies
  TENANTS ||--o{ STAKEHOLDER_PROFILES : owns
  STAKEHOLDER_PROFILES ||--o{ STAKEHOLDER_LINKS : from
  STAKEHOLDER_PROFILES ||--o{ STAKEHOLDER_LINKS : to
  TENANTS ||--o{ STAKEHOLDER_LINKS : scopes

## 4) Workforce, orders, and assignments

### 4.1 Model keys

| Model | Primary key | Foreign keys | Important unique keys |
|---|---|---|---|
| `staffs` | `id` | `tenant_id -> tenants.id`, `user_id -> users.id` | `(tenant_id, user_id)` (nullable until activation if needed) |
| `staff_roles` | `id` | `staff_id -> staffs.id`, `role_id -> roles.id` | `(staff_id, role_id)` |
| `workforce` | `id` | `tenant_id -> tenants.id`, `vendor_id -> vendors.id`, `staff_id -> staffs.id` | active `(vendor_id, staff_id)` |
| `orders` | `id` | `tenant_id -> tenants.id`, `business_id -> businesses.id`, `vendor_id -> vendors.id` | `(tenant_id, external_ref)` optional |
| `order_schedule_rules` | `id` | `order_id -> orders.id` | `(order_id, rule_type, effective_from)` |
| `shifts` | `id` | `tenant_id -> tenants.id`, `order_id -> orders.id` | `(order_id, start_time, end_time)` |
| `shift_roles` | `id` | `shift_id -> shifts.id`, `role_id -> roles.id` | `(shift_id, role_id)` |
| `shift_managers` | `id` | `shift_id -> shifts.id`, `team_member_id -> team_members.id` | `(shift_id, team_member_id)` |
| `applications` | `id` | `tenant_id -> tenants.id`, `shift_id -> shifts.id`, `role_id -> roles.id`, `staff_id -> staffs.id` | `(shift_id, role_id, staff_id)` |
| `assignments` | `id` | `tenant_id -> tenants.id`, `shift_role_id -> shift_roles.id`, `workforce_id -> workforce.id` | `(shift_role_id, workforce_id)` active |
| `staff_reviews` | `id` | `tenant_id -> tenants.id`, `business_id -> businesses.id`, `staff_id -> staffs.id`, `assignment_id -> assignments.id` | `(business_id, assignment_id, staff_id)` |
| `staff_favorites` | `id` | `tenant_id -> tenants.id`, `business_id -> businesses.id`, `staff_id -> staffs.id` | `(business_id, staff_id)` |

### 4.2 Diagram

```mermaid
erDiagram
  TENANTS ||--o{ STAFFS : owns
  USERS ||--o| STAFFS : identity
  STAFFS ||--o{ STAFF_ROLES : has
  ROLES ||--o{ STAFF_ROLES : classifies

  TENANTS ||--o{ ORDERS : scopes
  BUSINESSES ||--o{ ORDERS : requests
  VENDORS ||--o{ ORDERS : fulfills
  ORDERS ||--o{ ORDER_SCHEDULE_RULES : configures
  ORDERS ||--o{ SHIFTS : expands_to
  SHIFTS ||--o{ SHIFT_ROLES : requires
  ROLES ||--o{ SHIFT_ROLES : typed_as
  SHIFTS ||--o{ SHIFT_MANAGERS : has

  VENDORS ||--o{ WORKFORCE : contracts
  STAFFS ||--o{ WORKFORCE : linked

  SHIFT_ROLES ||--o{ APPLICATIONS : receives
  STAFFS ||--o{ APPLICATIONS : applies
  SHIFT_ROLES ||--o{ ASSIGNMENTS : allocates
  WORKFORCE ||--o{ ASSIGNMENTS : executes
  BUSINESSES ||--o{ STAFF_REVIEWS : rates
  STAFFS ||--o{ STAFF_REVIEWS : receives
  ASSIGNMENTS ||--o{ STAFF_REVIEWS : references
  BUSINESSES ||--o{ STAFF_FAVORITES : favorites
  STAFFS ||--o{ STAFF_FAVORITES : selected

## 5) Attendance and offense governance

### 5.1 Model keys

| Model | Primary key | Foreign keys | Important unique keys |
|---|---|---|---|
| `clock_points` | `id` | `tenant_id -> tenants.id`, `business_id -> businesses.id` | `(tenant_id, nfc_tag_uid)` nullable |
| `attendance_events` | `id` | `tenant_id -> tenants.id`, `assignment_id -> assignments.id`, `clock_point_id -> clock_points.id` | append-only event log |
| `attendance_sessions` | `id` | `tenant_id -> tenants.id`, `assignment_id -> assignments.id` | one open session per assignment |
| `timesheets` | `id` | `tenant_id -> tenants.id`, `assignment_id -> assignments.id`, `staff_id -> staffs.id` | `(assignment_id)` |
| `timesheet_adjustments` | `id` | `timesheet_id -> timesheets.id`, `actor_user_id -> users.id` | - |
| `offense_policies` | `id` | `tenant_id -> tenants.id`, `business_id -> businesses.id` nullable | `(tenant_id, name, business_id)` |
| `offense_rules` | `id` | `policy_id -> offense_policies.id` | `(policy_id, trigger_type, threshold_count)` |
| `offense_events` | `id` | `tenant_id -> tenants.id`, `staff_id -> staffs.id`, `assignment_id -> assignments.id` nullable, `rule_id -> offense_rules.id` nullable | `(staff_id, occurred_at, offense_type)` |
| `enforcement_actions` | `id` | `offense_event_id -> offense_events.id`, `actor_user_id -> users.id` | - |

### 5.2 Diagram

```mermaid
erDiagram
  BUSINESSES ||--o{ CLOCK_POINTS : defines
  CLOCK_POINTS ||--o{ ATTENDANCE_EVENTS : validates
  ASSIGNMENTS ||--o{ ATTENDANCE_EVENTS : emits
  ASSIGNMENTS ||--o{ ATTENDANCE_SESSIONS : opens
  ASSIGNMENTS ||--o{ TIMESHEETS : settles
  TIMESHEETS ||--o{ TIMESHEET_ADJUSTMENTS : adjusts
  USERS ||--o{ TIMESHEET_ADJUSTMENTS : made_by

  TENANTS ||--o{ OFFENSE_POLICIES : defines
  BUSINESSES ||--o{ OFFENSE_POLICIES : overrides
  OFFENSE_POLICIES ||--o{ OFFENSE_RULES : contains
  STAFFS ||--o{ OFFENSE_EVENTS : incurs
  OFFENSE_RULES ||--o{ OFFENSE_EVENTS : triggered_by
  OFFENSE_EVENTS ||--o{ ENFORCEMENT_ACTIONS : causes
  USERS ||--o{ ENFORCEMENT_ACTIONS : applied_by

## 6) Compliance and verification

### 6.1 Model keys

| Model | Primary key | Foreign keys | Important unique keys |
|---|---|---|---|
| `documents` | `id` | `tenant_id -> tenants.id` | `(tenant_id, document_type, name)` |
| `staff_documents` | `id` | `staff_id -> staffs.id`, `document_id -> documents.id` | `(staff_id, document_id)` |
| `certificates` | `id` | `staff_id -> staffs.id` | `(staff_id, certificate_number)` optional |
| `compliance_requirements` | `id` | `tenant_id -> tenants.id`, `business_id -> businesses.id` nullable, `role_id -> roles.id` nullable | `(tenant_id, requirement_type, business_id, role_id)` |
| `verification_jobs` | `id` | `tenant_id -> tenants.id`, `staff_id -> staffs.id`, `document_id -> documents.id` nullable | `(tenant_id, idempotency_key)` |
| `verification_reviews` | `id` | `verification_job_id -> verification_jobs.id`, `reviewer_user_id -> users.id` | - |
| `verification_events` | `id` | `verification_job_id -> verification_jobs.id` | - |

### 6.2 Diagram

```mermaid
erDiagram
  STAFFS ||--o{ STAFF_DOCUMENTS : uploads
  DOCUMENTS ||--o{ STAFF_DOCUMENTS : references
  STAFFS ||--o{ CERTIFICATES : has

  TENANTS ||--o{ COMPLIANCE_REQUIREMENTS : defines
  BUSINESSES ||--o{ COMPLIANCE_REQUIREMENTS : overrides
  ROLES ||--o{ COMPLIANCE_REQUIREMENTS : role_scope

  STAFFS ||--o{ VERIFICATION_JOBS : subject
  VERIFICATION_JOBS ||--o{ VERIFICATION_REVIEWS : reviewed
  VERIFICATION_JOBS ||--o{ VERIFICATION_EVENTS : logs
  USERS ||--o{ VERIFICATION_REVIEWS : reviewer

## 7) Finance and settlement

### 7.1 Model keys

| Model | Primary key | Foreign keys | Important unique keys |
|---|---|---|---|
| `invoices` | `id` | `tenant_id -> tenants.id`, `order_id -> orders.id`, `business_id -> businesses.id`, `vendor_id -> vendors.id` | `(tenant_id, invoice_number)` |
| `invoice_line_items` | `id` | `invoice_id -> invoices.id`, `assignment_id -> assignments.id` nullable | `(invoice_id, line_number)` |
| `invoice_status_history` | `id` | `invoice_id -> invoices.id`, `actor_user_id -> users.id` | - |
| `payment_runs` | `id` | `tenant_id -> tenants.id` | `(tenant_id, run_reference)` |
| `payment_allocations` | `id` | `payment_run_id -> payment_runs.id`, `invoice_id -> invoices.id` | `(payment_run_id, invoice_id)` |
| `remittance_documents` | `id` | `payment_run_id -> payment_runs.id` | `(payment_run_id, document_url)` |
| `account_token_refs` | `id` | `tenant_id -> tenants.id`, `owner_business_id -> businesses.id` nullable, `owner_vendor_id -> vendors.id` nullable | one primary per owner |

### 7.2 Diagram

```mermaid
erDiagram
  ORDERS ||--o{ INVOICES : billed
  BUSINESSES ||--o{ INVOICES : billed_to
  VENDORS ||--o{ INVOICES : billed_from
  INVOICES ||--o{ INVOICE_LINE_ITEMS : contains
  INVOICES ||--o{ INVOICE_STATUS_HISTORY : transitions
  USERS ||--o{ INVOICE_STATUS_HISTORY : changed_by

  PAYMENT_RUNS ||--o{ PAYMENT_ALLOCATIONS : allocates
  INVOICES ||--o{ PAYMENT_ALLOCATIONS : receives
  PAYMENT_RUNS ||--o{ REMITTANCE_DOCUMENTS : publishes

  TENANTS ||--o{ ACCOUNT_TOKEN_REFS : scopes
  BUSINESSES ||--o{ ACCOUNT_TOKEN_REFS : business_owner
  VENDORS ||--o{ ACCOUNT_TOKEN_REFS : vendor_owner

## 8) Audit and reliability

### 8.1 Model keys

| Model | Primary key | Foreign keys | Important unique keys |
|---|---|---|---|
| `domain_events` | `id` | `tenant_id -> tenants.id`, `actor_user_id -> users.id` | `(tenant_id, aggregate_type, aggregate_id, sequence)` |
| `idempotency_keys` | `id` | `tenant_id -> tenants.id`, `actor_user_id -> users.id` | `(tenant_id, actor_user_id, route, key)` |
| `activity_logs` | `id` | `tenant_id -> tenants.id`, `user_id -> users.id` | `(tenant_id, created_at, id)` |

### 8.2 Diagram

```mermaid
erDiagram
  TENANTS ||--o{ DOMAIN_EVENTS : scopes
  USERS ||--o{ DOMAIN_EVENTS : actor
  TENANTS ||--o{ IDEMPOTENCY_KEYS : scopes
  USERS ||--o{ IDEMPOTENCY_KEYS : actor
  TENANTS ||--o{ ACTIVITY_LOGS : scopes
  USERS ||--o{ ACTIVITY_LOGS : actor

## 9) Practical note for current system
Current schema already has:
1. `businesses.userId` (single business owner user).
2. `vendors.userId` (single vendor owner user).
3. `team_members` (multi-user workaround).

Target schema improves this by adding explicit:
1. `business_memberships`
2. `vendor_memberships`

This is the key upgrade for clean client/vendor user partitioning.