TenantAtlas/specs/128-rbac-baseline-compare/plan.md
2026-03-09 19:43:13 +01:00

253 lines
16 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.

# 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 tenants Role Definition baseline items and findings cannot match another tenants 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.