164 lines
7.3 KiB
Markdown
164 lines
7.3 KiB
Markdown
# Data Model: Stored Reports Surface v1
|
|
|
|
**Date**: 2026-05-06
|
|
**Branch**: `277-stored-reports-surface`
|
|
|
|
## Overview
|
|
|
|
This slice introduces no new persisted entity. `StoredReport` remains the only stored-report source of truth. The new surface adds tenant-scoped, read-only view models over that truth and keeps downstream evidence, review, and review-pack consumers separate.
|
|
|
|
## Existing Persisted Truth
|
|
|
|
### 1. Stored Report
|
|
|
|
**Persistence**: Existing `stored_reports` table
|
|
**Ownership**: Tenant-owned
|
|
**Scope**: Many retained rows per tenant and report family
|
|
|
|
| Field | Type | Nullable | Notes |
|
|
|-------|------|----------|-------|
|
|
| `id` | bigint | no | Internal stored-report id |
|
|
| `workspace_id` | bigint | no | Required workspace isolation anchor |
|
|
| `tenant_id` | bigint | no | Required tenant isolation anchor |
|
|
| `report_type` | string | no | Current v1 supported values: `permission_posture`, `entra.admin_roles` |
|
|
| `payload` | jsonb | no | Family-specific report payload |
|
|
| `fingerprint` | string | yes | Integrity anchor for the current row |
|
|
| `previous_fingerprint` | string | yes | Historical lineage anchor |
|
|
| `created_at` | datetime | yes | Persisted creation time |
|
|
| `updated_at` | datetime | yes | Standard timestamp |
|
|
|
|
**Behavior rules**:
|
|
|
|
- Stored reports are immutable retained artifacts after creation.
|
|
- `current` versus `historical` is derived by comparing the row with the latest row for the same `tenant_id` and `report_type`.
|
|
- `retention_state` stays derived as `retained` in this slice.
|
|
- No new persisted lifecycle, publication, or browse metadata is introduced.
|
|
|
|
### 2. Evidence Snapshot Item Source Reference
|
|
|
|
**Persistence**: Existing `evidence_snapshot_items.source_record_type` and `source_record_id` fields
|
|
**Ownership**: Existing evidence domain
|
|
|
|
These fields remain downstream identity anchors when evidence items point to a stored report. They are context only in this slice and do not create an additional v1 launch seam.
|
|
|
|
### 3. Review Pack / Tenant Review Consumption
|
|
|
|
**Persistence**: Existing review-pack and tenant-review summaries
|
|
**Ownership**: Existing reporting domains
|
|
|
|
These consumers remain separate business truth. The stored-report surface may describe that stored reports are reused downstream, but it does not converge their operator routes in v1.
|
|
|
|
## Derived Read Models
|
|
|
|
### 4. Stored Report Row Summary
|
|
|
|
**Persistence**: none, derived at runtime
|
|
**Owner**: stored-report register
|
|
|
|
| Field | Type | Required | Notes |
|
|
|-------|------|----------|-------|
|
|
| `report_id` | int | yes | Backed by `stored_reports.id` |
|
|
| `display_reference` | string | yes | `Stored report #{id} ({family label})` from existing artifact-truth wording |
|
|
| `report_type` | string | yes | Raw family key |
|
|
| `report_family_label` | string | yes | Headline-style label such as `Permission posture report` |
|
|
| `lifecycle_state` | string | yes | `current` or `historical` from artifact truth |
|
|
| `retention_state` | string | yes | `retained` |
|
|
| `measured_at` | datetime | yes | Derived from payload timestamp when present, otherwise `created_at` |
|
|
| `summary_highlights` | list | yes | Bounded, family-specific summary facts |
|
|
| `history_visibility` | bool | yes | Whether the row is included only when history is revealed |
|
|
|
|
### 5. Stored Report Detail View
|
|
|
|
**Persistence**: none, derived at runtime
|
|
**Owner**: stored-report detail page
|
|
|
|
| Field | Type | Required | Notes |
|
|
|-------|------|----------|-------|
|
|
| `artifact_truth` | array | yes | Reused `ArtifactTruthPresenter::forStoredReport()` envelope |
|
|
| `display_reference` | string | yes | Stable stored-report reference |
|
|
| `report_family_label` | string | yes | Headline family label |
|
|
| `measured_at` | datetime | yes | Payload timestamp or `created_at` |
|
|
| `integrity_anchor` | string | no | `fingerprint`, shown when present |
|
|
| `previous_fingerprint` | string | no | Historical lineage anchor |
|
|
| `current_report_id` | int | no | Latest same-family row when viewing a historical record |
|
|
| `current_report_url` | string | no | Canonical detail URL for the current row |
|
|
| `summary_branch` | object | yes | One of the two supported family-specific summary shapes below |
|
|
| `raw_payload` | array | yes | Present but collapsed by default |
|
|
|
|
### 6. Permission Posture Summary
|
|
|
|
**Persistence**: none, derived from `StoredReport.payload`
|
|
|
|
Payload anchors from current repo truth:
|
|
|
|
- `posture_score`
|
|
- `required_count`
|
|
- `granted_count`
|
|
- `checked_at`
|
|
- `permissions[]` entries with `key`, `type`, `status`, and `features`
|
|
|
|
Derived summary fields:
|
|
|
|
| Field | Type | Notes |
|
|
|-------|------|-------|
|
|
| `posture_score` | int or null | Primary posture score |
|
|
| `required_count` | int | Required permissions count |
|
|
| `granted_count` | int | Granted permissions count |
|
|
| `missing_count` | int | Derived as `required_count - granted_count`, never persisted |
|
|
| `at_risk_permissions` | list | First few non-granted permission entries for operator summary |
|
|
| `checked_at` | datetime or null | Preferred measurement timestamp when present |
|
|
|
|
### 7. Entra Admin Roles Summary
|
|
|
|
**Persistence**: none, derived from `StoredReport.payload`
|
|
|
|
Payload anchors from current repo truth:
|
|
|
|
- `measured_at`
|
|
- `totals.roles_total`
|
|
- `totals.assignments_total`
|
|
- `totals.high_privilege_assignments`
|
|
- `high_privilege[]` entries with role, principal, scope, and severity fields
|
|
|
|
Derived summary fields:
|
|
|
|
| Field | Type | Notes |
|
|
|-------|------|-------|
|
|
| `roles_total` | int | Total role definitions captured |
|
|
| `assignments_total` | int | Total assignments captured |
|
|
| `high_privilege_assignments` | int | Count of privileged assignments |
|
|
| `highest_risk_assignment` | array or null | First highest-severity assignment for operator context |
|
|
| `measured_at` | datetime or null | Preferred measurement timestamp |
|
|
|
|
### 8. Stored Report Launch Seam
|
|
|
|
**Persistence**: none, derived from existing operator routes
|
|
|
|
| Field | Type | Required | Notes |
|
|
|-------|------|----------|-------|
|
|
| `source_surface` | string | yes | Current v1 source surface name |
|
|
| `target_url` | string | yes | Canonical stored-report detail URL |
|
|
|
|
**Current v1 seam**:
|
|
|
|
- `AdminRolesSummaryWidget` is the only confirmed canonical drilldown source in this slice.
|
|
|
|
## Derived State Rules
|
|
|
|
| Rule | Derived Behavior |
|
|
|------|------------------|
|
|
| Current row selection | Latest row by `created_at desc, id desc` for the same `tenant_id` and `report_type` |
|
|
| Lifecycle state | `current` for the latest row, otherwise `historical` |
|
|
| Retention state | `retained` for v1 stored-report browsing |
|
|
| Measured time | `payload.measured_at` or `payload.checked_at` when available, otherwise `created_at` |
|
|
| Register visibility | Only rows with an explicit family-to-capability mapping are listed |
|
|
| Detail authorization | Family-aware capability check after workspace and tenant membership is established |
|
|
| Unexpected report family | Outside v1 browse and detail scope until a follow-up spec adds support |
|
|
|
|
## Boundaries Explicitly Preserved
|
|
|
|
- No new report-generation, export, or lifecycle-mutation truth is introduced.
|
|
- Evidence snapshots, tenant reviews, review packs, and stored reports remain separate artifacts.
|
|
- Current versus historical stays derived and is not persisted as a new domain state.
|
|
- No generic report registry, schema catalog, or renderer framework is created.
|
|
- Evidence, review, and review-pack routes remain context only and are not v1 convergence targets. |