Summary: - Baseline Compare landing: enterprise UI (stats grid, critical drift banner, better actions), navigation grouping under Governance, and Action Surface Contract declaration. - Baseline Profile view page: switches from disabled form fields to proper Infolist entries for a clean read-only view. - Fixes tenant name column usages (`display_name` → `name`) in baseline assignment flows. - Dashboard: improved baseline governance widget with severity breakdown + last compared. Notes: - Filament v5 / Livewire v4 compatible. - Destructive actions remain confirmed (`->requiresConfirmation()`). Tests: - `vendor/bin/sail artisan test --compact tests/Feature/Baselines` - `vendor/bin/sail artisan test --compact tests/Feature/Guards/ActionSurfaceContractTest.php` Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #123
168 lines
13 KiB
Markdown
168 lines
13 KiB
Markdown
# Feature Specification: Golden Master / Baseline Governance v1 (R1.1–R1.4)
|
||
|
||
**Feature Branch**: `101-golden-master-baseline-governance-v1`
|
||
**Created**: 2026-02-19
|
||
**Status**: Draft
|
||
**Input**: Introduce a workspace-owned “Golden Master” baseline that can be captured from a tenant, compared against the current tenant state, and surfaced in the UI as “Soll vs Ist” with drift findings and an operational summary.
|
||
|
||
## Spec Scope Fields *(mandatory)*
|
||
|
||
- **Scope**: workspace
|
||
- **Primary Routes**: Admin Governance “Baselines” area; tenant-facing dashboard card; drift comparison landing (“Soll vs Ist”)
|
||
- **Data Ownership**: Baselines and snapshots are workspace-owned; tenant assignments are tenant-owned (workspace_id + tenant_id); drift findings are tenant-owned (workspace_id + tenant_id)
|
||
- **RBAC**: workspace membership required for any visibility; capability gating for view vs manage (see Requirements)
|
||
|
||
## Clarifications
|
||
|
||
### Session 2026-02-19
|
||
|
||
- Q: Which v1 drift severity mapping should we use? → A: Fixed mapping: missing_policy=high, different_version=medium, unexpected_policy=low.
|
||
- Q: Which snapshot should compare runs use in v1? → A: Always use BaselineProfile.active_snapshot; if missing, block compare with a clear reason.
|
||
- Q: How should compare/capture handle precondition failures? → A: UI disables where possible AND server blocks start with 422 + stable reason_code; no OperationRun is created for failed preconditions.
|
||
- Q: Where should drift findings be stored in v1? → A: Use existing findings storage with source=baseline.compare; fingerprint/idempotency lives there.
|
||
- Q: How should tenant override filters combine with the profile filter? → A: Override narrows scope; effective filter = profile ∩ override.
|
||
|
||
## User Scenarios & Testing *(mandatory)*
|
||
|
||
### User Story 1 - Create and manage a baseline profile (Priority: P1)
|
||
|
||
As a workspace governance owner/manager, I can define what “good” looks like by creating a Baseline Profile, controlling its status (draft/active/archived), and scoping which policy domains are included.
|
||
|
||
**Why this priority**: Without a baseline profile, there is nothing to capture or compare against.
|
||
|
||
**Independent Test**: A user with baseline manage rights can create/edit/archive a baseline profile and see it listed; a read-only user can list/view but cannot mutate.
|
||
|
||
**Acceptance Scenarios**:
|
||
|
||
1. **Given** I am a workspace member with baseline manage capability, **When** I create a baseline profile with a name, optional description, optional version label, and a policy-domain filter, **Then** the profile is persisted and visible in the Baselines list.
|
||
2. **Given** I am a workspace member with baseline view-only capability, **When** I open a baseline profile, **Then** I can view its details but cannot edit/archive/delete it.
|
||
3. **Given** I am not a member of the workspace, **When** I attempt to access baseline pages, **Then** I receive a not-found response (deny-as-not-found).
|
||
|
||
---
|
||
|
||
### User Story 2 - Capture an immutable baseline snapshot from a tenant (Priority: P2)
|
||
|
||
As a baseline manager, I can capture a snapshot of the current tenant configuration that the baseline profile covers, producing an immutable “baseline snapshot” that can later be compared.
|
||
|
||
**Why this priority**: The baseline must be based on a real, point-in-time state to be meaningful and auditable.
|
||
|
||
**Independent Test**: Capturing twice with unchanged tenant state reuses the same snapshot identity and does not create duplicates.
|
||
|
||
**Acceptance Scenarios**:
|
||
|
||
1. **Given** a baseline profile exists and I have baseline manage capability, **When** I trigger “Capture from tenant” and choose a source tenant, **Then** a new capture operation is created and eventually produces an immutable snapshot.
|
||
2. **Given** a capture was already completed for the same baseline profile and the tenant’s relevant policies are unchanged, **When** I capture again, **Then** the system reuses the existing snapshot (idempotent/deduped).
|
||
3. **Given** the baseline profile is active, **When** a capture completes successfully, **Then** the profile’s “active snapshot” points to the captured snapshot.
|
||
|
||
---
|
||
|
||
### User Story 3 - Compare baseline vs current tenant to detect drift (Priority: P3)
|
||
|
||
As an operator/manager, I can run “Compare now” for a tenant, and the system produces drift findings and a summary that can be used for assurance and triage.
|
||
|
||
**Why this priority**: Drift detection is the core governance signal; it makes the baseline actionable.
|
||
|
||
**Independent Test**: A compare run produces findings for missing policies, different versions, and unexpected policies, and stores a compact summary.
|
||
|
||
**Acceptance Scenarios**:
|
||
|
||
1. **Given** a tenant is assigned to an active baseline profile with an active snapshot, **When** I run “Compare now”, **Then** a compare operation runs and produces drift findings and a drift summary.
|
||
2. **Given** the same drift item is detected in repeated compares, **When** compares are run multiple times, **Then** the same finding is updated (idempotent fingerprint) rather than duplicated.
|
||
3. **Given** I am a workspace member without baseline view capability, **When** I try to start a compare, **Then** the request is forbidden.
|
||
|
||
### Edge Cases
|
||
|
||
- Baseline profile is draft or archived: compare is blocked; users are told what must be changed (e.g., “activate baseline”).
|
||
- Tenant has no baseline assignment: compare button is disabled and the UI explains why.
|
||
- Baseline profile has no active snapshot yet: compare is blocked with a clear reason.
|
||
- Concurrent operation starts: the system prevents multiple “active” captures/compares for the same scope.
|
||
- Baseline filter yields no relevant policies: capture creates an empty snapshot and compare returns “no items checked”, without errors.
|
||
|
||
## Requirements *(mandatory)*
|
||
|
||
### Functional Requirements
|
||
|
||
- **FR-001 (Baseline Profiles)**: The system MUST allow workspace baseline managers to create, edit, activate, deactivate (return to draft), and archive baseline profiles. Status transitions: draft ↔ active → archived (archived is terminal in v1).
|
||
- **FR-002 (Scope Control)**: Each baseline profile MUST define which policy domains/types are included in the baseline.
|
||
- **FR-003 (Tenant Assignment)**: The system MUST support assigning exactly one baseline profile per tenant per workspace (v1), with an optional per-tenant override of the profile’s scope; in v1 the override may only narrow scope (effective filter = profile ∩ override).
|
||
- **FR-004 (Capture as Operation)**: Capturing a baseline snapshot MUST be tracked as an observable operation with a clear lifecycle (started/completed/failed).
|
||
- **FR-005 (Immutable Snapshots)**: Baseline snapshots and their snapshot items MUST be immutable once created.
|
||
- **FR-006 (Capture Idempotency)**: Captures MUST be deduplicated so that repeated captures with the same effective content reuse the existing snapshot identity.
|
||
- **FR-007 (Compare as Operation)**: Comparing a tenant against its baseline MUST be tracked as an observable operation with a clear lifecycle.
|
||
- **FR-008 (Drift Findings)**: Compare MUST produce drift findings using at least these drift types: missing baseline policy, different version, and unexpected policy within the baseline scope.
|
||
- **FR-009 (Severity Rules)**: Drift findings MUST be assigned severities using centrally defined rules so that severity is consistent and testable; v1 fixed mapping is: missing baseline policy = high, different version = medium, unexpected policy = low.
|
||
- **FR-010 (Finding Idempotency)**: Drift findings MUST be deduplicated with a stable fingerprint so repeated compares update existing open findings instead of creating duplicates.
|
||
- **FR-011 (Summary Output)**: Each compare operation MUST persist a summary containing totals and severity breakdowns suitable for dashboards.
|
||
- **FR-012 (UI “Soll vs Ist”)**: The UI MUST allow selecting a baseline profile and viewing the latest compare runs and drift findings for a tenant.
|
||
- **FR-013 (Compare Snapshot Selection)**: Compare runs MUST always use the baseline profile’s active snapshot; if no active snapshot exists, compare MUST be blocked with a clear reason.
|
||
- **FR-014 (Precondition Failure Contract)**: When capture/compare cannot start due to unmet preconditions, the UI MUST disable the action where possible, and the server MUST reject the request with HTTP 422 containing a stable `reason_code`; in this case, the system MUST NOT create an OperationRun.
|
||
- **FR-015 (Findings Storage)**: Drift findings produced by compare MUST be persisted using the existing findings system with `source = baseline.compare`.
|
||
|
||
### Precondition `reason_code` (v1)
|
||
|
||
- `baseline.compare.no_assignment`
|
||
- `baseline.compare.profile_not_active`
|
||
- `baseline.compare.no_active_snapshot`
|
||
- `baseline.capture.missing_source_tenant`
|
||
|
||
### Constitution Alignment: Safety, Isolation, Observability
|
||
|
||
- **Ops/Observability**: Capture and compare MUST be observable and auditable operations, and surfaced in the existing operations monitoring experience.
|
||
- **DB-only UI**: Baseline pages and drift pages MUST NOT require outbound network calls during page render or user clicks that only start/view operations; external calls (if any) must happen in background work.
|
||
- **Tenant Isolation / RBAC semantics**:
|
||
- non-member of workspace or tenant scope → deny-as-not-found (404)
|
||
- member but missing capability → forbidden (403)
|
||
- **Least privilege**: Two capabilities MUST exist and be enforced:
|
||
- baseline view: can view baselines and start compare operations
|
||
- baseline manage: can manage baselines, assignments, and capture snapshots
|
||
- **Auditability**: Baseline-related operations MUST emit audit log entries for started/completed/failed events.
|
||
- **Safe logging**: Failures MUST not include secrets or sensitive tenant data; failure reasons must be stable and suitable for support triage.
|
||
|
||
## UI Action Matrix *(mandatory when Filament is changed)*
|
||
|
||
| Surface | Location | Header Actions | Inspect Affordance (List/Table) | Row Actions (max 2 visible) | Bulk Actions (grouped) | Empty-State CTA(s) | View Header Actions | Create/Edit Save+Cancel | Audit log? | Notes / Exemptions |
|
||
|---|---|---|---|---|---|---|---|---|---|---|
|
||
| Baseline Profiles | Admin → Governance → Baselines | Create baseline profile | View/Edit page available | View, Edit | Archive (grouped) | Create baseline profile | Capture from tenant; Activate/Deactivate; Assign to tenants | Save, Cancel | Yes | Destructive actions require confirmation; mutations are manage-gated; hard-delete is out of scope for v1 (archive only) |
|
||
| Drift Landing (“Soll vs Ist”) | Tenant view | Run compare now | Links to last compare operation and findings list | — | — | — | — | — | Yes | Starting compare is view-gated; results visible only to entitled users |
|
||
| Tenant Dashboard Card | Tenant dashboard | Run compare now | Click to drift landing and/or last compare operation | — | — | — | — | — | Yes | Button disabled with explanation when no assignment or no active snapshot |
|
||
|
||
## Key Entities *(include if feature involves data)*
|
||
|
||
- **Baseline Profile**: A workspace-owned definition of what should be in-scope for governance, with a lifecycle status (draft/active/archived).
|
||
- **Tenant Assignment**: A workspace-managed mapping that declares which baseline applies to a tenant, optionally overriding scope.
|
||
- **Baseline Snapshot**: An immutable point-in-time record of the baseline’s in-scope policy references captured from a tenant.
|
||
- **Snapshot Item**: A single baseline entry representing one in-scope policy reference in the snapshot.
|
||
- **Drift Finding**: A record representing an observed deviation between baseline and tenant state, deduplicated by a stable fingerprint.
|
||
- **Drift Finding Source**: Drift findings produced by this feature use `source = baseline.compare`.
|
||
- **Operation Summary**: A compact, persisted summary of a capture/compare run suitable for dashboard display.
|
||
|
||
## Success Criteria *(mandatory)*
|
||
|
||
### Measurable Outcomes
|
||
|
||
- **SC-001**: A baseline manager can create a baseline profile and perform an initial capture in under 5 minutes.
|
||
- **SC-002**: For a typical tenant (≤ 500 in-scope policies), a compare run completes and surfaces a summary within 2 minutes.
|
||
- **SC-003**: Re-running compare within 24 hours for unchanged drift does not create duplicate findings (0 duplicate drift fingerprints).
|
||
- **SC-004**: Unauthorized users (non-members) receive no baseline visibility (deny-as-not-found) and members without capability cannot mutate (forbidden).
|
||
|
||
## Non-Goals (v1)
|
||
|
||
- Evidence packs / stored reports for audit exports
|
||
- Advanced findings workflow (exceptions, auto-closing, recurrence handling)
|
||
- Cross-tenant portfolio comparisons
|
||
- Baseline inheritance across organizations (e.g., MSP → customer)
|
||
- Assignment/scope-tag baselines beyond the policy domains/types included in the profile scope
|
||
|
||
## Assumptions
|
||
|
||
- Tenants already have an inventory of policy versions that can be referenced for capture and compare.
|
||
- An operations monitoring experience exists where capture/compare runs can be viewed.
|
||
- A drift findings system exists that can store and display findings and severities.
|
||
|
||
## Dependencies
|
||
|
||
- Inventory + policy version history is available and trustworthy.
|
||
- Operation run tracking and monitoring is available.
|
||
- RBAC + UI enforcement semantics are already established (404 for non-member, 403 for missing capability).
|
||
- Alerts are optional for v1; the feature remains valuable without alert integrations.
|