# Implementation Plan: Intune RBAC Baseline Compare & Findings v1 **Branch**: `feat/128-rbac-baseline-compare` | **Date**: 2026-03-09 | **Spec**: `specs/128-rbac-baseline-compare/spec.md` **Input**: Feature specification from `specs/128-rbac-baseline-compare/spec.md` ## Summary Extend the existing baseline capture and compare engine to support one additional foundation type, `intuneRoleDefinition`, by introducing explicit baseline-support metadata, capturing Role Definition baseline references from the existing RBAC version history, and adding an ID-based normalized compare path that emits unified baseline.compare findings with RBAC-specific severity, evidence, and summaries. Keep `intuneRoleAssignment` explicitly excluded from baseline scope, compare, and findings. ## Technical Context **Language/Version**: PHP 8.4 **Primary Dependencies**: Laravel 12, Filament v5, Livewire v4 **Storage**: PostgreSQL via Laravel Sail **Testing**: Pest v4 on PHPUnit 12 **Target Platform**: Dockerized Laravel web application (Sail) **Project Type**: Web application **Performance Goals**: Baseline capture and compare stay chunked and DB-first, avoid UI-time Graph calls, and remain bounded by covered in-scope subjects **Constraints**: Existing Ops-UX `OperationRun` contract, numeric-only `summary_counts`, deny-as-not-found tenant/workspace isolation, no RBAC write path, and deterministic compare/finding identity **Scale/Scope**: Workspace-owned baseline profiles and snapshots plus tenant-scoped compare runs and findings across potentially large in-scope baseline subject sets ## Constitution Check *GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* - Inventory-first: PASS — current state remains `InventoryItem` plus existing RBAC `PolicyVersion` evidence from Spec 127; baseline snapshots stay immutable. - Read/write separation: PASS — this release is read-only governance analysis; no restore or Graph write behavior is added. - Graph contract path: PASS — existing `intuneRoleDefinition` and `intuneRoleAssignment` contracts already exist in `config/graph_contracts.php`; compare and UI rendering remain DB-only at runtime. - Deterministic capabilities: PASS — scope eligibility can be derived from config metadata and helper filters with tests; no raw capability strings are needed. - RBAC-UX planes and isolation: PASS — workspace baseline management stays in the tenant/admin plane, compare and findings stay tenant-context, and cross-plane behavior is unchanged. - Workspace isolation: PASS — baseline profiles and snapshots remain workspace-owned; workspace membership enforcement remains unchanged. - Tenant isolation: PASS — compare runs, current inventory, and findings remain tenant-owned and deny-as-not-found for non-members. - Destructive confirmation: PASS — no new destructive RBAC action is introduced; existing baseline archive/capture surfaces keep their current confirmation semantics. - Global search: PASS — no new searchable resource is added, so the existing global-search rules are unaffected. - Run observability: PASS — baseline capture and compare already use `OperationRun` and queued jobs; this release only extends their scope and result payloads. - Ops-UX 3-surface feedback: PASS — baseline start surfaces already use queued-only toasts and canonical run links. - Ops-UX lifecycle: PASS — `OperationRun` transitions remain service-owned through `OperationRunService`. - Ops-UX summary counts: PASS — RBAC-specific counts can live in `context.baseline_compare` while `summary_counts` stay numeric-only. - Ops-UX guards: PASS — existing baseline compare guard tests can be extended for the new foundation type. - Ops-UX system runs: PASS — unchanged; initiator-null behavior remains handled by the existing operation framework. - Data minimization: PASS — inventory remains metadata-only; baseline evidence reuses existing version references and normalized snapshots without introducing secret-bearing payloads. - Badge semantics: PASS — severity and label additions can use the centralized badge and renderer system. - Filament Action Surface Contract: PASS — only existing Baseline Profile, Baseline Snapshot, Baseline Compare, and Findings surfaces are extended; no new resource or unreviewed action surface is introduced. - Filament UX-001: PASS — the feature only adjusts scope options, summaries, and evidence blocks inside established sectioned forms and detail screens. - Filament v5 / Livewire v4 compliance: PASS — no version or API changes are introduced. - Provider registration (`bootstrap/providers.php`): PASS — no new providers or panels are introduced. - Global search resource rule: PASS — no new globally searchable resource is added. - Asset strategy: PASS — no new panel or shared assets are needed; `filament:assets` deployment behavior is unchanged. ## Project Structure ### Documentation (this feature) ```text specs/128-rbac-baseline-compare/ ├── plan.md ├── research.md ├── data-model.md ├── quickstart.md ├── contracts/ │ └── openapi.yaml ├── checklists/ │ └── requirements.md └── tasks.md ``` ### Source Code (repository root) ```text app/ ├── Filament/ │ ├── Pages/ │ │ └── BaselineCompareLanding.php │ └── Resources/ │ ├── BaselineProfileResource.php │ └── BaselineSnapshotResource.php ├── Jobs/ │ ├── CaptureBaselineSnapshotJob.php │ └── CompareBaselineToTenantJob.php ├── Models/ │ ├── BaselineProfile.php │ ├── BaselineSnapshot.php │ ├── BaselineSnapshotItem.php │ ├── Finding.php │ ├── InventoryItem.php │ └── PolicyVersion.php ├── Services/ │ ├── Baselines/ │ │ ├── BaselineCaptureService.php │ │ ├── BaselineCompareService.php │ │ ├── BaselineAutoCloseService.php │ │ ├── BaselineSnapshotIdentity.php │ │ └── Evidence/ │ ├── Intune/ │ │ ├── IntuneRoleDefinitionNormalizer.php │ │ └── PolicyNormalizer.php │ └── Inventory/ │ └── InventorySyncService.php ├── Support/ │ ├── Baselines/ │ │ ├── BaselineCompareReasonCode.php │ │ ├── BaselineScope.php │ │ └── BaselineSubjectKey.php │ ├── Badges/ │ └── Inventory/ │ └── InventoryPolicyTypeMeta.php config/ ├── graph_contracts.php └── tenantpilot.php tests/ ├── Feature/ │ ├── Baselines/ │ ├── Findings/ │ ├── Filament/ │ └── Inventory/ └── Unit/ └── IntuneRoleDefinitionNormalizerTest.php ``` **Structure Decision**: Keep all work in the existing Laravel application. The feature is a targeted extension of current baseline capture/compare services, baseline Filament surfaces, config-driven type metadata, and existing baseline/findings tests. ## Complexity Tracking No constitution violations are required for this feature. ## Phase 0 — Outline & Research (DONE) Outputs: - `specs/128-rbac-baseline-compare/research.md` Key findings captured: - Baseline scope already supports `foundation_types`, but the current UI options list every configured foundation type without an explicit baseline-support gate. - Baseline capture already queries `scope->allTypes()` and can include foundations, but current subject identity is display-name-derived via `BaselineSubjectKey`, which is not acceptable for Role Definition compare. - Compare and finding upsert infrastructure already supports unified lifecycle, evidence provenance, coverage guards, and recurrence logic; this feature should plug into those seams rather than inventing a separate RBAC compare engine. ## Phase 1 — Design & Contracts (DONE) Outputs: - `specs/128-rbac-baseline-compare/data-model.md` - `specs/128-rbac-baseline-compare/contracts/openapi.yaml` - `specs/128-rbac-baseline-compare/quickstart.md` Design highlights: - Add explicit baseline-compare metadata on foundation types so `intuneRoleDefinition` is opt-in supported and `intuneRoleAssignment` remains explicitly unsupported. - Extend baseline capture and compare with a Role Definition ID identity strategy while leaving the existing display-name subject-key flow in place for previously supported policy types. - Reuse `IntuneRoleDefinitionNormalizer::flattenForDiff()` for normalized governance diffs and feed the result into the existing baseline evidence and finding contracts. ## Phase 1 — Agent Context Update (DONE) Run: - `.specify/scripts/bash/update-agent-context.sh copilot` ## Phase 2 — Implementation Plan ### Step 1 — Add explicit baseline-support metadata for foundation types Goal: implement FR-128-01 through FR-128-04. Changes: - Extend `config/tenantpilot.php` foundation-type rows with explicit baseline-compare support metadata instead of implicitly treating every foundation as baseline-eligible. - Mark `intuneRoleDefinition` as supported for baseline compare and `intuneRoleAssignment` as unsupported. - Add helper accessors in `App\Support\Inventory\InventoryPolicyTypeMeta` for baseline-supported foundations so scope selection, summaries, and tests read from one canonical source. - Update `BaselineProfileResource::foundationTypeOptions()` and related infolist formatting to show only eligible foundation types while making the RBAC Role Definition label explicit. Tests: - Add or update focused tests proving `intuneRoleDefinition` is baseline-supported and `intuneRoleAssignment` is excluded. - Add or update baseline profile selection tests proving Role Definitions can be chosen and Assignments cannot leak into the saved scope. ### Step 2 — Introduce Role Definition ID identity for baseline capture and compare Goal: implement FR-128-05 through FR-128-10. Changes: - Extend the baseline subject identity model so `intuneRoleDefinition` can use stable external ID matching instead of the current display-name-derived `BaselineSubjectKey` flow. - Update `CaptureBaselineSnapshotJob::collectInventorySubjects()` and `buildSnapshotItems()` to preserve the tenant Role Definition ID, a workspace-safe external reference, and an explicit identity marker for Role Definition baseline items. - Update compare-side loaders in `CompareBaselineToTenantJob` so Role Definitions are loaded and matched by Role Definition ID while existing policy types keep their current behavior. - Ensure delete-and-recreate with a new ID resolves to `missing` + `unexpected`, not a silent rename match. Tests: - Add capture tests proving baseline snapshot items for `intuneRoleDefinition` keep evidence-ready references and exclude `intuneRoleAssignment`. - Add compare tests proving same-name/different-ID Role Definitions do not match and instead produce missing/unexpected outcomes. ### Step 3 — Add normalized RBAC Role Definition diffing and classification Goal: implement FR-128-11 through FR-128-19. Changes: - Introduce a narrow Role Definition compare helper that uses `IntuneRoleDefinitionNormalizer::flattenForDiff()` as the governance-normalized diff surface. - Define classification logic for unchanged, modified, missing, and unexpected Role Definitions. - Split modified Role Definition diffs into metadata-only versus permission-impacting changes so severity can map to Low versus High. - Reuse existing coverage-guard and evidence-gap handling so provider or permission issues suppress false findings instead of inventing RBAC-only failure semantics. Tests: - Add normalized diff tests that prove ordering noise in permission blocks is ignored. - Add compare classification tests for unchanged, modified, missing, and unexpected Role Definitions. - Add severity tests proving permission changes are High, missing is High, unexpected is Medium, and metadata-only is Low. ### Step 4 — Extend finding evidence, fingerprints, and run summaries for RBAC Goal: implement FR-128-18 through FR-128-26. Changes: - Implement the `intuneRoleDefinition` finding fingerprint composition explicitly in the compare job so the stable fingerprint includes baseline profile scope, Role Definition identity, change kind, and normalized diff fingerprint inputs. - Reuse baseline compare finding upsert and recurrence behavior, keeping fingerprints recurrence-stable and profile-scoped while adding `intuneRoleDefinition`-specific diff fingerprints as evidence inputs. - Extend the evidence contract builder to emit an RBAC-specific `summary.kind`, readable before/after normalized evidence, baseline and current version references, and built-in/custom visibility. - Extend compare-run context with an RBAC Role Definition summary bucket: total compared, unchanged, modified, missing, and unexpected. - Update label and presentation helpers so findings and run detail surfaces identify these records as Intune RBAC Role Definition drift, not generic policy drift. Tests: - Add or update evidence contract tests for modified, missing, and unexpected RBAC Role Definition findings. - Add fingerprint/idempotency tests for repeated identical compare runs and recurrence tests for resolved-then-reappearing RBAC drift. - Add summary serialization tests for the RBAC run-level counts. ### Step 5 — Extend existing Filament surfaces without introducing RBAC restore semantics Goal: implement FR-128-24 through FR-128-29 and the UX-001 layout and UI Action Matrix constraints already defined in the spec and constitution. Changes: - Update existing baseline profile, baseline snapshot, compare landing or run detail, and findings detail surfaces to surface Role Definition scope, summary counts, RBAC-specific wording, and readable evidence blocks. - Keep action surfaces unchanged except for the new scope option and evidence presentation; no new destructive or restore actions are introduced. - Use existing badge and tag renderers for any new severity or compare-state display values. - Ensure no screen text implies Role Assignment coverage or executable RBAC restore. Tests: - Add or update Filament tests asserting the baseline profile scope picker shows Intune Role Definition and not Intune Role Assignment. - Add or update UI tests for compare landing and finding detail labels so RBAC findings are clearly labeled, show readable evidence, and do not imply restore support. ### Step 6 — Preserve safe degradation, auditability, and isolation semantics Goal: implement FR-128-28 through FR-128-30 and the failure-path test requirements. Changes: - Reuse compare coverage and evidence-gap reason codes for RBAC so unavailable current-state data results in warning or partial-success outcomes instead of false drift. - Ensure RBAC compare audit events and `OperationRun.context` capture effective scope, RBAC compare counts, and suppression reasons without adding non-canonical `summary_counts` keys. - Confirm workspace and tenant scoping on compare queries, finding upserts, and UI read paths. Tests: - Add isolation coverage ensuring one tenant’s Role Definition baseline items and findings cannot match another tenant’s current state. - Add failure-path tests proving provider or permission gaps emit zero false RBAC findings. - Keep existing baseline compare coverage-guard, run-authorization, and stale auto-close tests passing. ## Post-design Constitution Re-check Expected: PASS. - Livewire v4.0+ compliance: unchanged and preserved because no new Filament panel or Livewire version changes are introduced. - Provider registration location: unchanged; no new providers or panel registration changes outside `bootstrap/providers.php`. - Globally searchable resources: unchanged; no new Resource is added, so no new Edit/View global-search requirement applies. - Destructive actions: unchanged; existing baseline archive actions remain confirmed and no new destructive RBAC action is added. - Asset strategy: unchanged; no new assets are introduced, so the existing deploy-time `php artisan filament:assets` behavior remains sufficient. - Testing plan: extend focused Pest coverage for baseline eligibility, scope selection, capture references, compare classification, evidence, severity, fingerprinting, assignment exclusion, isolation, failure paths, and unchanged baseline behavior for existing supported types.