16 KiB
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
InventoryItemplus existing RBACPolicyVersionevidence 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
intuneRoleDefinitionandintuneRoleAssignmentcontracts already exist inconfig/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
OperationRunand 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 —
OperationRuntransitions remain service-owned throughOperationRunService. - Ops-UX summary counts: PASS — RBAC-specific counts can live in
context.baseline_comparewhilesummary_countsstay 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:assetsdeployment behavior is unchanged.
Project Structure
Documentation (this feature)
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)
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 viaBaselineSubjectKey, 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.mdspecs/128-rbac-baseline-compare/contracts/openapi.yamlspecs/128-rbac-baseline-compare/quickstart.md
Design highlights:
- Add explicit baseline-compare metadata on foundation types so
intuneRoleDefinitionis opt-in supported andintuneRoleAssignmentremains 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.phpfoundation-type rows with explicit baseline-compare support metadata instead of implicitly treating every foundation as baseline-eligible. - Mark
intuneRoleDefinitionas supported for baseline compare andintuneRoleAssignmentas unsupported. - Add helper accessors in
App\Support\Inventory\InventoryPolicyTypeMetafor 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
intuneRoleDefinitionis baseline-supported andintuneRoleAssignmentis 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
intuneRoleDefinitioncan use stable external ID matching instead of the current display-name-derivedBaselineSubjectKeyflow. - Update
CaptureBaselineSnapshotJob::collectInventorySubjects()andbuildSnapshotItems()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
CompareBaselineToTenantJobso 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
intuneRoleDefinitionkeep evidence-ready references and excludeintuneRoleAssignment. - 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
intuneRoleDefinitionfinding 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.contextcapture effective scope, RBAC compare counts, and suppression reasons without adding non-canonicalsummary_countskeys. - 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:assetsbehavior 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.