142 lines
5.9 KiB
Markdown
142 lines
5.9 KiB
Markdown
# Implementation Plan: Entra Group Directory Cache (Groups v1)
|
|
|
|
**Branch**: `051-entra-group-directory-cache` | **Date**: 2026-01-11 | **Spec**: [specs/051-entra-group-directory-cache/spec.md](spec.md)
|
|
**Input**: Feature specification from `/specs/051-entra-group-directory-cache/spec.md`
|
|
|
|
**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/scripts/` for helper scripts.
|
|
|
|
## Summary
|
|
|
|
Provide a tenant-scoped Entra ID Groups metadata cache (no membership/owners) populated by queued sync runs (manual + scheduled), so the admin UI and other modules can resolve group IDs to friendly names without making live directory calls during page render.
|
|
|
|
## Technical Context
|
|
|
|
**Language/Version**: PHP 8.4 (Laravel 12)
|
|
**Primary Dependencies**: Filament v4, Livewire v3, Microsoft Graph integration via internal `GraphClientInterface`
|
|
**Storage**: PostgreSQL
|
|
**Testing**: Pest v4
|
|
**Target Platform**: Web application (Sail-first locally, Dokploy-first deploy)
|
|
**Project Type**: web
|
|
**Performance Goals**: background sync handles large tenants via paging; UI list/search remains responsive with DB indexes
|
|
**Constraints**: no live directory calls during page render; app-only Graph auth; safe error summaries; strict tenant isolation; idempotent runs; retention purge after 90 days last-seen
|
|
**Scale/Scope**: full-tenant group enumeration; plan for up to ~100k groups/tenant for v1
|
|
|
|
## Constitution Check
|
|
|
|
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
|
|
|
- Inventory-first: cache is “last observed” directory state; no snapshot payloads.
|
|
- Read/write separation: read-only to the directory; only internal DB writes.
|
|
- Graph contract path: directory group list endpoint/fields/permissions must be added to `config/graph_contracts.php` and accessed via `GraphClientInterface`.
|
|
- Deterministic capabilities: selection identifier for groups sync is deterministic (stable per tenant + groups-v1).
|
|
- Tenant isolation: all cached groups + run records tenant-scoped; no cross-tenant access paths.
|
|
- Automation: manual + scheduled sync runs use locks/idempotency, run records, safe error codes; handle 429/503 with backoff+jitter.
|
|
- Data minimization: store group metadata only (no membership/owners); logs contain no secrets/tokens.
|
|
|
|
**Gate status (pre-Phase 0)**: PASS (no violations). Re-check after Phase 1 design.
|
|
|
|
**Gate status (post-Phase 1)**: PASS (design artifacts present: research.md, data-model.md, contracts/*, quickstart.md).
|
|
|
|
## Project Structure
|
|
|
|
### Documentation (this feature)
|
|
|
|
```text
|
|
specs/051-entra-group-directory-cache/
|
|
├── plan.md # This file (/speckit.plan command output)
|
|
├── research.md # Phase 0 output (/speckit.plan command)
|
|
├── data-model.md # Phase 1 output (/speckit.plan command)
|
|
├── quickstart.md # Phase 1 output (/speckit.plan command)
|
|
├── contracts/ # Phase 1 output (/speckit.plan command)
|
|
└── tasks.md # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan)
|
|
```
|
|
|
|
### Source Code (repository root)
|
|
```text
|
|
app/
|
|
├── Filament/
|
|
│ ├── Pages/
|
|
│ └── Resources/
|
|
├── Jobs/
|
|
├── Models/
|
|
├── Services/
|
|
│ ├── Graph/
|
|
│ └── Directory/
|
|
└── Support/
|
|
|
|
config/
|
|
├── graph.php
|
|
└── graph_contracts.php
|
|
|
|
database/
|
|
└── migrations/
|
|
|
|
routes/
|
|
└── web.php
|
|
|
|
tests/
|
|
├── Feature/
|
|
└── Unit/
|
|
```
|
|
|
|
**Structure Decision**: Laravel web application. Implement directory group sync + cache as tenant-scoped Models, Services, Jobs, Filament pages/resources, migrations, and Pest tests.
|
|
|
|
## Key Implementation Decisions (Pinned)
|
|
|
|
- **Schedule default**: daily at **02:00 UTC** (environment default) + manual sync always available.
|
|
- **Auth mode**: app-only (service principal). UI must not require delegated tokens.
|
|
- **Permission**: `Group.Read.All` (application).
|
|
- **Graph strategy (v1)**: `GET /groups` with `$select=id,displayName,groupTypes,securityEnabled,mailEnabled` + paging via `@odata.nextLink`.
|
|
- **Retention**: stale threshold default 30 days; purge after 90 days since `last_seen_at`.
|
|
- **Render safety**: fail-hard test ensuring no Graph client calls during render.
|
|
|
|
## Execution Model
|
|
|
|
### Sync Run Lifecycle
|
|
|
|
- Run statuses: `pending` → `running` → `succeeded` | `failed` (optionally `partial` if we intentionally record partial completion).
|
|
- Each run records: initiator (nullable for scheduled), timestamps, observed/upserted/error counters, and a safe error summary + category.
|
|
|
|
### Idempotency & Concurrency
|
|
|
|
- Deterministic selection key for v1: `groups-v1:all`.
|
|
- Deduplication rule: at most one active run per tenant + selection key.
|
|
- Scheduled dispatcher must be idempotent per tenant and schedule slot (no duplicate run creation).
|
|
|
|
### Error Categorization
|
|
|
|
- Supported categories: `permission`, `throttling`, `transient`, `unknown`.
|
|
- Store only safe-to-display summaries (no secrets/tokens).
|
|
|
|
## Definition of Done (per phase)
|
|
|
|
### Phase 2 (Foundational)
|
|
|
|
- Migrations created for EntraGroup and EntraGroupSyncRun.
|
|
- Models + factories exist and are tenant-scoped.
|
|
- Graph contract registry has a groups directory read contract.
|
|
|
|
### Phase 3 (US1)
|
|
|
|
- Manual sync creates a run and dispatches a job.
|
|
- Job pages through Graph, upserts groups, updates counters, and enforces retention purge.
|
|
- Filament run list/detail exists; scheduler dispatch is wired.
|
|
- Pest tests for run creation, upsert behavior, retention purge, and scheduled run dispatch are green.
|
|
|
|
### Phase 4 (US2)
|
|
|
|
- Directory → Groups list/detail exists with search + stale filter + type filter.
|
|
- All browsing renders from DB cache only.
|
|
|
|
### Phase 5 (US3)
|
|
|
|
- Shared label resolver resolves from DB cache and provides consistent fallback formatting.
|
|
- Key pages render friendly labels without Graph calls.
|
|
- Fail-hard render guard test is green.
|
|
|
|
## Complexity Tracking
|
|
|
|
> **Fill ONLY if Constitution Check has violations that must be justified**
|
|
|
|
N/A (no constitution violations anticipated)
|