## Summary - add Intune RBAC role definitions and role assignments as foundation-backed inventory, backup, and versioned snapshot types - add RBAC-specific normalization, coverage, permission-warning handling, and preview-only restore safety behavior across existing Filament and service surfaces - add spec 127 artifacts, contracts, audits, and focused regression coverage for inventory, backup, versioning, verification, and authorization behavior ## Testing - `vendor/bin/sail bin pint --dirty --format agent` - `vendor/bin/sail artisan test --compact tests/Feature/Inventory/InventorySyncServiceTest.php tests/Feature/Filament/InventoryCoverageTableTest.php tests/Feature/FoundationBackupTest.php tests/Feature/Filament/RestoreExecutionTest.php tests/Feature/RestoreUnknownPolicyTypeSafetyTest.php tests/Unit/GraphContractRegistryTest.php tests/Unit/FoundationSnapshotServiceTest.php tests/Feature/Verification/IntuneRbacPermissionCoverageTest.php tests/Unit/IntuneRoleDefinitionNormalizerTest.php tests/Unit/IntuneRoleAssignmentNormalizerTest.php` ## Notes - tasks in `specs/127-rbac-inventory-backup/tasks.md` are complete except `T041`, which is the documented manual QA validation step Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #155
9.0 KiB
9.0 KiB
Research — Intune RBAC Inventory & Backup v1 (127)
Decisions
Decision 1 — Register RBAC as two new foundation types
- Chosen: Add
intuneRoleDefinitionandintuneRoleAssignmenttoconfig/tenantpilot.phpunderfoundation_types, with categoryRBAC, deterministic risk metadata, andrestore: preview-only. - Rationale: Existing foundation support is config-driven.
InventorySyncService,BackupService,InventoryPolicyTypeMeta,BackupItem::isFoundation(), and Filament coverage/backup surfaces already derive classification and restore/risk semantics fromconfig('tenantpilot.foundation_types'). - Alternatives considered:
- Model RBAC as supported policy types rather than foundations. Rejected because the product and existing architecture already distinguish governance foundations from policy families.
- Merge definitions and assignments into a single RBAC type. Rejected because payloads, risks, and future compare/restore semantics differ materially.
Decision 2 — Use new inventory-grade Graph contract keys for RBAC
- Chosen: Introduce inventory-grade contract keys named
intuneRoleDefinitionandintuneRoleAssignmentinconfig/graph_contracts.php, while leavingdirectoryRoleDefinitionsandrbacRoleAssignmentintact for current health/onboarding usage. - Rationale: The repo already contains narrowly scoped RBAC keys used by onboarding and health services.
GraphContractRegistry::directoryRoleDefinitionsListPath()andGraphContractRegistry::rbacRoleAssignmentListPath()are referenced byRbacOnboardingServiceandRbacHealthService. Reusing those keys for full-fidelity snapshot capture would silently change behavior in existing flows. - Alternatives considered:
- Expand the existing keys in place. Rejected because it couples inventory fidelity requirements to onboarding and health-check behavior, increasing regression risk.
- Hardcode RBAC endpoints in feature services. Rejected because it violates the constitution’s single contract path to Graph.
Decision 3 — Reuse existing inventory sync persistence through InventoryItem
- Chosen: Keep RBAC inventory in
InventoryItemrows created byApp\Services\Inventory\InventorySyncService, using sanitizedmeta_jsonbonly. - Rationale: Existing foundation inventory already works this way.
InventorySyncServicemergessupported_policy_typesandfoundation_types, fetches by configured type, and stores a tenant-scopedInventoryItemkeyed bytenant_id + policy_type + external_id. Existing tests already verify this behavior forroleScopeTag. - Alternatives considered:
- Add dedicated RBAC inventory tables. Rejected because the repo already has a generic inventory store and the feature does not require custom query semantics.
- Persist full RBAC payloads in inventory. Rejected because the constitution requires metadata-only inventory and immutable payload storage elsewhere.
Decision 4 — Reuse foundation backup capture for immutable RBAC payloads
- Chosen: Reuse
App\Services\Intune\FoundationSnapshotServiceandApp\Services\Intune\BackupService::captureFoundations()for RBAC payload capture, but create synthetic tenant-scopedPolicyanchors plus immutablePolicyVersionrows forintuneRoleDefinitionandintuneRoleAssignment. - Rationale: RBAC still enters the system through the existing foundation capture path and
include_foundations, but linkedPolicyVersionrows let backup-set detail and policy-version detail reuse the same immutable review surface. This preserves foundation semantics without introducing a separate RBAC snapshot model. - Alternatives considered:
- Keep RBAC backup items permanently detached from
PolicyVersion. Rejected after implementation because it would leave backup-set detail and version detail on different immutable paths and make readable RBAC history inconsistent with the product goal. - Create a separate RBAC snapshot model. Rejected because it duplicates current foundation backup behavior and would fragment restore/preview surfaces.
- Keep RBAC backup items permanently detached from
Decision 5 — Add dedicated RBAC normalizers through the existing policy normalizer registry
- Chosen: Implement
IntuneRoleDefinitionNormalizerandIntuneRoleAssignmentNormalizerasPolicyTypeNormalizerimplementations and register them through thepolicy-type-normalizerstag inAppServiceProvider. - Rationale:
PolicyNormalizeralready resolves type-specific normalizers this way. This keeps RBAC readable in version and backup surfaces while preserving the repo’s normalization strategy for future diffing. - Alternatives considered:
- Use the default normalizer only. Rejected because raw RBAC payloads are not sufficiently readable for governance and audit use.
- Add ad-hoc formatting inside Filament screens. Rejected because it would bypass the centralized normalization path and make future diffs inconsistent.
Decision 6 — Handle missing DeviceManagementRBAC.Read.All as a verification warning and capture failure reason
- Chosen: Reuse existing provider reason and verification cluster patterns so missing RBAC read permission results in a clear warning or non-blocking failure reason rather than a pipeline crash.
- Rationale: The repo already has soft-permission patterns in
TenantPermissionCheckClusters,ProviderReasonCodes,RunFailureSanitizer, and fail-soft Graph helper code such asScopeTagResolver. Existing RBAC health and onboarding services also route reason codes throughProviderReasonCodes. - Alternatives considered:
- Let Graph 403 bubble as a generic exception. Rejected because it produces opaque failures and weak operator guidance.
- Mark missing delegated or application permission as hard-blocking for all inventory. Rejected because the spec explicitly requires graceful handling and partial continuity for unrelated inventory types.
Decision 7 — Preserve preview-only restore by configuration, not special-case UI logic
- Chosen: Rely on existing restore-mode evaluation through config metadata and current restore-risk checks rather than adding RBAC-only branches.
- Rationale:
InventoryPolicyTypeMeta,RestoreRiskChecker,RestoreService, and badge tests already treatpreview-onlyas a first-class mode. Adding the new foundation types withrestore: preview-onlylets existing restore guards and UI labels work consistently. - Alternatives considered:
- Add RBAC-specific UI suppression logic. Rejected because it is redundant and risks diverging from the canonical restore-mode rules.
Decision 8 — Treat Entra admin role reporting as a normalization reference, not a storage model
- Chosen: Reuse payload-shaping ideas from
EntraAdminRolesReportServicefor readable role-definition/assignment summaries, but keep Intune RBAC data inside the inventory/foundation architecture. - Rationale: The Entra reporting code already demonstrates stable role/assignment mapping and fingerprint-friendly sorting, which is useful for readable RBAC normalization. However, it is a separate feature and not a replacement for tenant inventory or backup storage.
- Alternatives considered:
- Reuse Entra role reporting models directly. Rejected because the domain is different and the feature scope is Intune RBAC foundations, not Entra directory roles.
Resolved Unknowns
-
Unknown: Whether existing RBAC contract keys could be reused safely.
- Resolved: No. Existing keys are already consumed by RBAC onboarding and health flows and should stay backward compatible.
-
Unknown: Whether foundations already had an immutable full-payload path.
- Resolved: Yes.
FoundationSnapshotService+BackupService::captureFoundations()already persists full foundation payloads asBackupItemrows.
- Resolved: Yes.
-
Unknown: Whether inventory stores full payloads or sanitized metadata.
- Resolved: Inventory stores sanitized metadata only in
InventoryItem.meta_jsonb; full payloads belong in backup/version artifacts.
- Resolved: Inventory stores sanitized metadata only in
-
Unknown: Whether preview-only restore needs a new specialized guard.
- Resolved: No. Existing config-driven restore-mode evaluation already supports
preview-onlyacross coverage and restore paths.
- Resolved: No. Existing config-driven restore-mode evaluation already supports
Repo Evidence
- Foundation registration baseline:
config/tenantpilot.php - Existing RBAC contract keys:
config/graph_contracts.php - Contract helper usage in onboarding and health:
app/Services/Graph/GraphContractRegistry.php,app/Services/Intune/RbacOnboardingService.php,app/Services/Intune/RbacHealthService.php - Inventory persistence path:
app/Services/Inventory/InventorySyncService.php - Coverage payload model:
app/Support/Inventory/InventoryCoverage.php - Foundation payload backup path:
app/Services/Intune/FoundationSnapshotService.php,app/Services/Intune/BackupService.php - Normalizer registry:
app/Services/Intune/PolicyNormalizer.php,app/Providers/AppServiceProvider.php - Permission-warning patterns:
app/Support/Verification/TenantPermissionCheckClusters.php,app/Support/Providers/ProviderReasonCodes.php,app/Services/Graph/ScopeTagResolver.php