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

6.4 KiB

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 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.phpexports disk
    • config/tenantpilot.phpreview_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