From 1075ed5ae353e96b4429571332d8d7318c8ff197 Mon Sep 17 00:00:00 2001 From: Ahmed Darrazi Date: Sat, 11 Apr 2026 14:49:52 +0200 Subject: [PATCH] spec: complete baseline compare operator mode plan --- .github/agents/copilot-instructions.md | 5 +- ...compare-operator-mode.logical.openapi.yaml | 501 ++++++++++++++++++ .../data-model.md | 166 ++++++ .../plan.md | 192 ++++--- .../quickstart.md | 70 +++ .../research.md | 111 ++++ .../tasks.md | 2 +- 7 files changed, 968 insertions(+), 79 deletions(-) create mode 100644 specs/191-baseline-compare-operator-mode/contracts/baseline-compare-operator-mode.logical.openapi.yaml create mode 100644 specs/191-baseline-compare-operator-mode/data-model.md create mode 100644 specs/191-baseline-compare-operator-mode/quickstart.md create mode 100644 specs/191-baseline-compare-operator-mode/research.md diff --git a/.github/agents/copilot-instructions.md b/.github/agents/copilot-instructions.md index d7b55ebe..16d1fcb1 100644 --- a/.github/agents/copilot-instructions.md +++ b/.github/agents/copilot-instructions.md @@ -167,6 +167,8 @@ ## Active Technologies - PostgreSQL via Laravel Eloquent with one new table `tenant_triage_reviews` and no new external caches or background stores (189-portfolio-triage-review-state) - PHP 8.4.15 + Laravel 12, Filament v5, Livewire v4, Pest v4, existing `BaselineCompareService`, `BaselineSnapshotTruthResolver`, `BaselineCompareStats`, `RelatedNavigationResolver`, `CanonicalNavigationContext`, `BadgeCatalog`, and `UiEnforcement` patterns (190-baseline-compare-matrix) - PostgreSQL via existing `baseline_profiles`, `baseline_snapshots`, `baseline_snapshot_items`, `baseline_tenant_assignments`, `operation_runs`, and `findings` tables; no new persistence planned (190-baseline-compare-matrix) +- PHP 8.4.15 + Laravel 12, Filament v5, Livewire v4, Pest v4, Tailwind CSS v4, existing `BaselineCompareMatrixBuilder`, `BadgeCatalog`, `CanonicalNavigationContext`, and `UiEnforcement` patterns (191-baseline-compare-operator-mode) +- PostgreSQL via existing baseline, assignment, compare-run, and finding tables; no new persistence planned (191-baseline-compare-operator-mode) - PHP 8.4.15 (feat/005-bulk-operations) @@ -201,8 +203,7 @@ ## Code Style PHP 8.4.15: Follow standard conventions ## Recent Changes +- 191-baseline-compare-operator-mode: Added PHP 8.4.15 + Laravel 12, Filament v5, Livewire v4, Pest v4, Tailwind CSS v4, existing `BaselineCompareMatrixBuilder`, `BadgeCatalog`, `CanonicalNavigationContext`, and `UiEnforcement` patterns - 190-baseline-compare-matrix: Added PHP 8.4.15 + Laravel 12, Filament v5, Livewire v4, Pest v4, existing `BaselineCompareService`, `BaselineSnapshotTruthResolver`, `BaselineCompareStats`, `RelatedNavigationResolver`, `CanonicalNavigationContext`, `BadgeCatalog`, and `UiEnforcement` patterns -- 189-portfolio-triage-review-state: Added PHP 8.4.15 + Laravel 12, Filament v5, Livewire v4, Pest v4, existing `WorkspaceOverviewBuilder`, `TenantResource`, `TenantDashboard`, `PortfolioArrivalContext`, `TenantBackupHealthResolver`, `RestoreSafetyResolver`, `BadgeCatalog`, `UiEnforcement`, and `AuditRecorder` patterns -- 188-provider-connection-state-cleanup: Added PHP 8.4.15 + Laravel 12, Filament v5, Livewire v4, Pest v4, existing `ProviderConnection` model, `ProviderConnectionResolver`, `ProviderConnectionStateProjector`, `ProviderConnectionMutationService`, `ProviderConnectionHealthCheckJob`, `StartVerification`, `ProviderConnectionResource`, `TenantResource`, system directory pages, `BadgeCatalog`, `BadgeRenderer`, and shared provider-state Blade entries diff --git a/specs/191-baseline-compare-operator-mode/contracts/baseline-compare-operator-mode.logical.openapi.yaml b/specs/191-baseline-compare-operator-mode/contracts/baseline-compare-operator-mode.logical.openapi.yaml new file mode 100644 index 00000000..5283fb2b --- /dev/null +++ b/specs/191-baseline-compare-operator-mode/contracts/baseline-compare-operator-mode.logical.openapi.yaml @@ -0,0 +1,501 @@ +openapi: 3.1.0 +info: + title: Baseline Compare Matrix Operator Mode Internal Surface Contract + version: 0.1.0 + summary: Internal logical contract for adaptive operator-density rendering on the existing baseline compare matrix route + description: | + This contract is an internal planning artifact for Spec 191. The affected surface + still renders HTML through Filament and Livewire. The schemas below define the + bounded request-scoped presentation models and staged filter interactions that must + be derivable from existing Spec 190 matrix truth before the operator-density + refactor can render safely. +servers: + - url: /internal +x-baseline-compare-operator-mode-consumers: + - surface: baseline.compare.matrix + sourceFiles: + - apps/platform/app/Filament/Pages/BaselineCompareMatrix.php + - apps/platform/resources/views/filament/pages/baseline-compare-matrix.blade.php + mustRender: + - reference + - presentation_state + - support_surface_state + - applied_filters + - tenant_summaries + - dense_rows_or_compact_results + - last_updated_at + - auto_refresh_state + mustAccept: + - mode + - policy_type + - state + - severity + - tenant_sort + - subject_sort + - subject_key + mustStage: + - selectedPolicyTypes + - selectedStates + - selectedSeverities +paths: + /admin/baseline-profiles/{profile}/compare-matrix: + get: + summary: Render the existing baseline compare matrix using adaptive operator-density presentation + operationId: viewBaselineCompareOperatorMode + parameters: + - name: profile + in: path + required: true + schema: + type: integer + - name: mode + in: query + required: false + schema: + $ref: '#/components/schemas/PresentationMode' + - name: policy_type + in: query + required: false + schema: + type: array + items: + type: string + - name: state + in: query + required: false + schema: + type: array + items: + $ref: '#/components/schemas/MatrixCellState' + - name: severity + in: query + required: false + schema: + type: array + items: + $ref: '#/components/schemas/FindingSeverity' + - name: tenant_sort + in: query + required: false + schema: + type: string + - name: subject_sort + in: query + required: false + schema: + type: string + - name: subject_key + in: query + required: false + schema: + type: string + responses: + '200': + description: Rendered matrix plus adaptive operator-density read models + content: + text/html: + schema: + type: string + application/vnd.tenantpilot.baseline-compare-operator-mode+json: + schema: + $ref: '#/components/schemas/BaselineCompareOperatorModeBundle' + '403': + description: Actor is in scope but lacks workspace baseline view capability + '404': + description: Workspace or baseline profile is outside actor scope + /internal/workspaces/{workspace}/baseline-profiles/{profile}/compare-matrix/apply-filters: + post: + summary: Apply staged heavy filters to the operator-density matrix route + operationId: applyBaselineCompareOperatorFilters + parameters: + - name: workspace + in: path + required: true + schema: + type: integer + - name: profile + in: path + required: true + schema: + type: integer + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/MatrixFilterDraft' + responses: + '200': + description: Updated operator-density bundle using the applied filter state + content: + application/vnd.tenantpilot.baseline-compare-operator-mode+json: + schema: + $ref: '#/components/schemas/BaselineCompareOperatorModeBundle' + '403': + description: Actor is in scope but lacks workspace baseline view capability + '404': + description: Workspace or baseline profile is outside actor scope + /internal/workspaces/{workspace}/baseline-profiles/{profile}/compare-matrix/reset-filters: + post: + summary: Reset staged and applied heavy filters for the operator-density matrix route + operationId: resetBaselineCompareOperatorFilters + parameters: + - name: workspace + in: path + required: true + schema: + type: integer + - name: profile + in: path + required: true + schema: + type: integer + responses: + '200': + description: Updated operator-density bundle with default filter state restored + content: + application/vnd.tenantpilot.baseline-compare-operator-mode+json: + schema: + $ref: '#/components/schemas/BaselineCompareOperatorModeBundle' + '403': + description: Actor is in scope but lacks workspace baseline view capability + '404': + description: Workspace or baseline profile is outside actor scope +components: + schemas: + PresentationMode: + type: string + enum: + - auto + - dense + - compact + MatrixCellState: + type: string + enum: + - match + - differ + - missing + - ambiguous + - not_compared + - stale_result + FindingSeverity: + type: string + enum: + - low + - medium + - high + - critical + FreshnessState: + type: string + enum: + - fresh + - stale + - never_compared + - unknown + TrustLevel: + type: string + enum: + - trustworthy + - limited_confidence + - diagnostic_only + - unusable + AttentionLevel: + type: string + enum: + - aligned + - review + - refresh_recommended + - needs_attention + MatrixReference: + type: object + additionalProperties: false + required: + - baselineProfileId + - baselineProfileName + - referenceState + - assignedTenantCount + - visibleTenantCount + properties: + baselineProfileId: + type: integer + baselineProfileName: + type: string + referenceSnapshotId: + type: + - integer + - 'null' + referenceState: + type: string + assignedTenantCount: + type: integer + visibleTenantCount: + type: integer + MatrixFilterDraft: + type: object + additionalProperties: false + required: + - selectedPolicyTypes + - selectedStates + - selectedSeverities + - tenantSort + - subjectSort + properties: + selectedPolicyTypes: + type: array + items: + type: string + selectedStates: + type: array + items: + $ref: '#/components/schemas/MatrixCellState' + selectedSeverities: + type: array + items: + $ref: '#/components/schemas/FindingSeverity' + tenantSort: + type: string + subjectSort: + type: string + focusedSubjectKey: + type: + - string + - 'null' + MatrixPresentationState: + type: object + additionalProperties: false + required: + - requestedMode + - resolvedMode + - visibleTenantCount + - activeFilterCount + - hasStagedFilterChanges + - autoRefreshActive + - canOverrideMode + properties: + requestedMode: + $ref: '#/components/schemas/PresentationMode' + resolvedMode: + type: string + enum: + - dense + - compact + visibleTenantCount: + type: integer + activeFilterCount: + type: integer + hasStagedFilterChanges: + type: boolean + autoRefreshActive: + type: boolean + lastUpdatedAt: + type: + - string + - 'null' + format: date-time + canOverrideMode: + type: boolean + MatrixTenantSummary: + type: object + additionalProperties: false + required: + - tenantId + - tenantName + - freshnessState + - differingCount + - missingCount + - ambiguousCount + - trustLevel + properties: + tenantId: + type: integer + tenantName: + type: string + freshnessState: + $ref: '#/components/schemas/FreshnessState' + lastComparedAt: + type: + - string + - 'null' + format: date-time + differingCount: + type: integer + missingCount: + type: integer + ambiguousCount: + type: integer + trustLevel: + $ref: '#/components/schemas/TrustLevel' + maxSeverity: + type: + - string + - 'null' + DenseCellView: + type: object + additionalProperties: false + required: + - tenantId + - subjectKey + - state + - freshnessState + - trustLevel + - attentionLevel + properties: + tenantId: + type: integer + subjectKey: + type: string + state: + $ref: '#/components/schemas/MatrixCellState' + freshnessState: + $ref: '#/components/schemas/FreshnessState' + trustLevel: + $ref: '#/components/schemas/TrustLevel' + severity: + type: + - string + - 'null' + attentionLevel: + $ref: '#/components/schemas/AttentionLevel' + reasonSummary: + type: + - string + - 'null' + primaryDrilldownUrl: + type: + - string + - 'null' + secondaryDrilldownUrls: + type: object + additionalProperties: + type: string + DenseSubjectRowView: + type: object + additionalProperties: false + required: + - subjectKey + - displayName + - policyType + - deviationBreadth + - missingBreadth + - ambiguousBreadth + - trustLevel + - cells + properties: + subjectKey: + type: string + displayName: + type: string + policyType: + type: string + baselineExternalId: + type: + - string + - 'null' + deviationBreadth: + type: integer + missingBreadth: + type: integer + ambiguousBreadth: + type: integer + maxSeverity: + type: + - string + - 'null' + trustLevel: + $ref: '#/components/schemas/TrustLevel' + cells: + type: array + items: + $ref: '#/components/schemas/DenseCellView' + CompactSubjectResultView: + type: object + additionalProperties: false + required: + - tenantId + - subjectKey + - displayName + - policyType + - state + - freshnessState + - trustLevel + properties: + tenantId: + type: integer + subjectKey: + type: string + displayName: + type: string + policyType: + type: string + state: + $ref: '#/components/schemas/MatrixCellState' + freshnessState: + $ref: '#/components/schemas/FreshnessState' + trustLevel: + $ref: '#/components/schemas/TrustLevel' + severity: + type: + - string + - 'null' + reasonSummary: + type: + - string + - 'null' + primaryDrilldownUrl: + type: + - string + - 'null' + runUrl: + type: + - string + - 'null' + MatrixSupportSurfaceState: + type: object + additionalProperties: false + required: + - legendMode + - showActiveFilterSummary + - showLastUpdated + - showAutoRefreshHint + - showBlockingRefreshState + properties: + legendMode: + type: string + showActiveFilterSummary: + type: boolean + showLastUpdated: + type: boolean + showAutoRefreshHint: + type: boolean + showBlockingRefreshState: + type: boolean + BaselineCompareOperatorModeBundle: + type: object + additionalProperties: false + required: + - reference + - presentation + - supportSurface + - appliedFilters + - tenantSummaries + properties: + reference: + $ref: '#/components/schemas/MatrixReference' + presentation: + $ref: '#/components/schemas/MatrixPresentationState' + supportSurface: + $ref: '#/components/schemas/MatrixSupportSurfaceState' + appliedFilters: + $ref: '#/components/schemas/MatrixFilterDraft' + tenantSummaries: + type: array + items: + $ref: '#/components/schemas/MatrixTenantSummary' + denseRows: + type: array + items: + $ref: '#/components/schemas/DenseSubjectRowView' + compactResults: + type: array + items: + $ref: '#/components/schemas/CompactSubjectResultView' \ No newline at end of file diff --git a/specs/191-baseline-compare-operator-mode/data-model.md b/specs/191-baseline-compare-operator-mode/data-model.md new file mode 100644 index 00000000..05e75d18 --- /dev/null +++ b/specs/191-baseline-compare-operator-mode/data-model.md @@ -0,0 +1,166 @@ +# Data Model: Baseline Compare Matrix: High-Density Operator Mode + +## Overview + +This follow-up introduces no new persisted entity. It reuses the existing Spec 190 matrix truth and adds derived presentation models for operator density, staged filtering, and non-blocking status cues. + +## Existing Source Truths Reused Without Change + +### Baseline compare truth from Spec 190 + +The following derived or canonical inputs remain authoritative and are not redefined by this spec: + +- workspace-scoped baseline reference truth +- visible tenant summaries +- subject summaries +- subject-by-tenant matrix cells +- compare-start availability and existing drilldown destinations + +This spec changes how those inputs are rendered and interacted with, not how they are computed. + +## New Derived Presentation Models + +### MatrixPresentationState + +**Type**: request-scoped page presentation contract +**Source**: route/query state + visible tenant count + existing run state + +| Field | Type | Notes | +|------|------|-------| +| `requestedMode` | string | `auto`, `dense`, or `compact` from route/query state | +| `resolvedMode` | string | Final mode used for rendering: `dense` or `compact` | +| `visibleTenantCount` | integer | Existing visible-set count from the matrix bundle | +| `activeFilterCount` | integer | Count of currently applied filters | +| `hasStagedFilterChanges` | boolean | Whether filter draft state differs from applied state | +| `autoRefreshActive` | boolean | True when background polling is active because compare work is queued or running | +| `lastUpdatedAt` | datetime or null | Timestamp for the currently rendered matrix data | +| `canOverrideMode` | boolean | Whether the operator may locally switch away from `auto` | + +### MatrixFilterDraft + +**Type**: request-scoped staged filter model +**Source**: page form state only + +| Field | Type | Notes | +|------|------|-------| +| `selectedPolicyTypes` | array | Draft policy-type filter selection | +| `selectedStates` | array | Draft state-group selection | +| `selectedSeverities` | array | Draft severity selection | +| `tenantSort` | string | Current tenant sort choice | +| `subjectSort` | string | Current subject sort choice | +| `focusedSubjectKey` | string or null | Optional current subject focus | + +### DenseSubjectRowView + +**Type**: request-scoped dense-mode row view +**Source**: existing subject summary + existing matrix cells + +| Field | Type | Notes | +|------|------|-------| +| `subjectKey` | string | Stable row key | +| `displayName` | string | Primary row label | +| `policyType` | string | Compact secondary label | +| `baselineExternalId` | string or null | Optional secondary context | +| `deviationBreadth` | integer | Existing subject summary metric | +| `missingBreadth` | integer | Existing subject summary metric | +| `ambiguousBreadth` | integer | Existing subject summary metric | +| `maxSeverity` | string or null | Existing subject summary severity | +| `trustLevel` | string | Existing subject summary trust | +| `cells` | array | One condensed cell per visible tenant | + +### DenseCellView + +**Type**: request-scoped dense-mode cell view +**Source**: existing matrix cell + existing tenant summary freshness + +| Field | Type | Notes | +|------|------|-------| +| `tenantId` | integer | Visible tenant identifier | +| `subjectKey` | string | Subject row key | +| `state` | string | Existing Spec 190 state | +| `freshnessState` | string | Freshness signal shown in compact form | +| `trustLevel` | string | Trust signal shown in compact form | +| `severity` | string or null | Optional attention signal | +| `attentionLevel` | string | Derived presentation label such as `aligned`, `refresh_recommended`, or `needs_attention` | +| `reasonSummary` | string or null | Short secondary explanation for compact reveal surfaces | +| `primaryDrilldownUrl` | string or null | Preferred next follow-up action | +| `secondaryDrilldownUrls` | array | Additional compact follow-up links when available | + +### CompactSubjectResultView + +**Type**: request-scoped single-tenant row view +**Source**: one visible tenant summary + existing matrix cell + existing subject summary + +| Field | Type | Notes | +|------|------|-------| +| `tenantId` | integer | The single visible tenant in compact mode | +| `subjectKey` | string | Stable subject key | +| `displayName` | string | Primary subject label | +| `policyType` | string | Secondary grouping/context | +| `state` | string | Existing Spec 190 state | +| `freshnessState` | string | Compact freshness label | +| `trustLevel` | string | Compact trust label | +| `severity` | string or null | Optional attention indicator | +| `reasonSummary` | string or null | Short explanation line | +| `primaryDrilldownUrl` | string or null | Main follow-up action | +| `runUrl` | string or null | Secondary run-level follow-up | + +### MatrixSupportSurfaceState + +**Type**: request-scoped supporting-context contract +**Source**: page state + existing legends + refresh metadata + +| Field | Type | Notes | +|------|------|-------| +| `legendMode` | string | `grouped`, `collapsed`, or equivalent compact support behavior | +| `showActiveFilterSummary` | boolean | Whether applied filters are summarized inline | +| `showLastUpdated` | boolean | Whether the page displays last-updated metadata | +| `showAutoRefreshHint` | boolean | Whether passive auto-refresh copy is visible | +| `showBlockingRefreshState` | boolean | Reserved for deliberate user-triggered reloads only | + +## Rendering and Resolution Rules + +### Mode resolution rules + +1. If `requestedMode = auto` and `visibleTenantCount > 1`, resolve to `dense`. +2. If `requestedMode = auto` and `visibleTenantCount = 1`, resolve to `compact`. +3. If a manual override is present, use it unless it would produce an invalid empty layout. +4. Manual override remains route-local and must never be persisted as product truth. + +### Dense-mode rules + +- The subject column remains sticky during horizontal scroll. +- The primary visible content per cell is state, trust, freshness, and attention. +- Long explanatory text and repeated action links do not render as the dominant cell body. + +### Compact single-tenant rules + +- The tenant header does not repeat as a pseudo-column structure. +- Each subject entry shows one primary status line and a reduced set of secondary metadata. +- Existing subject focus and drilldown continuity remain available. + +### Filter workflow rules + +- Heavy multi-select filters use staged state first and apply only when the operator confirms. +- Applied filter count and scope summary reflect the applied state, not merely the draft state. +- Reset may clear both draft and applied state in one explicit action. + +### Status signal rules + +- `blocking refresh` is reserved for deliberate user-triggered reload or recalculation moments. +- `auto-refresh active` indicates passive polling while compare work is still queued or running. +- `lastUpdatedAt` reflects the timestamp of the rendered matrix payload, not merely the latest compare run in the system. + +### Safety rules + +- No rendering path may widen tenant visibility beyond the existing visible set. +- No presentation-state change may change the underlying compare state, trust, or freshness semantics. +- No grouped legend or compact cell may invent new status vocabulary outside existing centralized badge semantics. + +## Relationships + +- One `MatrixPresentationState` governs one rendered matrix page. +- One `MatrixFilterDraft` belongs to one `MatrixPresentationState`. +- In dense mode, one `DenseSubjectRowView` maps to many `DenseCellView` entries. +- In compact mode, one visible tenant yields many `CompactSubjectResultView` entries. +- One `MatrixSupportSurfaceState` coordinates legends, refresh hints, and active-filter summaries for the same page render. \ No newline at end of file diff --git a/specs/191-baseline-compare-operator-mode/plan.md b/specs/191-baseline-compare-operator-mode/plan.md index 34499099..a4d8de51 100644 --- a/specs/191-baseline-compare-operator-mode/plan.md +++ b/specs/191-baseline-compare-operator-mode/plan.md @@ -3,64 +3,108 @@ # Implementation Plan: Baseline Compare Matrix: High-Density Operator Mode **Branch**: `191-baseline-compare-operator-mode` | **Date**: 2026-04-11 | **Spec**: `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/191-baseline-compare-operator-mode/spec.md` **Input**: Feature specification from `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/191-baseline-compare-operator-mode/spec.md` +**Note**: This plan formalizes the existing 191 spec slice and keeps the work strictly inside the already-shipped Spec 190 matrix surface. + ## Summary -Rework the existing baseline compare matrix route into an operator-density follow-up to Spec 190. The route stays workspace-scoped and fully derived, but gains adaptive presentation rules: dense multi-tenant scanning when several visible tenants are present, compact single-tenant comparison when only one visible tenant remains, and calmer filter, legend, action, and refresh surfaces. +Refactor the existing workspace baseline compare matrix into an adaptive operator-density surface. The route, baseline reference, visible-set-only truth, compare-start behavior, and drilldowns stay unchanged, but the page gains local presentation-mode state, dense multi-tenant scanning, compact single-tenant rendering, staged heavy-filter application, grouped legends, and clearer separation between blocking refresh, passive auto-refresh, and last-updated status. ## Technical Context **Language/Version**: PHP 8.4.15 -**Primary Dependencies**: Laravel 12, Filament v5, Livewire v4, Pest v4, existing `BaselineCompareMatrixBuilder`, `BadgeCatalog`, `CanonicalNavigationContext`, and `UiEnforcement` patterns -**Storage**: Existing PostgreSQL truth only; no new tables or artifacts -**Testing**: Pest feature tests and one browser smoke path through Sail +**Primary Dependencies**: Laravel 12, Filament v5, Livewire v4, Pest v4, Tailwind CSS v4, existing `BaselineCompareMatrixBuilder`, `BadgeCatalog`, `CanonicalNavigationContext`, and `UiEnforcement` patterns +**Storage**: PostgreSQL via existing baseline, assignment, compare-run, and finding tables; no new persistence planned +**Testing**: Pest feature tests and browser smoke coverage run through Laravel Sail **Target Platform**: Laravel monolith web application under `apps/platform` **Project Type**: web application -**Performance Goals**: Improve operator scan throughput without adding more data queries than Spec 190; keep heavy filter changes explicit rather than chatty -**Constraints**: No compare-logic changes, no new persistence, no hidden-tenant leakage, no generalized UI framework, no Filament provider changes -**Scale/Scope**: One existing matrix page, one existing view, one existing builder, and focused test coverage updates +**Performance Goals**: Improve scan throughput without increasing query shape beyond Spec 190, keep heavy filter changes non-chatty, and preserve DB-only render-time matrix surfaces +**Constraints**: No compare-logic change, no new persistence, no hidden-tenant leakage, no generalized density framework, no provider or panel changes, and no new asset pipeline +**Scale/Scope**: One existing matrix page, one existing Blade view, one existing builder, one logical contract file, and focused feature plus browser regressions ## Constitution Check -*GATE: Passed before design. No new source-of-truth or persistence changes are expected.* +*GATE: Passed before Phase 0 research. Re-checked after Phase 1 design and still passing.* -| Principle | Status | Notes | -|-----------|--------|-------| -| Inventory-first / snapshots-second | PASS | The feature changes presentation only and keeps Spec 190 truth sources intact. | -| Read/write separation | PASS | `Compare assigned tenants` remains the only mutation and already exists. | -| Workspace + tenant isolation | PASS | Visible-set-only behavior remains unchanged. | -| RBAC-UX | PASS | Existing `404` vs `403` semantics stay intact; only presentation changes. | -| Ops-UX 3-surface feedback | PASS | Refresh and polling surfaces are clarified visually without changing run semantics. | -| Proportionality / anti-bloat | PASS | No new persistence, enum, framework, or cross-domain abstraction is introduced. | -| UI semantics / few layers | PASS | Dense and compact modes reuse existing badge and compare semantics rather than inventing new status taxonomies. | -| Filament v5 / Livewire v4 compliance | PASS | Work remains on the existing Filament page and Livewire-backed route. | -| Provider registration location | PASS | No provider changes; registration remains in `bootstrap/providers.php`. | -| Global search hard rule | PASS | No new global-searchable resource or page is introduced. | -| Destructive action safety | PASS | No destructive action is added by this spec. | -| Asset strategy | PASS | No new panel assets or shared assets are required. Existing deployment use of `filament:assets` remains unchanged. | +| Principle | Pre-Research | Post-Design | Notes | +|-----------|--------------|-------------|-------| +| Inventory-first / snapshots-second | PASS | PASS | The spec changes presentation only and keeps Spec 190 truth sources intact. | +| Read/write separation | PASS | PASS | `Compare assigned tenants` remains the only mutation and is unchanged. | +| Graph contract path | N/A | N/A | No new Graph behavior or contract-registry work is introduced. | +| Deterministic capabilities | PASS | PASS | Existing capabilities remain canonical and unchanged. | +| Workspace + tenant isolation | PASS | PASS | Visible-set-only aggregation and drilldown scope remain unchanged. | +| RBAC-UX authorization semantics | PASS | PASS | Existing `404` vs `403` semantics and server-side enforcement remain unchanged. | +| Run observability / Ops-UX | PASS | PASS | Compare-run truth is reused exactly as in Spec 190; this spec only clarifies the visual cues around it. | +| Data minimization | PASS | PASS | No new data copies, exports, or persisted UI artifacts are introduced. | +| Proportionality / anti-bloat | PASS | PASS | The work stays local to one page and does not add a new abstraction or stored artifact. | +| Persisted truth / behavioral state | PASS | PASS | Presentation mode and staged filter state remain request-scoped only. | +| UI semantics / few layers | PASS | PASS | Existing state, trust, freshness, and severity semantics are reused rather than redefined. | +| Filament v5 / Livewire v4 compliance | PASS | PASS | The work remains inside the existing Filament page and Livewire-backed route. | +| Provider registration location | PASS | PASS | No provider changes are required; Laravel 11+ registration remains in `bootstrap/providers.php`. | +| Global search hard rule | PASS | PASS | No new searchable resource or page is introduced. | +| Destructive action safety | PASS | PASS | No destructive action is added. Existing confirmation behavior for compare-start remains unchanged. | +| Asset strategy | PASS | PASS | No new assets are required. Existing deployment behavior for `cd apps/platform && php artisan filament:assets` remains unchanged. | ## Filament-Specific Compliance Notes -- **Livewire v4.0+ compliance**: This plan stays on the existing Filament v5 + Livewire v4 page stack and does not introduce legacy APIs. -- **Provider registration location**: No panel/provider work is needed. Laravel 11+ provider registration remains in `bootstrap/providers.php`. -- **Global search**: This spec does not add a new globally searchable resource. Existing baseline-resource search behavior is unchanged. +- **Livewire v4.0+ compliance**: This plan remains on Filament v5 + Livewire v4 and does not introduce legacy APIs. +- **Provider registration location**: No panel or provider changes are needed; Laravel 11+ provider registration remains in `bootstrap/providers.php`. +- **Global search**: The feature does not add a new globally searchable resource. Existing baseline-resource search behavior is unchanged. - **Destructive actions**: No new destructive action is introduced. Existing compare-start actions remain confirmation-gated where already defined. - **Asset strategy**: No new global or on-demand asset registration is planned. Deployment handling of `cd apps/platform && php artisan filament:assets` remains unchanged. -- **Testing plan**: Extend the existing matrix feature and browser suites to cover presentation mode, density, compact controls, and non-blocking status surfaces. +- **Testing plan**: Extend the existing matrix feature, builder, guard, and browser suites to cover presentation mode, staged filter application, and non-blocking status surfaces. + +## Phase 0 Research + +Research outcomes are captured in `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/191-baseline-compare-operator-mode/research.md`. + +Key decisions: + +- Keep the existing matrix route and truth model and change presentation only. +- Resolve `auto`, `dense`, and `compact` mode from visible tenant count, with a route-local override only. +- Make dense mode state-first rather than action-first. +- Render single-tenant review as a compact compare list rather than a one-column matrix. +- Convert heavy filters to staged apply/reset semantics. +- Replace the long policy-type checkbox stack with a more compact operator-first selector. +- Group legends into compact support context and separate blocking refresh from passive auto-refresh and last-updated cues. +- Reuse existing drilldown and visible-set semantics unchanged. + +## Phase 1 Design + +Design artifacts are created under `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/191-baseline-compare-operator-mode/`: + +- `research.md`: decisions and rejected alternatives for local operator-density work +- `data-model.md`: request-scoped presentation models for mode state, staged filters, dense rows, compact results, and support-surface state +- `contracts/baseline-compare-operator-mode.logical.openapi.yaml`: internal logical contract for adaptive rendering and staged filter application +- `quickstart.md`: implementation and verification sequence for the follow-up spec + +Design decisions: + +- `auto` remains the default requested mode and resolves to `dense` for multiple visible tenants and `compact` for exactly one visible tenant. +- Manual mode override remains route-local and must never become stored product truth. +- Dense mode reuses existing compare truth but condenses cell content to state, trust, freshness, and attention. +- Compact mode reuses the same truth but removes pseudo-matrix structure once only one visible tenant remains. +- Heavy filter inputs stage locally and apply explicitly; lightweight route-state changes may remain immediate. +- Grouped legends, passive auto-refresh, and last-updated signals become support context rather than competing top-level content. ## Project Structure -### Documentation +### Documentation (this feature) ```text specs/191-baseline-compare-operator-mode/ -├── spec.md ├── plan.md +├── research.md +├── data-model.md +├── quickstart.md +├── spec.md ├── tasks.md +├── contracts/ +│ └── baseline-compare-operator-mode.logical.openapi.yaml └── checklists/ └── requirements.md ``` -### Source Code +### Source Code (repository root) ```text apps/platform/ @@ -77,84 +121,80 @@ ### Source Code ├── Browser/ │ └── Spec190BaselineCompareMatrixSmokeTest.php ├── Feature/ + │ ├── Baselines/ + │ │ └── BaselineCompareMatrixBuilderTest.php │ ├── Filament/ │ │ └── BaselineCompareMatrixPageTest.php │ └── Guards/ │ └── ActionSurfaceContractTest.php - └── Feature/Baselines/ - └── BaselineCompareMatrixBuilderTest.php + └── Unit/ + └── Badges/ ``` -**Structure Decision**: Keep the work inside the existing Spec 190 implementation surface. This follow-up spec is a refactor of one page and its supporting builder/view behavior, not a new domain slice. +**Structure Decision**: Keep the work inside the existing Spec 190 matrix implementation surface. This is a presentation refactor of one existing page and its supporting builder/view behavior, not a new domain slice or a new application area. -## Key Design Decisions +## Complexity Tracking -### D-001 — Keep the route and truth model unchanged +| Violation | Why Needed | Simpler Alternative Rejected Because | +|-----------|------------|-------------------------------------| +| None | N/A | The follow-up stays within the existing page, builder, and test surfaces and introduces no new structural violation. | -This spec modifies the existing `/compare-matrix` route only. No second route, second matrix artifact, or separate dense-report model is created. +## Proportionality Review -### D-002 — Use adaptive presentation, not separate features - -`auto` mode is the canonical default. `dense` and `compact` exist as local operator overrides, but the product concept remains one matrix page with adaptive presentation. - -### D-003 — Keep dense cells state-first - -Dense mode cells must prioritize compare state, trust, freshness, and attention. Detailed reasons and multiple navigation targets become secondary reveals rather than permanent default chrome. - -### D-004 — Treat controls as supporting context - -Filters, legends, and refresh hints remain available but must become visibly subordinate to the matrix body. The page should read as a working surface, not a form-first screen. - -### D-005 — Keep single-tenant mode honest - -If only one visible tenant remains, the operator should see a compact comparison surface rather than an artificially wide matrix. The page should not preserve multi-tenant structure when it no longer helps. +- **Current operator problem**: The matrix already answers the right governance question, but not with enough density or calmness for repeated operator scanning. +- **Existing structure is insufficient because**: The current single rendering shape tries to serve both multi-tenant and single-tenant workflows, so supporting context, action repetition, and cell chrome are too heavy in both cases. +- **Narrowest correct implementation**: Keep the same route, truth sources, drilldowns, and compare semantics while adding route-local presentation state, denser rendering, and staged filter application. +- **Ownership cost created**: Additional view-state logic, a logical contract file, and focused regression coverage for mode resolution, filter workflow, and status visibility. +- **Alternative intentionally rejected**: A generalized density framework, a separate dense-report route, or a stored matrix artifact were rejected because the problem is local to the existing matrix surface. +- **Release truth**: current-release operator workflow compression ## Implementation Strategy ### Phase A — Presentation Mode Contract -- Add `auto`, `dense`, and `compact` mode state to the page. -- Keep override state local to the route and compatible with existing drilldown URLs. -- Reuse the current derived matrix bundle instead of adding a second persisted view model. +- Add route-local `auto`, `dense`, and `compact` mode state. +- Resolve the active mode from visible tenant count unless manually overridden. +- Expose `lastUpdatedAt`, `hasStagedFilterChanges`, and passive auto-refresh state to the page. ### Phase B — Dense Multi-Tenant Surface -- Reduce per-cell chrome and prioritize state/trust/freshness. -- Keep the subject axis sticky and readable across horizontal scroll. -- Move repeated actions into compact secondary affordances where necessary. +- Keep the subject column sticky during horizontal scroll. +- Condense dense cells to state, trust, freshness, and attention signals. +- Move repeated actions into compact secondary affordances without breaking drilldown continuity. ### Phase C — Compact Single-Tenant Surface -- Replace pseudo-matrix presentation with a shorter, calmer list optimized for one visible tenant. -- Remove repeated tenant headers and duplicated labels. -- Preserve subject focus and drilldowns. +- Replace pseudo-matrix rendering with a compact subject-result list when only one visible tenant remains. +- Remove repeated tenant headers and duplicated secondary metadata. +- Preserve subject focus and the existing compare/finding/run destinations. ### Phase D — Supporting Context Compression -- Convert heavy filters to an apply/reset workflow. -- Compress legends into grouped or collapsible supporting blocks. -- Clarify background polling, manual refresh, and last-updated status without using blocking loading surfaces. +- Convert heavy matrix filters to staged apply/reset behavior. +- Replace the current long policy-type control with a more compact selector. +- Group or collapse legends. +- Separate blocking refresh from passive auto-refresh and last-updated status. ### Phase E — Verification -- Extend feature coverage for mode selection and density rules. -- Extend browser coverage for one dense-mode path and one compact-mode path. -- Keep existing Spec 190 truth and RBAC guarantees intact. +- Extend focused feature coverage for mode resolution, staged filter behavior, and support-surface state. +- Extend browser smoke coverage for one dense-mode path and one compact-mode path. +- Keep existing Spec 190 matrix truth, drilldown continuity, and RBAC guarantees green. -## Risks & Mitigations +## Risk Assessment | Risk | Impact | Likelihood | Mitigation | |------|--------|------------|------------| -| Dense mode becomes another framework | Medium | Low | Keep presentation logic local to the matrix page and view. | -| Compact mode hides too much drilldown value | Medium | Medium | Keep one clear follow-up path per subject and preserve existing drilldowns. | -| Apply/reset feels stale compared with live filters | Medium | Medium | Make staged filter state obvious and keep reset immediate. | -| Manual override confuses operators | Low | Medium | Keep `auto` as default and label override state clearly. | +| Dense mode becomes another framework | Medium | Low | Keep presentation logic local to the matrix page and avoid generalized shared abstractions. | +| Compact mode hides too much follow-up value | Medium | Medium | Preserve one clear primary drilldown per subject and keep existing follow-up destinations intact. | +| Staged filtering feels slow or unclear | Medium | Medium | Show explicit staged/applied state and keep reset obvious. | +| Manual override confuses operators | Low | Medium | Keep `auto` as the default and surface the resolved mode clearly. | +| Last-updated and auto-refresh cues drift out of sync | Medium | Low | Derive both cues from the same rendered matrix payload and active-run state. | ## Test Strategy -- Extend feature tests for mode resolution based on visible tenant count. -- Add assertions for dense multi-tenant sticky subject behavior and reduced visible action noise. -- Add assertions for compact single-tenant rendering and shorter supporting chrome. -- Add coverage for explicit filter apply/reset behavior, grouped legends, and page-level last-updated messaging. -- Reuse existing browser smoke coverage and extend it for one dense path plus one compact-mode path. -- Run `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` and the focused matrix-related Pest suite before sign-off. \ No newline at end of file +- Extend `BaselineCompareMatrixPageTest` for requested vs resolved mode, active filter application, compact vs dense rendering, and non-blocking refresh cues. +- Extend `BaselineCompareMatrixBuilderTest` for any new derived presentation metadata required by the page. +- Keep `ActionSurfaceContractTest` green so calmer actions do not regress the surface contract. +- Extend `Spec190BaselineCompareMatrixSmokeTest` to prove one dense-mode and one compact-mode operator path on the Livewire page. +- Run the focused Sail verification pack from `quickstart.md` and re-run `update-agent-context.sh copilot` after the plan is finalized. diff --git a/specs/191-baseline-compare-operator-mode/quickstart.md b/specs/191-baseline-compare-operator-mode/quickstart.md new file mode 100644 index 00000000..359a49d2 --- /dev/null +++ b/specs/191-baseline-compare-operator-mode/quickstart.md @@ -0,0 +1,70 @@ +# Quickstart: Baseline Compare Matrix: High-Density Operator Mode + +## Goal + +Turn the existing baseline compare matrix into a denser operator surface without changing its underlying compare truth. Multi-tenant use should favor high-density cross-tenant scanning, while single-tenant use should collapse into a calmer compact comparison view. + +## Implementation Sequence + +1. Add page-level presentation state. + - Add `auto`, `dense`, and `compact` route-local mode state. + - Resolve the active mode from visible tenant count unless the operator explicitly overrides it. + - Expose `lastUpdatedAt`, staged-filter state, and passive auto-refresh state on the page. + +2. Build the dense multi-tenant rendering contract. + - Keep the subject column sticky. + - Reduce dense-cell chrome to state, trust, freshness, and attention. + - Move repeated follow-up links into compact secondary affordances. + +3. Build the compact single-tenant rendering contract. + - Replace the pseudo-matrix layout with a compact subject-result list. + - Remove repeated tenant headers and repeated metadata blocks. + - Preserve subject focus and existing drilldowns. + +4. Compress supporting context. + - Convert heavy filters to staged apply/reset semantics. + - Replace the current long policy-type list with a more compact operator-first control. + - Group or collapse legends so they remain available without dominating the page. + - Separate blocking refresh from passive auto-refresh and last-updated status. + +5. Extend regression coverage. + - Cover mode resolution, dense multi-tenant layout, compact single-tenant layout, staged filters, and non-blocking refresh cues. + - Keep existing Spec 190 matrix truth, drilldown continuity, and RBAC guarantees green. + +## Suggested Test Files + +- `apps/platform/tests/Feature/Filament/BaselineCompareMatrixPageTest.php` +- `apps/platform/tests/Feature/Baselines/BaselineCompareMatrixBuilderTest.php` +- `apps/platform/tests/Feature/Guards/ActionSurfaceContractTest.php` +- `apps/platform/tests/Browser/Spec190BaselineCompareMatrixSmokeTest.php` + +## Minimum Verification Commands + +Run all commands through Sail from `apps/platform`. + +```bash +cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Filament/BaselineCompareMatrixPageTest.php +cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Baselines/BaselineCompareMatrixBuilderTest.php +cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Guards/ActionSurfaceContractTest.php +cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Browser/Spec190BaselineCompareMatrixSmokeTest.php +cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent +``` + +## Manual Acceptance Checklist + +1. Open a baseline profile whose matrix has multiple visible tenants and confirm `auto` resolves to dense mode. +2. Verify the first subject column remains visible while horizontally scrolling dense mode. +3. Confirm dense cells foreground compare state, trust, freshness, and attention before links or long prose. +4. Open a matrix that resolves to one visible tenant and confirm `auto` resolves to compact mode instead of a one-column matrix. +5. Change heavy filters and confirm the page stages those changes until the operator applies them. +6. Confirm active filter count and filter summary reflect the applied state clearly. +7. Confirm legends are still understandable but no longer dominate the top of the page. +8. Trigger or observe queued/running compare work and confirm passive auto-refresh does not look like a permanent blocking load. +9. Confirm the page shows when the current matrix payload was last updated. +10. Verify tenant compare, finding, and run drilldowns still preserve the existing matrix context. + +## Deployment Notes + +- No migration is expected. +- No new asset registration is expected. +- No queue topology change is expected because compare execution semantics stay unchanged. \ No newline at end of file diff --git a/specs/191-baseline-compare-operator-mode/research.md b/specs/191-baseline-compare-operator-mode/research.md new file mode 100644 index 00000000..76f5fc40 --- /dev/null +++ b/specs/191-baseline-compare-operator-mode/research.md @@ -0,0 +1,111 @@ +# Research: Baseline Compare Matrix: High-Density Operator Mode + +## Decision: Keep the existing matrix route and truth model, and change presentation only + +### Rationale + +Spec 190 already established the correct workspace route, the correct baseline reference model, and the correct visible-set-only compare truth. The operator-density follow-up should stay on `/admin/baseline-profiles/{record}/compare-matrix` and must not introduce a second route, a second report artifact, or a second source of matrix truth. + +### Alternatives considered + +- Add a separate `dense report` page: rejected because it would duplicate the same baseline-scoped workflow on a second route. +- Add a stored matrix snapshot: rejected because the operator problem is scan efficiency, not missing persistence. + +## Decision: Resolve presentation mode from visible tenant count, with a local override only + +### Rationale + +The core operator split is real: one visible tenant is a compact review problem, while several visible tenants create a cross-tenant scan problem. The narrowest implementation is one requested mode (`auto`, `dense`, or `compact`) and one resolved mode at render time. `auto` should remain the default, while manual override stays local to the matrix route and must not become stored user preference or domain truth. + +### Alternatives considered + +- Separate feature flags or separate navigation entries for each mode: rejected because the matrix should remain one operator surface. +- Persist mode preference per user: rejected because the current need is local workflow control, not profile-level personalization. + +## Decision: Dense mode must be state-first, not action-first + +### Rationale + +In multi-tenant reading, the primary questions are where drift exists, how severe it is, whether the signal is trustworthy, and what deserves follow-up next. Dense cells should therefore foreground compare state, trust, freshness, and attention, while detailed reasons and repeated links move into compact secondary affordances. + +### Alternatives considered + +- Keep the current repeated open-link pattern in every cell: rejected because repeated actions visually outrank the state being scanned. +- Remove cell-level follow-up completely: rejected because the matrix must remain a decision surface, not a dead-end report. + +## Decision: Single-tenant mode should be a compact compare list, not a one-column matrix + +### Rationale + +Once only one visible tenant remains, the value of cross-tenant columns disappears. The surface should switch to a shorter subject-result list that reuses the same truth but removes repeated tenant headers, empty width, and oversized cell chrome. + +### Alternatives considered + +- Reuse dense mode even for one tenant: rejected because it preserves the wrong reading model. +- Route single-tenant viewing away to the tenant compare page: rejected because the operator still started from the workspace baseline matrix context and should not lose that context automatically. + +## Decision: Heavy filters should use staged apply/reset semantics + +### Rationale + +The current matrix is dense enough that chatty recomputation on every multi-select click works against operator flow. Policy types and other heavy matrix filters should stage changes locally, show that staged state clearly, and apply them deliberately. This improves calmness and makes the surface feel less like a form page. + +### Alternatives considered + +- Keep all filters live: rejected because heavy multi-select controls create noisy redraw behavior. +- Convert every filter to manual apply: rejected because lightweight interactions such as mode switching or focused-subject clearing should remain immediate. + +## Decision: Replace the long policy-type checkbox stack with a more compact operator-first selector + +### Rationale + +The policy-type filter is the most visually expensive control on the page. The follow-up spec should use a denser selection pattern such as searchable multi-select, type-to-find, or another compact control that exposes the same filter truth without the current long vertical list. + +### Alternatives considered + +- Keep the long checkbox list and only restyle it: rejected because vertical space is the actual product problem. +- Hide policy type filtering behind a modal by default: rejected because the filter remains core enough to deserve immediate access. + +## Decision: Legends should become grouped support context, optionally collapsible + +### Rationale + +State, freshness, and trust legends remain semantically valuable, especially for onboarding or occasional operators, but they should no longer compete with the matrix for top-of-screen attention. Grouped, compact legend blocks are the narrowest way to preserve semantics while reducing dominance. + +### Alternatives considered + +- Remove legends entirely: rejected because trust and freshness semantics still need an on-page reference. +- Leave three separate full-width legend sections: rejected because they displace the primary working surface. + +## Decision: Separate loading, auto-refresh, and last-updated cues + +### Rationale + +Spec 190 already exposed the risk of background polling reading like permanent blocking load. This follow-up should make three states explicit: active loading for user-triggered refresh, passive auto-refresh while queued or running compare work exists, and last-updated time for the currently rendered matrix. + +### Alternatives considered + +- Reuse one generic refresh chip for all states: rejected because operators cannot tell whether the page is blocked or simply polling. +- Hide refresh state entirely: rejected because operator trust depends on understanding when the matrix is current. + +## Decision: Reuse the existing drilldown and visible-set semantics without change + +### Rationale + +This spec is a presentation refactor, not a navigation or authorization redesign. The existing tenant compare, finding, run-detail, and canonical-navigation context from Spec 190 remain correct and should carry forward unchanged. + +### Alternatives considered + +- Introduce a dense-mode-specific drilldown model: rejected because it would create new behavior where existing follow-up paths are already sufficient. +- Add aggregated hidden-tenant remainder summaries: rejected because visible-set-only semantics explicitly avoid hidden-tenant leakage. + +## Decision: Validate primarily with focused page, builder, guard, and browser coverage + +### Rationale + +The highest-risk changes are mode resolution, dense-cell hierarchy, compact single-tenant rendering, filter apply behavior, and non-blocking refresh cues. These are best covered with focused feature tests plus one browser smoke path for the interactive Livewire surface. + +### Alternatives considered + +- Browser-test every combination exhaustively: rejected because most of the behavior is deterministic and cheaper to validate through feature tests. +- Limit validation to visual inspection: rejected because mode resolution and filter workflow are important enough to guard in CI. \ No newline at end of file diff --git a/specs/191-baseline-compare-operator-mode/tasks.md b/specs/191-baseline-compare-operator-mode/tasks.md index 1c9ed423..94937fb5 100644 --- a/specs/191-baseline-compare-operator-mode/tasks.md +++ b/specs/191-baseline-compare-operator-mode/tasks.md @@ -1,7 +1,7 @@ # Tasks: Baseline Compare Matrix: High-Density Operator Mode **Input**: Design documents from `/specs/191-baseline-compare-operator-mode/` -**Prerequisites**: `plan.md`, `spec.md` +**Prerequisites**: `plan.md`, `spec.md`, `research.md`, `data-model.md`, `quickstart.md` **Tests**: Tests are REQUIRED. Extend Pest feature coverage and browser smoke coverage around the existing matrix route. **Operations**: This feature reuses existing `baseline_compare` run truth only. No new `OperationRun` type, no new run-summary contract, and no new notification channel should be introduced.