# 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.