# Data Model: Enterprise Evidence Gap Details for Baseline Compare ## Overview This feature does not introduce new database tables. It extends the tenant-owned `OperationRun` JSONB context for `baseline_compare` runs and defines a derived operator-facing row model for evidence-gap detail rendering. ## Entity: OperationRun - Ownership: Tenant-owned operational artifact with `workspace_id` and `tenant_id` - Storage: Existing `operation_runs` table - Relevant relationships: - Belongs to `Tenant` - Belongs to `Workspace` - Belongs to initiating `User` - Relevant invariant: - `status` and `outcome` remain service-owned through `OperationRunService` - `context` may be enriched by the compare job without changing lifecycle semantics ## Subdocument: `baseline_compare` Stored under `OperationRun.context['baseline_compare']`. ### Fields | Field | Type | Required | Description | |---|---|---|---| | `inventory_sync_run_id` | integer | no | Source inventory sync run used for coverage and freshness context | | `subjects_total` | integer | no | Total compare subjects considered for the run | | `evidence_capture` | object | no | Capture stats such as requested, succeeded, skipped, failed, throttled | | `coverage` | object | no | Coverage proof and covered/uncovered policy types | | `fidelity` | string | no | Compare fidelity, typically `meta` or `content` | | `reason_code` | string | no | Top-level compare reason code used by the explanation layer | | `resume_token` | string or null | no | Resume state for incomplete content capture | | `evidence_gaps` | object | no | Aggregate and subject-level evidence-gap contract | ## Subdocument: `baseline_compare.evidence_gaps` ### Fields | Field | Type | Required | Description | |---|---|---|---| | `count` | integer | no | Total evidence-gap count across all reasons | | `by_reason` | object | no | Aggregate counts keyed by evidence-gap reason code | | `subjects` | object> | no | Bounded reason-grouped list of concrete subject keys | ### Validation Rules - `count` must be a non-negative integer. - Each `by_reason` value must be a non-negative integer. - Each `subjects` key must be a non-empty reason code string. - Each `subjects[reason]` list item must be a non-empty string in `policy_type|subject_key` format. - Subject lists are deduplicated per reason. - Subject lists are capped at the compare job limit, currently 50 items per reason. ## Derived Entity: EvidenceGapDetailRow This is not stored separately. It is derived from `baseline_compare.evidence_gaps.subjects` for rendering and filtering. ### Fields | Field | Type | Source | Description | |---|---|---|---| | `reason_code` | string | map key | Stable reason identifier such as `ambiguous_match` or `policy_not_found` | | `reason_label` | string | derived | Operator-facing label from `reason_code` | | `policy_type` | string | parsed from subject string | Policy family segment before the first pipe | | `subject_key` | string | parsed from subject string | Subject identity segment after the first pipe | | `search_text` | string | derived | Lowercased concatenation of reason, policy type, and subject key for local filtering | ### Validation Rules - `policy_type` must be non-empty. - `subject_key` may be human-readable text, GUID-like values, or workspace-safe identifiers, but must be non-empty once persisted. - `search_text` must be deterministic and derived only from persisted row values. ## Derived Entity: EvidenceGapReasonBucket Groups detail rows for rendering. ### Fields | Field | Type | Description | |---|---|---| | `reason_code` | string | Stable bucket key | | `reason_label` | string | Operator-facing label | | `count` | integer | Number of visible persisted subjects in the bucket | | `rows` | list | Rows shown for the reason | ## State Model ### Run detail evidence-gap states 1. `NoGaps` - `evidence_gaps.count` is absent or `0` - No evidence-gap detail section is required 2. `GapsWithRecordedSubjects` - `evidence_gaps.count > 0` - `evidence_gaps.subjects` exists with at least one non-empty reason bucket - Render searchable grouped rows 3. `GapsWithoutRecordedSubjects` - `evidence_gaps.count > 0` - `evidence_gaps.subjects` is absent or empty - Render explicit fallback copy indicating detail was not recorded for that run ## Example Payload ```json { "baseline_compare": { "subjects_total": 50, "fidelity": "meta", "reason_code": "evidence_capture_incomplete", "evidence_capture": { "requested": 50, "succeeded": 47, "skipped": 0, "failed": 3, "throttled": 0 }, "evidence_gaps": { "count": 5, "by_reason": { "ambiguous_match": 3, "policy_not_found": 2 }, "subjects": { "ambiguous_match": [ "deviceConfiguration|WiFi-Corp-Profile", "deviceConfiguration|VPN-Always-On" ], "policy_not_found": [ "deviceConfiguration|Deleted-Policy-ABC" ] } } } } ```