# Implementation Plan: RBAC Role Definition Diff UX Upgrade

**Branch**: `142-rbac-role-definition-diff-ux-upgrade` | **Date**: 2026-03-14 | **Spec**: [spec.md](./spec.md)
**Input**: Feature specification from `/specs/142-rbac-role-definition-diff-ux-upgrade/spec.md`

**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/scripts/` for helper scripts.

## Summary

Adopt the shared diff presentation foundation from Spec 141 on the existing RBAC role definition finding detail surface so operators can scan changed rows immediately, treat unchanged rows as quieter context, and understand Allowed Actions through inline add/remove list semantics. The implementation stays presentation-only inside the existing Laravel 12 / Filament v5 / Livewire v4 stack, reuses the current shared `App\Support\Diff` primitives and shared Blade partials, adds a thin RBAC-specific shaping layer at the consumer boundary, preserves the existing Findings resource and route structure, and limits test changes to shared diff support coverage plus the current RBAC finding detail feature test.

## Technical Context

**Language/Version**: PHP 8.4.15 / Laravel 12  
**Primary Dependencies**: Filament v5, Livewire v4.0+, Tailwind CSS v4, shared `App\Support\Diff` foundation from Spec 141  
**Storage**: PostgreSQL via Laravel Sail for existing `findings.evidence_jsonb`; no schema or persistence changes  
**Testing**: Pest v4 feature and unit tests on PHPUnit 12  
**Target Platform**: Laravel Sail web application with the Filament admin panel at `/admin` and the existing Findings view page  
**Project Type**: Laravel monolith / Filament web application  
**Performance Goals**: Keep diff shaping server-side and lightweight, preserve responsive finding detail rendering for representative RBAC payloads, and keep inline list rendering readable for permission lists up to roughly 25 actions without client-side diffing  
**Constraints**: No new routes, no Graph calls, no `OperationRun`, no authorization-rule changes, no backend diff-producer rewrite, no unrelated diff-screen migrations, no heavy Alpine/Livewire toggle complexity, and dark-mode-safe accessible semantics are required  
**Scale/Scope**: One existing RBAC consumer on the finding detail page, reuse of the shared diff support layer and shared Blade partials, one RBAC-local shaping adapter, and focused regression coverage for view rendering and consumer shaping

## Constitution Check

*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*

- Inventory-first: PASS — the feature only changes how existing finding evidence is presented and does not alter inventory, snapshot, or finding ownership.
- Read/write separation: PASS — the feature is presentation-only and adds no mutation flow.
- Graph contract path: PASS — no Microsoft Graph call is introduced.
- Deterministic capabilities: PASS — no capability derivation or registry mapping changes are introduced.
- RBAC-UX planes and isolation: PASS — tenant/admin and platform planes remain unchanged, and the RBAC consumer renders only already-authorized finding evidence.
- Workspace isolation: PASS — no new workspace-scoped data access path is introduced.
- RBAC-UX destructive confirmation: PASS / N/A — no destructive action is added or modified.
- RBAC-UX global search: PASS / N/A — no global-search behavior changes; the Findings resource already has a View page and this feature does not alter searchability.
- Tenant isolation: PASS — the feature remains within the existing authorized finding detail view.
- Run observability and Ops-UX: PASS / N/A — no long-running, remote, queued, or scheduled work is introduced.
- Data minimization: PASS — the consumer uses existing evidence payloads and shared stringification without broadening stored or fetched data.
- Badge semantics (BADGE-001): PASS — diff-state badges continue to use centralized `BadgeCatalog` mapping through the shared diff foundation.
- UI naming (UI-NAMING-001): PASS — no new operator-facing actions, toasts, runs, or audit prose are added; existing descriptive copy stays domain-specific.
- Filament UI Action Surface Contract: PASS — the Findings `View` page remains the same action surface and introduces no new actions or mutations.
- Filament UI UX-001: PASS — the feature upgrades one infolist entry within the existing View page rather than changing page layout structure.
- Filament v5 / Livewire v4 compliance: PASS — the implementation stays inside the current Filament v5 panel and Livewire v4 runtime.
- Provider registration (`bootstrap/providers.php`): PASS / unchanged — no new provider or panel registration is introduced.
- Asset strategy: PASS — no new global or on-demand assets are needed; deployment continues to rely on the existing Filament asset pipeline and standard `php artisan filament:assets` step when assets change generally.

Gate result before research: PASS.

Gate result after Phase 1 design: PASS.

- The chosen design uses existing shared diff primitives rather than inventing new diff semantics in Blade.
- No constitution exceptions or complexity justifications are required.

## Project Structure

### Documentation (this feature)

```text
specs/142-rbac-role-definition-diff-ux-upgrade/
├── plan.md
├── research.md
├── data-model.md
├── quickstart.md
├── contracts/
│   └── rbac-finding-diff-view.openapi.yaml
├── checklists/
│   └── requirements.md
└── tasks.md
```

### Source Code (repository root)

```text
app/
├── Filament/
│   └── Resources/
│       └── FindingResource.php                         # existing finding detail surface; may host consumer wiring
├── Support/
│   ├── Diff/
│   │   ├── DiffPresenter.php                          # existing shared presenter from Spec 141
│   │   ├── DiffPresentation.php                       # existing shared presentation DTO
│   │   ├── DiffRow.php                                # existing shared row DTO
│   │   ├── DiffSummary.php                            # existing shared summary DTO
│   │   ├── DiffRowStatus.php                          # existing shared status enum
│   │   ├── ValueStringifier.php                       # existing shared value formatter
│   │   └── RbacRoleDefinitionDiffBuilder.php          # planned thin RBAC-specific adapter
│   └── Badges/
│       └── Domains/
│           └── DiffRowStatusBadge.php                # existing centralized diff badge semantics
resources/
└── views/
    └── filament/
        ├── infolists/
        │   └── entries/
        │       └── rbac-role-definition-diff.blade.php  # existing RBAC consumer to refactor
        └── partials/
            └── diff/
                ├── summary-badges.blade.php          # existing shared summary partial
                ├── row.blade.php                     # existing shared row dispatcher
                ├── row-changed.blade.php             # existing changed-row partial
                ├── row-added.blade.php               # existing added-row partial
                ├── row-removed.blade.php             # existing removed-row partial
                ├── row-unchanged.blade.php           # existing unchanged-row partial
                └── inline-list.blade.php             # existing inline list partial
