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

6.5 KiB

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