TenantAtlas/specs/155-tenant-review-layer/data-model.md
ahmido a4f2629493 feat: add tenant review layer (#185)
## Summary
- add the tenant review domain with tenant-scoped review library, canonical workspace review register, lifecycle actions, and review-derived executive pack export
- extend review pack, operations, audit, capability, and badge infrastructure to support review composition, publication, export, and recurring review cycles
- add product backlog and audit documentation updates for tenant review and semantic-clarity follow-up candidates

## Testing
- `vendor/bin/sail bin pint --dirty --format agent`
- `vendor/bin/sail artisan test --compact --filter="TenantReview"`
- `CI=1 vendor/bin/sail artisan test --compact`

## Notes
- Livewire v4+ compliant via existing Filament v5 stack
- panel providers remain in `bootstrap/providers.php` via existing Laravel 12 structure; no provider registration moved to `bootstrap/app.php`
- `TenantReviewResource` is not globally searchable, so the Filament edit/view global-search constraint does not apply
- destructive review actions use action handlers with confirmation and policy enforcement

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #185
2026-03-21 22:03:01 +00:00

134 lines
5.2 KiB
Markdown

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