tests/
├── Feature/
│   ├── Filament/
│   │   └── FindingViewRbacEvidenceTest.php           # existing RBAC finding detail coverage to extend
│   └── Support/
│       └── Diff/
│           ├── SharedDiffRowPartialTest.php          # existing shared row partial coverage
│           ├── SharedDiffSummaryPartialTest.php      # existing shared summary coverage
│           └── SharedInlineListDiffPartialTest.php   # existing inline list coverage
└── Unit/
    └── Support/
        └── Diff/
            ├── DiffPresenterTest.php                 # existing presenter coverage to extend if RBAC-specific shaping is added
            └── ValueStringifierTest.php              # existing stringifier coverage if formatting assumptions change
```

**Structure Decision**: Keep the implementation inside the existing Laravel/Filament monolith and localize the feature to the Findings consumer plus the shared diff support layer it already depends on. Introduce only a thin RBAC-specific adapter under `app/Support/Diff` to translate `rbac_role_definition` evidence into `DiffPresentation`, refactor the RBAC infolist entry to compose shared partials, and extend existing feature/support tests instead of building a new module or page.

## Complexity Tracking

> No Constitution Check violations. No justifications needed.

| Violation | Why Needed | Simpler Alternative Rejected Because |
|-----------|------------|-------------------------------------|
| — | — | — |

## Phase 0 — Research (DONE)

Output:
- `specs/142-rbac-role-definition-diff-ux-upgrade/research.md`

Key findings captured:
- The shared Spec 141 diff foundation already exists in `app/Support/Diff` and in `resources/views/filament/partials/diff`, so this feature is a consumer-adoption pass, not new framework work.
- The current RBAC renderer in `resources/views/filament/infolists/entries/rbac-role-definition-diff.blade.php` still duplicates summary, formatting, and side-by-side rendering logic that should move to the shared partials.
- `FindingResource` already exposes the RBAC diff region through a View entry on the Findings `view` page, so no route, page, or resource restructuring is needed.
- The existing `FindingViewRbacEvidenceTest` is the right feature-level anchor, while shared presenter and partial tests already exist for lower-level reuse.
- A local “show only changes” toggle is not needed for the first consumer because the spec’s core UX goals can be met through stronger row emphasis and quieter unchanged rows.

## Phase 1 — Design & Contracts (DONE)

Outputs:
- `specs/142-rbac-role-definition-diff-ux-upgrade/data-model.md`
- `specs/142-rbac-role-definition-diff-ux-upgrade/contracts/rbac-finding-diff-view.openapi.yaml`
- `specs/142-rbac-role-definition-diff-ux-upgrade/quickstart.md`

Design highlights:
- Add a thin `RbacRoleDefinitionDiffBuilder` that converts `rbac_role_definition` finding evidence into shared `DiffPresentation` rows using explicit RBAC field labels, deterministic field order, and explicit list-field treatment for Allowed Actions and similar simple arrays.
- Refactor the existing RBAC Blade entry so it renders shared summary badges and shared row partials instead of custom side-by-side blocks.
- Keep unchanged rows visible but visually quieter by reusing the shared unchanged-row partial with dimmed semantics rather than hiding rows by default.
- Keep one-sided rows explicit by relying on the shared added/removed states instead of treating them as generic changed rows.
- Do not implement the optional local toggle in this first pass unless it is still clearly low-risk during implementation.

## Phase 1 — Agent Context Update (DONE)

Run:
- `.specify/scripts/bash/update-agent-context.sh copilot`

## Phase 2 — Implementation Outline (tasks created in `/speckit.tasks`)

### Step 1 — Add RBAC consumer shaping

Goal: turn the existing `rbac_role_definition` evidence payload into a shared `DiffPresentation` without changing the authoritative comparison engine.

Changes:
- Add a thin RBAC-specific builder under `app/Support/Diff`.
- Normalize baseline/current rows from `baseline.normalized` and `current.normalized`.
- Apply a stable RBAC field label map and explicit row-order rules.
- Mark Allowed Actions and other simple RBAC list fields for inline list rendering.

Tests:
- Add or extend unit coverage for RBAC-specific row shaping, state assignment, and summary counts.

### Step 2 — Refactor the finding detail consumer

Goal: replace the one-off RBAC Blade rendering with shared summary and row partial composition.

Changes:
- Update the existing RBAC infolist entry to consume the shared presentation output.
- Render summary badges through `filament.partials.diff.summary-badges`.
- Render each row through `filament.partials.diff.row`.
- Keep the existing explanatory copy about assignments exclusion and unsupported restore.

Tests:
- Extend the existing finding detail feature test to assert changed-row emphasis, summary/detail coherence, and one-sided row semantics.

### Step 3 — Harden inline list readability

Goal: ensure Allowed Actions and similar list fields are readable as added/removed differences.

Changes:
- Feed shared inline list rendering with RBAC list rows.
- Preserve unchanged list items only in muted form where helpful.
- Keep list rendering accessible and dark-mode-safe.

Tests:
- Add or extend feature and partial tests for added, removed, and unchanged list chips in the RBAC consumer context.

### Step 4 — Validate no-change and sparse states

Goal: make the consumer safe for empty, sparse, and no-change payloads.

Changes:
- Ensure the builder returns empty or no-change summary states consistently.
- Keep layout stable when optional metadata is absent.

Tests:
- Add feature assertions for no-change messaging and sparse payload safety.

### Step 5 — Keep the rollout consumer-local

Goal: prove Spec 141 through RBAC without dragging in other diff screens.

Changes:
- Do not migrate assignments, scope tags, normalized diff, or other compare consumers in this feature.
- Keep any RBAC-specific logic at the consumer boundary rather than expanding shared foundation scope.

Tests:
- No new cross-consumer tests required; existing unrelated diff consumers should remain untouched.

