30 KiB
| 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)
Status snapshot
- Done: Phases 1–13 (US1–US4, Settings normalization/display, Highlander, permissions/health, housekeeping/UX, ops)
- Next up: Phase 14 (US8) delegated Intune RBAC onboarding wizard (synchronous)
Phase 1: Setup (Shared Infrastructure)
- T001 [P] [Shared] Confirm Sail/Env ready; ensure
.envhas PostgreSQL settings for Sail and Filament admin user seeded (if missing) indatabase/seeders/. - 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)
- T003 [Shared] Add tenant-aware migrations for
tenants,policies,policy_versions,backup_sets,backup_items,restore_runs,audit_logswith JSONB payloads and FK/time indexes indatabase/migrations/. - T004 [Shared] Create models with relationships and guarded attributes for the above entities in
app/Models/. - T005 [Shared] Implement Graph abstraction contracts (
GraphClientInterface, error mapping, logging hooks) inapp/Services/Graph/with a mockable adapter. - T006 [Shared] Add audit logging service/helper to capture actor, tenant, operation, resources, outcome in
app/Services/Intune/AuditLogger.php. - T007 [Shared] Seed supported policy types/metadata for initial scope in
database/seeders/PoliciesSeeder.phpand ensure tenant scoping.
Phase 3: User Story 1 - Policy inventory listing (Priority: P1)
Tests for User Story 1
- T008 [P] [US1] Feature test for Filament policy listing and filtering (tenant-scoped) in
tests/Feature/Filament/PolicyListingTest.phpusing mocked Graph sync.
Implementation for User Story 1
- T009 [US1] Implement policy sync/import orchestrator using Graph abstraction in
app/Services/Intune/PolicySyncService.php(no direct Graph in UI). - T010 [US1] Create Filament resource/table for policies with filters and metadata columns in
app/Filament/Resources/PolicyResource.php. - T011 [US1] Add command/job to sync policies (queues-ready) in
app/Console/Commands/SyncPolicies.phpand queue job underapp/Jobs/.
Phase 4: User Story 2 - Backup creation and browsing (Priority: P1)
Tests for User Story 2
- 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
-
T013 [US2] Implement backup domain service to assemble snapshots from policies with Graph payload retrieval in
app/Services/Intune/BackupService.php. -
T014 [US2] Add Filament resource/pages for backup sets and items (list/detail) in
app/Filament/Resources/BackupSetResource.php. -
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
BackupItemsRelationManagertoBackupSetResourceshowing 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).
-
T132 [P] [US2] Update/extend
tests/Feature/Filament/BackupCreationTest.phpto cover the new UX flow:- Create BackupSet without policies.
- Add multiple policies via RelationManager action.
- Verify immutable JSONB snapshots + audit log behavior remains correct.
-
T015 [US2] Wire audit logging for backup creation events in
app/Services/Intune/BackupService.phpusingAuditLogger.
Phase 5: User Story 3 - Version history and diff (Priority: P1)
Tests for User Story 3
- T016 [P] [US3] Feature test for version capture and timeline display in
tests/Feature/Filament/PolicyVersionTest.php. - T017 [P] [US3] Unit test for diff generation (human summary + JSON diff) in
tests/Unit/VersionDiffTest.php.
Implementation for User Story 3
- T018 [US3] Implement version capture service with immutable JSONB writes in
app/Services/Intune/VersionService.php. - T019 [US3] Create diff helper (summary + structured JSON) in
app/Services/Intune/VersionDiff.phpand surface in Filament version compare view inapp/Filament/Resources/PolicyVersionResource.php. - 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
- T021 [P] [US4] Feature test for restore preview (change summary, conflicts, selective items) in
tests/Feature/Filament/RestorePreviewTest.php. - 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
- 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. - T024 [US4] Add Filament restore UI (wizard or pages) showing preview, warnings, and confirmation gate in
app/Filament/Resources/RestoreRunResource.php. - T025 [US4] Record restore run lifecycle (start, per-item result, completion) and audit events in
restore_runsandaudit_logs.
Phase 7: User Story 5 - Operational readiness and environments (Priority: P2)
Implementation for User Story 5
- T026 [US5] Document Dokploy staging→production promotion steps, required env vars, queue/worker expectations, and migration safety notes in
README.mdordocs/deploy.md. - 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)
-
T030 [US6] Migration für
tenantsergä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.
- Felder:
-
T031 [US6] Eloquent Model
Tenant:- Beziehungen zu
policies,backup_sets,restore_runs,policy_versions,audit_logsübertenant_id. - Tenant-aware Scopes, falls vorhanden (z. B.
forTenant()).
- Beziehungen zu
-
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“.
-
T033 [US6]
TenantConfigService(oder Erweiterung des Graph-Clients):- Methode
testConnectivity(Tenant $tenant): führt einen einfachen Graph-Call aus (z. B./organizationoder ähnliches) mit den App-Daten des Tenants. - Rückgabe: DTO/Array mit
success,error_message(falls vorhanden).
- Methode
-
T034 [US6] Action „Verify configuration“ in
TenantResource:- Ruft
testConnectivity()auf, - setzt
app_statusauf z. B.ok,erroroderconsent_required, - zeigt eine Filament-Notification mit dem Ergebnis,
- schreibt einen Audit-Log-Eintrag (
tenant.config.verified).
- Ruft
-
T035 [US6] Tenant-Kontext in bestehende Services integrieren:
PolicySyncService,BackupService,RestoreServiceso anpassen, dass sie einenTenantodertenant_idübergeben bekommen und den Graph-Client mit diesem Kontext verwenden.- Sicherstellen, dass alle policy/backup/restore/audit-Datensätze
tenant_idsetzen.
-
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.
- einmal mit erfolgreichem Call →
- Prüfen, dass Audit-Logs geschrieben werden.
-
T037 [US6] Admin-Consent Callback Route
- Route/Controller, der als
redirect_urider Entra-ID-App dient. - Liest
tenant/error/admin_consentaus der Query. - Ordnet das dem richtigen
Tenantzu (z. B. viastate). - Aktualisiert
app_status(z. B.ok,error,consent_denied). - Zeigt eine Bestätigungs-/Fehlerseite für den Admin.
- Route/Controller, der als
Phase 9: User Story 7 - Berechtigungsübersicht & Health-Status (Priority: P1)
-
T040 [US7] Zentrale Permissions-Liste anlegen:
config/intune_permissions.phpmit 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"]).
- technischer Name (z. B.
- Optional:
docs/permissions.mdmit einer Tabelle Feature ↔ Permission als menschlich lesbare Referenz.
-
T041 [US7] Datenmodell für Tenant-Berechtigungen:
- Variante A (einfach): JSONB-Feld
granted_permissionsintenants(Liste von Permission-Keys). - Variante B (feiner): Tabelle
tenant_permissionsmit(tenant_id, permission_key, status, last_checked_at). statusmindestens:ok,missing,error.
- Variante A (einfach): JSONB-Feld
-
T042 [US7] Service
TenantPermissionService:getRequiredPermissions(): array– liest ausconfig/intune_permissions.php.getGrantedPermissions(Tenant $tenant): array– liest aus Graph oder austenant_permissions/granted_permissions.compare(Tenant $tenant): TenantPermissionStatusDTO– liefert pro Permission den Status (ok/missing/error) + Gesamthealth.
-
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“).
- Auf der
-
T044 [US7] Action „Verify configuration“ erweitern:
- Zusätzlich zu
testConnectivity()auchTenantPermissionService::compare()aufrufen. - Ergebnisse in
tenant_permissions/granted_permissionsspeichern. app_statusund Permission-Health aktualisieren.- Audit-Log-Eintrag
tenant.permissions.checkedschreiben.
- Zusätzlich zu
-
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.
- Unit-Tests für
Phase 9b: Scope-Ausrichtung auf neue Objekttypen
- T028 [Scope] Konfiguration
config/tenantpilot.phpauf die inscope.supported_typesdefinierten 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. - 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
-
T060 [HK] BackupSets soft deletable machen:
backup_sets(und ggf.backup_items) Migration/Model mitSoftDeletes(deleted_at).- Sicherstellen, dass RestoreRuns keine gelöschten BackupSets verwenden; Delete nur erlauben, wenn keine zugehörigen RestoreRuns existieren.
-
T061 [HK] Filament-Delete-Action für BackupSets:
- In
BackupSetResourceDelete-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_runsfür das Set existieren. - Nach Delete Audit-Log (
backup.deleted) schreiben.
- In
-
T062 [HK] PolicyVersions soft deletable machen:
policy_versionsMigration/Model umSoftDeleteserweitern.- Alle Queries und Filament-Resources so lassen, dass standardmäßig nur non-deleted Versions angezeigt werden.
-
T063 [HK] Filament-Delete-Action für PolicyVersions:
- In
PolicyVersionResourceDelete-Action hinzufügen (List/Detail). - Confirmation + Audit-Log (
policy_version.deleted).
- In
-
T064 [HK] Tests für Housekeeping:
- Feature-Test: Löschen eines BackupSets ohne RestoreRun →
deleted_atgesetzt, UI-Eintrag weg, Audit-Log vorhanden. - Feature-Test: BackupSet mit RestoreRun → Delete-Action nicht verfügbar.
- Feature-Test: Löschen einer PolicyVersion →
deleted_atgesetzt, nicht mehr in List sichtbar.
- Feature-Test: Löschen eines BackupSets ohne RestoreRun →
Phase 11: Housekeeping – Tenant löschen/deaktivieren
-
T070 [HK] Tenants soft deletable machen:
tenantsModel umSoftDeleteserweitern, Migration ggf.deleted_athinzufügen.- Optional: Feld
status(enum/string:active,archived) einführen; beim Delete aufarchivedsetzen. - Alle Standard-Queries für Tenants nur
active/ nicht gelöscht anzeigen.
-
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_atsetzen (und ggf.status = archived),- Audit-Log
tenant.deletedodertenant.archivedschreiben.
-
T072 [HK] Verhalten für deaktivierte Tenants:
- In
PolicySyncService,BackupService,RestoreServiceprü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).
- In
-
T073 [HK] (Optional) RestoreRuns soft deletable machen:
restore_runsModel/Migration mitSoftDeletes.- Delete-Action in
RestoreRunResourcehinzufügen (nur UI-Aufräumung, keine Folgen für Backups). - Audit-Log
restore_run.deletedschreiben.
-
T074 [HK] Tests für Tenant-Delete:
- Feature-Test: Tenant löschen/deaktivieren → Tenant taucht nicht mehr in Standardlisten auf,
deleted_at(undstatus) 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).
- Feature-Test: Tenant löschen/deaktivieren → Tenant taucht nicht mehr in Standardlisten auf,
Phase 12: Housekeeping – Hard Deletes (Force Delete)
- 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")
-
T120 [TENANT] Migration
add_is_current_to_tenants:- Spalte
is_current(boolean, default false, not null) zutenantshinzufü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.
- Spalte
-
T121 [TENANT] Tenant-Model anpassen:
- Methode
makeCurrent()implementieren:- Transaktion: alle anderen Tenants
is_current = false, dieser Tenantis_current = true.
- Transaktion: alle anderen Tenants
- Methode
static current()implementieren:- Wenn
INTUNE_TENANT_IDgesetzt ist → Tenant mit dieser GUID laden, sonst Exception, wenn nicht gefunden / deaktiviert. - Wenn nicht gesetzt → Tenant mit
is_current = trueundstatus = active(unddeleted_atnull) zurückgeben. - Wenn keiner → Exception “No current tenant selected”.
- Wenn
findOrCreateDefault()deprecaten/entfernen; keine Dummy-Tenants mehr erzeugen.
- Methode
-
T122 [TENANT] Data-Migration / Cleanup:
- Falls mindestens ein Tenant mit
app_status = okexistiert:- einen als
is_current = truemarkieren (z. B. den ersten). local-tenantaufstatus = archived,is_current = falsesetzen.
- einen als
- Sicherstellen, dass
local-tenantnie wieder als aktueller Kontext verwendet wird.
- Falls mindestens ein Tenant mit
-
T123 [TENANT] Filament
TenantResourceUI:- Spalte/Badge für
is_currentin der Liste hinzufügen. - Table-Action "Make current" ergänzen:
- nur sichtbar für aktive Tenants, die nicht
is_currentsind. - ruft
makeCurrent()auf und zeigt Notification.
- nur sichtbar für aktive Tenants, die nicht
- Alte Logik entfernen, die
local-tenantautomatisch als Default nutzt.
- Spalte/Badge für
-
T124 [TENANT] Consumers refactoren:
- Alle Vorkommen von
findOrCreateDefault()suchen und durchTenant::current()(oder expliziten Tenant) ersetzen:- Policy-Sync (Command + Filament-Action),
- BackupSet-Erstellung,
- RestoreRun-Erstellung,
- ggf. weitere Services.
- Alle Vorkommen von
-
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 anderenfalse.
- Nach der Action ist genau ein Tenant
-
Optional: Test, dass
local-tenantnach Cleanup nicht mehr als Kontext gewählt wird. -
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 eineTables\Actions\ActionGroupmit "⋯"-Icon verschieben.
-
Prüfen, ob in anderen Ressourcen mit vielen Row-Actions (z.B. Backups, RestoreRuns) ebenfalls eine
ActionGroupsinnvoll ist und diese konsistent einsetzen.
-
Phase 13: Settings Normalization & Display (Priority: P1)
User Story: US1b - "Admin can open a policy detail page and see the effective Intune settings in a readable, normalized way (not raw JSON dumps)"
Prerequisites: Phase 3 (Policy inventory) complete, PolicyResource and PolicyVersionResource exist
Scope: FR-019 / FR-019a / FR-019b, Edge-001 (malformed snapshots), Edge-002 (@odata.type mismatch), NFR-006 (normalization coverage) per .specify/plan.md dated 2025-12-10.
Tests for User Story 1b
-
T140 [P] [US1b] Unit test for PolicyNormalizer service in
tests/Unit/PolicyNormalizerTest.php:- Test OMA-URI/custom policy transformation → path/value table structure
- Test Settings Catalog policy transformation → flattened key-value structure
- Test standard object transformation → labeled key-value with metadata filtered
- Test edge case: malformed snapshot (keys lost) → returns warning indicator
- Test edge case: @odata.type mismatch detection → returns mismatch flag
-
T141 [P] [US1b] Feature test for Settings section in Policy detail view in
tests/Feature/Filament/PolicySettingsDisplayTest.php:- Create policy with sample JSONB snapshot (OMA-URI, Settings Catalog, standard object)
- Visit Policy detail page via PolicyResource
- Assert "Settings" section exists using Infolist component
- Assert normalized settings are displayed (not raw JSON)
- Assert metadata keys (@odata.type, internal IDs) are hidden
-
T142 [P] [US1b] Feature test for pretty JSON + normalized settings in Version detail in
tests/Feature/Filament/PolicyVersionSettingsTest.php:- Create policy version with JSONB snapshot
- Visit PolicyVersion detail page via PolicyVersionResource
- Assert pretty-printed JSON exists (monospace, copyable format)
- Assert normalized settings section exists below JSON
- Assert both views show same data in different formats
-
T143 [P] [Edge] Feature test for malformed snapshot warning in
tests/Feature/Filament/MalformedSnapshotWarningTest.php:- Create policy version with malformed snapshot (array-only, keys lost)
- Visit detail pages (Policy and PolicyVersion)
- Assert UI warning displayed: "This snapshot may be incomplete or malformed"
- Assert partial settings display attempted with warning badge
-
T144 [P] [Edge] Feature test for @odata.type mismatch flag in
tests/Feature/Filament/ODataTypeMismatchTest.php:- Create policy with @odata.type that doesn't match expected platform/type mapping
- Visit Policy detail page
- Assert warning badge/banner displayed
- Create restore run with mismatched policy → assert validation error prevents execution
Implementation for User Story 1b
-
T145 [US1b] Create PolicyNormalizer service in
app/Services/Intune/PolicyNormalizer.php:- Method
normalize(array $snapshot, string $policyType): array:- Returns
['status' => 'success|warning|error', 'settings' => [...], 'warnings' => [...]]
- Returns
- Implement OMA-URI transformation:
- Extract
omaSettingsarray → convert to[['path' => '...', 'value' => '...']]table - Filter metadata keys (@odata.type, id, version, etc.)
- Extract
- Implement Settings Catalog transformation:
- Flatten nested
settingsorsettingsDeltastructures - Convert to labeled key-value pairs with categories
- Flatten nested
- Implement standard object transformation:
- Extract top-level properties (displayName, description, etc.)
- Group by logical sections (General, Assignment, Advanced)
- Hide Graph-specific metadata
- Add edge case detection:
- Check for malformed snapshots (empty keys, string-only serialization)
- Check for @odata.type mismatch with policy type
- Return warnings array with human-readable messages
- Method
-
T146 [US1b] Add Settings Infolist section to PolicyResource ViewPolicy page in
app/Filament/Resources/PolicyResource/Pages/ViewPolicy.php:- Override
infolist()method to add "Settings" section - Use
Infolist\Components\Sectionfor grouping - Call
PolicyNormalizer::normalize()with policy's latest snapshot - Render normalized settings using:
TextEntryfor simple key-value pairsRepeatableEntryfor tables (OMA-URI paths)KeyValueEntryfor nested structures
- Display warnings if normalization returns warning status
- Add fallback: "No settings available" if snapshot missing/empty
- Override
-
T147 [US1b] Enhance PolicyVersionResource ViewPolicyVersion with pretty JSON component in
app/Filament/Resources/PolicyVersionResource/Pages/ViewPolicyVersion.php:- Add "Snapshot" section with two subsections:
- Raw JSON (collapsible):
- Use
ViewEntrywith custom view blade - Format JSON with
json_encode($snapshot, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) - Wrap in
<pre><code class="language-json">for syntax highlighting - Add "Copy" button using Filament action
- Use
- Normalized Settings:
- Call
PolicyNormalizer::normalize()with version snapshot - Render same structure as T146 (reuse component if possible)
- Call
- Raw JSON (collapsible):
- Handle edge case: malformed snapshot → show warning above both views
- Add "Snapshot" section with two subsections:
-
T148 [US1b] Integrate PolicyNormalizer output into existing views:
- Update
PolicyResourcelist table: add hint column showing "Settings available" badge - Update
PolicyVersionResourcediff view: optionally show normalized diff (if feasible) - Ensure tenant-scoped queries when fetching policies for normalization
- Add caching consideration: normalize on-demand, don't store normalized output
- Update
-
T149 [Edge] Add snapshot validation helper in
app/Services/Intune/SnapshotValidator.php:- Method
validate(array $snapshot): ValidationResult:- Check for empty/null snapshot → return error
- Check for array-only structure (keys lost) → return warning
- Check for string-only serialization → return error
- Check for required Graph keys (@odata.type, displayName) → return warning if missing
- Return structured result with:
isValid: boolwarnings: arrayerrors: array
- Use in PolicyNormalizer before transformation
- Method
-
T150 [Edge] Add @odata.type validator in Policy/BackupItem models:
- Add static method
Policy::validateODataType(array $snapshot, string $expectedType): bool - Check if
$snapshot['@odata.type']matches expected Graph type for policy platform/type - Create mapping array:
'deviceConfiguration' => [ 'windows' => '#microsoft.graph.windowsConfiguration', 'ios' => '#microsoft.graph.iosConfiguration', // etc. ] - Return bool + optional error message
- Call in BackupService before storing backup item
- Call in RestoreService before executing restore (gate check)
- Add static method
-
T151 [Edge] Display warnings in Filament UI for malformed/mismatched data:
- Update
PolicyResource/ViewPolicyinfolist:- Add
Placeholdercomponent with warning icon + message if validation fails - Show "⚠️ This snapshot may be incomplete or malformed" banner
- Add
- Update
PolicyVersionResource/ViewPolicyVersion:- Add warning badge next to "Snapshot" section title if malformed
- Update
RestoreRunResourcepreview/execution:- Add validation step that checks all selected items for @odata.type mismatch
- Show modal with list of problematic items + "Cancel" / "Proceed anyway" options
- Log validation warnings to audit log
- Ensure warnings are visible but not blocking (except for restore execution gate)
- Update
Documentation for Phase 13
-
T152 [US1b] Update
README.mdwith Settings normalization feature:- Add section "Policy Settings Display" explaining:
- Normalized views for better readability
- Supported policy types (OMA-URI, Settings Catalog, standard objects)
- Edge case handling (malformed snapshots, type mismatches)
- Add screenshot/example of Settings section in Policy detail
- Add section "Policy Settings Display" explaining:
-
T153 [US1b] Add inline documentation in PolicyNormalizer:
- PHPDoc blocks for all public methods
- Examples of input/output structures in comments
- Edge case handling notes
Phase 13 Completion Criteria:
- All 14 tasks (T140-T153) completed
- Tests passing for normalization logic and UI display
- Policy detail shows readable Settings section
- Version detail shows pretty JSON + normalized settings
- Edge cases handled with clear warnings
- Constitution Check: still 7/7 ✅ (auditability maintained, safety-first for restore gates)
Phase 14: User Story 8 – Intune RBAC Onboarding Wizard (Delegated, Synchronous)
Scope: FR-023 to FR-030; delegated login and grant run synchronously in Filament (no queue for grant). Optional jobs/CLI only for CHECK/REPORT (no grant).
-
T160 [US8] Add TenantResource ActionGroup entry “Setup Intune RBAC”: visible for active tenants with
app_client_id; sits alongside Admin consent/Verify; guarded by Highlander rules (no deactivated tenants). -
T161 [US8] Wizard UI (Filament): Step 1 preconditions/summary with inputs for Role (default Policy and Profile Manager, warn on Intune Admin), Scope (All devices or scope group), Group mode (create default TenantPilot-Intune-RBAC vs select existing security-enabled group); surface least-privilege warning for production.
-
T162 [US8] Delegated auth step: initiate delegated login for selected tenant; stop with clear error + audit on failure/denied consent; short-lived token only (no storage).
-
T163 [US8] Execution service (synchronous) with audit per step: resolve service principal by
app_client_id; ensure/create security group; add SP as member (idempotent); ensure/create/update Intune role assignment for chosen role/scope using Graph abstraction; no queue usage. -
T164 [US8] Post-check (mandatory): force fresh token acquisition (clear tenant token cache) and run canary reads:
GET /deviceManagement/deviceConfigurations?$top=1GET /deviceManagement/deviceCompliancePolicies?$top=1- Optional:
GET /identity/conditionalAccess/policies?$top=1only if CA features are enabled - Update
app_status/permissions health + write audit entries (start/login/group/member/assignment/verify outcomes).
-
T165 [US8] Tests (Pest, mocked Graph): happy path; rerun idempotent (no duplicates); missing permissions → clear error mapping; scope-limited selection → warning surfaced; delegated login failure path.
-
T166 [US8] Documentation: README note for wizard behavior (delegated, synchronous), least-privilege defaults, audit expectations, and how to rerun safely.
-
T167 [US8-Optional] CLI/Job for CHECK/REPORT only (no grant) to inspect RBAC state; explicitly exclude grant actions from async/queue.
-
T168 [US8] Extend Verify configuration / Health panel to include “Intune RBAC status”:
- Determine whether the configured Enterprise App (service principal) is covered by an Intune role assignment for required scopes (OK / Missing / Error).
- Show actionable message (“Run Setup Intune RBAC”) when missing.
- Persist last_checked_at + short reason code; log audit
tenant.rbac.checked.
-
T169 [US8] Persist RBAC artifacts on Tenant for stable idempotency:
- Add nullable columns to
tenants:rbac_group_id,rbac_role_assignment_id,rbac_role_key,rbac_scope_mode,rbac_scope_id. - Prefer stored IDs on reruns; fall back to discovery only if missing.
- Add migration + model casts; include IDs in audit context (IDs only; no secrets).
- Add nullable columns to