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