# Quickstart: 109 — Tenant Review Pack Export v1

**Date**: 2026-02-23
**Branch**: `109-review-pack-export`

---

## Prerequisites

- Sail services running (`vendor/bin/sail up -d`)
- Database migrated to latest
- At least one workspace with a connected tenant
- Tenant has stored reports (permission_posture and/or entra.admin_roles) and findings

---

## Implementation Phases

### Phase 1: Foundation (Data Layer + Config)

**Goal:** Database table, model, factory, enum additions, config entries.

1. **Migration**: `create_review_packs_table`
   - See [data-model.md](data-model.md) for full schema
   - Includes partial unique index on `(workspace_id, tenant_id, fingerprint)` for non-expired/failed packs

2. **Model**: `ReviewPack`
   - Uses `DerivesWorkspaceIdFromTenant`
   - Casts: summary/options as array, generated_at/expires_at as datetime
   - Relationships: workspace, tenant, operationRun, initiator
   - Scopes: ready, expired, pastRetention, forTenant, latestReady

3. **Factory**: `ReviewPackFactory` with states: queued, generating, ready, failed, expired

4. **Enum**: Add `ReviewPackGenerate` to `OperationRunType`

5. **Enum**: Add `ReviewPackStatus` (standalone enum at `App\Support\ReviewPackStatus`)

6. **Config**:
   - `config/filesystems.php` → `exports` disk
   - `config/tenantpilot.php` → `review_pack` section

7. **Capabilities**: Add `REVIEW_PACK_VIEW` and `REVIEW_PACK_MANAGE` to `Capabilities.php`

8. **Badge**: Add `ReviewPackStatus` to `BadgeDomain` + create `ReviewPackStatusBadge` mapper

**Run**: `vendor/bin/sail artisan migrate && vendor/bin/sail artisan test --compact --filter=ReviewPack`

---

### Phase 2: Service Layer + Job

**Goal:** Generation logic, ZIP assembly, fingerprint computation.

1. **Service**: `ReviewPackService`
   - `generate(Tenant, User, array $options): ReviewPack` — orchestrates creation
   - `computeFingerprint(Tenant, array $options): string` — deterministic hash
   - `generateDownloadUrl(ReviewPack): string` — signed URL
   - `findExistingPack(Tenant, string $fingerprint): ?ReviewPack` — dedupe check

2. **Job**: `GenerateReviewPackJob`
   - Collects data from StoredReport, Finding, Tenant, OperationRun
   - Builds file map → assembles ZIP → stores on exports disk
   - Updates ReviewPack status + metadata
   - Sends notification on completion/failure

3. **Notification**: `ReviewPackStatusNotification`
   - Database channel
   - Ready: includes view page link
   - Failed: includes sanitized reason

**Run**: `vendor/bin/sail artisan test --compact --filter=GenerateReviewPack`

---

### Phase 3: Download Controller

**Goal:** Signed URL download endpoint.

1. **Controller**: `ReviewPackDownloadController`
   - Single `__invoke` method
   - Validates pack exists + status is ready
   - Streams file with proper headers (Content-Type, Content-Disposition, SHA256)

2. **Route**: `GET /admin/review-packs/{reviewPack}/download`
   - Named `admin.review-packs.download`
   - Middleware: `signed`

**Run**: `vendor/bin/sail artisan test --compact --filter=ReviewPackDownload`

---

### Phase 4: Filament UI

**Goal:** Resource, view page, dashboard widget.

1. **Resource**: `ReviewPackResource`
   - List page: table with status badge, generated_at, expires_at, file_size
   - Header action: "Generate Pack" (modal with PII/operations toggles)
   - Row actions: Download (ready), Expire (destructive + confirmation)
   - Empty state: "No review packs yet" + "Generate first pack" CTA
   - Filters: status, date range

2. **View Page**: `ViewReviewPack`
   - Infolist layout with sections
   - Header actions: Download, Regenerate
   - Summary display, data freshness table, options used

3. **Widget**: `TenantReviewPackCard`
   - Shows latest pack status + metadata
   - Actions: Generate, Download (conditional)

**Run**: `vendor/bin/sail artisan test --compact --filter=ReviewPackResource`

---

### Phase 5: Prune Command + Schedule

**Goal:** Retention automation.

1. **Command**: `tenantpilot:review-pack:prune`
   - Marks expired, deletes files, optional hard-delete
   - `--hard-delete` flag for DB row removal after grace period

2. **Schedule**: Wire in `routes/console.php`
   - `daily()` + `withoutOverlapping()`

3. **AlertRule cleanup**: Remove `sla_due` from dropdown options

**Run**: `vendor/bin/sail artisan test --compact --filter="ReviewPackPrune|AlertRule"`

---

### Phase 6: Integration Tests

**Goal:** End-to-end coverage.

1. RBAC enforcement tests (404 for non-members, 403 for insufficient caps)
2. Fingerprint dedupe test (duplicate generation → reuse)
3. Active-run dedupe test (concurrent generation → rejection)
4. Prune test (expired packs → status update + file deletion)
5. Download test (signed URL → file stream with correct headers)
6. Empty state + widget display tests

**Run**: `vendor/bin/sail artisan test --compact tests/Feature/ReviewPack/`

---

## File Inventory (New Files)

```
database/migrations/XXXX_create_review_packs_table.php
app/Models/ReviewPack.php
database/factories/ReviewPackFactory.php
app/Support/ReviewPackStatus.php
app/Support/Badges/Mappers/ReviewPackStatusBadge.php
app/Services/ReviewPackService.php
app/Jobs/GenerateReviewPackJob.php
app/Notifications/ReviewPackStatusNotification.php
app/Http/Controllers/ReviewPackDownloadController.php
app/Filament/Resources/ReviewPackResource.php
app/Filament/Resources/ReviewPackResource/Pages/ListReviewPacks.php
app/Filament/Resources/ReviewPackResource/Pages/ViewReviewPack.php
app/Filament/Widgets/TenantReviewPackCard.php
app/Console/Commands/PruneReviewPacksCommand.php
tests/Feature/ReviewPack/ReviewPackGenerationTest.php
tests/Feature/ReviewPack/ReviewPackDownloadTest.php
tests/Feature/ReviewPack/ReviewPackRbacTest.php
tests/Feature/ReviewPack/ReviewPackPruneTest.php
tests/Feature/ReviewPack/ReviewPackResourceTest.php
tests/Feature/ReviewPack/ReviewPackWidgetTest.php
```

## Modified Files

```
app/Support/OperationRunType.php          — add ReviewPackGenerate case
app/Support/Auth/Capabilities.php         — add REVIEW_PACK_VIEW, REVIEW_PACK_MANAGE
app/Support/Badges/BadgeDomain.php        — add ReviewPackStatus case
config/filesystems.php                    — add exports disk
config/tenantpilot.php                    — add review_pack section
routes/web.php                            — add download route
routes/console.php                        — add prune schedule entry
app/Filament/Resources/AlertRuleResource.php — hide sla_due option
```
