## Summary - add baseline compare evidence gap detail modeling and a dedicated Livewire table surface - extend baseline compare landing and operation run detail surfaces to expose evidence gap details and stats - add spec artifacts for feature 162 and expand feature coverage with focused Filament and baseline tests ## Notes - branch: `162-baseline-gap-details` - commit: `a92dd812` - working tree was clean after push ## Validation - tests were not run in this step Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #192
140 lines
5.0 KiB
Markdown
140 lines
5.0 KiB
Markdown
# 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<string,int> | no | Aggregate counts keyed by evidence-gap reason code |
|
|
| `subjects` | object<string,list<string>> | 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<EvidenceGapDetailRow> | 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"
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|