TenantAtlas/specs/109-review-pack-export/quickstart.md

186 lines
6.4 KiB
Markdown

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