# Data Model: Tenant Review Layer ## Overview The first slice introduces a new tenant-owned review aggregate that consumes existing evidence-domain and governance outputs without replacing them. ## Entities ### TenantReview - **Purpose**: The primary recurring governance review record for one tenant, anchored to one chosen evidence basis. - **Ownership**: Tenant-owned (`workspace_id`, `tenant_id` required). - **Core fields**: - `id` - `workspace_id` - `tenant_id` - `evidence_snapshot_id` - `current_export_review_pack_id` nullable - `operation_run_id` nullable for async composition/refresh - `initiated_by_user_id` - `published_by_user_id` nullable - `superseded_by_review_id` nullable self-reference - `fingerprint` nullable, deterministic for dedupe of unchanged draft composition - `status` enum: `draft`, `ready`, `published`, `archived`, `superseded`, `failed` - `completeness_state` enum: `complete`, `partial`, `missing`, `stale` - `summary` JSONB for aggregate executive metadata and publish blockers - `published_at` nullable timestamp - `archived_at` nullable timestamp - `generated_at` nullable timestamp - `created_at`, `updated_at` - **Derived presentation states**: - publication state is derived from `status` plus `published_at`; no separate persisted publication enum is introduced in this slice - export readiness is derived from `current_export_review_pack_id`, review completeness/readiness rules, and the latest related review-pack state; no separate persisted export-readiness enum is introduced in this slice - **Relationships**: - belongs to `Tenant` - belongs to `Workspace` - belongs to `EvidenceSnapshot` - belongs to `User` as initiator - belongs to `User` as publisher - belongs to `OperationRun` for async generation/refresh only - has many `TenantReviewSection` - has many `ReviewPack` export artifacts if export history is retained per review - **Validation rules**: - `workspace_id` and `tenant_id` must match the anchored evidence snapshot - exactly one active evidence basis per review - published reviews cannot be mutated in place - archived reviews cannot return to draft/ready - **State transitions**: - `draft -> ready` - `draft -> failed` - `ready -> published` - `ready -> failed` - `published -> superseded` - `published -> archived` - `ready -> archived` - terminal: `archived`, `superseded` ### TenantReviewSection - **Purpose**: Ordered section-level composition for one tenant review. - **Ownership**: Tenant-owned through `tenant_review_id`; redundantly stores `workspace_id` and `tenant_id` for isolation and query simplicity. - **Core fields**: - `id` - `tenant_review_id` - `workspace_id` - `tenant_id` - `section_key` enum-like string such as `executive_summary`, `open_risks`, `accepted_risks`, `permission_posture`, `baseline_drift_posture`, `operations_health` - `title` - `sort_order` - `required` boolean - `completeness_state` - `source_snapshot_fingerprint` nullable - `summary_payload` JSONB - `render_payload` JSONB - `measured_at` nullable timestamp - `created_at`, `updated_at` - **Relationships**: - belongs to `TenantReview` - **Validation rules**: - unique per `tenant_review_id + section_key` - `sort_order` must be stable and non-negative - `summary_payload` and `render_payload` must be sanitized, summary-first, and secret-free ### ReviewPack (existing, extended) - **Purpose**: Existing export artifact reused as the stakeholder-facing executive pack output. - **Change in this feature**: - add `tenant_review_id` nullable foreign key - treat executive-pack exports as review-derived artifacts when `tenant_review_id` is present - **Important existing fields reused**: - `workspace_id`, `tenant_id` - `operation_run_id` - `evidence_snapshot_id` - `status` - `fingerprint` - `options` - `file_disk`, `file_path`, `file_size`, `sha256` - `generated_at`, `expires_at` - **State transitions**: - existing `queued -> generating -> ready|failed -> expired` - derived from review export pipeline, not from review publication state itself ## Derived Views ### Workspace Review Register Row - **Purpose**: Canonical `/admin/reviews` row projection for entitled tenants only. - **Fields**: - `tenant_id` - `tenant_name` - `review_id` - `review_status` - `completeness_state` - `published_at` - `generated_at` - `evidence_snapshot_id` - `has_ready_export` - `publish_blockers_count` ## Indexing / Constraints - `tenant_reviews(workspace_id, tenant_id, created_at desc)` - `tenant_reviews(tenant_id, status, published_at desc)` - partial unique index for one current mutable review per `tenant_id` if desired: `status in ('draft','ready')` - unique `tenant_review_sections(tenant_review_id, section_key)` - `review_packs(tenant_review_id, generated_at desc)` ## Invariants - Tenant-owned review tables always include both `workspace_id` and `tenant_id`. - A published review is immutable. - A review always points to exactly one anchored evidence snapshot. - Review completeness is explicit and never inferred from pack readiness alone. - Export artifacts never become the source of truth for review content; they remain derived from `TenantReview`.