## Summary - replace the baseline snapshot detail page with a structured summary-first rendering flow - add a presenter plus renderer registry with RBAC, compliance, and fallback renderers - add grouped policy-type browsing, fidelity and gap badges, and workspace authorization coverage - add Feature 130 spec, plan, contract, research, quickstart, and completed task artifacts ## Testing - focused Pest coverage was added for structured rendering, fallback behavior, degraded states, authorization, presenter logic, renderer resolution, and badge mapping - I did not rerun the full validation suite in this final PR step ## Notes - base branch: `dev` - feature branch: `130-structured-snapshot-rendering` Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #158
248 lines
9.4 KiB
Markdown
248 lines
9.4 KiB
Markdown
# Data Model: Structured Snapshot Rendering & Type-Agnostic Item Browser
|
||
|
||
**Feature**: 130-structured-snapshot-rendering | **Date**: 2026-03-09
|
||
|
||
## Overview
|
||
|
||
This feature introduces no database schema changes. It adds a normalized read model over existing workspace-owned baseline snapshot records so the snapshot detail page can render every captured policy type through a shared summary and grouped-browser abstraction.
|
||
|
||
The design relies on:
|
||
|
||
1. existing persistent snapshot records,
|
||
2. existing persistent snapshot-item records,
|
||
3. a new normalized page presentation model,
|
||
4. renderer-specific enrichment layered on top of a shared fallback contract.
|
||
|
||
## Existing Persistent Entities
|
||
|
||
### BaselineSnapshot
|
||
|
||
| Attribute | Type | Notes |
|
||
|-----------|------|-------|
|
||
| `id` | int | Snapshot identity shown on the page |
|
||
| `workspace_id` | int | Workspace isolation boundary |
|
||
| `baseline_profile_id` | int | Source baseline profile reference |
|
||
| `summary_jsonb` | array/jsonb | Snapshot-level counts, fidelity, policy-type totals, and gap summaries |
|
||
| `snapshot_identity_hash` | string | Stable snapshot fingerprint shown as technical metadata |
|
||
| `captured_at` | datetime | Snapshot capture timestamp |
|
||
|
||
**Relationships**:
|
||
- belongs to `Workspace`
|
||
- belongs to `BaselineProfile`
|
||
- has many `BaselineSnapshotItem`
|
||
|
||
**Usage rules**:
|
||
- The snapshot is immutable and read-only in this feature.
|
||
- Snapshot detail can render only within the active workspace scope.
|
||
- `summary_jsonb` remains the page’s aggregation source but not the primary raw UI payload.
|
||
|
||
### BaselineSnapshotItem
|
||
|
||
| Attribute | Type | Notes |
|
||
|-----------|------|-------|
|
||
| `id` | int | Item identity for rendering and ordering |
|
||
| `baseline_snapshot_id` | int | Parent snapshot reference |
|
||
| `policy_type` | string | Primary grouping key for the browser |
|
||
| `subject_type` | string | Stable subject class hint, typically policy |
|
||
| `subject_key` | string | Stable identity hint for operator inspection |
|
||
| `subject_external_id` | string | Workspace-safe external identity hint |
|
||
| `baseline_hash` | string | Captured evidence hash |
|
||
| `meta_jsonb` | array/jsonb | Best-available display, evidence, version-reference, and optional type-specific metadata |
|
||
|
||
**Usage rules**:
|
||
- Items are grouped by `policy_type` for summary and browser rendering.
|
||
- Items must remain visible even when type-specific enrichment is unavailable.
|
||
- `meta_jsonb` is the canonical source for fallback rendering.
|
||
|
||
### BaselineProfile
|
||
|
||
| Attribute | Type | Notes |
|
||
|-----------|------|-------|
|
||
| `id` | int | Reference-only profile link |
|
||
| `workspace_id` | int | Workspace ownership boundary |
|
||
| `name` | string | Profile label shown in snapshot metadata |
|
||
|
||
**Usage rules**:
|
||
- Used only as metadata context in this feature.
|
||
- No baseline-profile lifecycle changes are introduced.
|
||
|
||
## Existing Snapshot Metadata Shape
|
||
|
||
### Snapshot summary payload
|
||
|
||
`summary_jsonb` is expected to provide enough information for snapshot-level aggregation:
|
||
|
||
| Key | Type | Purpose |
|
||
|-----|------|---------|
|
||
| `total_items` | int | Total item count for snapshot-level overview |
|
||
| `policy_type_counts` | map<string,int> | Count by policy type for summary rows |
|
||
| `fidelity_counts` | object | Aggregate fidelity counts already tracked at snapshot level |
|
||
| `gaps` | object | Aggregate gap count and by-reason summaries |
|
||
|
||
### Snapshot item metadata payload
|
||
|
||
`meta_jsonb` is expected to provide enough information for the minimum rendering contract:
|
||
|
||
| Key | Type | Purpose |
|
||
|-----|------|---------|
|
||
| `display_name` | string nullable | Best available item label |
|
||
| `category` | string nullable | Optional contextual attribute |
|
||
| `platform` | string nullable | Optional contextual attribute |
|
||
| `evidence.fidelity` | string | Item-level fidelity source |
|
||
| `evidence.source` | string | Capture or reference provenance |
|
||
| `evidence.observed_at` | date-time nullable | Best available observed timestamp |
|
||
| `identity.strategy` | string nullable | Identity interpretation hint |
|
||
| `version_reference.policy_version_id` | int nullable | Source reference hint |
|
||
| `version_reference.capture_purpose` | string nullable | Optional provenance hint |
|
||
| `rbac.*` | type-specific object | Optional RBAC enrichment fields |
|
||
|
||
## New Computed Read Models
|
||
|
||
### RenderedSnapshot
|
||
|
||
Page-level presentation model built from one snapshot and its items.
|
||
|
||
| Field | Type | Description |
|
||
|------|------|-------------|
|
||
| `snapshot` | SnapshotMeta | Top-of-page metadata summary |
|
||
| `summaryRows` | list<RenderedSnapshotSummaryRow> | One row per policy type |
|
||
| `groups` | list<RenderedSnapshotGroup> | Grouped item browser content |
|
||
| `technicalDetail` | RenderedTechnicalDetail | Secondary disclosure payload |
|
||
| `hasItems` | bool | Empty-state signal |
|
||
|
||
### SnapshotMeta
|
||
|
||
| Field | Type | Description |
|
||
|------|------|-------------|
|
||
| `snapshotId` | int | Visible snapshot identifier |
|
||
| `baselineProfileName` | string nullable | Visible baseline label |
|
||
| `capturedAt` | date-time nullable | Capture timestamp |
|
||
| `snapshotIdentityHash` | string nullable | Technical identity hint |
|
||
| `overallFidelity` | FidelityState | Overall fidelity status for the page |
|
||
| `overallGapCount` | int | Total known gaps |
|
||
|
||
### RenderedSnapshotSummaryRow
|
||
|
||
| Field | Type | Description |
|
||
|------|------|-------------|
|
||
| `policyType` | string | Canonical type key |
|
||
| `label` | string | Human-readable type label |
|
||
| `itemCount` | int | Number of items in this group |
|
||
| `fidelity` | FidelityState | Group-level fidelity summary |
|
||
| `gapCount` | int | Group-level gap count |
|
||
| `capturedAt` | date-time nullable | Most relevant timing summary for the group |
|
||
| `coverageHint` | string nullable | Optional summary hint |
|
||
|
||
### RenderedSnapshotGroup
|
||
|
||
| Field | Type | Description |
|
||
|------|------|-------------|
|
||
| `policyType` | string | Canonical type key |
|
||
| `label` | string | Group heading |
|
||
| `itemCount` | int | Count shown in group header |
|
||
| `fidelity` | FidelityState | Group-level fidelity badge state |
|
||
| `gapSummary` | GapSummary | Group-level degraded-state explanation |
|
||
| `initiallyCollapsed` | bool | Default UI state |
|
||
| `items` | list<RenderedSnapshotItem> | Visible item rows |
|
||
| `renderingError` | string nullable | Per-group error message when renderer fails |
|
||
|
||
### RenderedSnapshotItem
|
||
|
||
| Field | Type | Description |
|
||
|------|------|-------------|
|
||
| `label` | string | Best available human-readable title |
|
||
| `typeLabel` | string | Human-readable policy type label |
|
||
| `identityHint` | string | Stable inspection identity |
|
||
| `referenceStatus` | string | Capture or reference status label |
|
||
| `fidelity` | FidelityState | Item-level fidelity state |
|
||
| `gapSummary` | GapSummary | Item-level degraded-state summary |
|
||
| `observedAt` | date-time nullable | Best available timestamp |
|
||
| `sourceReference` | string nullable | PolicyVersion or equivalent reference hint |
|
||
| `structuredAttributes` | list<RenderedAttribute> | Shared and type-specific key-value attributes |
|
||
|
||
### RenderedAttribute
|
||
|
||
| Field | Type | Description |
|
||
|------|------|-------------|
|
||
| `label` | string | Operator-readable field label |
|
||
| `value` | scalar or string | Display value |
|
||
| `priority` | string enum | `primary` or `secondary` |
|
||
|
||
### FidelityState
|
||
|
||
| Value | Meaning |
|
||
|-------|---------|
|
||
| `full` | Structured rendering is complete and content-backed |
|
||
| `partial` | Structured rendering exists but some expected detail is missing |
|
||
| `reference_only` | Rendering is based on metadata or reference-only evidence |
|
||
| `unsupported` | Only the minimum fallback path is available |
|
||
|
||
### GapSummary
|
||
|
||
| Field | Type | Description |
|
||
|------|------|-------------|
|
||
| `count` | int | Number of known gaps |
|
||
| `hasGaps` | bool | Shortcut for UI conditions |
|
||
| `messages` | list<string> | Human-readable explanations or translated reason labels |
|
||
|
||
### RenderedTechnicalDetail
|
||
|
||
| Field | Type | Description |
|
||
|------|------|-------------|
|
||
| `defaultCollapsed` | bool | Always true for initial render |
|
||
| `summaryPayload` | array | Raw summary payload for debugging |
|
||
| `groupPayloads` | map<string,mixed> | Optional renderer debug payloads where safe |
|
||
|
||
## Relationships and Aggregation Rules
|
||
|
||
### Snapshot to group aggregation
|
||
|
||
```text
|
||
BaselineSnapshot
|
||
-> many BaselineSnapshotItem
|
||
-> grouped by policy_type
|
||
-> one RenderedSnapshotSummaryRow per group
|
||
-> one RenderedSnapshotGroup per group
|
||
```
|
||
|
||
### Group derivation rules
|
||
|
||
```text
|
||
policy_type group
|
||
-> item count = number of items in the group
|
||
-> fidelity = worst effective fidelity across item set or unsupported if fallback only
|
||
-> gap count = derived from item gaps plus any renderer-level degraded-state signals
|
||
-> captured/observed timing = most relevant available timing summary
|
||
```
|
||
|
||
### Renderer resolution rules
|
||
|
||
```text
|
||
Known supported type with specific renderer
|
||
-> render shared item contract + type-specific enrichments
|
||
|
||
Known type with no specific renderer
|
||
-> fallback renderer
|
||
|
||
Unknown type
|
||
-> fallback renderer
|
||
|
||
Specific renderer throws or returns invalid data
|
||
-> group-level rendering error + fallback group shell
|
||
```
|
||
|
||
## Validation Rules
|
||
|
||
| Rule | Result |
|
||
|------|--------|
|
||
| Every captured policy type yields a summary row | Required |
|
||
| Every captured policy type yields a grouped browser section | Required |
|
||
| Every item yields at least the minimum rendering contract | Required |
|
||
| Unknown types never disappear from the page | Required |
|
||
| Technical payload is secondary and collapsed by default | Required |
|
||
| One renderer failure cannot break the rest of the snapshot page | Required |
|
||
|
||
## Schema Impact
|
||
|
||
No schema migration is expected for this feature.
|