TenantAtlas/specs/285-workspace-rbac-environment-access/data-model.md
ahmido c7b38606a9 feat: implement spec 285 workspace-first environment access (#344)
Implements platform feature branch `285-workspace-rbac-environment-access`.

Summary:
- switch managed environment authorization to workspace-first role resolution with explicit environment-scope narrowing
- rewire Filament pages, resources, policies, and user tenant access helpers to the shared access-scope resolver
- add Spec 285 coverage across unit, feature, and browser tests plus full spec artifacts

Validation:
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/Auth/WorkspaceFirstCapabilityResolverTest.php tests/Unit/Auth/ManagedEnvironmentAccessScopeResolverTest.php`
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Auth/WorkspaceFirstManagedEnvironmentAccessTest.php tests/Feature/Filament/ManagedEnvironmentAccessScopeManagementTest.php tests/Feature/Filament/WorkspaceMembershipRoleManagementTest.php tests/Feature/Rbac/GovernanceArtifactsWorkspaceFirstAuthorizationTest.php tests/Feature/Rbac/OperationRunWorkspaceFirstAuthorizationTest.php tests/Feature/Rbac/ProviderConnectionWorkspaceFirstPolicyTest.php`
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Verification/ProviderExecutionReauthorizationTest.php tests/Feature/ProviderConnections/ProviderConnectionHealthCheckStartSurfaceTest.php tests/Feature/Tenants/TenantProviderBackedActionStartTest.php`
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Audit/TenantMembershipAuditLogTest.php tests/Feature/Filament/TenantMembersTest.php tests/Feature/TenantRBAC/TenantMembershipCrudTest.php tests/Feature/TenantRBAC/TenantSwitcherScopeTest.php`
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Browser/Spec285WorkspaceRbacEnvironmentAccessSmokeTest.php`
- `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent`

Target branch: `platform-dev`.

Follow-up integration path after merge:
- `platform-dev` -> `dev`.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #344
2026-05-09 12:40:50 +00:00

200 lines
6.5 KiB
Markdown

# Data Model: Workspace-first RBAC & Environment Access Scoping
## Overview
Feature `285` does not add a new persisted source of truth. It clarifies and narrows responsibility across the existing membership data.
## Entities and responsibilities
### 1. WorkspaceMembership
**Status**: existing, canonical, role-bearing
**Responsibility**:
- defines whether a user belongs to a workspace
- defines the single role-bearing authority for capability resolution
- remains the source for last-owner protection and workspace membership audit
**Core attributes**:
- `workspace_id`
- `user_id`
- `role`
- `created_by_user_id`
- timestamps
**Rules**:
- every access decision starts here
- if no `WorkspaceMembership` exists, the user is out of scope for every workspace-owned and managed-environment-owned surface in that workspace
### 2. ManagedEnvironmentAccessScope
**Status**: logical successor to the current role-bearing `ManagedEnvironmentMembership` semantics
**Responsibility**:
- optionally narrows which managed environments a workspace member may open
- never grants capabilities by itself
- never stores a second role authority
**Expected backing**:
- the current `managed_environment_memberships` persistence reused or renamed in place
- no dual-write and no compatibility read path
**Required data meaning**:
- `managed_environment_id`
- `user_id`
- audit metadata such as creator and timestamps
**Forbidden data meaning**:
- role-bearing semantics
- capability-bearing semantics
- owner semantics separate from workspace ownership
**Rules**:
- scope rows are valid only for users who already have a `WorkspaceMembership` in the same workspace
- if no scope rows exist for a workspace member, environment visibility is inherited across the currently selectable managed environments in that workspace
- if scope rows exist for a workspace member, they form an allowlist
### 3. ManagedEnvironment
**Status**: existing, workspace-owned child resource
**Responsibility**:
- anchors provider connections, runs, findings, review artifacts, and other environment-owned resources to `workspace_id`
- provides the workspace boundary that the scope overlay must match
**Rules**:
- every managed-environment access decision must confirm that the environment belongs to the current workspace
- scope rows may only reference environments within the member's workspace
### 4. Environment-owned Resources
**Status**: existing derived subjects of the access contract
**In-scope examples**:
- `ProviderConnection`
- `OperationRun`
- `Finding`
- `EvidenceSnapshot`
- `ReviewPack`
- `TenantReview`
- onboarding drafts and sessions tied to a managed environment
**Responsibility**:
- reuse the shared access contract instead of implementing local role logic
## Derived access decision shapes
### Workspace membership summary
Used by membership-management surfaces and policy helpers.
**Fields**:
- `workspace_id`
- `user_id`
- `workspace_member` boolean
- `workspace_role` string or `null`
- `owner_guarded` boolean
### Managed-environment access decision
Used by `User::canAccessTenant()`, `WorkspaceContext`, selection flows, and environment-owned policies.
**Fields**:
- `workspace_id`
- `managed_environment_id`
- `user_id`
- `workspace_role` string or `null`
- `workspace_member` boolean
- `explicit_scope_rows_present` boolean
- `managed_environment_allowed` boolean
- `failed_boundary` string or `null`
- `required_capability` string or `null`
- `capability_allowed` boolean
- `denial_http_status` integer or `null`
- `provider_capability_context` string or `null`
**Decision order**:
1. confirm workspace membership
2. confirm the environment belongs to the workspace
3. if explicit scope rows exist, require the environment to be in the allowlist
4. evaluate required capability from the workspace role
5. hand off to downstream provider-capability or operability gates only after local access passes
### OperationRun access decision
Used by run drilldowns and monitoring surfaces.
**Fields**:
- `operation_run_id`
- `workspace_id`
- `managed_environment_id` nullable
- `user_id`
- `workspace_role` string or `null`
- `workspace_member` boolean
- `managed_environment_allowed` boolean
- `failed_boundary` string or `null`
- `required_capability` string or `null`
- `capability_allowed` boolean
- `denial_http_status` integer or `null`
**Decision order**:
- if the run is workspace-bound, use workspace membership plus required capability
- if the run is managed-environment-bound, apply the managed-environment access decision first and then required capability
## Invariants
- `WorkspaceMembership` is the only role-bearing source of truth.
- no managed-environment scope record may grant access without workspace membership.
- no managed-environment scope record may point outside the workspace of the current member.
- deleting workspace membership removes or invalidates any managed-environment scope rows for that user in the same workspace.
- non-membership or out-of-scope access resolves to `404`.
- in-scope members missing the required capability resolve to `403`.
- provider capability and operability checks do not run until local access passes.
## Audit expectations
- workspace role changes keep using the existing workspace membership audit path
- managed-environment scope mutations write audit records that describe scope narrowing or widening, not role reassignment
- last-owner protection remains anchored to workspace ownership only
## Denied-access diagnostic context
Denied-access logging remains derived rather than persisted.
**Expected diagnostic fields**:
- `workspace_id`
- `managed_environment_id` nullable
- `user_id`
- `failed_boundary` as a descriptive string such as `workspace_membership`, `managed_environment_scope`, or `capability`
- `required_capability` nullable
**Rules**:
- diagnostics must be sufficient to explain whether the denial happened at workspace membership, environment scope, or capability evaluation
- diagnostics must not include raw provider payloads, secrets, or implementation-only provider evidence
- the shared access decision is the only source from which denial diagnostics are derived
## Migration notes for implementation
- runtime implementation may repurpose the current `ManagedEnvironmentMembership` model or replace it in place with a clearer successor, but it must preserve the logical rules above
- if the current table name is retained temporarily, the implementation must still remove the old role-bearing meaning in the same slice
- no parallel legacy projection is allowed beyond what is strictly required to land the cutover atomically