---

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.
