TenantAtlas/specs/130-structured-snapshot-rendering/data-model.md
ahmido 3c445709af feat: add structured baseline snapshot rendering (#158)
## 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
2026-03-10 08:28:06 +00:00

248 lines
9.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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