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

183 lines
8.3 KiB
Markdown

# 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.