TenantAtlas/specs/128-rbac-baseline-compare/research.md
ahmido ef41c9193a feat: add Intune RBAC baseline compare support (#156)
## Summary
- add Intune RBAC Role Definition baseline scope support, capture references, compare classification, findings evidence, and landing/detail UI labels
- keep Intune Role Assignments explicitly excluded from baseline compare scope, summaries, findings, and restore messaging
- add focused Pest coverage for baseline scope selection, capture, compare behavior, recurrence, isolation, findings rendering, inventory anchoring, and RBAC summaries

## Verification
- `vendor/bin/sail bin pint --dirty --format agent`
- `vendor/bin/sail artisan test --compact tests/Unit/Inventory/InventoryPolicyTypeMetaBaselineSupportTest.php tests/Unit/Baselines/BaselinePolicyVersionResolverTest.php tests/Unit/Baselines/BaselineScopeTest.php tests/Unit/IntuneRoleDefinitionNormalizerTest.php tests/Feature/Baselines/BaselineCaptureRbacRoleDefinitionsTest.php tests/Feature/Baselines/BaselineCompareRbacRoleDefinitionsTest.php tests/Feature/Baselines/BaselineCompareDriftEvidenceContractRbacTest.php tests/Feature/Baselines/BaselineCompareCoverageGuardTest.php tests/Feature/Baselines/BaselineCompareCrossTenantMatchTest.php tests/Feature/Baselines/BaselineCompareFindingRecurrenceKeyTest.php tests/Feature/Baselines/BaselineCompareWhyNoFindingsReasonCodeTest.php tests/Feature/Filament/BaselineProfileFoundationScopeTest.php tests/Feature/Filament/BaselineSnapshotRbacRoleDefinitionsTest.php tests/Feature/Filament/BaselineCompareLandingRbacLabelsTest.php tests/Feature/Filament/FindingViewRbacEvidenceTest.php tests/Feature/Findings/FindingRecurrenceTest.php tests/Feature/Findings/DriftStaleAutoResolveTest.php tests/Feature/Inventory/InventorySyncButtonTest.php tests/Feature/Inventory/InventorySyncServiceTest.php tests/Feature/RunAuthorizationTenantIsolationTest.php`
- result: `71 passed (467 assertions)`

## Filament / Platform Notes
- Livewire compliance: unchanged and compatible with Livewire v4.0+
- Provider registration: no panel/provider changes; `bootstrap/providers.php` remains the registration location
- Global search: no new globally searchable resource added; existing global search behavior is unchanged
- Destructive actions: no new destructive actions introduced; existing confirmed actions remain unchanged
- Assets: no new Filament assets introduced; deploy asset handling remains unchanged, including `php artisan filament:assets`
- Testing plan covered: baseline profile scope, snapshot detail, compare job, findings recurrence, findings detail, compare landing labels, inventory sync anchoring, and tenant isolation

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #156
2026-03-09 18:49:20 +00:00

5.6 KiB
Raw Blame History

Research — Intune RBAC Baseline Compare & Findings v1

Decision: Add explicit baseline-compare support metadata on foundation types

Rationale: BaselineProfileResource::foundationTypeOptions() currently lists all foundation types from InventoryPolicyTypeMeta::foundations(), which would make every configured foundation implicitly selectable for baseline compare. The spec requires intentional, type-level baseline eligibility metadata so only intuneRoleDefinition becomes eligible and intuneRoleAssignment stays excluded.

Alternatives considered:

  • Filter foundation options with hard-coded type checks in Filament resource code: rejected because it hides eligibility rules in UI code and does not create a reusable source of truth.
  • Treat every foundation as baseline-eligible by default: rejected because the spec explicitly disallows broad foundation rollout.

Decision: Reuse the existing RBAC Graph contracts and inventory/version evidence from Spec 127

Rationale: config/graph_contracts.php already contains inventory-grade contracts for intuneRoleDefinition and intuneRoleAssignment, and Spec 127 already established tenant-scoped inventory plus immutable PolicyVersion evidence for Role Definitions. The compare feature can therefore stay DB-first and does not need new live Graph calls for baseline rendering or run detail.

Alternatives considered:

  • Add new RBAC-specific Graph contracts for compare: rejected because current contracts already include the fields needed for normalized Role Definition diffs.
  • Re-hydrate live Graph payloads during compare: rejected because it would violate the existing DB-first compare and monitoring model and introduce misleading drift when live state is unavailable.

Decision: Use Role Definition ID as the compare identity for this foundation type

Rationale: Current baseline capture and compare use display-name-derived BaselineSubjectKey matching. That is adequate for some policy surfaces but does not meet the spec requirement for Role Definition identity, where a delete-and-recreate with a new ID is meaningful drift. The capture and compare pipeline needs a targeted identity-model extension for intuneRoleDefinition so baseline items and current inventory match by stable external ID.

Alternatives considered:

  • Keep display-name matching and treat renamed duplicates as ambiguous: rejected because it would hide recreated roles and violate the specs identity model.
  • Match by a composite of display name plus built-in/custom state: rejected because the object identity still changes when a role is recreated.

Decision: Reuse IntuneRoleDefinitionNormalizer::flattenForDiff() as the normalized compare surface

Rationale: The existing normalizer already produces deterministic, order-insensitive permission-block output and readable summary blocks for Role Definitions. Reusing that normalization path keeps diff semantics aligned with the inventory and version-display model from Spec 127 and avoids a second RBAC diff representation.

Alternatives considered:

  • Hash raw snapshot payloads directly: rejected because transport noise and ordering differences would create unstable findings.
  • Build a separate RBAC-only diff normalizer: rejected because the existing normalizer already captures the governance-relevant fields required by the spec.

Decision: Keep unified baseline.compare finding lifecycle and recurrence behavior

Rationale: CompareBaselineToTenantJob::upsertFindings() already provides profile-scoped recurrence keys, idempotent times_seen updates, reopen behavior, and baseline auto-close support. The RBAC feature should plug into that same lifecycle so findings behave consistently with the rest of the drift engine.

Alternatives considered:

  • Introduce an RBAC-specific finding table or source lifecycle: rejected because it would duplicate the compare engine and break existing findings surfaces.
  • Make recurrence snapshot-scoped for RBAC only: rejected because the current implementation and tests are profile-scoped, and the new spec only requires baseline profile participation in the fingerprint inputs.

Decision: Extend the existing evidence contract instead of inventing an RBAC-only UI model

Rationale: Existing baseline compare evidence already carries summary kind, provenance, baseline and current version references, and diff-compatible structures consumed by findings and run-detail surfaces. RBAC compare should extend those contracts with an RBAC-specific summary kind and before/after normalized evidence so existing UI surfaces can render the new findings without a dedicated page.

Alternatives considered:

  • Render RBAC changes from ad-hoc JSON blobs in the UI: rejected because it would bypass the unified finding evidence contract and create fragile presentation logic.
  • Create a new RBAC findings screen: rejected because the spec explicitly requires compatibility with existing baseline, drift, and findings surfaces.

Decision: Reuse existing coverage-guard and partial-success semantics for RBAC safe degradation

Rationale: CompareBaselineToTenantJob already suppresses findings for uncovered types and records partial-success outcomes with coverage reason codes. RBAC compare should use the same mechanism so missing provider or permission coverage produces a clear run/report warning without emitting false drift.

Alternatives considered:

  • Emit missing findings when current RBAC data is unavailable: rejected because it would create false drift.
  • Fail the entire compare run hard on any RBAC coverage gap: rejected because the current engine already supports safer partial-success semantics with explicit suppression reasons.