## Summary - add a workspace-scoped baseline compare matrix page under baseline profiles - derive matrix tenant summaries, subject rows, cell states, freshness, and trust from existing snapshots, compare runs, and findings - add confirmation-gated `Compare assigned tenants` actions on the baseline detail and matrix surfaces without introducing a workspace umbrella run - preserve matrix navigation context into tenant compare and finding drilldowns and add centralized matrix badge semantics - include spec, plan, data model, contracts, quickstart, tasks, and focused feature/browser coverage for Spec 190 ## Verification - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/Badges/BaselineCompareMatrixBadgesTest.php tests/Feature/Baselines/BaselineCompareMatrixBuilderTest.php tests/Feature/Baselines/BaselineCompareMatrixCompareAllActionTest.php tests/Feature/Baselines/BaselineComparePerformanceGuardTest.php tests/Feature/Filament/BaselineCompareMatrixPageTest.php tests/Feature/Filament/BaselineProfileCompareStartSurfaceTest.php tests/Feature/Rbac/BaselineCompareMatrixAuthorizationTest.php tests/Feature/Guards/ActionSurfaceContractTest.php tests/Feature/Guards/NoAdHocStatusBadgesTest.php tests/Feature/Guards/NoDiagnosticWarningBadgesTest.php` - `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` - completed an integrated-browser smoke flow locally for matrix render, differ filter, finding drilldown round-trip, and `Compare assigned tenants` confirmation/action Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #221
192 lines
11 KiB
Markdown
192 lines
11 KiB
Markdown
# Data Model: Workspace Baseline Compare Matrix V1
|
|
|
|
## Overview
|
|
|
|
This feature introduces no new persisted entity. The matrix is a derived workspace-scoped read model over existing baseline reference truth, tenant compare execution truth, and compare-created finding truth.
|
|
|
|
## Existing Source Truths
|
|
|
|
### Baseline reference truth
|
|
|
|
**Types**: Existing workspace-owned source of truth
|
|
**Sources**: `BaselineProfile`, `BaselineSnapshot`, `BaselineSnapshotTruthResolver`, `BaselineSnapshotItem`
|
|
|
|
| Source | Purpose | Key Fields |
|
|
|--------|---------|------------|
|
|
| `BaselineProfile` | Selects the reference standard and active snapshot pointer | `id`, `workspace_id`, `name`, `status`, `active_snapshot_id`, `scope_jsonb`, `capture_mode` |
|
|
| `BaselineSnapshot` | Represents the effective baseline reference state | `id`, `baseline_profile_id`, `workspace_id`, `state`, `completed_at`, `operation_run_id` |
|
|
| `BaselineSnapshotItem` | Defines the subject row axis for the matrix | `baseline_snapshot_id`, `policy_type`, `subject_key`, `external_id`, `display_name` |
|
|
|
|
### Assignment truth
|
|
|
|
**Type**: Existing workspace-to-tenant mapping truth
|
|
**Source**: `BaselineTenantAssignment`
|
|
|
|
| Source | Purpose | Key Fields |
|
|
|--------|---------|------------|
|
|
| `BaselineTenantAssignment` | Defines which tenants belong in the matrix target set | `workspace_id`, `tenant_id`, `baseline_profile_id`, `override_scope_jsonb` |
|
|
|
|
### Tenant compare truth
|
|
|
|
**Type**: Existing tenant-owned execution and diagnostic truth
|
|
**Sources**: `OperationRun` with `type = baseline_compare`, `BaselineCompareStats`, `baseline_compare` run context
|
|
|
|
| Source | Purpose | Key Fields |
|
|
|--------|---------|------------|
|
|
| `OperationRun` | Stores compare lifecycle, timestamps, summary counts, and context | `tenant_id`, `workspace_id`, `type`, `status`, `outcome`, `completed_at`, `summary_counts`, `context` |
|
|
| `OperationRun.context.baseline_compare` | Stores compare diagnostics needed for trust and no-result interpretation | `coverage`, `reason_code`, `reason_translation`, `evidence_gaps`, `subjects_total`, `fidelity`, `inventory_sync_run_id` |
|
|
| `BaselineCompareStats` | Existing single-tenant projection for freshness, coverage, findings count, and operator explanation | `state`, `reasonCode`, `reasonMessage`, `lastComparedIso`, `coverageStatus`, `evidenceGapDetails`, `operatorExplanation` |
|
|
|
|
### Drift finding truth
|
|
|
|
**Type**: Existing tenant-owned technical drift and workflow truth
|
|
**Source**: `Finding` with `finding_type = drift` and `source = baseline.compare`
|
|
|
|
| Source | Purpose | Key Fields |
|
|
|--------|---------|------------|
|
|
| `Finding` | Stores subject-level technical differences plus workflow metadata | `tenant_id`, `workspace_id`, `scope_key`, `subject_external_id`, `subject_type`, `severity`, `status`, `baseline_operation_run_id`, `current_operation_run_id`, `evidence_jsonb` |
|
|
|
|
## New Derived Read Models
|
|
|
|
### BaselineCompareMatrixReference
|
|
|
|
**Type**: Request-scoped reference bundle
|
|
**Source**: `BaselineProfile` + resolved effective `BaselineSnapshot`
|
|
|
|
| Field | Type | Notes |
|
|
|------|------|-------|
|
|
| `workspaceId` | integer | Active workspace scope |
|
|
| `baselineProfileId` | integer | Selected baseline profile |
|
|
| `baselineProfileName` | string | Operator-facing reference label |
|
|
| `baselineStatus` | string | Existing profile lifecycle/status |
|
|
| `referenceSnapshotId` | integer or null | Null when compare is blocked |
|
|
| `referenceSnapshotCapturedAt` | datetime or null | For freshness context |
|
|
| `referenceState` | string | `ready` or a blocked state such as `no_snapshot` |
|
|
| `referenceReasonCode` | string or null | Existing compare-snapshot truth reason |
|
|
| `assignedTenantCount` | integer | Total assigned tenant count in workspace |
|
|
| `visibleTenantCount` | integer | Count after visibility filtering |
|
|
|
|
### MatrixTenantSummary
|
|
|
|
**Type**: Request-scoped visible tenant summary
|
|
**Source**: visible tenant set + latest relevant compare run + derived cell states
|
|
|
|
| Field | Type | Notes |
|
|
|------|------|-------|
|
|
| `tenantId` | integer | Visible tenant identifier |
|
|
| `tenantName` | string | Operator-facing column label |
|
|
| `compareRunId` | integer or null | Latest relevant compare run for this baseline |
|
|
| `compareRunStatus` | string or null | `queued`, `running`, `completed`, or null |
|
|
| `compareRunOutcome` | string or null | Existing `OperationRun` outcome |
|
|
| `freshnessState` | string | `fresh`, `stale`, `never_compared`, or `unknown` |
|
|
| `lastComparedAt` | datetime or null | Latest completed compare time |
|
|
| `matchedCount` | integer | Derived from cell states |
|
|
| `differingCount` | integer | Derived from cell states |
|
|
| `missingCount` | integer | Derived from cell states |
|
|
| `ambiguousCount` | integer | Derived from cell states |
|
|
| `notComparedCount` | integer | Derived from cell states |
|
|
| `maxSeverity` | string or null | Highest visible severity among differing cells |
|
|
| `trustLevel` | string | Existing trustworthiness semantics reused at tenant level |
|
|
|
|
### MatrixSubjectSummary
|
|
|
|
**Type**: Request-scoped baseline subject summary
|
|
**Source**: `BaselineSnapshotItem` + visible tenant cell states
|
|
|
|
| Field | Type | Notes |
|
|
|------|------|-------|
|
|
| `subjectKey` | string | Reused existing subject identity |
|
|
| `policyType` | string | For filter and grouping |
|
|
| `displayName` | string or null | Operator-facing row label |
|
|
| `baselineExternalId` | string or null | Secondary drilldown metadata |
|
|
| `deviationBreadth` | integer | Count of visible tenants in `differ` or `missing` |
|
|
| `missingBreadth` | integer | Count of visible tenants in `missing` |
|
|
| `ambiguousBreadth` | integer | Count of visible tenants in `ambiguous` |
|
|
| `notComparedBreadth` | integer | Count of visible tenants in `not_compared` |
|
|
| `maxSeverity` | string or null | Highest visible severity across differing cells |
|
|
| `trustLevel` | string | Highest-risk trust signal across visible cells |
|
|
|
|
### MatrixCell
|
|
|
|
**Type**: Request-scoped cell read model
|
|
**Source**: `BaselineSnapshotItem` + latest relevant compare run context + compare-created findings
|
|
|
|
| Field | Type | Notes |
|
|
|------|------|-------|
|
|
| `tenantId` | integer | Visible tenant column key |
|
|
| `subjectKey` | string | Subject row key |
|
|
| `state` | string | `match`, `differ`, `missing`, `ambiguous`, `not_compared`, or `stale_result` |
|
|
| `severity` | string or null | From current technical drift finding when present |
|
|
| `trustLevel` | string | Reused trustworthiness semantics or cell-level downgraded trust |
|
|
| `reasonCode` | string or null | Existing reason/evidence-gap code when the state is not a plain match |
|
|
| `compareRunId` | integer or null | Latest relevant compare run |
|
|
| `findingId` | integer or null | Existing finding drilldown target when a related finding exists |
|
|
| `findingWorkflowState` | string or null | Secondary workflow context; never overrides technical state |
|
|
| `lastComparedAt` | datetime or null | Latest compare timestamp for this tenant |
|
|
| `policyTypeCovered` | boolean | False when the run never covered this subject's policy type |
|
|
|
|
### CompareAssignedTenantsLaunchResult
|
|
|
|
**Type**: Request-scoped action result bundle
|
|
**Source**: repeated calls to existing `BaselineCompareService::startCompare()`
|
|
|
|
| Field | Type | Notes |
|
|
|------|------|-------|
|
|
| `baselineProfileId` | integer | Selected baseline profile |
|
|
| `visibleAssignedTenantCount` | integer | Size of eligible visible set considered by the action |
|
|
| `queuedCount` | integer | New compare runs started |
|
|
| `alreadyQueuedCount` | integer | Existing active runs reused |
|
|
| `blockedCount` | integer | Compare starts refused by normal service preconditions |
|
|
| `targets` | array | Per-tenant action outcome bundle with tenant id, run id or reason code |
|
|
|
|
## Validation Rules
|
|
|
|
### Visibility rules
|
|
|
|
- Only tenants visible to the current actor may produce columns, counts, or drilldown targets.
|
|
- Summary counts are always computed from the visible tenant set only.
|
|
- Hidden tenants are never represented as anonymous remainder counts.
|
|
|
|
### Reference rules
|
|
|
|
- The matrix may render technical compare truth only when the selected baseline profile resolves to a usable effective snapshot.
|
|
- If no usable snapshot exists, the page remains in a blocked state and no cell state may imply compare health.
|
|
|
|
### Cell derivation precedence
|
|
|
|
1. If no usable reference snapshot exists, the page is blocked and no cells are materialized.
|
|
2. If the tenant has no completed compare against this baseline reference or the subject's policy type was not covered, the cell state is `not_compared`.
|
|
3. If the latest completed compare result predates the current effective snapshot or is stale under existing stale-result policy, the cell state is `stale_result` unless a stronger blocked or absent condition applies.
|
|
4. If the latest relevant compare run records an evidence-gap or reason code indicating ambiguous or low-confidence identity matching for the subject, the cell state is `ambiguous`.
|
|
5. If the latest relevant compare run records the subject as missing from tenant truth, the cell state is `missing`.
|
|
6. If the latest relevant compare output created or updated a drift finding for the subject under the current compare run, the cell state is `differ` regardless of finding workflow status.
|
|
7. Otherwise the cell state is `match`.
|
|
|
|
### Freshness rules
|
|
|
|
- `fresh` means the tenant has a completed compare result against the current effective snapshot and it does not exceed the existing stale-result threshold.
|
|
- `stale` means the compare result predates the current effective snapshot or breaches the existing stale-result threshold.
|
|
- `never_compared` means no relevant compare run exists for the tenant and selected baseline.
|
|
- `unknown` is reserved for unexpected cases where timestamps or run truth are missing but a cell still exists.
|
|
|
|
### Trust rules
|
|
|
|
- Trust levels reuse existing compare explanation semantics rather than a new matrix-only taxonomy.
|
|
- `match` may only be shown when the subject was covered and no ambiguity or missing-basis signal exists.
|
|
- `not_compared` and uncovered policy types are treated as low-trust or unusable for operator interpretation.
|
|
|
|
## Relationships
|
|
|
|
- One `BaselineProfile` resolves to zero or one effective `BaselineSnapshot` for compare.
|
|
- One `BaselineSnapshot` has many `BaselineSnapshotItem` rows.
|
|
- One `BaselineProfile` has many `BaselineTenantAssignment` rows.
|
|
- One visible tenant may have many `baseline_compare` `OperationRun` rows over time, but the matrix resolves one latest relevant run per tenant for the selected baseline.
|
|
- One latest relevant compare run may have many related drift `Finding` rows.
|
|
- One matrix cell may link to zero or one preferred finding drilldown and always belongs to exactly one visible tenant and one subject row.
|
|
|
|
## Rendering Rules
|
|
|
|
- Technical deviation state is primary; finding workflow state is secondary.
|
|
- Tenant running or queued compare state is shown at tenant-summary level and does not replace the last known completed technical truth unless the latest completed truth is absent.
|
|
- Subject-focused views reuse the same row and cell models and simply reduce the visible subject set to one selected subject.
|
|
- Empty and degraded states remain page-level states rather than synthetic match rows. |