# 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 constitution’s 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 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 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`
