generateFromReview($review, $user, [ 'include_pii' => false, 'include_operations' => true, ]); app()->call([new GenerateReviewPackJob( reviewPackId: (int) $pack->getKey(), operationRunId: (int) $pack->operation_run_id, ), 'handle']); $pack->refresh(); [$zip, $tempFile, $filenames] = spec347ReadinessZip($pack); $summary = json_decode((string) $zip->getFromName('summary.json'), true, 512, JSON_THROW_ON_ERROR); $sections = collect(json_decode((string) $zip->getFromName('sections.json'), true, 512, JSON_THROW_ON_ERROR)); $executive = (string) $zip->getFromName(ReviewPackService::EXECUTIVE_ENTRYPOINT_FILENAME); $limitedSection = $sections->first(fn (array $section): bool => (string) $section['completeness_state'] !== 'complete'); expect($pack->status)->toBe(ReviewPackStatus::Ready->value) ->and(data_get($summary, 'has_ready_export'))->toBeTrue() ->and(data_get($summary, 'output_readiness.readiness_state'))->toBe('published_with_limitations') ->and(data_get($summary, 'output_readiness.evidence_completeness_state'))->toBe((string) $snapshot->completeness_state) ->and((int) data_get($summary, 'output_readiness.section_summary.required_limited'))->toBeGreaterThan(0) ->and($limitedSection)->not->toBeNull(); $limitedSectionFilename = sprintf( 'sections/%02d-%s.json', (int) $limitedSection['sort_order'], (string) $limitedSection['section_key'], ); expect($filenames)->toContain($limitedSectionFilename) ->and($executive)->toContain('## Limitations') ->and($executive)->toContain('incomplete evidence basis') ->and($executive)->toContain('structured appendices but are marked missing'); $zip->close(); unlink($tempFile); }); it('distinguishes ready export from customer-safe readiness when pii is included', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); $review = composeEnvironmentReviewForTest($tenant, $user); $review = markEnvironmentReviewCustomerSafeReady($review); $pack = app(ReviewPackService::class)->generateFromReview($review, $user, [ 'include_pii' => true, 'include_operations' => true, ]); app()->call([new GenerateReviewPackJob( reviewPackId: (int) $pack->getKey(), operationRunId: (int) $pack->operation_run_id, ), 'handle']); $pack->refresh(); [$zip, $tempFile] = spec347ReadinessZip($pack); $summary = json_decode((string) $zip->getFromName('summary.json'), true, 512, JSON_THROW_ON_ERROR); $executive = (string) $zip->getFromName(ReviewPackService::EXECUTIVE_ENTRYPOINT_FILENAME); expect(data_get($summary, 'has_ready_export'))->toBeTrue() ->and(data_get($summary, 'output_readiness.readiness_state'))->toBe('internal_review_package_available') ->and(data_get($summary, 'output_readiness.customer_safe_state'))->toBe('internal_only') ->and(data_get($summary, 'output_readiness.contains_pii'))->toBeTrue() ->and($executive)->toContain('PII is included in this package'); $zip->close(); unlink($tempFile); }); /** * @return array{0: ZipArchive, 1: string, 2: list} */ function spec347ReadinessZip(ReviewPack $pack): array { $zipContent = Storage::disk('exports')->get((string) $pack->file_path); $tempFile = tempnam(sys_get_temp_dir(), 'spec347-readiness-pack-'); file_put_contents($tempFile, $zipContent); $zip = new ZipArchive; $zip->open($tempFile); $filenames = collect(range(0, $zip->numFiles - 1)) ->map(fn (int $index): string => (string) $zip->getNameIndex($index)) ->values() ->all(); return [$zip, $tempFile, $filenames]; }