TenantAtlas/specs/285-workspace-rbac-environment-access/research.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

8.3 KiB

Research: Workspace-first RBAC & Environment Access Scoping

Scope of this research

This note records the repo-truth decisions behind Feature 285. It stays prep-only and does not introduce runtime implementation.

Sources reviewed

  • docs/product/roadmap.md
  • docs/product/spec-candidates.md
  • specs/280-workspace-tenancy-environment-routing/
  • specs/281-provider-connection-scope/
  • specs/283-provider-capability-registry/
  • specs/062-tenant-rbac-v1/
  • specs/065-tenant-rbac-v1/
  • apps/platform/app/Models/WorkspaceMembership.php
  • apps/platform/app/Models/ManagedEnvironmentMembership.php
  • apps/platform/app/Models/User.php
  • apps/platform/app/Services/Auth/WorkspaceCapabilityResolver.php
  • apps/platform/app/Services/Auth/CapabilityResolver.php
  • apps/platform/app/Support/Workspaces/WorkspaceContext.php
  • apps/platform/app/Policies/ProviderConnectionPolicy.php
  • apps/platform/app/Policies/OperationRunPolicy.php
  • apps/platform/app/Policies/FindingPolicy.php
  • apps/platform/app/Policies/EvidenceSnapshotPolicy.php
  • apps/platform/app/Policies/ReviewPackPolicy.php
  • apps/platform/app/Policies/TenantReviewPolicy.php
  • apps/platform/app/Filament/Resources/TenantResource/Pages/ManageTenantMemberships.php
  • apps/platform/app/Filament/Resources/TenantResource/RelationManagers/TenantMembershipsRelationManager.php

Decision 1: Workspace membership is already the right canonical role-bearing seam

Findings

  • WorkspaceMembership already exists as a workspace-owned role-bearing pivot.
  • WorkspaceCapabilityResolver already resolves workspace membership, caches it per request, and exposes getRole, can, and isMember.
  • Workspace-owned policies and management flows already rely on that seam.

Decision

WorkspaceMembership remains the only role-bearing truth for Feature 285.

Why

The repo already contains the correct workspace-first substrate. Adding a third RBAC layer or preserving a second role-bearing environment path would increase drift instead of resolving it.

Decision 2: The real repo problem is dual role-bearing truth, not missing RBAC from scratch

Findings

  • ManagedEnvironmentMembership is still treated as a role-bearing pivot.
  • CapabilityResolver still resolves access from managed-environment membership and RoleCapabilityMap.
  • User::canAccessTenant() still relies on CapabilityResolver::isMember().
  • WorkspaceContext still reaches managed-environment access through that tenant-first user path.
  • Key policies for provider connections, runs, findings, evidence, review packs, and tenant reviews use different combinations of workspace membership and managed-environment membership.
  • ManageTenantMemberships and TenantMembershipsRelationManager still expose operator-facing role editing for managed environments.

Decision

Feature 285 is a consolidation cutover. It must replace the split role-bearing model rather than stack a new compatibility layer on top.

Why

The raw candidate text talks about TenantMembership, but current repo truth uses ManagedEnvironmentMembership. The real cutover target is therefore the duplicated role-bearing access model itself.

Decision 3: Managed-environment membership becomes a narrow access-scope overlay only

Findings

  • The roadmap still wants environment access scoping to remain possible after the workspace-first cutover.
  • The repo has many environment-owned resources where full workspace inheritance may need narrowing.
  • Replacing environment role authority does not require removing the concept of selective environment visibility.

Decision

The current managed-environment membership persistence may survive only as a logical access-scope overlay or be replaced in place by an equivalent successor. It must not remain role-bearing.

Why

This preserves the narrow product need for selective environment visibility without rebuilding a second ACL system.

Decision 4: The default environment access rule is inheritance, not mandatory per-environment grants

Findings

  • Workspace-first tenancy is meant to reduce operator friction, not require duplicate allowlists for every member.
  • The current product already anchors ManagedEnvironment to workspace_id.

Decision

If a workspace member has no explicit scope rows, they inherit visibility to the managed environments in that workspace. If scope rows exist, they narrow visibility to an allowlist.

Why

This is the smallest rule that preserves optional narrowing while keeping workspace membership authoritative.

Decision 5: CapabilityResolver should be retargeted, not replaced by another resolver family

Findings

  • CapabilityResolver is already the shared entry point for many environment-owned capability checks.
  • Policies, User, and other helpers already call into it.

Decision

Retarget CapabilityResolver to derive the current role from workspace membership and use managed-environment scope only for visibility narrowing.

Why

Replacing callers across the repo with a third resolver type would add avoidable migration spread.

Decision 6: User and WorkspaceContext must share the same access contract

Findings

  • User::getTenants(), User::getDefaultTenant(), and User::canAccessTenant() currently shape Filament tenant access.
  • WorkspaceContext tracks current workspace, remembered tenant context, and initial workspace resolution, but still delegates tenant access to the old user path.

Decision

User and WorkspaceContext must consume the same workspace-first access contract so context selection, remembered tenant state, and page policy outcomes cannot drift.

Why

If the shell and the policies resolve access differently, operators will continue to see allowed selection with denied pages or denied selection with allowed deep links.

Decision 7: Membership-management UI must split role authority from visibility scope

Findings

  • ManageTenantMemberships currently titles the page around tenant memberships and says managed-environment access is managed there.
  • TenantMembershipsRelationManager still offers add, change, and remove role actions using tenant capability checks.
  • Workspace membership management already exists separately.

Decision

The operator-facing role editor remains the workspace membership surface. The current managed-environment membership page is removed or retargeted into access-scope management with no second role selector.

Why

Leaving the current UI intact would let operators recreate the duplicate role model even after backend changes land.

Decision 8: OperationRun authorization stays shared but must follow the same workspace-first rule

Findings

  • OperationRunPolicy currently mixes workspace membership, managed-environment membership, and required capability logic.
  • Some runs are workspace-bound; others are managed-environment-bound.

Decision

Workspace-bound runs authorize from workspace membership only. Managed-environment-bound runs authorize from workspace membership, then environment scope, then required capability.

Why

This preserves the existing distinction between workspace-wide and environment-bound operations without keeping role truth in two places.

Rejected alternatives

Keep both role-bearing membership models and synchronize them

Rejected because it adds more compatibility logic to a pre-production codebase and preserves the core ambiguity that 285 is meant to remove.

Introduce a third access resolver beside the current two

Rejected because most callers already rely on CapabilityResolver or WorkspaceCapabilityResolver. A third resolver family would create a longer migration window and more drift.

Remove all environment-level scoping entirely

Rejected because the roadmap explicitly keeps environment access scoping in scope after the workspace-first cutover.

Turn environment scope into per-environment role overrides

Rejected because that would introduce a second ACL product and exceed the reserved scope of 285.

Resulting design constraints

  • No compatibility shim or dual-write path.
  • No new role family.
  • No new persisted source of truth.
  • No widening into provider capability, route-shell, source taxonomy, copy/localization, or cutover-guardrail work reserved for adjacent specs.
  • The proof set stays bounded to unit, feature, and one browser smoke.