TenantAtlas/specs/051-entra-group-directory-cache/tasks.md
ahmido bc846d7c5c 051-entra-group-directory-cache (#57)
Summary

Adds a tenant-scoped Entra Groups “Directory Cache” to enable DB-only group name resolution across the app (no render-time Graph calls), plus sync runs + observability.

What’s included
	•	Entra Groups cache
	•	New entra_groups storage (tenant-scoped) for group metadata (no memberships).
	•	Retention semantics: groups become stale / retained per spec (no hard delete on first miss).
	•	Group Sync Runs
	•	New “Group Sync Runs” UI (list + detail) with tenant isolation (403 on cross-tenant access).
	•	Manual “Sync Groups” action: creates/reuses a run, dispatches job, DB notification with “View run” link.
	•	Scheduled dispatcher command wired in console.php.
	•	DB-only label resolution (US3)
	•	Shared EntraGroupLabelResolver with safe fallback Unresolved (…last8) and UUID guarding.
	•	Refactors to prefer cached names (no typeahead / no live Graph) in:
	•	Tenant RBAC group selects
	•	Policy version assignments widget
	•	Restore results + restore wizard group mapping labels

Safety / Guardrails
	•	No render-time Graph calls: fail-hard guard test verifies UI paths don’t call GraphClientInterface during page render.
	•	Tenant isolation & authorization: policies + scoped queries enforced (cross-tenant access returns 403, not 404).
	•	Data minimization: only group metadata is cached (no membership/owners).

Tests / Verification
	•	Added/updated tests under tests/Feature/DirectoryGroups and tests/Unit/DirectoryGroups:
	•	Start sync → run record + job dispatch + upserts
	•	Retention purge semantics
	•	Scheduled dispatch wiring
	•	Render-time Graph guard
	•	UI/resource access isolation
	•	Ran:
	•	./vendor/bin/pint --dirty
	•	./vendor/bin/sail artisan test tests/Feature/DirectoryGroups
	•	./vendor/bin/sail artisan test tests/Unit/DirectoryGroups

Notes / Follow-ups
	•	UI polish remains (picker/lookup UX, consistent progress widget/toasts across modules, navigation grouping).
	•	pr-gate checklist still has non-blocking open items (mostly UX/ops polish); requirements gate is green.

Co-authored-by: Ahmed Darrazi <ahmeddarrazi@adsmac.local>
Reviewed-on: #57
2026-01-11 23:24:12 +00:00

185 lines
9.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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 35)** → 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: T010T013
- Parallel foundational code: T014 and T018 can be developed alongside T015T016 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 T029T032 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.