Implements Spec 093 (SCOPE-001) workspace isolation at the data layer. What changed - Adds `workspace_id` to 12 tenant-owned tables and enforces correct binding. - Model write-path enforcement derives workspace from tenant + rejects mismatches. - Prevents `tenant_id` changes (immutability) on tenant-owned records. - Adds queued backfill command + job (`tenantpilot:backfill-workspace-ids`) with OperationRun + AuditLog observability. - Enforces DB constraints (NOT NULL + FK `workspace_id` → `workspaces.id` + composite FK `(tenant_id, workspace_id)` → `tenants(id, workspace_id)`), plus audit_logs invariant. UI / operator visibility - Monitor backfill runs in **Monitoring → Operations** (OperationRun). Tests - `vendor/bin/sail artisan test --compact tests/Feature/WorkspaceIsolation` Notes - Backfill is queued: ensure a queue worker is running (`vendor/bin/sail artisan queue:work`). Spec package - `specs/093-scope-001-workspace-id-isolation/` (plan, tasks, contracts, quickstart, research) Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #112
166 lines
12 KiB
Markdown
166 lines
12 KiB
Markdown
#+#+#+#+markdown
|
||
# Feature Specification: SCOPE-001 Workspace ID Isolation
|
||
|
||
**Feature Branch**: `093-scope-001-workspace-id-isolation`
|
||
**Created**: 2026-02-14
|
||
**Status**: Draft
|
||
**Input**: Enforce workspace isolation by binding all tenant-owned records to a workspace, with a safe staged rollout and corrected audit invariants.
|
||
|
||
## Spec Scope Fields *(mandatory)*
|
||
|
||
- **Scope**: workspace
|
||
- **Primary Routes**: No new pages/routes. Affects all create/write paths that persist tenant-owned records in the tables listed below, plus operational tooling for a one-time backfill.
|
||
- **Data Ownership**:
|
||
- Tenant-owned tables impacted (must become explicitly workspace-bound):
|
||
- policies
|
||
- policy_versions
|
||
- backup_sets
|
||
- backup_items
|
||
- restore_runs
|
||
- backup_schedules
|
||
- inventory_items
|
||
- inventory_links
|
||
- entra_groups
|
||
- findings
|
||
- entra_role_definitions
|
||
- tenant_permissions
|
||
- Audit trail invariants impacted: audit_logs
|
||
- **RBAC**: No user-facing permissions change. Backfill execution is an operator-only workflow (platform/ops).
|
||
|
||
## Clarifications
|
||
|
||
### Session 2026-02-14
|
||
|
||
- Q: For the 12 tenant-owned tables, how strict should deterministic workspace binding be when a caller explicitly provides a workspace binding? → A: Reject any mismatch; if a record references a tenant, its workspace binding MUST equal the tenant’s workspace.
|
||
- Q: During backfill, what should happen if a row’s tenant reference is invalid (tenant missing / cannot map tenant → workspace)? → A: Abort the backfill run and report the offending table plus sample identifiers for remediation.
|
||
- Q: Should tenant_id be allowed to change on existing rows in the 12 tenant-owned tables? → A: No; tenant_id is immutable and updates are rejected.
|
||
- Q: How should the backfill workflow be recorded for observability/audit? → A: Create/reuse an OperationRun for the backfill and also write an AuditLog summary for start/end/outcome.
|
||
- Q: Should this feature include updating existing canonical/operational queries/views to scope by workspace binding? → A: No; query/view changes are out of scope for 093 (follow-up later).
|
||
|
||
## User Scenarios & Testing *(mandatory)*
|
||
|
||
### User Story 1 - Enforce workspace ownership at the data layer (Priority: P1)
|
||
|
||
As a platform owner, I want every tenant-owned record to be explicitly bound to a workspace so that workspace isolation does not depend on application-only guardrails.
|
||
|
||
**Why this priority**: This closes a class of potential cross-workspace data leakage and makes operational reporting safer and simpler.
|
||
|
||
**Independent Test**: Can be tested by creating a tenant-owned record with a tenant reference and asserting that `workspace_id` is derived from the tenant and that any explicit mismatched `workspace_id` is rejected; backfill behavior is validated in User Story 2.
|
||
|
||
**Acceptance Scenarios**:
|
||
|
||
1. **Given** tenant-owned records exist without a workspace binding, **When** the staged rollout completes, **Then** all tenant-owned records are workspace-bound.
|
||
2. **Given** the rollout is in progress, **When** the system is used normally, **Then** there is no required downtime for administrators.
|
||
|
||
---
|
||
|
||
### User Story 2 - Safely backfill existing production data (Priority: P2)
|
||
|
||
As an operator, I want a safe, resumable way to backfill missing workspace bindings so that large datasets can be migrated without risky one-shot operations.
|
||
|
||
**Why this priority**: Without a safe backfill, enforcing data-level constraints risks outages and operational incidents.
|
||
|
||
**Independent Test**: Can be tested by seeding rows with missing workspace bindings, running the backfill workflow twice, and confirming idempotent outcomes.
|
||
|
||
**Acceptance Scenarios**:
|
||
|
||
1. **Given** a table contains rows missing workspace bindings, **When** the backfill is executed, **Then** those rows are updated to the correct workspace.
|
||
2. **Given** the backfill is interrupted and restarted, **When** it runs again, **Then** it resumes safely without corrupting already-correct rows.
|
||
|
||
---
|
||
|
||
### User Story 3 - Make audit logs unambiguous across scopes (Priority: P3)
|
||
|
||
As an auditor, I want tenant-scoped audit events to always be workspace-bound so that workspace isolation semantics are preserved in audit trails.
|
||
|
||
**Why this priority**: Audit trails are only reliable if their scope is structurally consistent and queryable.
|
||
|
||
**Independent Test**: Can be tested by attempting to store a tenant-scoped audit entry without a workspace binding and verifying it is rejected, while allowing workspace-only and platform-only entries.
|
||
|
||
**Acceptance Scenarios**:
|
||
|
||
1. **Given** an audit log entry references a tenant, **When** it is persisted, **Then** it must also reference a workspace.
|
||
2. **Given** an audit log entry is workspace-only or platform-only, **When** it is persisted, **Then** it remains valid according to the documented rules.
|
||
|
||
---
|
||
|
||
### Edge Cases
|
||
|
||
- Tenant-owned row references a tenant that no longer exists (or is otherwise invalid).
|
||
- Backfill is executed concurrently (must not produce conflicting outcomes).
|
||
- New rows are created while the backfill is in progress (must not reintroduce missing bindings).
|
||
- Partial completion: some tables are fully backfilled while others are pending.
|
||
- Mixed-scope audit events: tenant-scoped vs workspace-only vs platform-only.
|
||
- Attempt to change tenant_id on an existing tenant-owned record.
|
||
|
||
## Requirements *(mandatory)*
|
||
|
||
**Constitution alignment (required):** If this feature introduces any Microsoft Graph calls, any write/change behavior,
|
||
or any long-running/queued/scheduled work, the spec MUST describe contract registry updates, safety gates
|
||
(preview/confirmation/audit), tenant isolation, run observability (`OperationRun` type/identity/visibility), and tests.
|
||
If security-relevant DB-only actions intentionally skip `OperationRun`, the spec MUST describe `AuditLog` entries.
|
||
|
||
**Constitution alignment (RBAC-UX):** If this feature introduces or changes authorization behavior, the spec MUST:
|
||
- state which authorization plane(s) are involved (tenant/admin `/admin` + tenant-context `/admin/t/{tenant}/...` vs platform `/system`),
|
||
- ensure any cross-plane access is deny-as-not-found (404),
|
||
- explicitly define 404 vs 403 semantics:
|
||
- non-member / not entitled to workspace scope OR tenant scope → 404 (deny-as-not-found)
|
||
- member but missing capability → 403
|
||
- describe how authorization is enforced server-side (Gates/Policies) for every mutation/operation-start/credential change,
|
||
- reference the canonical capability registry (no raw capability strings; no role-string checks in feature code),
|
||
- ensure global search is tenant-scoped and non-member-safe (no hints; inaccessible results treated as 404 semantics),
|
||
- ensure destructive-like actions require confirmation (`->requiresConfirmation()`),
|
||
- include at least one positive and one negative authorization test, and note any RBAC regression tests added/updated.
|
||
|
||
**Constitution alignment (OPS-EX-AUTH-001):** OIDC/SAML login handshakes may perform synchronous outbound HTTP (e.g., token exchange)
|
||
on `/auth/*` endpoints without an `OperationRun`. This MUST NOT be used for Monitoring/Operations pages.
|
||
|
||
**Constitution alignment (BADGE-001):** If this feature changes status-like badges (status/outcome/severity/risk/availability/boolean),
|
||
the spec MUST describe how badge semantics stay centralized (no ad-hoc mappings) and which tests cover any new/changed values.
|
||
|
||
**Constitution alignment (Filament Action Surfaces):** If this feature adds or modifies any Filament Resource / RelationManager / Page,
|
||
the spec MUST include a “UI Action Matrix” (see below) and explicitly state whether the Action Surface Contract is satisfied.
|
||
If the contract is not satisfied, the spec MUST include an explicit exemption with rationale.
|
||
|
||
### Functional Requirements
|
||
|
||
- **FR-001 (Schema coverage)**: The system MUST represent a workspace binding for every tenant-owned record in the 12 listed tables.
|
||
- **FR-002 (No missing bindings post-rollout)**: After completion of the rollout, the system MUST prevent creation or persistence of tenant-owned records without a workspace binding.
|
||
- **FR-003 (Deterministic binding for new writes)**: For any new or updated tenant-owned record, the system MUST derive the workspace binding from the referenced tenant (not from user/session context alone).
|
||
- **FR-003a (Mismatch handling)**: If a caller provides a workspace binding that does not match the referenced tenant’s workspace, the write MUST be rejected.
|
||
- **FR-004 (Safe staged rollout)**: The system MUST support a staged rollout that allows introducing the new field, deploying write-path enforcement, backfilling existing data, and only then enforcing strict constraints.
|
||
- **FR-005 (Idempotent backfill)**: Operators MUST be able to re-run the backfill safely; repeated runs MUST not regress already-correct data.
|
||
- **FR-006 (Operational safety)**: The backfill workflow MUST be safe for large datasets (batching/resume) and MUST prevent concurrent executions.
|
||
- **FR-006a (Invalid mapping handling)**: If the backfill workflow encounters a tenant-owned row that cannot be mapped from tenant → workspace, it MUST abort and report the offending table and sample identifiers for operator remediation.
|
||
- **FR-007 (Validation)**: Operators MUST be able to validate that no tenant-owned records remain without a workspace binding before strict constraints are enforced.
|
||
- **FR-011 (Tenant immutability)**: For tenant-owned records, tenant identity MUST be immutable after creation; attempts to change tenant_id MUST be rejected.
|
||
- **FR-012 (Backfill observability and audit)**: The backfill workflow MUST be observable via an OperationRun (progress + outcome) and MUST write an audit log summary entry for start/end/outcome.
|
||
- **FR-013 (Query/view scope)**: This feature MUST NOT require broad refactors of canonical/operational queries; it MUST focus on making workspace scoping structurally correct at the data layer.
|
||
- **FR-008 (Audit log invariant)**: If an audit log entry references a tenant, it MUST also reference a workspace.
|
||
- **FR-009 (Audit scope flexibility)**: The audit log MUST continue to support workspace-only events (workspace present, tenant absent) and platform-only events (both absent).
|
||
- **FR-010 (Canonical view scoping readiness)**: After rollout, operational/canonical queries SHOULD be able to scope tenant-owned data by workspace without relying on implicit joins or assumptions.
|
||
|
||
### Assumptions & Dependencies
|
||
|
||
- Each tenant belongs to exactly one workspace, and that mapping is the source of truth for deriving workspace bindings.
|
||
- The 12 listed tables are “tenant-owned” per SCOPE-001 and are expected to remain tenant-owned.
|
||
- Any existing tooling that creates tenant-owned records has enough information to reference the tenant (directly or indirectly) at write time.
|
||
|
||
### Key Entities *(include if feature involves data)*
|
||
|
||
- **Workspace**: The top-level isolation boundary for data access and operations.
|
||
- **Tenant**: A unit of configuration/data that is owned by exactly one workspace.
|
||
- **Tenant-owned record**: Any record that must be both tenant-scoped and workspace-scoped.
|
||
- **Audit event**: An immutable entry describing an action/event, which may be tenant-scoped, workspace-scoped, or platform-scoped.
|
||
- **Backfill run**: A controlled operational execution that updates legacy data and reports progress/outcomes.
|
||
|
||
## Success Criteria *(mandatory)*
|
||
|
||
### Measurable Outcomes
|
||
|
||
- **SC-001 (Completeness)**: 100% of records in the 12 tenant-owned tables have a workspace binding after backfill and validation.
|
||
- **SC-002 (Integrity)**: After strict constraints are enabled, creating a tenant-owned record without a workspace binding is rejected.
|
||
- **SC-003 (Audit correctness)**: 100% of tenant-scoped audit events include a workspace binding; workspace-only and platform-only audit events remain valid.
|
||
- **SC-004 (Operational safety)**: The rollout requires no planned downtime for administrators.
|
||
- **SC-005 (Repeatability)**: Re-running the backfill after completion does not change already-correct records and can be used as a safety check.
|