TenantAtlas/specs/127-rbac-inventory-backup/research.md
ahmido c6e7591d19 feat: add Intune RBAC inventory and backup support (#155)
## 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
2026-03-09 10:40:51 +00:00

9.0 KiB
Raw Permalink Blame History

Research — Intune RBAC Inventory & Backup v1 (127)

Decisions

Decision 1 — Register RBAC as two new foundation types

  • Chosen: Add intuneRoleDefinition and intuneRoleAssignment to config/tenantpilot.php under foundation_types, with category RBAC, deterministic risk metadata, and restore: 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 from config('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 intuneRoleDefinition and intuneRoleAssignment in config/graph_contracts.php, while leaving directoryRoleDefinitions and rbacRoleAssignment intact for current health/onboarding usage.
  • Rationale: The repo already contains narrowly scoped RBAC keys used by onboarding and health services. GraphContractRegistry::directoryRoleDefinitionsListPath() and GraphContractRegistry::rbacRoleAssignmentListPath() are referenced by RbacOnboardingService and RbacHealthService. 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 constitutions single contract path to Graph.

Decision 3 — Reuse existing inventory sync persistence through InventoryItem

  • Chosen: Keep RBAC inventory in InventoryItem rows created by App\Services\Inventory\InventorySyncService, using sanitized meta_jsonb only.
  • Rationale: Existing foundation inventory already works this way. InventorySyncService merges supported_policy_types and foundation_types, fetches by configured type, and stores a tenant-scoped InventoryItem keyed by tenant_id + policy_type + external_id. Existing tests already verify this behavior for roleScopeTag.
  • 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\FoundationSnapshotService and App\Services\Intune\BackupService::captureFoundations() for RBAC payload capture, but create synthetic tenant-scoped Policy anchors plus immutable PolicyVersion rows for intuneRoleDefinition and intuneRoleAssignment.
  • Rationale: RBAC still enters the system through the existing foundation capture path and include_foundations, but linked PolicyVersion rows 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.

Decision 5 — Add dedicated RBAC normalizers through the existing policy normalizer registry

  • Chosen: Implement IntuneRoleDefinitionNormalizer and IntuneRoleAssignmentNormalizer as PolicyTypeNormalizer implementations and register them through the policy-type-normalizers tag in AppServiceProvider.
  • Rationale: PolicyNormalizer already resolves type-specific normalizers this way. This keeps RBAC readable in version and backup surfaces while preserving the repos 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 as ScopeTagResolver. Existing RBAC health and onboarding services also route reason codes through ProviderReasonCodes.
  • 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 treat preview-only as a first-class mode. Adding the new foundation types with restore: preview-only lets 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 EntraAdminRolesReportService for 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 as BackupItem rows.
  • 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.
  • Unknown: Whether preview-only restore needs a new specialized guard.

    • Resolved: No. Existing config-driven restore-mode evaluation already supports preview-only across coverage and restore paths.

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