--- description: "Task list for implementing Entra Group Directory Cache (Groups v1)" --- # Tasks: Entra Group Directory Cache (Groups v1) **Input**: Design documents from `/specs/051-entra-group-directory-cache/` **Prerequisites**: plan.md (required), spec.md (required for user stories), research.md, data-model.md, contracts/ **Tests**: Required (Pest), per FR-012 in `spec.md`. **Organization**: Tasks are grouped by user story so each story can be implemented and tested independently. ## Format: `- [ ] T### [P?] [US#?] Description (with file path)` - **[P]**: Can run in parallel (different files, no dependencies) - **[US#]**: User story mapping (US1/US2/US3) ## Path Conventions (Laravel) - App code: `app/` - Config: `config/` - DB: `database/migrations/`, `database/factories/` - Filament admin: `app/Filament/Resources/` - Console + scheduler wiring: `app/Console/Commands/`, `routes/console.php` - Tests (Pest): `tests/Feature/`, `tests/Unit/` --- ## Phase 1: Setup (Shared Infrastructure) **Purpose**: Introduce feature configuration and permission metadata. - [x] T001 Create feature config defaults in config/directory_groups.php (staleness_days=30, retention_days=90, schedule enabled/interval, page_size) - [x] T002 Update Group.Read.All feature tagging in config/intune_permissions.php (include directory-groups / group-directory-cache) --- ## Phase 2: Foundational (Blocking Prerequisites) **Purpose**: Database schema + core domain objects required by all user stories. **⚠️ CRITICAL**: No user story work can begin until this phase is complete. - [x] T003 Create migrations for Entra groups + sync runs in database/migrations/*_create_entra_groups_table.php and database/migrations/*_create_entra_group_sync_runs_table.php - [x] T004 [P] Create EntraGroup model in app/Models/EntraGroup.php (tenant-scoped, casts for group_types) - [x] T005 [P] Create EntraGroupSyncRun model in app/Models/EntraGroupSyncRun.php (status constants, counters, safe error fields) - [x] T006 [P] Create EntraGroup factory in database/factories/EntraGroupFactory.php - [x] T007 [P] Create EntraGroupSyncRun factory in database/factories/EntraGroupSyncRunFactory.php - [x] T008 Add tenant relationships in app/Models/Tenant.php (entraGroups(), entraGroupSyncRuns()) - [x] T009 Add directory-groups contract metadata in config/graph_contracts.php and accessor(s) in app/Services/Graph/GraphContractRegistry.php (select fields + base path for /groups) **Checkpoint**: Foundation ready — user stories can now proceed. --- ## Phase 3: User Story 1 - Sync groups into a tenant-scoped cache (Priority: P1) 🎯 MVP **Goal**: Manual + scheduled async sync writes tenant-scoped group cache and run records (including safe error summaries). **Independent Test**: Trigger a sync for a tenant and verify a run record exists and group rows are populated for that tenant. ### Tests for User Story 1 (REQUIRED) ⚠️ > Write these tests FIRST and ensure they fail before implementation. - [x] T010 [P] [US1] Add test for manual sync creating a run + dispatching a job in tests/Feature/DirectoryGroups/StartSyncTest.php - [x] T011 [P] [US1] Add test that sync job upserts groups + updates counters in tests/Feature/DirectoryGroups/SyncJobUpsertsGroupsTest.php - [x] T012 [P] [US1] Add test that retention purge deletes groups older than retention window in tests/Feature/DirectoryGroups/SyncRetentionPurgeTest.php - [x] T013 [P] [US1] Add test that scheduled dispatcher creates a run without a user initiator in tests/Feature/DirectoryGroups/ScheduledSyncDispatchTest.php - [x] T014 [P] [US1] Implement deterministic selection key helper in app/Services/Directory/EntraGroupSelection.php - [x] T015 [US1] Implement sync service in app/Services/Directory/EntraGroupSyncService.php (Graph paging via GraphClientInterface, upsert, counters, retention purge) - [x] T016 [US1] Implement queued job in app/Jobs/EntraGroupSyncJob.php (run lifecycle: pending→running→succeeded/failed; safe error_category + error_summary) - [x] T017 [US1] Add audit logging for sync start/completion in app/Jobs/EntraGroupSyncJob.php using app/Services/Intune/AuditLogger.php - [x] T018 [P] [US1] Create Filament run resource in app/Filament/Resources/EntraGroupSyncRunResource.php and app/Filament/Resources/EntraGroupSyncRunResource/Pages/{ListEntraGroupSyncRuns,ViewEntraGroupSyncRun}.php - [x] T019 [US1] Add “Sync Groups” action on List page in app/Filament/Resources/EntraGroupSyncRunResource/Pages/ListEntraGroupSyncRuns.php (creates run, dispatches job, shows notification) - [x] T020 [US1] Implement scheduled dispatcher command in app/Console/Commands/TenantpilotDispatchDirectoryGroupsSync.php (idempotent per tenant + minute-slot, respects config/directory_groups.php) - [x] T021 [US1] Register scheduler entry in routes/console.php for tenantpilot:directory-groups:dispatch (every minute) **Checkpoint**: US1 complete — cache population works, runs are visible, and scheduled runs can be observed. --- ## Phase 4: User Story 2 - Browse groups (Priority: P2) **Goal**: Provide a cached-only “Directory → Groups” browse/search/filter/detail UI. **Independent Test**: After a sync run, open the groups list and verify search/filter/detail views work using only cached data. ### Tests for User Story 2 (REQUIRED) ⚠️ - [x] T022 [P] [US2] Add test for listing/searching/filtering cached groups in tests/Feature/DirectoryGroups/BrowseGroupsTest.php ### Implementation for User Story 2 - [x] T023 [P] [US2] Create Filament groups resource in app/Filament/Resources/EntraGroupResource.php and app/Filament/Resources/EntraGroupResource/Pages/{ListEntraGroups,ViewEntraGroup}.php - [x] T024 [US2] Implement list query + filters (q search, stale filter, group type filter) in app/Filament/Resources/EntraGroupResource.php - [x] T025 [US2] Add “Sync Groups” header action that links to run list or starts a sync in app/Filament/Resources/EntraGroupResource/Pages/ListEntraGroups.php **Checkpoint**: US2 complete — operators can browse/search cached groups and triage staleness. --- ## Phase 5: User Story 3 - Name resolution across the suite (Priority: P3) **Goal**: Any UI that displays group IDs shows a friendly cached label (or unresolved fallback) without live directory calls during render. **Independent Test**: Load a page that includes group GUID references and verify it renders with names from DB cache (and fallbacks when missing) without calling Graph. ### Tests for User Story 3 (REQUIRED) ⚠️ - [x] T026 [P] [US3] Add unit test for label resolver formatting + fallbacks in tests/Unit/DirectoryGroups/EntraGroupLabelResolverTest.php - [x] T027 [P] [US3] Add feature test ensuring Tenant/Restore/PolicyVersion UI renders without Graph calls during render in tests/Feature/DirectoryGroups/NoLiveGraphOnRenderTest.php ### Implementation for User Story 3 - [x] T028 [US3] Implement DB-backed label resolver in app/Services/Directory/EntraGroupLabelResolver.php (resolveOne/resolveMany, tenant-scoped, stable formatting) - [x] T029 [US3] Refactor group label rendering in app/Filament/Resources/TenantResource.php to use EntraGroupLabelResolver instead of Graph lookups - [x] T030 [US3] Refactor Livewire assignments widget to use cached labels: app/Livewire/PolicyVersionAssignmentsWidget.php and resources/views/livewire/policy-version-assignments-widget.blade.php - [x] T031 [US3] Refactor restore results to show cached labels where possible: resources/views/filament/infolists/entries/restore-results.blade.php - [x] T032 [US3] Refactor restore group-mapping inputs/labels to prefer cached labels: app/Filament/Resources/RestoreRunResource.php **Checkpoint**: US3 complete — name resolution is consistent and render-safe across key pages. --- ## Phase 6: Polish & Cross-Cutting Concerns **Purpose**: Validation, cleanup, and operational readiness. - [x] T033 [P] Run formatting on changed files with vendor/bin/pint --dirty - [x] T034 Run targeted Pest suite for this feature (e.g., php artisan test tests/Feature/DirectoryGroups and tests/Unit/DirectoryGroups) - [x] T035 Validate operator workflow against specs/051-entra-group-directory-cache/quickstart.md --- ## Dependencies & Execution Order ### Phase Dependencies - **Setup (Phase 1)** → blocks nothing, but should be done first. - **Foundational (Phase 2)** → BLOCKS all user stories. - **User Stories (Phase 3–5)** → depend on Foundational; proceed in priority order (US1 → US2 → US3). - **Polish (Phase 6)** → depends on all desired user stories. ### User Story Dependencies - **US1 (P1)**: Depends on Phase 2 only. - **US2 (P2)**: Depends on US1 (needs data to browse). - **US3 (P3)**: Depends on US1 (needs cache populated); can begin in parallel with US2 once US1 is stable. --- ## Parallel Execution Examples ### US1 - Parallel tests: T010–T013 - Parallel foundational code: T014 and T018 can be developed alongside T015–T016 once schema/models exist. ### US2 - T023 can start while T022 is being written (different files). ### US3 - T028 can start while T026/T027 are being written; integration tasks T029–T032 can be split across different files. --- ## Implementation Strategy ### MVP (US1 only) 1. Phase 1 → Phase 2 2. Phase 3 (US1): tests → sync service/job → run UI → scheduler 3. Stop and validate via quickstart workflow ### Incremental Delivery - Add US2 (browse) after US1 is stable. - Add US3 (name resolution) after US1, then refactor page-by-page.