TenantAtlas/specs/277-stored-reports-surface/data-model.md
Ahmed Darrazi db83112edc
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 6m41s
WIP: commit changes for PR to platform-dev
2026-05-06 01:50:13 +02:00

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.