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
13 KiB
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:
- 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.
- 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.
- 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:
- 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.
- 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).
- 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:
- 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.
- 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.
- 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_assignmentbaseline.compare.profile_not_activebaseline.compare.no_active_snapshotbaseline.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.