--- description: "Task list for TenantPilot v1 implementation" --- # Tasks: TenantPilot v1 **Input**: Design documents from `.specify/spec.md` and `.specify/plan.md` **Prerequisites**: plan.md (complete), spec.md (complete) ## Phase 1: Setup (Shared Infrastructure) - [x] T001 [P] [Shared] Confirm Sail/Env ready; ensure `.env` has PostgreSQL settings for Sail and Filament admin user seeded (if missing) in `database/seeders/`. - [x] T002 [P] [Shared] Add baseline docs for local dev and staging promotion notes in `README.md` (Sail commands, staging-before-prod reminder). ## Phase 2: Foundational (Blocking Prerequisites) - [x] T003 [Shared] Add tenant-aware migrations for `tenants`, `policies`, `policy_versions`, `backup_sets`, `backup_items`, `restore_runs`, `audit_logs` with JSONB payloads and FK/time indexes in `database/migrations/`. - [x] T004 [Shared] Create models with relationships and guarded attributes for the above entities in `app/Models/`. - [x] T005 [Shared] Implement Graph abstraction contracts (`GraphClientInterface`, error mapping, logging hooks) in `app/Services/Graph/` with a mockable adapter. - [x] T006 [Shared] Add audit logging service/helper to capture actor, tenant, operation, resources, outcome in `app/Services/Intune/AuditLogger.php`. - [x] T007 [Shared] Seed supported policy types/metadata for initial scope in `database/seeders/PoliciesSeeder.php` and ensure tenant scoping. ## Phase 3: User Story 1 - Policy inventory listing (Priority: P1) ### Tests for User Story 1 - [x] T008 [P] [US1] Feature test for Filament policy listing and filtering (tenant-scoped) in `tests/Feature/Filament/PolicyListingTest.php` using mocked Graph sync. ### Implementation for User Story 1 - [x] T009 [US1] Implement policy sync/import orchestrator using Graph abstraction in `app/Services/Intune/PolicySyncService.php` (no direct Graph in UI). - [x] T010 [US1] Create Filament resource/table for policies with filters and metadata columns in `app/Filament/Resources/PolicyResource.php`. - [x] T011 [US1] Add command/job to sync policies (queues-ready) in `app/Console/Commands/SyncPolicies.php` and queue job under `app/Jobs/`. ## Phase 4: User Story 2 - Backup creation and browsing (Priority: P1) ### Tests for User Story 2 - [x] T012 [P] [US2] Feature test for creating backup sets with multiple policies and verifying immutable JSONB snapshots + audit log in `tests/Feature/Filament/BackupCreationTest.php`. ### Implementation for User Story 2 - [x] T013 [US2] Implement backup domain service to assemble snapshots from policies with Graph payload retrieval in `app/Services/Intune/BackupService.php`. - [x] T014 [US2] Add Filament resource/pages for backup sets and items (list/detail) in `app/Filament/Resources/BackupSetResource.php`. - [x] T131 [UX] [US2] Refactor BackupSet policy selection to RelationManager: - Remove the multi-select policy picker from the BackupSet **Create** form (keep Create minimal: name/description). - After create, redirect to BackupSet **Edit/View** where items can be managed. - Add `BackupItemsRelationManager` to `BackupSetResource` showing a table with columns: Policy Name, Type (badge), Restore (badge), Risk (badge). - Add header action “Policies hinzufügen” (searchable, multiple) that adds items/attaches policies **tenant-scoped** and prevents duplicates per BackupSet. - Provide a remove action (detach/soft-delete as per domain rules). - [x] T132 [P] [US2] Update/extend `tests/Feature/Filament/BackupCreationTest.php` to cover the new UX flow: - Create BackupSet without policies. - Add multiple policies via RelationManager action. - Verify immutable JSONB snapshots + audit log behavior remains correct. - [x] T015 [US2] Wire audit logging for backup creation events in `app/Services/Intune/BackupService.php` using `AuditLogger`. ## Phase 5: User Story 3 - Version history and diff (Priority: P1) ### Tests for User Story 3 - [x] T016 [P] [US3] Feature test for version capture and timeline display in `tests/Feature/Filament/PolicyVersionTest.php`. - [x] T017 [P] [US3] Unit test for diff generation (human summary + JSON diff) in `tests/Unit/VersionDiffTest.php`. ### Implementation for User Story 3 - [x] T018 [US3] Implement version capture service with immutable JSONB writes in `app/Services/Intune/VersionService.php`. - [x] T019 [US3] Create diff helper (summary + structured JSON) in `app/Services/Intune/VersionDiff.php` and surface in Filament version compare view in `app/Filament/Resources/PolicyVersionResource.php`. - [x] T020 [US3] Hook version capture into relevant flows (manual trigger + backup/restore hooks) ensuring audit logging. ## Phase 6: User Story 4 - Restore with preview and confirmation (Priority: P1) ### Tests for User Story 4 - [x] T021 [P] [US4] Feature test for restore preview (change summary, conflicts, selective items) in `tests/Feature/Filament/RestorePreviewTest.php`. - [x] T022 [P] [US4] Feature test for confirmed restore execution capturing audit logs and per-item outcomes in `tests/Feature/Filament/RestoreExecutionTest.php`. ### Implementation for User Story 4 - [x] T023 [US4] Implement restore service with preview/dry-run and selective item application in `app/Services/Intune/RestoreService.php`, integrating Graph adapter and conflict detection. - [x] T024 [US4] Add Filament restore UI (wizard or pages) showing preview, warnings, and confirmation gate in `app/Filament/Resources/RestoreRunResource.php`. - [x] T025 [US4] Record restore run lifecycle (start, per-item result, completion) and audit events in `restore_runs` and `audit_logs`. ## Phase 7: User Story 5 - Operational readiness and environments (Priority: P2) ### Implementation for User Story 5 - [x] T026 [US5] Document Dokploy staging→production promotion steps, required env vars, queue/worker expectations, and migration safety notes in `README.md` or `docs/deploy.md`. - [x] T027 [US5] Add quick Sail commands and test invocation notes to `README.md` (e.g., `./vendor/bin/sail artisan test`) and ensure sample env entries for Graph credentials. ## Phase 8: User Story 6 - Tenant hinzufügen & Entra ID App-Setup (Priority: P1) - [x] T030 [US6] Migration für `tenants` ergänzen/prüfen: - Felder: `name`, `tenant_id` (GUID), `domain`, `app_client_id`, `app_status`, `app_notes`, `created_at`, `updated_at`. - Optional: Felder für Secret/Certificate-Config (verschlüsselt), falls benötigt. - [x] T031 [US6] Eloquent Model `Tenant`: - Beziehungen zu `policies`, `backup_sets`, `restore_runs`, `policy_versions`, `audit_logs` über `tenant_id`. - Tenant-aware Scopes, falls vorhanden (z. B. `forTenant()`). - [x] T032 [US6] Filament-Resource `TenantResource`: - Listenansicht: Name, Tenant ID, Domain, App-Status, erstellt/am. - Create/Edit-Form: Name, Tenant ID, Domain, App-Client-ID, optionale Notizen. - Detailseite mit Actions: - „Open in Entra“ (Link zur App/Tenant im Entra-Portal), - optional: „Copy Admin Consent URL“. - [x] T033 [US6] `TenantConfigService` (oder Erweiterung des Graph-Clients): - Methode `testConnectivity(Tenant $tenant)`: führt einen einfachen Graph-Call aus (z. B. `/organization` oder ähnliches) mit den App-Daten des Tenants. - Rückgabe: DTO/Array mit `success`, `error_message` (falls vorhanden). - [x] T034 [US6] Action „Verify configuration“ in `TenantResource`: - Ruft `testConnectivity()` auf, - setzt `app_status` auf z. B. `ok`, `error` oder `consent_required`, - zeigt eine Filament-Notification mit dem Ergebnis, - schreibt einen Audit-Log-Eintrag (`tenant.config.verified`). - [x] T035 [US6] Tenant-Kontext in bestehende Services integrieren: - `PolicySyncService`, `BackupService`, `RestoreService` so anpassen, dass sie einen `Tenant` oder `tenant_id` übergeben bekommen und den Graph-Client mit diesem Kontext verwenden. - Sicherstellen, dass alle policy/backup/restore/audit-Datensätze `tenant_id` setzen. - [x] T036 [US6] Feature-Test `TenantSetupTest`: - Erstellen eines Tenants via Filament (Create-Form). - Aufruf der Action „Verify configuration“ mit gemocktem Graph-Client: - einmal mit erfolgreichem Call → `app_status = ok`, - einmal mit Fehler → `app_status = error` + passende Notification. - Prüfen, dass Audit-Logs geschrieben werden. - [x] T037 [US6] Admin-Consent Callback Route - Route/Controller, der als `redirect_uri` der Entra-ID-App dient. - Liest `tenant` / `error` / `admin_consent` aus der Query. - Ordnet das dem richtigen `Tenant` zu (z. B. via `state`). - Aktualisiert `app_status` (z. B. `ok`, `error`, `consent_denied`). - Zeigt eine Bestätigungs-/Fehlerseite für den Admin. --- ## Phase 9: User Story 7 - Berechtigungsübersicht & Health-Status (Priority: P1) - [x] T040 [US7] Zentrale Permissions-Liste anlegen: - `config/intune_permissions.php` mit allen aktuell benötigten Graph-Berechtigungen: - technischer Name (z. B. `DeviceManagementConfiguration.ReadWrite.All`), - Typ: `application` / `delegated`, - kurze Beschreibung, - Feature-Tags (z. B. `["policy-sync", "backup"]`). - Optional: `docs/permissions.md` mit einer Tabelle Feature ↔ Permission als menschlich lesbare Referenz. - [x] T041 [US7] Datenmodell für Tenant-Berechtigungen: - Variante A (einfach): JSONB-Feld `granted_permissions` in `tenants` (Liste von Permission-Keys). - Variante B (feiner): Tabelle `tenant_permissions` mit `(tenant_id, permission_key, status, last_checked_at)`. - `status` mindestens: `ok`, `missing`, `error`. - [x] T042 [US7] Service `TenantPermissionService`: - `getRequiredPermissions(): array` – liest aus `config/intune_permissions.php`. - `getGrantedPermissions(Tenant $tenant): array` – liest aus Graph oder aus `tenant_permissions`/`granted_permissions`. - `compare(Tenant $tenant): TenantPermissionStatusDTO` – liefert pro Permission den Status (ok/missing/error) + Gesamthealth. - [x] T043 [US7] Integration in Tenant-Detail-UI: - Auf der `TenantResource`-Detailseite ein Panel/Section „Permissions“: - Liste aller **required permissions**, - pro Zeile: Name, Typ, Feature-Tags, Status (Icon + Label: OK/fehlt/Fehler). - Optional: Link zu Doku oder Entra-Darstellung (z. B. „How to grant these permissions“). - [x] T044 [US7] Action „Verify configuration“ erweitern: - Zusätzlich zu `testConnectivity()` auch `TenantPermissionService::compare()` aufrufen. - Ergebnisse in `tenant_permissions`/`granted_permissions` speichern. - `app_status` und Permission-Health aktualisieren. - Audit-Log-Eintrag `tenant.permissions.checked` schreiben. - [x] T045 [US7] Tests für Permissions: - Unit-Tests für `TenantPermissionService::compare()`: - Szenarien: alle ok, Permission fehlt, Graph-Error. - Feature-Test für Tenant-Detailseite: - required permissions werden angezeigt, - fehlende werden als fehlend markiert, - „Verify configuration“ aktualisiert den Status wie erwartet. ## Phase 9b: Scope-Ausrichtung auf neue Objekttypen - [x] T028 [Scope] Konfiguration `config/tenantpilot.php` auf die in `scope.supported_types` definierten Objekttypen erweitern (type/key, endpoint, label/category, optional risk/restore-Hinweis). Sicherstellen, dass diese Liste die einzige Quelle für Policy-Sync/Backup/Restore ist. - [x] T029 [Scope] Filament-UI an neue Typen anpassen: Tabellenfilter/Grouping nach Kategorie (z. B. Config/Compliance/Scripts/Apps/CA), Backup/Restore-Formulare mit Hinweisen zu Restore-Level aus `scope.restore_matrix` (z. B. CA/enrollment restrictions = preview-only). ## Phase 10: Housekeeping – Delete-Funktionen für Backups & Versions - [x] T060 [HK] BackupSets soft deletable machen: - `backup_sets` (und ggf. `backup_items`) Migration/Model mit `SoftDeletes` (deleted_at). - Sicherstellen, dass RestoreRuns keine gelöschten BackupSets verwenden; Delete nur erlauben, wenn keine zugehörigen RestoreRuns existieren. - [x] T061 [HK] Filament-Delete-Action für BackupSets: - In `BackupSetResource` Delete-Action in List- und/oder Detail-View hinzufügen. - Mit Confirmation-Dialog (“This will archive this backup set and hide it from the UI.”). - Delete disabled/hidden, wenn `restore_runs` für das Set existieren. - Nach Delete Audit-Log (`backup.deleted`) schreiben. - [x] T062 [HK] PolicyVersions soft deletable machen: - `policy_versions` Migration/Model um `SoftDeletes` erweitern. - Alle Queries und Filament-Resources so lassen, dass standardmäßig nur non-deleted Versions angezeigt werden. - [x] T063 [HK] Filament-Delete-Action für PolicyVersions: - In `PolicyVersionResource` Delete-Action hinzufügen (List/Detail). - Confirmation + Audit-Log (`policy_version.deleted`). - [x] T064 [HK] Tests für Housekeeping: - Feature-Test: Löschen eines BackupSets ohne RestoreRun → `deleted_at` gesetzt, UI-Eintrag weg, Audit-Log vorhanden. - Feature-Test: BackupSet mit RestoreRun → Delete-Action nicht verfügbar. - Feature-Test: Löschen einer PolicyVersion → `deleted_at` gesetzt, nicht mehr in List sichtbar. ## Phase 11: Housekeeping – Tenant löschen/deaktivieren - [x] T070 [HK] Tenants soft deletable machen: - `tenants` Model um `SoftDeletes` erweitern, Migration ggf. `deleted_at` hinzufügen. - Optional: Feld `status` (enum/string: `active`, `archived`) einführen; beim Delete auf `archived` setzen. - Alle Standard-Queries für Tenants nur `active` / nicht gelöscht anzeigen. - [x] T071 [HK] Tenant-Delete-Action (Deaktivieren) in `TenantResource`: - Delete-/Archive-Action in der Tenant-Liste und/oder Detailseite hinzufügen. - Deutlich machen: “Deaktiviert diesen Tenant. Historische Daten bleiben vorhanden, neue Aktionen sind nicht mehr möglich.” - Bei Ausführung: - `deleted_at` setzen (und ggf. `status = archived`), - Audit-Log `tenant.deleted` oder `tenant.archived` schreiben. - [x] T072 [HK] Verhalten für deaktivierte Tenants: - In `PolicySyncService`, `BackupService`, `RestoreService` prüfen, dass nur aktive Tenants verwendet werden; bei deaktiviertem Tenant frühzeitig mit verständlicher Fehlermeldung abbrechen. - In Filament-Navigation Tenants, Policies, Backups, Restores eines deaktivierten Tenants nicht mehr in Standard-Listen anzeigen (es sei denn, es gibt explizite “Show archived”-Filter). - [x] T073 [HK] (Optional) RestoreRuns soft deletable machen: - `restore_runs` Model/Migration mit `SoftDeletes`. - Delete-Action in `RestoreRunResource` hinzufügen (nur UI-Aufräumung, keine Folgen für Backups). - Audit-Log `restore_run.deleted` schreiben. - [x] T074 [HK] Tests für Tenant-Delete: - Feature-Test: Tenant löschen/deaktivieren → Tenant taucht nicht mehr in Standardlisten auf, `deleted_at` (und `status`) ist gesetzt, Audit-Event existiert. - Feature-Test: Versuch, mit deaktiviertem Tenant einen Policy-Sync/Backup/Restore zu starten, führt zu einem klaren Fehler (und kein Graph-Call wird ausgeführt). ## Phase 12: Housekeeping – Hard Deletes (Force Delete) - [x] T075 [HK] Force-Delete-Actions ergänzen: - Filament-Listen für Tenants, BackupSets, PolicyVersions, RestoreRuns erhalten „Force delete“ Aktionen (sichtbar nur im Trashed-Filter), mit klarer Confirmation. - BackupSets: Force delete nur, wenn keine RestoreRuns existieren; löscht Items mit. - Tenants: Force delete nur, wenn archiviert; blockiert für aktive Tenants. - Alle Force-Deletes schreiben Audit-Log-Einträge vor der endgültigen Löschung. - Tests für Force-Delete-Flows (erfolgreich/blockiert) ergänzen. ## Phase 12: Single current tenant ("Highlander") - [x] T120 [TENANT] Migration `add_is_current_to_tenants`: - Spalte `is_current` (boolean, default false, not null) zu `tenants` hinzufügen. - Partielle Unique-Index anlegen, z. B.: - `UNIQUE INDEX tenants_current_unique ON tenants (is_current) WHERE is_current = true AND deleted_at IS NULL`. - [x] T121 [TENANT] Tenant-Model anpassen: - Methode `makeCurrent()` implementieren: - Transaktion: alle anderen Tenants `is_current = false`, dieser Tenant `is_current = true`. - Methode `static current()` implementieren: - Wenn `INTUNE_TENANT_ID` gesetzt ist → Tenant mit dieser GUID laden, sonst Exception, wenn nicht gefunden / deaktiviert. - Wenn nicht gesetzt → Tenant mit `is_current = true` und `status = active` (und `deleted_at` null) zurückgeben. - Wenn keiner → Exception “No current tenant selected”. - `findOrCreateDefault()` deprecaten/entfernen; keine Dummy-Tenants mehr erzeugen. - [x] T122 [TENANT] Data-Migration / Cleanup: - Falls mindestens ein Tenant mit `app_status = ok` existiert: - einen als `is_current = true` markieren (z. B. den ersten). - `local-tenant` auf `status = archived`, `is_current = false` setzen. - Sicherstellen, dass `local-tenant` nie wieder als aktueller Kontext verwendet wird. - [x] T123 [TENANT] Filament `TenantResource` UI: - Spalte/Badge für `is_current` in der Liste hinzufügen. - Table-Action "Make current" ergänzen: - nur sichtbar für aktive Tenants, die nicht `is_current` sind. - ruft `makeCurrent()` auf und zeigt Notification. - Alte Logik entfernen, die `local-tenant` automatisch als Default nutzt. - [x] T124 [TENANT] Consumers refactoren: - Alle Vorkommen von `findOrCreateDefault()` suchen und durch `Tenant::current()` (oder expliziten Tenant) ersetzen: - Policy-Sync (Command + Filament-Action), - BackupSet-Erstellung, - RestoreRun-Erstellung, - ggf. weitere Services. - [x] T125 [TENANT] Tests: - Unit-Tests für `Tenant::current()`: - INTUNE_TENANT_ID gesetzt → nimmt diesen Tenant, Fehler wenn nicht vorhanden. - INTUNE_TENANT_ID nicht gesetzt → nimmt den mit `is_current = true`. - kein current Tenant → Exception. - Feature-Test für "Make current" in `TenantResource`: - Nach der Action ist genau ein Tenant `is_current = true`, alle anderen `false`. - Optional: Test, dass `local-tenant` nach Cleanup nicht mehr als Kontext gewählt wird. - [x] T130 [UX] Tabellen-Aktionen in Dropdown bündeln (ActionGroup) - In `TenantResource` (Tenants-Liste) die Zeilen-Aktionen refaktorieren: - `View` (optional) direkt anzeigen. - Alle weiteren Aktionen (`Edit`, `Admin consent`, `Verify configuration`, `Deactivate`, `Force delete`) in eine `Tables\Actions\ActionGroup` mit "⋯"-Icon verschieben. - Prüfen, ob in anderen Ressourcen mit vielen Row-Actions (z.B. Backups, RestoreRuns) ebenfalls eine `ActionGroup` sinnvoll ist und diese konsistent einsetzen.