--- description: "Task list for Workspace-first RBAC & Environment Access Scoping" --- # Tasks: Workspace-first RBAC & Environment Access Scoping **Input**: Design documents from `specs/285-workspace-rbac-environment-access/` **Prerequisites**: `specs/285-workspace-rbac-environment-access/spec.md`, `specs/285-workspace-rbac-environment-access/plan.md`, `specs/285-workspace-rbac-environment-access/checklists/requirements.md`, `specs/285-workspace-rbac-environment-access/research.md`, `specs/285-workspace-rbac-environment-access/data-model.md`, `specs/285-workspace-rbac-environment-access/quickstart.md`, and `specs/285-workspace-rbac-environment-access/contracts/workspace-rbac-environment-access.logical.openapi.yaml` **Implementation Posture**: Runtime implementation completed in this branch. **Tests**: REQUIRED (Pest). Keep proof bounded to `apps/platform/tests/Unit/Auth/WorkspaceFirstCapabilityResolverTest.php`, `apps/platform/tests/Unit/Auth/ManagedEnvironmentAccessScopeResolverTest.php`, `apps/platform/tests/Feature/Auth/WorkspaceFirstManagedEnvironmentAccessTest.php`, `apps/platform/tests/Feature/Rbac/ProviderConnectionWorkspaceFirstPolicyTest.php`, `apps/platform/tests/Feature/Rbac/OperationRunWorkspaceFirstAuthorizationTest.php`, `apps/platform/tests/Feature/Rbac/GovernanceArtifactsWorkspaceFirstAuthorizationTest.php`, `apps/platform/tests/Feature/Filament/WorkspaceMembershipRoleManagementTest.php`, `apps/platform/tests/Feature/Filament/ManagedEnvironmentAccessScopeManagementTest.php`, and `apps/platform/tests/Browser/Spec285WorkspaceRbacEnvironmentAccessSmokeTest.php`. **Operations**: No new `OperationRun` family. Reuse `apps/platform/app/Support/Operations/OperationRunCapabilityResolver.php`, the current `OperationRunPolicy`, existing run links or monitoring surfaces, and the current `OperationRunService` lifecycle ownership. **RBAC**: Workspace membership is the first `404` boundary, managed-environment scope is the second `404` boundary, and in-scope capability denials remain `403`. Provider capability or operability blockers remain downstream of local RBAC and must not be folded into user authorization. **Shared Pattern Reuse**: Reuse `WorkspaceCapabilityResolver`, `CapabilityResolver`, `WorkspaceContext`, `WorkspaceMembershipManager`, `TenantMembershipManager` or its in-slice successor, `OperationRunCapabilityResolver`, and the current Filament relation-manager patterns. Do not add a new role family, a second ACL product, a compatibility shim, or adjacent Spec `284`, `286`, or `287` work. **Filament / Panel Guardrails**: Filament remains v5 on Livewire v4. Provider registration remains in `apps/platform/bootstrap/providers.php`. `ProviderConnectionResource` remains non-globally-searchable while keeping valid `View` and `Edit` pages. Any touched destructive action must continue to use `->action(...)`, `->requiresConfirmation()`, and current server authorization. Asset strategy stays unchanged. **Compatibility Posture**: Reject dual-write or fallback reads between workspace and environment role truth, reject per-environment role overrides, reject a second role selector on environment-scope surfaces, and keep Specs `284`, `286`, and `287` deferred. **External Prerequisites**: Specs `280`, `281`, and `283` must already be merged or otherwise present on the implementation branch before any runtime or test task starts. **Organization**: Tasks are grouped by user story so workspace-first core authorization, explicit environment-scope narrowing, and operator-facing membership-surface migration remain independently testable. **Review Outcome**: `implemented-and-validated` **Workflow Outcome**: `complete` **Test-governance Outcome**: `keep` ## Test Governance Checklist - [x] Lane assignment stays `fast-feedback`, `confidence`, and one narrow `browser` lane. - [x] New or changed tests stay in the named unit, feature, and browser files only. - [x] Workspace, managed-environment, and access-scope fixtures remain explicit and opt-in; no hidden global defaults or compatibility fixtures are planned. - [x] Planned validation commands match `spec.md`, `plan.md`, and `quickstart.md` exactly. - [x] `standard-native-filament`, `global-context-shell`, and shared authorization expectations stay explicit for touched surfaces. - [x] Any attempt to absorb Specs `284`, `286`, or `287` resolves as `split` or `reject-or-split`, not hidden follow-up. ## Phase 0: External Gate **Purpose**: Confirm the prerequisite cutover slices are present before implementation begins. - [x] T000 Confirm Specs `280`, `281`, and `283` are already merged or otherwise present on the implementation branch before any runtime or test task begins. --- ## Phase 1: Setup (Shared Context) **Purpose**: Confirm the exact repo seams, bounded proof files, and deferred-scope posture before runtime edits begin. - [x] T001 Review `specs/285-workspace-rbac-environment-access/spec.md`, `plan.md`, `checklists/requirements.md`, `research.md`, `data-model.md`, `quickstart.md`, and `contracts/workspace-rbac-environment-access.logical.openapi.yaml` together so implementation stays on Spec `285` only. - [x] T002 [P] Confirm the current workspace-role seams in `apps/platform/app/Models/WorkspaceMembership.php`, `apps/platform/app/Services/Auth/WorkspaceCapabilityResolver.php`, `apps/platform/app/Services/Auth/WorkspaceMembershipManager.php`, and any supporting workspace-role enums or policies before changing shared authorization logic. - [x] T003 [P] Confirm the current managed-environment membership seams in `apps/platform/app/Models/ManagedEnvironmentMembership.php`, `apps/platform/app/Services/Auth/CapabilityResolver.php`, `apps/platform/app/Services/Auth/TenantMembershipManager.php`, and any supporting tenant-role or role-capability mapping files before retargeting environment access. - [x] T004 [P] Confirm the current user and workspace-context seams in `apps/platform/app/Models/User.php` and `apps/platform/app/Support/Workspaces/WorkspaceContext.php` before retargeting tenant selection, remembered context, and `canAccessTenant()`. - [x] T005 [P] Confirm the current policy and run-authorization seams in `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/Policies/TenantOnboardingSessionPolicy.php`, and `apps/platform/app/Support/Operations/OperationRunCapabilityResolver.php` before changing environment-owned access rules. - [x] T006 [P] Confirm the current operator-facing membership and searchable-resource seams in `apps/platform/app/Filament/Resources/TenantResource/Pages/ManageTenantMemberships.php`, `apps/platform/app/Filament/Resources/TenantResource/RelationManagers/TenantMembershipsRelationManager.php`, `apps/platform/app/Filament/Resources/Workspaces/RelationManagers/WorkspaceMembershipsRelationManager.php`, `apps/platform/app/Filament/Resources/TenantResource.php`, `apps/platform/app/Filament/Resources/Workspaces/WorkspaceResource.php`, `apps/platform/app/Filament/Resources/ProviderConnectionResource.php`, and any touched action helpers so the implementation does not leave duplicate role-editing paths or unsafe searchable results behind. --- ## Phase 2: Foundational (Blocking Prerequisites) **Purpose**: Establish the proving suite and the canonical workspace-first access contract that all user stories depend on. **Critical**: No user-story work should begin until this phase is complete. - [x] T007 [P] Add failing coverage in `apps/platform/tests/Unit/Auth/WorkspaceFirstCapabilityResolverTest.php` for workspace-role resolution, per-request caching, capability evaluation without role-bearing managed-environment membership fallback, and boundary-safe denied-access diagnostics. - [x] T008 [P] Add failing coverage in `apps/platform/tests/Unit/Auth/ManagedEnvironmentAccessScopeResolverTest.php` for inherited access when no scope rows exist, allowlist narrowing when scope rows exist, invalid scope rows outside the workspace boundary, and scope-row invalidation when workspace membership ends. - [x] T009 [P] Add failing coverage in `apps/platform/tests/Feature/Auth/WorkspaceFirstManagedEnvironmentAccessTest.php` for `User::canAccessTenant()`, `getTenants()`, `getDefaultTenant()`, `WorkspaceContext`, and representative environment-owned list, run-list, and bulk-authorization preflight behavior using one shared workspace-first access contract without avoidable N+1 membership or scope lookups. - [x] T010 [P] Add failing coverage in `apps/platform/tests/Feature/Rbac/ProviderConnectionWorkspaceFirstPolicyTest.php`, `apps/platform/tests/Feature/Rbac/OperationRunWorkspaceFirstAuthorizationTest.php`, and `apps/platform/tests/Feature/Rbac/GovernanceArtifactsWorkspaceFirstAuthorizationTest.php` for `404` versus `403` semantics across provider connections, runs, findings, evidence, review packs, and tenant reviews, including boundary-safe denied-access logging with no raw provider detail. - [x] T011 [P] Add failing coverage in `apps/platform/tests/Feature/Filament/WorkspaceMembershipRoleManagementTest.php` and `apps/platform/tests/Feature/Filament/ManagedEnvironmentAccessScopeManagementTest.php` for workspace role management staying canonical, last-owner protection remaining workspace-scoped, environment-scope CRUD staying scope-only, and destructive actions preserving confirmation and authorization. - [x] T012 [P] Add the narrow browser smoke in `apps/platform/tests/Browser/Spec285WorkspaceRbacEnvironmentAccessSmokeTest.php` for one workspace owner managing a member role, narrowing that member to one managed environment, and confirming shell access plus one environment-bound drilldown follow the new contract. - [x] T013 Introduce the smallest shared workspace-first access contract under `apps/platform/app/Services/Auth/` by retargeting `WorkspaceCapabilityResolver`, `CapabilityResolver`, and any supporting helper seam so one canonical access decision answers workspace membership, optional environment scope, required capability, search visibility, and boundary-safe denied-access diagnostics without keeping dual role authority. - [x] T014 Update `apps/platform/app/Models/ManagedEnvironmentMembership.php`, `apps/platform/app/Services/Auth/TenantMembershipManager.php`, and any directly cooperating support or audit seams so managed-environment membership semantics become a narrow access-scope overlay with no role-bearing authority and so scope rows are removed or invalidated when workspace membership ends. **Checkpoint**: The proving files exist, the shared access contract is explicit, and no later story needs to invent a second authorization path. --- ## Phase 3: User Story 1 - Workspace membership alone authorizes environment resources (Priority: P1) **Goal**: A workspace member with the required role can open environment-owned resources without any second role-bearing managed-environment membership. **Independent Test**: Add a user through workspace membership only, open an allowed managed environment in that workspace, and confirm provider connections plus governance artifacts authorize from workspace role plus capability while non-members remain `404`. ### Tests for User Story 1 - [x] T015 [P] [US1] Extend `apps/platform/tests/Feature/Auth/WorkspaceFirstManagedEnvironmentAccessTest.php` after T013-T014 to prove workspace membership alone is sufficient when no explicit environment scope rows exist. - [x] T016 [P] [US1] Extend `apps/platform/tests/Feature/Rbac/ProviderConnectionWorkspaceFirstPolicyTest.php` and `apps/platform/tests/Feature/Rbac/GovernanceArtifactsWorkspaceFirstAuthorizationTest.php` after T013-T014 to prove provider connections, findings, evidence snapshots, review packs, tenant reviews, onboarding sessions, and any touched searchable-resource destinations all authorize from workspace membership first and keep `404` versus `403` semantics honest. ### Implementation for User Story 1 - [x] T017 [US1] Update `apps/platform/app/Models/User.php` and `apps/platform/app/Support/Workspaces/WorkspaceContext.php` so tenant selection, default tenant resolution, remembered tenant context, and `canAccessTenant()` all consume the shared workspace-first access contract. - [x] T018 [US1] Update `apps/platform/app/Policies/ProviderConnectionPolicy.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/Policies/TenantOnboardingSessionPolicy.php`, and any directly touched searchable-resource visibility helpers or queries so workspace membership is the only role-bearing authority for environment-owned resources and inaccessible results stay hidden. **Checkpoint**: Environment-owned pages and artifacts no longer require a second role-bearing managed-environment membership to authorize. --- ## Phase 4: User Story 2 - Explicit environment scope narrows access without changing roles (Priority: P1) **Goal**: Workspace role stays canonical while optional explicit scope rows narrow which managed environments a member may open. **Independent Test**: Give a workspace member access to only one managed environment, confirm the allowed environment opens, confirm a sibling environment in the same workspace returns `404`, and confirm the same workspace role capabilities apply inside the allowed environment. ### Tests for User Story 2 - [x] T019 [P] [US2] Extend `apps/platform/tests/Feature/Auth/WorkspaceFirstManagedEnvironmentAccessTest.php` after T013-T014 to prove explicit environment-scope allowlists narrow visibility, stale remembered tenant context is cleared, and missing scope rows default to inheritance across currently selectable managed environments in the workspace. - [x] T020 [P] [US2] Extend `apps/platform/tests/Feature/Rbac/OperationRunWorkspaceFirstAuthorizationTest.php` after T013-T014 to prove workspace-bound runs authorize from workspace membership only while environment-bound runs additionally honor explicit environment scope before capability checks. ### Implementation for User Story 2 - [x] T021 [US2] Update `apps/platform/app/Models/User.php`, `apps/platform/app/Support/Workspaces/WorkspaceContext.php`, and any directly touched chooser or tenant-list helpers so explicit environment-scope rows narrow selection visibility and stale out-of-scope remembered environments are cleared. - [x] T022 [US2] Update `apps/platform/app/Support/Operations/OperationRunCapabilityResolver.php`, `apps/platform/app/Policies/OperationRunPolicy.php`, and any directly touched run-link or monitoring helpers so workspace-bound and environment-bound runs follow the same workspace-first access contract. **Checkpoint**: Environment scope acts only as visibility narrowing, and mixed workspace-bound versus environment-bound runs still authorize correctly. --- ## Phase 5: User Story 3 - Membership management surfaces stop editing duplicate role truth (Priority: P2) **Goal**: Operators manage workspace roles in one place and managed-environment visibility in another, with no second role selector on the environment surface. **Independent Test**: Open workspace membership management and the retargeted managed-environment access-scope surface, confirm roles are edited only at workspace level, confirm environment scope CRUD never exposes a role selector, and confirm destructive actions remain confirmation-protected. ### Tests for User Story 3 - [x] T023 [P] [US3] Extend `apps/platform/tests/Feature/Filament/WorkspaceMembershipRoleManagementTest.php` after T013-T014 to prove workspace membership remains the sole role editor, last-owner protection stays anchored at workspace scope, and audit logging remains intact. - [x] T024 [P] [US3] Extend `apps/platform/tests/Feature/Filament/ManagedEnvironmentAccessScopeManagementTest.php` after T013-T014 to prove the retargeted managed-environment surface manages visibility scope only, never shows a second role selector, keeps destructive mutations behind `->requiresConfirmation()`, and writes audit records for scope add, remove, and clear or narrowing actions. ### Implementation for User Story 3 - [x] T025 [US3] Update `apps/platform/app/Services/Auth/WorkspaceMembershipManager.php`, `apps/platform/app/Filament/Resources/Workspaces/RelationManagers/WorkspaceMembershipsRelationManager.php`, and any directly touched workspace-role actions so workspace membership remains the only operator-facing role-editing path. - [x] T026 [US3] Retarget `apps/platform/app/Filament/Resources/TenantResource/Pages/ManageTenantMemberships.php`, `apps/platform/app/Filament/Resources/TenantResource/RelationManagers/TenantMembershipsRelationManager.php`, and any directly touched action or label helpers so the surface becomes managed-environment access-scope management with no second role authority. **Checkpoint**: The operator-facing UI can no longer recreate dual role truth after the backend cutover. --- ## Phase 6: Polish & Cross-Cutting Validation **Purpose**: Run the exact bounded proof set, review the touched auth and Filament seams, and confirm the slice stayed inside Spec `285`. - [x] T027 [P] Run `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && (cd "$REPO_ROOT/apps/platform" && ./vendor/bin/sail artisan test --compact tests/Unit/Auth/WorkspaceFirstCapabilityResolverTest.php tests/Unit/Auth/ManagedEnvironmentAccessScopeResolverTest.php)`. - [x] T028 [P] Run `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && (cd "$REPO_ROOT/apps/platform" && ./vendor/bin/sail artisan test --compact tests/Feature/Auth/WorkspaceFirstManagedEnvironmentAccessTest.php tests/Feature/Rbac/ProviderConnectionWorkspaceFirstPolicyTest.php tests/Feature/Rbac/OperationRunWorkspaceFirstAuthorizationTest.php tests/Feature/Rbac/GovernanceArtifactsWorkspaceFirstAuthorizationTest.php tests/Feature/Filament/WorkspaceMembershipRoleManagementTest.php tests/Feature/Filament/ManagedEnvironmentAccessScopeManagementTest.php)`. - [x] T029 [P] Run `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && (cd "$REPO_ROOT/apps/platform" && ./vendor/bin/sail artisan test --compact tests/Browser/Spec285WorkspaceRbacEnvironmentAccessSmokeTest.php)`. - [x] T030 [P] Run `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && (cd "$REPO_ROOT/apps/platform" && ./vendor/bin/sail bin pint --dirty --format agent)`. - [x] T031 [P] Review `apps/platform/app/Models/User.php`, `apps/platform/app/Models/WorkspaceMembership.php`, `apps/platform/app/Models/ManagedEnvironmentMembership.php`, `apps/platform/app/Services/Auth/WorkspaceCapabilityResolver.php`, `apps/platform/app/Services/Auth/CapabilityResolver.php`, `apps/platform/app/Services/Auth/WorkspaceMembershipManager.php`, `apps/platform/app/Services/Auth/TenantMembershipManager.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/Policies/TenantOnboardingSessionPolicy.php`, `apps/platform/app/Filament/Resources/TenantResource/Pages/ManageTenantMemberships.php`, `apps/platform/app/Filament/Resources/TenantResource/RelationManagers/TenantMembershipsRelationManager.php`, `apps/platform/app/Filament/Resources/Workspaces/RelationManagers/WorkspaceMembershipsRelationManager.php`, `apps/platform/app/Support/Operations/OperationRunCapabilityResolver.php`, `apps/platform/app/Filament/Resources/TenantResource.php`, `apps/platform/app/Filament/Resources/Workspaces/WorkspaceResource.php`, `apps/platform/app/Filament/Resources/ProviderConnectionResource.php`, and `apps/platform/bootstrap/providers.php` to confirm Filament v5 / Livewire v4 compliance, unchanged provider-registration location, truthful non-global-search posture, preserved destructive-action confirmation plus authorization, boundary-safe denied-access diagnostics, unchanged asset strategy, and Specs `284`, `286`, and `287` staying deferred. --- ## Dependencies & Execution Order ### Phase Dependencies - **Phase 0 (External Gate)**: no dependencies; complete before implementation starts. - **Phase 1 (Setup)**: depends on Phase 0. - **Phase 2 (Foundational)**: depends on Phase 1 and blocks all story work. - **Phase 3 (US1)**: depends on Phase 2 and establishes workspace-first authorization for environment-owned resources. - **Phase 4 (US2)**: depends on Phase 2 and should ship with or immediately after US1 so explicit environment narrowing lands on the final shared access contract. - **Phase 5 (US3)**: depends on Phase 2 and should land after or with US1 and US2 so UI semantics match the backend contract. - **Phase 6 (Polish)**: depends on all desired user stories being complete. ### User Story Dependencies - **US1 (P1)**: independently testable after Phase 2 and is the first required increment. - **US2 (P1)**: independently testable after Phase 2, but should ship after or with US1 because explicit scope narrowing depends on the final shared workspace-first access contract. - **US3 (P2)**: independently testable after Phase 2, but should land after or with US1 and US2 so the UI reflects the final backend semantics instead of an intermediate state. ### Within Each User Story - Write or extend the listed Pest coverage first and make it fail for the intended gap. - Apply the smallest shared-seam changes needed to satisfy the story without reopening Specs `284`, `286`, or `287`. - Re-run the narrowest relevant validation command for that story before moving to the next story. ## Parallel Execution Examples - **Setup**: T002 through T006 can run in parallel once T000 and T001 fix the bounded scope. - **Foundational**: T007 through T012 can run in parallel before T013 and T014 converge the shared access contract. - **US1**: T015 and T016 can run in parallel; T017 and T018 should merge serially around shared user, context, and policy files. - **US2**: T019 and T020 can run in parallel; T021 and T022 should merge serially around shared tenant-access and run-authorization seams. - **US3**: T023 and T024 can run in parallel; T025 and T026 should merge serially around shared Filament membership surfaces. - **Polish**: T027 through T030 can run in parallel after implementation is complete; T031 should close the bounded-scope review last. ## Implementation Strategy ### Suggested MVP Scope - MVP = **US1 + US2 + US3**. The spec is not complete until the operator-facing role and scope surfaces are split in the same workspace-first model. ### Incremental Delivery 1. Complete Phase 0, Phase 1, and Phase 2. 2. Deliver US1 so workspace membership alone authorizes environment-owned resources. 3. Deliver US2 so optional explicit environment scope narrows visibility without changing role semantics. 4. Deliver US3 so operators can no longer edit duplicate role truth in the UI. 5. Finish with the exact validation commands and final bounded-scope review in Phase 6. ### Team Strategy 1. Parallelize the failing test work first. 2. Serialize merges around `apps/platform/app/Services/Auth/`, `apps/platform/app/Models/User.php`, `apps/platform/app/Support/Workspaces/WorkspaceContext.php`, `apps/platform/app/Policies/`, and the touched Filament relation managers to avoid conflicting contract-shape edits. 3. Reject any implementation branch that introduces a second role family, compatibility fallbacks, provider-boundary work, or adjacent-spec cutover work. ## Deferred Follow-Ups / Non-Goals - Spec `284` provider-neutral artifact source taxonomy work - Spec `286` broader UI copy, IA, and localization neutralization - Spec `287` cutover quality gates and no-legacy enforcement beyond this bounded RBAC slice