TenantAtlas/specs/093-scope-001-workspace-id-isolation/quickstart.md
2026-02-14 22:32:38 +01:00

2.1 KiB

Quickstart — 093 Workspace ID Isolation

Goal

Run the staged rollout locally/staging to ensure all tenant-owned tables are workspace-bound and audit invariants hold.

Prereqs

  • Sail is running: vendor/bin/sail up -d
  • DB is migrated: vendor/bin/sail artisan migrate

Rollout Order

  1. Phase 1 — Add nullable workspace_id columns + indexes
  • Deploy migrations
  1. Phase 1.5 — Deploy app write-path enforcement
  • New/updated tenant-owned writes derive workspace_id from tenant.workspace_id
  • Mismatches are rejected
  1. Phase 2 — Backfill existing rows
  • Dry-run:
    • vendor/bin/sail artisan tenantpilot:backfill-workspace-ids --dry-run
  • Execute:
    • vendor/bin/sail artisan tenantpilot:backfill-workspace-ids
  1. Phase 3 — Enforce constraints + validate + final indexes
  • Apply NOT NULL + FKs + composite FKs
  • Add audit_logs check constraint

Validation SQL (Postgres)

Run these to confirm no missing bindings remain.

Tenant-owned tables:

  • SELECT count(*) FROM policies WHERE workspace_id IS NULL;
  • SELECT count(*) FROM policy_versions WHERE workspace_id IS NULL;
  • SELECT count(*) FROM backup_sets WHERE workspace_id IS NULL;
  • SELECT count(*) FROM backup_items WHERE workspace_id IS NULL;
  • SELECT count(*) FROM restore_runs WHERE workspace_id IS NULL;
  • SELECT count(*) FROM backup_schedules WHERE workspace_id IS NULL;
  • SELECT count(*) FROM inventory_items WHERE workspace_id IS NULL;
  • SELECT count(*) FROM inventory_links WHERE workspace_id IS NULL;
  • SELECT count(*) FROM entra_groups WHERE workspace_id IS NULL;
  • SELECT count(*) FROM findings WHERE workspace_id IS NULL;
  • SELECT count(*) FROM entra_role_definitions WHERE workspace_id IS NULL;
  • SELECT count(*) FROM tenant_permissions WHERE workspace_id IS NULL;

Audit invariant:

  • SELECT count(*) FROM audit_logs WHERE tenant_id IS NOT NULL AND workspace_id IS NULL;

Rollback notes

  • Phase 1 migrations are reversible (drop columns / indexes) but may be large operations on production datasets.
  • Prefer forward-fix for production if Phase 2/3 is partially applied.