52 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)
Constitution Evidence Ledger (Discovery + Verification)
This section is the canonical evidence record to satisfy Constitution VII (Spec-Driven Development) and IV (Auditability). Each completed phase has: (a) discovery notes, (b) verification commands, (c) where to look in repo/UX.
Evidence: Phases 1–6 (US1–US4 core)
- Discovery: Verified existing Filament resources and services implement tenant scoping and Graph abstraction; restore flow includes preview + confirmation; versions stored immutable JSONB; audits written for critical operations.
- Verification:
./vendor/bin/pest tests/Feature/Filament/PolicyListingTest.php./vendor/bin/pest tests/Feature/Filament/BackupCreationTest.php./vendor/bin/pest tests/Feature/Filament/RestorePreviewTest.php tests/Feature/Filament/RestoreExecutionTest.php./vendor/bin/pest tests/Feature/Filament/PolicyVersionTest.php tests/Unit/VersionDiffTest.php./vendor/bin/pint --dirty
- Manual checks: Filament UI: Policies list/filter, BackupSet detail + items, RestoreRun preview/execution, PolicyVersion view/diff.
Evidence: Phase 13 (US1b settings display + safety gates)
- Discovery: Normalized settings display added; malformed snapshot warnings; @odata.type mismatch gates block restore execution.
- Verification:
./vendor/bin/pest tests/Unit/PolicyNormalizerTest.php./vendor/bin/pest tests/Feature/Filament/PolicySettingsDisplayTest.php./vendor/bin/pest tests/Feature/Filament/PolicyVersionSettingsTest.php./vendor/bin/pint --dirty
Evidence: Phase 14 (US7 RBAC wizard)
- Discovery: RBAC wizard stack present (TenantResource action, delegated auth controller, onboarding service, health panel, migrations, tests).
- Verification:
./vendor/bin/pest tests/Feature/Filament/TenantRbacWizardTest.php./vendor/bin/pest tests/Unit/RbacOnboardingServiceTest.php./vendor/bin/pint --dirty
Evidence: Phase 15 (US8 Graph Contract Registry & Drift Guard)
- Discovery: Contract registry + fallback integrated in Graph client; drift-check command added; type-family tolerant @odata validation added.
- Verification:
./vendor/bin/pest tests/Unit/GraphContractRegistryTest.php tests/Unit/GraphContractFallbackTest.php./vendor/bin/pest tests/Feature/Filament/ODataTypeMismatchTest.php./vendor/bin/pint --dirty
Evidence: Settings Catalog (settingsCatalogPolicy) extensions
- Discovery: Added first-class sync/type + restore hardening + hydration + normalized display improvements.
- Verification:
./vendor/bin/pest tests/Feature/Filament/SettingsCatalogPolicySyncTest.php./vendor/bin/pest tests/Feature/Filament/SettingsCatalogRestoreTest.php./vendor/bin/pest tests/Feature/Filament/SettingsCatalogPolicyHydrationTest.php./vendor/bin/pest tests/Unit/PolicyNormalizerSettingsCatalogTest.php./vendor/bin/pint --dirty
FR → Tasks Traceability Matrix (Explicit)
This matrix makes FR coverage explicit (tooling/audits). Tasks also carry local Implements: tags where most useful.
- FR-001 (Inventory) → T008–T011
- FR-002 (Backups) → T012–T015, T131–T132
- FR-003 (Auditability baseline) → T006, T015, T020, T025
- FR-004 (Versions) → T016–T020
- FR-005 (Diffs) → T017, T019
- FR-006–FR-010 (Restore safety + preview + gating) → T021–T026, T144, T151
- FR-011–FR-018 (Tenant-aware + Graph abstraction + governance basics) → T003–T007, T035, T120–T125
- FR-019.1–FR-019.2 (Settings normalization + edge cases) → T140–T153
- FR-023–FR-030 (RBAC onboarding wizard) → T160–T169
- FR-031–FR-034 (Contract registry + drift guard) → T170–T175
- FR-035 (Rerun restore) → T156
Tasks: TenantPilot v1
Measurable Thresholds (NFR/UX)
These thresholds make qualitative terms measurable and testable.
Payload / Rendering Limits
- Settings table max rows: 1000 rows per rendered table block (truncate with notice).
- Flatten recursion depth: max depth 8; if exceeded, stop and warn.
- Max value length: 500 characters rendered inline; provide copy/full view for longer values.
- Max JSON pretty print: 1 MB rendered inline; above that show “download/copy only”.
Graph Request Limits
- Default Graph request timeout: 30s per request.
- Hydration pagination limit: max 50 pages per subresource; if exceeded → warning + partial snapshot.
Restore Safety
- Dry-run is binary: a restore run is either dry-run or execute; no “default dry-run=true” semantics.
- Type mismatch gate:
@odata.typemismatch MUST block execution (preview may show).
Retention / Housekeeping
- Soft-deleted entities: retained indefinitely unless explicitly force-deleted.
- Audit logs: retained indefinitely by default (configurable later).
“Large payload” definition
- Any snapshot JSONB > 1 MB OR settings table > 1000 rows is considered large and triggers truncation rules above.
FR-019 Settings Normalization & Display
FR-019.1 Normalized Settings View
- Admin can view a policy and policy version with settings rendered in a readable normalized format.
- The normalized output MUST hide Graph metadata keys unless explicitly requested.
FR-019.2 Raw Snapshot + Copy
- Admin can view raw JSON snapshot (pretty-printed where possible) and copy it.
FR-019.3 Edge Handling
- Malformed snapshots MUST show a warning banner and attempt partial rendering.
@odata.typemismatch MUST show a warning; restore execution MUST be blocked.
FR-019.4 Thresholds
-
Rendering and snapshot size limits MUST follow “Measurable Thresholds (NFR/UX)”.
-
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.- Implements: FR-006, FR-008, FR-009
- Implements: FR-021
- Implements: FR-020
- Verified by:
./vendor/bin/pest tests/Feature/Filament/RestorePreviewTest.php tests/Feature/Filament/RestoreExecutionTest.php
-
T145 [US1b] Create PolicyNormalizer service in
app/Services/Intune/PolicyNormalizer.php.- Implements: FR-019.1, FR-019.3
- Verified by:
./vendor/bin/pest tests/Unit/PolicyNormalizerTest.php
-
T160 [US7] Add TenantResource ActionGroup entry “Setup Intune RBAC” …
- Implements: FR-023, FR-024
- Verified by:
./vendor/bin/pest tests/Feature/Filament/TenantRbacWizardTest.php
-
T170 [US8] Add contract registry artifact …
- Implements: FR-031
- Verified by:
./vendor/bin/pest tests/Unit/GraphContractRegistryTest.php(See above for file contents. You may not need to search or read the file again.)
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/.- Implements: FR-013
- 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. -
T176 [Scope][US1] Add Settings Catalog Policies as first-class type (
settingsCatalogPolicy)- Goal: Intune Settings Catalog Policies werden als eigener Typ synchronisiert, angezeigt und sind für Backup/Version/Diff/Preview/Restore (gemäß Matrix) korrekt routbar.
- Why: Settings Catalog Policies liegen in Graph unter
deviceManagement/configurationPolicies(nicht unterdeviceManagement/deviceConfigurations). Aktuell erscheinen sie daher nicht (oder nur unvollständig).
Implementation
-
Config: supported_types erweitern (Single Source of Truth)
- In
config/tenantpilot.php(oder eurem zentralen Type-Registry-File) neuen Typ hinzufügen:key:settingsCatalogPolicyname:Settings Catalog Policygraph_resource:deviceManagement/configurationPoliciescategory:Configurationplatform:windows(oderall+ später per snapshot/@odata ableiten – je nach eurer Modelllogik)
- UI-Label so wählen, dass Admin sofort erkennt: “Settings Catalog” (z. B. Badge/Label).
- In
-
Restore-Matrix erweitern
- In eurer Restore-Konfig (
scope.restore_matrixbzw. config-driven Matrix):settingsCatalogPolicy: backup: full, restore: enabled, risk: medium(optionalmedium-highfalls ihr strenger sein wollt)
- Restore-Warnungen/Badges müssen den neuen Typ korrekt anzeigen.
- In eurer Restore-Konfig (
-
Graph Contract Registry erweitern
- In
config/graph_contracts.phpContract fürsettingsCatalogPolicyhinzufügen:- Resource paths (collection + single item)
allowed_select/allowed_expand(konservativ starten)type_family/ erlaubte@odata.typeWerte für diesen Typ- Create/Update routing (
POST/PATCHwie bei euren anderen Typen)
- Sicherstellen, dass capability fallback (downgrade ohne
$select/$expand) auch hier greift.
- In
-
PolicySyncService erweitern
- Sync-Pipeline muss zusätzlich
deviceManagement/configurationPoliciesabfragen und upserten:policies.type_key = settingsCatalogPolicyexternal_id = Graph iddisplay_name,description,last_modified, etc.
- Tenant-scoping beibehalten.
- No duplicates: gleiche
external_iddarf nicht in zwei Typen landen (Unique/Guard prüfen).
- Sync-Pipeline muss zusätzlich
-
Snapshots / Settings availability
- Für die Spalte/Badge “Settings” (Available/Missing):
- Snapshot-Fetch muss für
settingsCatalogPolicyüber den neuen Endpoint laufen (single item fetch).
- Snapshot-Fetch muss für
- Normalizer/Validator:
@odata.typemuss für diesen Typ als kompatibel erkannt werden (über Contract/type-family).
- Für die Spalte/Badge “Settings” (Available/Missing):
-
UI (Filament)
PolicyResource:- Type/Category Filter um
Settings Catalog Policyerweitern - Optional: Category bleibt
Configuration, aber Typ klarSettings Catalog
- Type/Category Filter um
- Detailseite:
- Normalized Settings anzeigen (wenn euer Normalizer Settings Catalog schon kann)
- sonst mind.: Raw JSON + Hinweis “Settings Catalog normalization pending” (kein silent fail).
-
Permissions/Health
- Verify/Permissions-Liste prüfen, ob für
deviceManagement/configurationPolicieszusätzliche Graph-Permissions nötig sind. - Falls ja:
config/intune_permissions.phpergänzen- Health Panel zeigt fehlende Permission sauber an.
- Verify/Permissions-Liste prüfen, ob für
Tests (Pest)
- Unit:
- Contract Registry erkennt
settingsCatalogPolicy - type-family ok (derived
@odata.typeaccepted) - fallback ok (capability downgrade)
- Contract Registry erkennt
- Feature:
- Policy Sync importiert
configurationPoliciesalssettingsCatalogPolicyund listet sie in der UI - Settings badge wird Available, sobald Snapshot vorhanden ist
- Policy Sync importiert
- Regression:
deviceConfigurationSync bleibt unverändert (keine Vermischung)
Verification
./vendor/bin/pest tests/Feature/Filament/PolicyListingTest.php- ggf. neue Tests:
./vendor/bin/pest tests/Feature/Filament/SettingsCatalogPolicySyncTest.php
- Registry Tests erweitern:
./vendor/bin/pest tests/Unit/GraphContractRegistryTest.php
Acceptance Criteria
- In der Policies-Liste erscheinen Intune Settings Catalog Policies als eigener Typ Settings Catalog Policy.
- Admin kann danach Backup/Version/Preview/Restore (gemäß Matrix) für diesen Typ nutzen.
- Keine Duplikate/Überlappung mit
deviceConfiguration.
-
T177 [US4][Bugfix] Settings Catalog Restore: Graph-Fehlerdetails speichern + PATCH-Payload sanitizen (contract-driven)
- Goal: Restore von
settingsCatalogPolicysoll nicht mehr als generisches400 Graph apply failedenden, sondern:- echte Graph-Fehlerdetails persistieren + im UI sichtbar machen
- beim PATCH nur ein zulässiges Payload senden (read-only/meta Felder raus, whitelist/contract-driven)
- Why:
deviceManagement/configurationPoliciesakzeptiert beim PATCH i. d. R. keinen vollständigen Snapshot → read-only Felder führen zu 400.
Implementation
-
RestoreRun Results verbessern (Fehlerdetails persistieren)
- In
RestoreService(oder zentralem Graph-Apply Catch):- Bei Graph-Exception zusätzlich in
restore_run_item_results/resultsJSON speichern:graph_error_codegraph_error_message- optional (falls vorhanden):
graph_request_id,graph_client_request_id,graph_date
- UI (RestoreRun Detail) soll bei failed Items neben
code/reasonauchgraph_error_messageanzeigen (kurz) + “Details” (expand/collapsible) für request ids.
- Bei Graph-Exception zusätzlich in
- In
-
Contract Registry: update sanitizer für settingsCatalogPolicy
- In
config/graph_contracts.phpbeisettingsCatalogPolicyergänzen:- entweder
update_whitelist(preferred) oderupdate_strip_keys
- entweder
update_whitelistkonservativ starten (nur Felder, die PATCH typischerweise akzeptiert), z. B.:name,description,settings,technologies,platforms,roleScopeTagIdsassignmentsnur wenn Restore wirklich Assignments patcht (sonst weglassen)
- In
GraphContractRegistry(oder äquivalent) Methode bereitstellen:sanitizeUpdatePayload(string $typeKey, array $snapshot): array- Entfernt immer:
id,createdDateTime,lastModifiedDateTime,@odata.*,version,roleScopeTagIds@odata.*, sowie unbekannte Keys
- In
RestoreServicebeim UPDATE/PATCH:- für
settingsCatalogPolicyvor dem Graph PATCH immersanitizeUpdatePayload()verwenden.
- für
- In
-
Graph apply: bessere Diagnose im Audit
- Audit-Event (z. B.
restore.item.failed) soll zusätzlichgraph_error_code+graph_request_identhalten (keine Tokens/payloads).
- Audit-Event (z. B.
Tests (Pest)
- Unit:
GraphContractRegistrysanitizer- Given snapshot mit read-only/meta Feldern → sanitized payload enthält nur whitelist
- Feature: Restore execution für settingsCatalogPolicy mit “bad payload”
- Mock Graph 400 mit error body → RestoreRun result speichert
graph_error_message+ IDs - UI assertion: Fehlermeldung sichtbar (kurz) + Details optional
- Mock Graph 400 mit error body → RestoreRun result speichert
Verification
./vendor/bin/pest tests/Unit/GraphContractRegistryTest.php./vendor/bin/pest tests/Feature/Filament/RestoreExecutionTest.php(oder neuesSettingsCatalogPolicyRestoreTest.php)- Manuell: RestoreRun detail zeigt bei 400 die echte Graph-Fehlermeldung + request-id; kein generisches “apply failed” ohne Details.
Acceptance Criteria
- Restore von
settingsCatalogPolicynutzt PATCH mit sanitiziertem Payload. - Bei Fehlern ist im RestoreRun klar ersichtlich warum (Graph error message), inkl. request ids für Support.
- Goal: Restore von
-
T178 [US4][Bugfix] Settings Catalog Restore: PATCH strikt auf {name, description, settings} begrenzen + Property-Mapping (displayName→name) + case-insensitive strip
- Problem:
- Restore von
settingsCatalogPolicyschlägt mit 400 fehl:- “Invalid patch, attempting to patch property Platforms is not allowed. Valid properties are Name, Description, and Settings.”
- Sanitizer lässt
platforms/Platformsnoch durch und/oder es wirddisplayNamestattnamegepatcht.
- Restore von
- Implementation:
- Contract fix (
config/graph_contracts.php)- Für
settingsCatalogPolicyupdate_whitelistauf exakt:name,description,settings
- Optional:
update_mapdefinieren:displayName→name- (und ggf.
Description/Settingscasing normalisieren)
- Für
- Sanitizer hardening (
app/Services/Graph/GraphContractRegistry.php)- Whitelist/Strip case-insensitive anwenden (z. B.
Platforms,platforms,PlatformSimmer entfernen). - Vor dem Final-Payload:
- Mapping anwenden (displayName→name)
- Blocklist zusätzlich hart erzwingen:
platforms,technologies,templateReference,id,@odata.*,createdDateTime,lastModifiedDateTime
- Ergebnis-Payload für update muss nur
name/description/settingsenthalten.
- Whitelist/Strip case-insensitive anwenden (z. B.
- RestoreService (
app/Services/Intune/RestoreService.php)- Sicherstellen, dass für
settingsCatalogPolicyUpdate-Payload aus Sanitizer kommt (kein “merge back” später). - Bei leerem Payload: als
noop/skippedbehandeln statt PATCH.
- Sicherstellen, dass für
- Contract fix (
- Tests (Pest):
- Unit: Sanitizer entfernt
platforms/Platformszuverlässig + mappingdisplayName→name:tests/Unit/GraphContractRegistryTest.php(erweitern)
- Feature: Restore Settings Catalog erzeugt PATCH ohne platforms und läuft durch (Graph mocked):
tests/Feature/Filament/SettingsCatalogRestoreTest.php(happy-path ergänzen)
- Unit: Sanitizer entfernt
- Verification:
./vendor/bin/pest tests/Unit/GraphContractRegistryTest.php tests/Feature/Filament/SettingsCatalogRestoreTest.php./vendor/bin/pint --dirty
- Acceptance:
- Restore von
settingsCatalogPolicyscheitert nicht mehr anPlatforms. - Results zeigen bei Fehlern weiterhin request-id/client-request-id (bleibt wie T177).
- Restore von
- Problem:
-
T179 [US1b][Scope][settingsCatalogPolicy] Hydrate Settings Catalog “Configuration settings” for all snapshots (versions, backups, previews) and ensure normalized display.
-
Goal: For
settingsCatalogPolicy, the Configuration settings (as seen in the Intune Portal under Configuration settings) must be visible throughout the system. This includes:- being part of the raw JSON in Policy Versions and Backup Snapshots.
- being displayed in the Normalized settings section in a readable list/table format.
- ensuring that Diff, Preview, and Restore operations are based on these detailed settings, not just on general metadata.
-
Why: The base entity for
deviceManagement/configurationPoliciesoften only provides metadata (name,platforms,settingCount, etc.). The actual settings reside in a subresource (e.g.,.../configurationPolicies/{id}/settings). Without hydrating this data, TenantPilot cannot display or work with the most relevant policy details like PIN length or biometric settings.
Implementation
1) Centralize Snapshot Hydration
- In the service responsible for capturing snapshots (e.g., a central
PolicySnapshotService, or withinVersionServiceandBackupService), implement a method to hydratesettingsCatalogPolicydata. - When the
type_keyissettingsCatalogPolicy:GET deviceManagement/configurationPolicies/{id}(Base entity).GET deviceManagement/configurationPolicies/{id}/settings(with proper paging).- Merge the retrieved settings into the snapshot under a consistent key (e.g.,
snapshot['settings'] = [...]).
- This hydration logic MUST be used for creating policy versions, backup items, and restore previews.
2) Enhance PolicyNormalizer
- In
app/Services/Intune/PolicyNormalizer.php, ensure the normalizer can interpret and display thesnapshot['settings']data forsettingsCatalogPolicy. - It should render a readable table/list of the settings, not just metadata.
3) Update UI Components
- Ensure the Policy Detail and Policy Version Detail pages use the hydrated snapshots to display the settings.
- The "Settings available" badge for
settingsCatalogPolicyshould only show "Available" if the snapshot contains the hydratedsettings.
4) Testing
- Feature Test: Create a
SettingsCatalogPolicyHydrationTest.phpthat:- Mocks the Graph API for both the base entity and the
/settingssubresource. - Triggers both a Version Capture and a Backup.
- Asserts that the resulting
PolicyVersionandBackupItemsnapshots contain the hydratedsettings. - Asserts that the Policy Detail and Version Detail pages display the normalized settings correctly.
- Mocks the Graph API for both the base entity and the
- Unit Test:
PolicyNormalizerSettingsCatalogTest.phpshould be updated to verify the rendering of a hydrated snapshot.
Verification
-
./vendor/bin/pest tests/Feature/Filament/SettingsCatalogPolicyHydrationTest.php -
./vendor/bin/pest tests/Unit/PolicyNormalizerSettingsCatalogTest.php -
./vendor/bin/pint --dirty -
T180 - DUPLICATE of T179. Merged into T179.
-
T182 [US1b][settingsCatalogPolicy][UX] Dynamic normalization of Settings Catalog “settings” (generic flatten + readable labels)
-
Goal:
settingsCatalogPolicysoll im Normalized settings Bereich nicht mehr nur “setting -” anzeigen, sondern die hydriertensettings[]generisch (ohne hartes Mapping pro Setting) als verständliche Liste/Tabelle darstellen:- pro Setting: SettingDefinitionId, Instance Type, Value (und ggf. Choice-Value)
- nested
children/ group collections werden rekursiv geflattet - optional: einfache Gruppierung (z. B. nach Prefix der definitionId oder “group root”)
-
Why: Microsoft hat unzählige Settings. Wir brauchen eine dynamische Darstellung, die immer funktioniert – auch für neue Settings, ohne dass wir jedes Setting kennen.
Implementation
1) PolicyNormalizer: settingsCatalogPolicy → generic flatten
- In
app/Services/Intune/PolicyNormalizer.php:- Bei
policyType === settingsCatalogPolicy:- Wenn
snapshot['settings']existiert:- Erzeuge eine Normalizer-Sektion
Settingsals Tabelle/Repeatable:definitionId(string)instanceType(string, aussettingInstance['@odata.type'])value(string/number/bool/json; aussimpleSettingValue.valueoderchoiceSettingValue.value)path(optional): zusammengesetzter Pfad zur Einordnung (z. B. parentDefinitionId > childDefinitionId)
- Implementiere
flattenSettingsCatalogSettingInstances(array $settings): array:- Iteriere
settings[]Einträge - Extrahiere
settingInstance - Unterstütze generisch (mindestens):
deviceManagementConfigurationSimpleSettingInstance→simpleSettingValue.valuedeviceManagementConfigurationChoiceSettingInstance→choiceSettingValue.valuedeviceManagementConfigurationGroupSettingCollectionInstance:- iteriere
groupSettingCollectionValue[] - rekursiv
children[]
- iteriere
- Fallback: wenn unbekannt →
value = json_encode(settingInstance)(kurz/gekürzt)
- Für Rekursion: maximal Depth (z. B. 8) + Schutz gegen Zyklen/zu große Payloads.
- Iteriere
- Optional: wenn Value ein “enum-like” String ist, zusätzlich
displayValue= letzter Token nach_(nur für bessere Lesbarkeit, ohne Semantik zu behaupten).
- Erzeuge eine Normalizer-Sektion
- Wenn
settingsfehlt:- Zeige Banner “Settings not hydrated” (oder “Partial snapshot”) und nur Metadaten.
- Wenn
- Bei
2) Filament View: bessere Darstellung (Table statt “setting -”)
- In
PolicyResource/ViewPolicyundPolicyVersionResource/ViewPolicyVersion:- Stelle sicher, dass die Normalizer-Ausgabe für
Settingsals Tabelle angezeigt wird:- Spalten:
Definition,Type,Value, optionalPath
- Spalten:
- Lange Values: truncated mit “copy” möglich (oder expand/collapse).
- Stelle sicher, dass die Normalizer-Ausgabe für
3) Diff: Fokus auf echte Settings (optional, aber empfohlen)
- In der diff-summary Logik (falls vorhanden):
- Wenn
policyType=settingsCatalogPolicyundsettingsvorhanden:- Summary soll zumindest sagen: “X setting values changed/added/removed”
- (Die JSON diff bleibt weiterhin verfügbar.)
- Wenn
4) Performance & Safety
- Guardrails:
- max rows (z. B. 1000) → danach “truncated”
- value length max (z. B. 500 chars) → danach “truncated”
- depth limit
- Keine Secrets loggen; Normalizer arbeitet nur auf Snapshot JSONB.
Tests (Pest)
Unit: tests/Unit/PolicyNormalizerSettingsCatalogFlattenTest.php
- Input Snapshot mit:
- simpleSettingInstance (int)
- choiceSettingInstance (string)
- groupSettingCollectionInstance mit children (mix)
- Assert:
- Normalizer liefert
SettingsSektion mit mehreren Zeilen - jede Zeile hat
definitionId,instanceType,value - rekursive children werden als eigene Zeilen enthalten
- Unknown instance type fällt auf fallback (json string) ohne crash
- Normalizer liefert
Feature: tests/Feature/Filament/SettingsCatalogPolicyNormalizedDisplayTest.php
- Erzeuge PolicyVersion (settingsCatalogPolicy) mit Snapshot inkl.
settings[] - Öffne Version-Detail und Policy-Detail
- Assert:
- In Normalized settings existiert Sektion “Settings”
- Tabelle enthält erwartete definitionIds und Werte (z. B. minimumpinlength=12, usebiometrics=true)
- Keine “setting -” Platzhalter mehr für diesen Snapshot
Regression
- Bestehende Normalizer-Ausgaben für andere Typen bleiben unverändert.
Verification
./vendor/bin/pest tests/Unit/PolicyNormalizerSettingsCatalogFlattenTest.php./vendor/bin/pest tests/Feature/Filament/SettingsCatalogPolicyNormalizedDisplayTest.php./vendor/bin/pint --dirty
Acceptance Criteria
-
Settings Catalog Policy zeigt im Normalized settings Bereich eine verständliche Settings-Tabelle (DefinitionId/Type/Value) statt generischem “setting -”.
-
Rekursive Group/Children-Settings werden sichtbar (nicht verloren).
-
Darstellung ist dynamisch (kein hardcoded mapping pro Setting).
-
Guardrails verhindern UI/Memory Explosion bei sehr großen Policies.
-
T183 [US1b][UX] Make Policy Version detail readable (Tabs + scroll-safe tables)
-
Goal: Policy Version Detail (und optional Policy Detail) soll für Admins lesbar sein:
- Normalized Settings ist Default/primär sichtbar
- Raw JSON ist weiterhin verfügbar, aber UI zerbricht nicht durch riesige Payloads
- Settings Catalog Tabellen/Paths/IDs werden sauber dargestellt (kein “Textsalat”)
-
Why: Aktuell verdrängt Raw JSON + lange SettingDefinitionIds/Paths die gesamte Seite. Admins sehen nicht mehr “was geändert wurde”, sondern nur Datenmüll.
Implementation
1) UI Layout: Tabs (Normalized default, Raw JSON secondary)
- In
app/Filament/Resources/PolicyVersionResource/Pages/ViewPolicyVersion.php(und optionalViewPolicy.php)- ersetze die aktuelle Darstellung durch Tabs:
- Tab 1: Normalized settings (Default)
- Tab 2: Raw JSON (mit Copy Button)
- optional Tab 3: Diff (falls vorhanden)
- ersetze die aktuelle Darstellung durch Tabs:
- Falls Filament Infolist-Komponenten keine Tabs erlauben:
- nutze eine
ViewEntryund rendere Tabs in Blade viax-filament::tabs.
- nutze eine
2) Raw JSON: Max height + scroll + monospace
- In der Raw JSON Blade-View (z.B.
resources/views/filament/infolists/entries/raw-json.blade.phpoder bestehende View)- Wrap
<pre>mit:class="max-h-[520px] overflow-auto rounded-lg border bg-gray-50 p-3 text-xs font-mono leading-relaxed"
- optional: “Expand” action (modal/slideOver) für Vollbildansicht.
- Wrap
3) Normalized settings tables: horizontal scroll + readable columns
- In
resources/views/filament/infolists/entries/normalized-settings.blade.php- Table container:
class="overflow-x-auto rounded-lg border"
- Table:
class="min-w-[900px] table-fixed"
- Cells:
- Definition/Path:
font-mono text-xs break-all whitespace-normal - Value:
break-words whitespace-normal
- Definition/Path:
- Column widths:
- Definition:
w-[35%], Type:w-[20%], Value:w-[25%], Path:w-[20%]
- Definition:
- Long values: clamp (optional):
line-clamp-2+ “Show more” (details/modal)
- Table container:
4) Optional: Search within Settings (nice-to-have)
- Add a small client-side filter input (Alpine) above settings table:
- filters rows by DefinitionId/Value/Path
- Keep it optional if you want minimal change in v1.
Tests (Pest)
Feature: tests/Feature/Filament/PolicyVersionReadableLayoutTest.php
- Given a
settingsCatalogPolicyversion with longsettingspayload - Assert:
- Tabs render (Normalized + Raw JSON)
- Raw JSON container has max-height/overflow classes
- Normalized table wrapper uses overflow-x
- Page does not contain extremely long unbroken lines without wrappers (basic assertion on classes)
Verification
./vendor/bin/pest tests/Feature/Filament/PolicyVersionReadableLayoutTest.php./vendor/bin/pint --dirty
Acceptance Criteria
-
Policy Version page is readable on normal screen widths:
- Normalized settings are immediately visible without scrolling past raw JSON
- Raw JSON is accessible in second tab and scrolls inside its container
- Settings table does not break layout; long IDs/paths wrap/scroll cleanly
-
No regressions for other policy types (deviceConfiguration/compliance/scripts).
-
T184 [US1b][UX] Use Filament Tables for Settings Catalog settings (Policy + Version) with responsive layout + SlideOver details
-
Goal:
settingsCatalogPolicySettings sollen lesbar, scannbar und bedienbar sein:- als echte Filament Table (nicht “pseudo table” im Infolist-Blade)
- mit truncate + tooltip, horizontal scroll, sticky header
- mit Details (SlideOver) + Copy pro Row
- identisch nutzbar in Policy Detail und Policy Version Detail
-
Why: Die aktuelle Darstellung bricht Layout/Spaltenbreiten (Definition/Type/Value laufen ineinander). Filament Tables lösen genau diese Probleme (fixed layout, responsive, actions, search).
Implementation
1) Introduce reusable Livewire component for Settings Catalog settings table
- New:
app/Livewire/SettingsCatalogSettingsTable.php- Props:
array $settingsRows(aus PolicyNormalizer Output oder direkt aus Snapshotsettings)string $context(policy|version) optional
- Intern: build a Filament
Tablewith columns:- Definition (
TextColumn::make('definition'))wrap(false),searchable(),tooltip(fn($state) => $state),limit(60)
- Type (
TextColumn::make('type'))wrap(false),toggleable(),limit(50),tooltip(...)
- Value (
TextColumn::make('value'))wrap(false),limit(60),tooltip(...)- render
(group)badge for group rows
- Path (
TextColumn::make('path'))toggleable(isToggledHiddenByDefault: true),limit(80),tooltip(...)
- Definition (
- Table config:
paginated([25, 50, 100])(default 25)searchPlaceholder('Search definition/value…')striped(),deferLoading()
- Row Action:
Action::make('details')->label('Details')->icon('heroicon-m-eye')- opens SlideOver
- shows full Definition/Type/Value/Path + optional raw setting JSON (pretty)
- Copy buttons for Definition/Value
- Props:
2) Embed component via ViewEntry in Policy + PolicyVersion detail
- Policy detail (
app/Filament/Resources/PolicyResource/Pages/ViewPolicy.php)- For
settingsCatalogPolicy:- render
SettingsCatalogSettingsTable(instead of current table block) - pass rows from Normalizer (
normalize()should expose a stable rows array)
- render
- For
- PolicyVersion detail (
app/Filament/Resources/PolicyVersionResource/Pages/ViewPolicyVersion.php)- same embedding for
settingsCatalogPolicy
- same embedding for
Rule: Nur für
settingsCatalogPolicyauf Table UI umstellen. Andere Typen bleiben Infolist/KeyValue.
3) Tailwind/Filament styling guardrails (no layout break)
- Ensure table container is responsive:
- wrap table in
div class="overflow-x-auto" - set columns non-wrapping by default (truncate)
- wrap table in
- Sticky header:
- enable sticky header in table (Filament supports sticky header via table wrapper CSS; if needed add a small CSS utility class in your Filament theme)
4) Normalizer output contract (stable)
- Ensure
PolicyNormalizerreturns for settingsCatalogPolicy:['settings_table' => ['columns' => [...], 'rows' => [...]]]- rows fields:
definition,type,value,path,raw(optional)
- Table uses rows, not parsing raw snapshot again (single source).
Tests (Pest)
Feature: tests/Feature/Filament/SettingsCatalogSettingsTableRenderTest.php (new)
- Create a policy + version with hydrated
settings - Visit Policy detail and PolicyVersion detail
- Assert:
- table headers visible (Definition/Type/Value)
- at least one known definition appears
- “Details” action exists
Unit (optional): tests/Unit/PolicyNormalizerSettingsCatalogRowsTest.php
- Given snapshot with nested settings instances
- Assert normalizer returns rows with
definition/type/value/path
Verification
./vendor/bin/pest tests/Feature/Filament/SettingsCatalogSettingsTableRenderTest.php./vendor/bin/pest tests/Unit/PolicyNormalizerSettingsCatalogRowsTest.php./vendor/bin/pint --dirty
Acceptance Criteria
-
In Policy Detail und Policy Version Detail sind Settings Catalog Settings als Filament Table sichtbar (lesbar, nicht überlappend).
-
Lange Werte sind truncated aber per Tooltip/Details vollständig erreichbar.
-
Pro Row gibt es Details SlideOver + Copy.
-
Kein Layout-Bruch auf typischen Screenbreiten (Laptop/FullHD).
-
T185 [UX][US1b][settingsCatalogPolicy] Make Settings Catalog settings readable (label/value parsing + table ergonomics)
-
Goal: Settings Catalog Policies sollen im Policy/Version Detail für Admins lesbar sein, ohne dass wir “alle Settings kennen müssen”.
- Tabelle zeigt sprechende Bezeichnung + kompakte Werte
- Lange IDs bleiben verfügbar (Tooltip/Copy/Details), aber dominieren nicht die UI
-
Why: Aktuell sind
definitionIdundchoiceSettingValue.valueso lang, dass sie in der Tabelle abgeschnitten werden und der Admin weder Setting noch Wert versteht.
Implementation
1) Presentation layer: generate human-friendly labels (no registry needed)
- Add helper in
PolicyNormalizer(oder kleinerSettingsCatalogPresenter):labelFromDefinitionId(string $definitionId): string- remove common prefixes:
device_vendor_msft_,user_vendor_msft_,policy_config_,admx_ - replace
_with spaces - keep last 2–4 segments if string is huge
- replace
{tenantid}with{tenant}
- remove common prefixes:
- Output example:
user_vendor_msft_policy_config_admx_desktop_nomydocumentsico...→Desktop: No My Documents Icon(heuristic), fallback: last segments nicely spaced
Heuristic only. If no good split possible, fallback to “last segments” label.
2) Parse values into a short “effective value”
- Implement
valuePreview(array $settingInstance): string:- For
SimpleSettingValue: return scalar (12,0,true/false) - For
ChoiceSettingValue.value: return last token after last_OR map known boolean patterns:- suffix
_true/_false→True/False - suffix
_0/_1for allowed/blocked → show0/1but also tagAllowed/Blockedif detectable
- suffix
- For group instances: show
(group)and put children into details view only
- For
3) Improve table ergonomics (Filament Table / Livewire)
- In
SettingsCatalogSettingsTable:- Columns:
- Setting (human label) + small muted secondary line showing truncated definitionId
- Value (valuePreview)
- Type (badge: Choice/Simple/Group)
- Optional: Path (toggleable, hidden by default)
- Add:
->searchable()should search both label + raw definitionId + raw value->wrap()/->limit()for long strings- tooltips showing full definitionId/value on hover
- “Copy” icon action in row details (SlideOver) for Definition + Raw JSON
- Ensure horizontal scroll only inside table container:
- wrapper
divwithoverflow-x-auto+max-w-full - table layout fixed where possible (
table-fixed) to prevent column blowouts
- wrapper
- Columns:
4) Keep Raw JSON accessible but not primary
- In PolicyVersion view:
- Put Raw JSON into collapsible section or separate tab.
- Normalized Settings tab becomes default for settingsCatalogPolicy.
Tests (Pest)
Unit: tests/Unit/SettingsCatalogPresenterTest.php
- labelFromDefinitionId() produces readable output and stable fallback
- valuePreview() returns expected previews for:
- choice true/false, numeric, group
Feature: tests/Feature/Filament/SettingsCatalogSettingsTableUsabilityTest.php
- Render policy detail with one very long definition + long choice value
- Assert:
- label column shows shortened readable label (not the full raw string)
- value column shows preview (e.g.,
True,12,Never) - details slide-over contains full raw definition/value + copy UI
Verification
./vendor/bin/pest tests/Unit/SettingsCatalogPresenterTest.php tests/Feature/Filament/SettingsCatalogSettingsTableUsabilityTest.php./vendor/bin/pint --dirty
Acceptance Criteria
- In Policy Detail, Settings table shows:
- Readable Setting name (not a cut-off vendor string)
- Readable Value preview (True/False/12/etc.)
- Full raw definitionId and raw value remain accessible via tooltip and SlideOver + copy button.
- No layout overflow/broken columns on common laptop viewport widths.
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).- Implements: FR-001
- T010 [US1] Create Filament resource/table for policies with filters and metadata columns in
app/Filament/Resources/PolicyResource.php.- Implements: FR-001
- 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.- Implements: FR-002
- Implements: FR-020
-
T014 [US2] Add Filament resource/pages for backup sets and items (list/detail) in
app/Filament/Resources/BackupSetResource.php.- Implements: FR-002
-
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.- Implements: FR-003
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.- Implements: FR-004
- 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.- Implements: FR-005
- 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.- Implements: FR-022
- T025 [US4] Record restore run lifecycle (start, per-item result, completion) and audit events in
restore_runsandaudit_logs.- Implements: FR-007
- T156 [US4][UX] Add “Rerun” action to RestoreRun row actions (ActionGroup): creates a new RestoreRun cloned from selected run (same backup_set_id, same selected items, same dry_run flag), enforces same safety gates/confirmations as original execution path, writes audit event restore_run.rerun_created with source_restore_run_id.
- Implements: FR-035
- Implements: FR-035
Phase 7: User Story 5 - Operational readiness and environments (Priority: P2)
- T026 [US5] Document Dokploy staging→production promotion steps, required env vars, queue/worker expectations, and migration safety notes in
README.mdordocs/deploy.md.- Implements: FR-010
- Implements: FR-011
- Implements: FR-012
- 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: Tenant Management (Tenant hinzufügen, App-Setup, Verify) (Priority: P1)
Hinweis: Diese Phase ist “Tenant Management” und nicht US6, damit US6 sauber “Permissions/Health” bleibt.
- T030 [TENANT] Migration für
tenantsergänzen/prüfen (name, tenant_id, domain, app_client_id, app_status, app_notes, timestamps).- Implements: FR-011
- Implements: FR-014
- Implements: FR-015
- Implements: FR-016
- Implements: FR-017
- Implements: FR-018
- T031 [TENANT] Eloquent Model
Tenant(Beziehungen, tenant-aware scopes). - T032 [TENANT] Filament
TenantResource(list/create/edit/detail; Actions: Open in Entra, Copy consent URL optional). - T033 [TENANT]
TenantConfigService/ Graph connectivity check. - T034 [TENANT] Action „Verify configuration“ + Audit (
tenant.config.verified). - T035 [TENANT] Tenant-Kontext in Policy/Backup/Restore/Audit Services (tenant_id überall setzen).
- Implements: FR-009
- T036 [TENANT] Feature-Test
TenantSetupTest(ok/error + Audit). - T037 [TENANT] Admin-Consent Callback Route (state → tenant mapping, status update, UI page).
Phase 9: User Story 6 - Berechtigungsübersicht & Health-Status (Priority: P1)
- T040 [P] [US6] Zentrale Permissions-Liste
config/intune_permissions.php(+ optionaldocs/permissions.md).- Implements: FR-006
- Implements: FR-008
- T041 [US6] Datenmodell Tenant-Berechtigungen (JSONB
granted_permissionsodertenant_permissionsTabelle; status ok/missing/error). - T042 [US6]
TenantPermissionService(required, granted, compare DTO). - T043 [US6] Tenant-Detail UI Panel „Permissions“ (required list + status).
- T044 [US6] Verify erweitern: compare + persist + Audit
tenant.permissions.checked.- Implements: FR-006
- Implements: FR-008
- T045 [US6] Tests: Unit compare + Feature Tenant detail status + Verify updates.
Phase 9b: Scope-Ausrichtung auf neue Objekttypen
- T028 [Scope]
config/tenantpilot.phpaufscope.supported_typeserweitern; single source for sync/backup/restore. - T029 [Scope] Filament-UI an neue Typen anpassen (Filter/Grouping + Restore-Level Hinweise).
Phase 10: Housekeeping – Delete-Funktionen für Backups & Versions
- T060 [HK] BackupSets soft deletable + Guard gegen RestoreRuns.
- T061 [HK] Filament Delete BackupSets + Confirmation + Audit (
backup.deleted) + Guard. - T062 [HK] PolicyVersions soft deletable + Queries/Resources default non-deleted.
- T063 [HK] Filament Delete PolicyVersions + Audit (
policy_version.deleted). - T064 [HK] Tests Housekeeping (BackupSet delete ok/block + PolicyVersion delete).
Phase 11: Housekeeping – Tenant löschen/deaktivieren
- T070 [HK] Tenants soft deletable (optional status active/archived).
- T071 [HK] Tenant deactivate/archive action + Audit (
tenant.archived). - T072 [HK] Block operations for deactivated tenants (sync/backup/restore early fail).
- T073 [HK] RestoreRuns soft deletable (optional) + Audit (
restore_run.deleted). - T074 [HK] Tests Tenant delete/deactivate behavior + clear errors, no Graph calls.
Phase 12: Housekeeping – Hard Deletes (Force Delete)
- T075 [HK] Force-Delete-Actions (only in trashed; guards; audit before delete) + tests.
Phase 12b: Single current tenant ("Highlander")
-
T120 [TENANT] Migration add
is_current+ partial unique index. -
T121 [TENANT] Tenant::current() + makeCurrent() + remove implicit defaults.
-
T122 [TENANT] Data cleanup (mark one current; archive local-tenant).
-
T123 [TENANT] Filament UI badge + “Make current” action.
-
T124 [TENANT] Consumers refactor to
Tenant::current()or explicit tenant. -
T125 [TENANT] Tests for current selection + “Make current”.
-
T130 [UX] Tabellen-Aktionen in Dropdown bündeln (ActionGroup) in TenantResource (+ optional others).
Phase 13: Settings Normalization & Display (Priority: P1)
-
T140 [P] [US1b] Unit test PolicyNormalizer.
-
T141 [P] [US1b] Feature test Policy Settings section.
-
T142 [P] [US1b] Feature test Version detail pretty JSON + normalized.
-
T143 [P] [Edge] Feature test malformed snapshot warning.
-
T144 [P] [Edge] Feature test @odata.type mismatch flag + restore exec block.
-
T145 [US1b] PolicyNormalizer service.
- Implements: FR-019
-
T146 [US1b] Settings infolist in PolicyResource.
-
T147 [US1b] PolicyVersion view pretty JSON + normalized.
-
T148 [US1b] Integrations (list badge, optional diff enhancements, tenant scoping).
-
T149 [Edge] SnapshotValidator helper.
-
T150 [Edge] @odata.type validator (policy/backup/restore gates).
-
T151 [Edge] UI warnings + restore execution gating (preview may show).
-
T152 [US1b] README docs for settings display.
-
T153 [US1b] Inline docs in PolicyNormalizer.
Phase 14: User Story 7 – 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 [US7] Add TenantResource ActionGroup entry “Setup Intune RBAC”: visible for active tenants with
app_client_id; sits alongside Admin consent/Verify; guarded by Highlander rules. Verified by:./vendor/bin/pest tests/Feature/Filament/TenantRbacWizardTest.php.- Implements: FR-023
- Implements: FR-025
- Implements: FR-026
- Implements: FR-027
- Implements: FR-028
- Implements: FR-029
-
T161 [US7] Wizard UI (Filament): Step 1 preconditions/summary with inputs for Role, Scope, Group mode; least-privilege warnings; review screen of planned changes. Verified by:
./vendor/bin/pest tests/Feature/Filament/TenantRbacWizardTest.php. -
T162 [US7] Delegated auth step: initiate delegated login; stop with clear error + audit on failure; token not persisted. Verified by:
./vendor/bin/pest tests/Feature/Filament/TenantRbacWizardTest.php. -
T163 [US7] Execution service (sync) with audit per step: resolve SP by
app_client_id; ensure/create security group (securityEnabled=true); add SP as member (idempotent); ensure/create/update Intune role assignment; persist IDs on tenant for idempotency. Verified by:./vendor/bin/pest tests/Feature/Filament/TenantRbacWizardTest.php.- Implements: FR-030
-
T164 [US7] Post-check (mandatory): clear app token cache / force fresh token acquisition and run canary reads:
GET /deviceManagement/deviceConfigurations?$top=1GET /deviceManagement/deviceCompliancePolicies?$top=1- optional CA canary only if CA features enabled
- update tenant health + audit verify outcome.
Verified by:
./vendor/bin/pest tests/Feature/Filament/TenantRbacWizardTest.php.
-
T165 [US7] Tests (Pest, mocked Graph): happy path; rerun idempotent; missing permissions error mapping; scope-limited warning; delegated login failure path; non-security-enabled group failure. Verified by:
./vendor/bin/pest tests/Feature/Filament/TenantRbacWizardTest.php. -
T166 [US7] Documentation: README note for wizard behavior (delegated, sync), least-privilege defaults, audit expectations, rerun safety. Verified by: manual review of README.md update.
-
T167 [US7-Optional] CLI/Job for CHECK/REPORT only (no grant), explicitly exclude async grant.
-
T168 [US7] Extend Verify configuration / Health panel to include “Intune RBAC status” (OK/Missing/Error) + CTA “Run Setup Intune RBAC”, persist last_checked_at + reason; Audit
tenant.rbac.checked. Verified by:./vendor/bin/pest tests/Feature/Filament/TenantRbacWizardTest.php.- Implements: FR-024
-
T169 [US7] Persist RBAC artifacts on Tenant for idempotency:
- migration add nullable columns:
rbac_group_id,rbac_group_name,rbac_role_assignment_id,rbac_role_key,rbac_scope_mode,rbac_scope_id - prefer stored IDs on reruns; discovery fallback.
Verified by:
./vendor/bin/pest tests/Feature/Filament/TenantRbacWizardTest.php.
- migration add nullable columns:
Phase 15: User Story 8 – Graph Contract Registry & Drift Guard
Scope: FR-031 to FR-034; contract registry per type, type-family handling, capability fallbacks, drift checks.
- T170 [US8] Add contract registry artifact (e.g.,
config/graph_contracts.php) capturing per supported type: resource paths, allowed$select/$expand, allowed @odata.type family, create/update methods, id field, hydration strategy. Verified by: manual review. - T171 [US8] Implement registry service + integration in Graph client to enforce allowed capabilities and downgrade on capability errors (retry without expand/select), logging warnings/audit entries. Verified by:
./vendor/bin/pest tests/Unit/GraphContractFallbackTest.php.- Implements: FR-032
- T172 [US8] Implement type-family handling so derived @odata.type within a family routes correctly for preview/restore (no odata_mismatch) while still blocking unknown types. Verified by:
./vendor/bin/pest tests/Feature/Filament/ODataTypeMismatchTest.php.- Implements: FR-033
- T173 [US8] Add verification command
php artisan graph:contract:check(staging/CI) to probe endpoints, detect drift, and emit actionable diff/log output; make prod opt-in/guarded. Verified by: manual review.- Implements: FR-034
- T174 [US8] Tests (Pest/unit/integration): registry lookups, fallback selection on capability errors, derived type acceptance, drift-check command behavior. Verified by:
./vendor/bin/pest tests/Unit/GraphContractRegistryTest.php tests/Unit/GraphContractFallbackTest.php. - T175 [US8] Documentation: describe registry format/update process, fallback behavior, and how/when to run
graph:contract:check. Verified by: manual review of README update.