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

83 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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`