# Data Model — 093 SCOPE-001 Workspace ID Isolation ## Core Entities ### Workspace - `workspaces`: - `id` (bigint) - `name`, `slug`, timestamps ### Tenant - `tenants`: - `id` (bigint) - `workspace_id` (bigint, intended non-null logically; currently nullable in schema) **Ownership rule**: A tenant belongs to exactly one workspace; this mapping is the source of truth for deriving workspace bindings. ## Tenant-owned Tables (must become workspace-bound) For each table below: - Add `workspace_id` (bigint FK to `workspaces.id`) - Enforce `workspace_id` derived from tenant (DB-level composite FK on Postgres/MySQL) - Keep `tenant_id` immutable (application enforcement) ### policies - Existing: `tenant_id`, `external_id`, `policy_type`, etc. - Add: `workspace_id` ### policy_versions - Existing: `tenant_id`, `policy_id`, `snapshot`, etc. - Add: `workspace_id` ### backup_sets - Existing: `tenant_id`, status/count, etc. - Add: `workspace_id` ### backup_items - Existing: `tenant_id`, `backup_set_id`, `payload`, etc. - Add: `workspace_id` ### restore_runs - Existing: `tenant_id`, `backup_set_id`, status, preview/results, etc. - Add: `workspace_id` ### backup_schedules - Existing: `tenant_id`, enabled/frequency/schedule fields - Add: `workspace_id` ### inventory_items - Existing: `tenant_id`, policy identifiers, `meta_jsonb`, last_seen fields - Add: `workspace_id` ### inventory_links - Existing: `tenant_id`, source/target relationship identifiers - Add: `workspace_id` ### entra_groups - Existing: `tenant_id`, entra_id, display fields - Add: `workspace_id` ### findings - Existing: `tenant_id`, fingerprint, status/severity, run references - Add: `workspace_id` ### entra_role_definitions - Existing: `tenant_id`, entra_id, display fields - Add: `workspace_id` ### tenant_permissions - Existing: `tenant_id`, permission_key, status - Add: `workspace_id` ## Audit Logs (scope invariants) ### audit_logs - Existing: `tenant_id` nullable, `workspace_id` nullable, action/resource fields **Invariant**: - Tenant-scoped audit entry: `tenant_id != null` implies `workspace_id != null`. - Workspace-only audit entry: `workspace_id != null` and `tenant_id == null` is allowed. - Platform-only audit entry: both null is allowed. ## Relationship + Constraint Strategy ### Tenant-owned enforcement (Postgres/MySQL) - Composite FK on each tenant-owned table: - `(tenant_id, workspace_id) → tenants(id, workspace_id)` - Standard FK on `workspace_id → workspaces.id` ### SQLite - Foreign key / composite constraint enforcement is limited. - Testing relies on application enforcement + basic NOT NULL where feasible.