# Tasks: RBAC UI Enforcement Helper v1 **Input**: Design documents from `/specs/066-rbac-ui-enforcement-helper/` **Prerequisites**: plan.md ✓, spec.md ✓, quickstart.md ✓ **Tests**: REQUIRED (Pest) — this feature changes runtime authorization behavior. **RBAC**: This feature IS the RBAC enforcement helper — all tasks enforce constitution RBAC-UX rules. **Organization**: Tasks grouped by user story for independent implementation. ## Format: `[ID] [P?] [Story?] Description` - **[P]**: Can run in parallel (different files, no dependencies) - **[Story]**: US1/US2/US3 for user story phases; omitted for Setup/Foundational/Polish --- ## Phase 1: Setup **Purpose**: Create helper infrastructure with no external dependencies - [X] T001 Create directory structure `app/Support/Rbac/` - [X] T002 [P] Create `UiTooltips.php` with tooltip constants in `app/Support/Rbac/UiTooltips.php` - [X] T003 [P] Create `TenantAccessContext.php` DTO in `app/Support/Rbac/TenantAccessContext.php` --- ## Phase 2: Foundational (Blocking Prerequisites) **Purpose**: Core `UiEnforcement` helper — MUST complete before any user story tests **⚠️ CRITICAL**: No user story work can begin until this phase is complete - [X] T004 Implement `UiEnforcement::forAction()` static method in `app/Support/Rbac/UiEnforcement.php` - [X] T005 Implement `->requireMembership()` method (default: true) in `app/Support/Rbac/UiEnforcement.php` - [X] T006 Implement `->requireCapability(string $capability)` method in `app/Support/Rbac/UiEnforcement.php` - [X] T007 Implement `->destructive()` method (confirmation modal) in `app/Support/Rbac/UiEnforcement.php` - [X] T008 Implement `->tooltip(string $message)` override method in `app/Support/Rbac/UiEnforcement.php` - [X] T009 Implement `->apply()` method (sets hidden/disabled/guards) in `app/Support/Rbac/UiEnforcement.php` - [X] T010 Implement `UiEnforcement::forTableAction()` static method in `app/Support/Rbac/UiEnforcement.php` - [X] T011 Implement `UiEnforcement::forBulkAction()` static method with all-or-nothing logic in `app/Support/Rbac/UiEnforcement.php` **Checkpoint**: `UiEnforcement` class ready — user story tests can now be written --- ## Phase 3: User Story 1 — Tenant member sees consistent disabled UX (Priority: P1) 🎯 MVP **Goal**: Members lacking capability see actions visible-but-disabled with standard tooltip; 403 on execution **Independent Test**: Visit tenant page as member with insufficient permission → action disabled with tooltip, cannot execute ### Tests for User Story 1 - [X] T012 [P] [US1] Test: member without capability sees disabled action + tooltip in `tests/Feature/Rbac/UiEnforcementMemberDisabledTest.php` - [X] T013 [P] [US1] Test: member without capability is blocked from execution in `tests/Feature/Rbac/UiEnforcementMemberDisabledTest.php` - [X] T014 [P] [US1] Test: member with capability sees enabled action + can execute in `tests/Feature/Rbac/UiEnforcementMemberDisabledTest.php` - [X] T014a [P] [US1] Test: destructive action shows confirmation modal before execution in `tests/Feature/Rbac/UiEnforcementDestructiveTest.php` ### Implementation for User Story 1 - [X] T015 [US1] Validate `->apply()` correctly sets `->disabled()` + `->tooltip()` for members lacking capability (logic in T009; this task verifies + adjusts if needed) in `app/Support/Rbac/UiEnforcement.php` - [X] T016 [US1] Validate `->apply()` correctly blocks unauthorized execution (via Filament's isDisabled check + defense-in-depth abort) in `app/Support/Rbac/UiEnforcement.php` - ~~T017 [US1] Migrate TenantResource table actions to UiEnforcement~~ **OUT OF SCOPE v1**: TenantResource is record==tenant, not tenant-scoped - [X] T018 [US1] Migrate ProviderConnectionResource actions to UiEnforcement (mixed visibility via `preserveVisibility()`) in `app/Filament/Resources/ProviderConnectionResource.php` **Checkpoint**: US1 complete — members see consistent disabled UX with tooltip (exemplar: ListPolicies) --- ## Phase 4: User Story 2 — Non-members cannot infer tenant resources (Priority: P2) **Goal**: Non-members receive 404 (deny-as-not-found) for all tenant-scoped actions; actions hidden in UI **Independent Test**: Access tenant page as non-member → actions hidden, execution returns 404 ### Tests for User Story 2 - [X] T019 [P] [US2] Test: non-member sees action hidden in UI in `tests/Feature/Rbac/UiEnforcementNonMemberHiddenTest.php` - [X] T020 [P] [US2] Test: non-member action is blocked (via Filament hidden-action semantics) in `tests/Feature/Rbac/UiEnforcementNonMemberHiddenTest.php` - [X] T021 [P] [US2] Test: membership revoked mid-session still enforces protection in `tests/Feature/Rbac/UiEnforcementNonMemberHiddenTest.php` ### Implementation for User Story 2 - [X] T022 [US2] Validate `->apply()` correctly sets `->hidden()` for non-members (logic in T009; this task verifies + adjusts if needed) in `app/Support/Rbac/UiEnforcement.php` - [X] T023 [US2] Validate `->apply()` blocks non-member execution (via Filament's isHidden → isDisabled chain; 404 server-side guard is defense-in-depth) in `app/Support/Rbac/UiEnforcement.php` - [X] T024 [US2] Migrate BackupSetResource actions (row + bulk) to UiEnforcement (mixed visibility via `preserveVisibility()`) in `app/Filament/Resources/BackupSetResource.php` - [X] T025 [US2] Migrate PolicyResource sync actions to UiEnforcement in `app/Filament/Resources/PolicyResource/Pages/ListPolicies.php` **Checkpoint**: US2 complete — non-members receive 404 semantics, no information leakage --- ## Phase 5: User Story 3 — Maintainers add actions safely by default (Priority: P3) **Goal**: CI-failing guard flags new ad-hoc authorization patterns; standard pattern documented **Independent Test**: Introduce ad-hoc `Gate::allows` or `abort_unless()` in Filament → guard test fails ### Tests for User Story 3 - [X] T026 [P] [US3] Guard test: scan `app/Filament/**` for forbidden ad-hoc patterns (Gate + abort helpers) in `tests/Feature/Guards/NoAdHocFilamentAuthPatternsTest.php` - [X] T027 [P] [US3] Unit test: UiEnforcement uses only canonical Capabilities constants in `tests/Unit/Support/Rbac/UiEnforcementTest.php` ### Implementation for User Story 3 - [X] T028 [US3] Replace Pest-Arch guard with stable file-scan guard (CI-failing, allowlist for legacy only) in `tests/Feature/Guards/NoAdHocFilamentAuthPatternsTest.php` - [X] T029 [US3] Migrate EntraGroupResource sync actions to UiEnforcement in `app/Filament/Resources/EntraGroupResource/Pages/ListEntraGroups.php` - [X] T030 [US3] Remove Gate facade usage from FindingResource (migrate auth to canonical checks) in `app/Filament/Resources/FindingResource.php` **Checkpoint**: US3 complete — guardrail prevents regression (file-scan), exemplar surfaces migrated --- ## Phase 6: Polish & Cross-Cutting Concerns **Purpose**: Cleanup, additional tests, documentation - [X] T031 [P] PHPDoc blocks present on all public methods in `app/Support/Rbac/UiEnforcement.php` - [X] T032 [P] Update quickstart.md with migration examples in `specs/066-rbac-ui-enforcement-helper/quickstart.md` - [X] T033 Run Pint formatter on new files with `vendor/bin/sail bin pint app/Support/Rbac` - [X] T034 Run full test suite with `vendor/bin/sail artisan test --compact` — 837 passed, 5 skipped - [X] T035 Validate quickstart.md examples work in codebase (ListPolicies migration verified) --- ## Phase 7: Follow-up — Findings capability cleanup (Mini-feature) **Purpose**: Avoid overloading broad capabilities (e.g. `TENANT_SYNC`) for findings acknowledgement. - [X] T036 Add `Capabilities::TENANT_FINDINGS_ACKNOWLEDGE` in `app/Support/Auth/Capabilities.php` - [X] T037 Grant `TENANT_FINDINGS_ACKNOWLEDGE` to Owner/Manager/Operator (not Readonly) + update role-matrix tests - [X] T038 Update Finding list acknowledge action to require `TENANT_FINDINGS_ACKNOWLEDGE` in `app/Filament/Resources/FindingResource/Pages/ListFindings.php` - [X] T039 Refactor `FindingPolicy::update()` to use `CapabilityResolver` with `TENANT_FINDINGS_ACKNOWLEDGE` (remove ad-hoc `Gate::forUser(...)->allows(...)`) --- ## Phase 8: Follow-up — Legacy allowlist shrink (Stepwise) **Purpose**: Keep shrinking the Filament guard allowlist with one-file migrations. - [X] T040 Remove `BackupScheduleResource.php` from the legacy allowlist after migration - [X] T041 Migrate `ListEntraGroupSyncRuns.php` to UiEnforcement + add a focused Livewire test - [X] T042 Remove `ListEntraGroupSyncRuns.php` from the legacy allowlist after migration - [X] T043 Migrate `ListProviderConnections.php` create action to UiEnforcement + add a focused Livewire test - [X] T044 Remove `ListProviderConnections.php` from the legacy allowlist after migration - [X] T045 Migrate `DriftLanding.php` generation permission check to `CapabilityResolver` (remove Gate facade) + add a focused Livewire test - [X] T046 Remove `DriftLanding.php` from the legacy allowlist after migration - [X] T047 Migrate `RegisterTenant.php` page-level checks to `CapabilityResolver` + replace `abort_unless()` with `abort()` - [X] T048 Remove `RegisterTenant.php` from the legacy allowlist after migration - [X] T049 Migrate `EditProviderConnection.php` actions + save guards to `UiEnforcement`/`CapabilityResolver` (remove Gate facade + abort_unless) + add a focused Livewire test - [X] T050 Remove `EditProviderConnection.php` from the legacy allowlist after migration - [X] T051 Migrate `CreateRestoreRun.php` page authorization to `CapabilityResolver` (remove Gate facade + abort_unless) + add a focused Livewire test - [X] T052 Remove `CreateRestoreRun.php` from the legacy allowlist after migration - [X] T053 Migrate `InventoryItemResource.php` resource authorization to `CapabilityResolver` (remove Gate facade) + add a focused Pest test - [X] T054 Remove `InventoryItemResource.php` from the legacy allowlist after migration - [X] T055 Migrate `VersionsRelationManager.php` restore action to `UiEnforcement`/`CapabilityResolver` (remove Gate facade + abort_unless) + add a focused Livewire test - [X] T056 Remove `VersionsRelationManager.php` from the legacy allowlist after migration - [X] T057 Migrate `BackupItemsRelationManager.php` actions to `UiEnforcement` (remove Gate facade) + add a focused Livewire test - [X] T058 Remove `BackupItemsRelationManager.php` from the legacy allowlist after migration - [X] T059 Migrate `PolicyVersionResource.php` actions/bulk actions off ad-hoc patterns to `UiEnforcement`/`CapabilityResolver` (remove Gate facade + abort_unless) while preserving metadata-only restore behavior - [X] T060 Remove `PolicyVersionResource.php` from the legacy allowlist after migration - [X] T061 Migrate `RestoreRunResource.php` actions/bulk actions off ad-hoc patterns to `UiEnforcement`/`CapabilityResolver` (remove Gate facade + abort_unless) - [X] T062 Remove `RestoreRunResource.php` from the legacy allowlist after migration - [X] T063 Fix `UiEnforcement` server-side guard to use Filament lifecycle hooks (`->before()`) to preserve Filament action parameter injection - [X] T064 Migrate `PolicyResource.php` actions/bulk actions off ad-hoc patterns to `UiEnforcement` (remove Gate facade + abort_unless) - [X] T065 Remove `PolicyResource.php` from the legacy allowlist after migration - [X] T066 Migrate `EditTenant.php` archive action off ad-hoc patterns to `UiEnforcement` (remove Gate facade + abort_unless) - [X] T067 Remove `EditTenant.php` from the legacy allowlist after migration - [X] T068 Migrate `TenantMembershipsRelationManager.php` actions off ad-hoc patterns to `UiEnforcement` (remove Gate facade) - [X] T069 Remove `TenantMembershipsRelationManager.php` from the legacy allowlist after migration - [X] T070 Migrate `TenantResource.php` off ad-hoc patterns to `CapabilityResolver` (remove Gate facade + abort_unless) - [X] T071 Remove `TenantResource.php` from the legacy allowlist after migration --- ## Dependencies & Execution Order ### Phase Dependencies - **Setup (Phase 1)**: No dependencies — can start immediately - **Foundational (Phase 2)**: Depends on Setup — BLOCKS all user stories - **User Stories (Phase 3–5)**: All depend on Foundational; can proceed in parallel or by priority - **Polish (Phase 6)**: Depends on all user stories ### User Story Dependencies - **US1 (P1)**: Foundational only — no cross-story dependencies - **US2 (P2)**: Foundational only — no cross-story dependencies - **US3 (P3)**: Foundational only — no cross-story dependencies ### Within Each User Story - Tests MUST be written FIRST and FAIL before implementation - Wire logic in `UiEnforcement.php` before migrating Filament surfaces - Migrate surfaces one at a time, verify tests pass ### Parallel Opportunities - T002 + T003 (Setup) can run in parallel - All test tasks (T012–T014, T019–T021, T026–T027) can run in parallel - US1, US2, US3 can run in parallel after Foundational - T031 + T032 (Polish) can run in parallel --- ## Parallel Example: User Story 1 ```bash # Launch all tests for US1 together: T012: "Test: member without capability sees disabled action + tooltip" T013: "Test: member without capability receives 403 on execution" T014: "Test: member with capability sees enabled action + can execute" # Then implement sequentially: T015 → T016 → T017 → T018 ``` --- ## Implementation Strategy ### MVP First (User Story 1 Only) 1. Complete Phase 1: Setup (T001–T003) 2. Complete Phase 2: Foundational (T004–T011) 3. Complete Phase 3: User Story 1 (T012–T018) 4. **STOP and VALIDATE**: Members see disabled + tooltip + 403 5. Deploy/demo if ready ### Incremental Delivery 1. Setup + Foundational → `UiEnforcement` ready 2. US1 → Consistent disabled UX for members (MVP!) 3. US2 → Non-member 404 enforcement 4. US3 → CI-failing guardrail + all 6 surfaces migrated 5. Polish → Docs, cleanup, full test suite --- ## Summary | Metric | Count | |--------|-------| | Total tasks | 40 | | Setup tasks | 3 | | Foundational tasks | 8 | | US1 tasks | 8 | | US2 tasks | 7 | | US3 tasks | 5 | | Polish tasks | 5 | | Follow-up tasks | 4 | | Parallel opportunities | 13 | | MVP scope | Phases 1–3 (T001–T018) |