loadMissing([ 'tenant.workspace', 'environmentReview.evidenceSnapshot', 'environmentReview.currentExportReviewPack', 'environmentReview.sections', ]); $user = $request->user(); $tenant = $reviewPack->tenant; $review = $reviewPack->environmentReview; if (! $user instanceof User || ! $tenant instanceof ManagedEnvironment || ! $review instanceof EnvironmentReview) { throw new NotFoundHttpException; } if (! $user->canAccessTenant($tenant)) { throw new NotFoundHttpException; } if (! $user->can(Capabilities::REVIEW_PACK_VIEW, $tenant)) { abort(403); } if ($reviewPack->status !== ReviewPackStatus::Ready->value) { throw new NotFoundHttpException; } if ($reviewPack->expires_at !== null && $reviewPack->expires_at->isPast()) { throw new NotFoundHttpException; } if ((int) $review->managed_environment_id !== (int) $tenant->getKey()) { throw new NotFoundHttpException; } if ((int) ($review->current_export_review_pack_id ?? 0) !== (int) $reviewPack->getKey()) { throw new NotFoundHttpException; } return response()->view('review-packs.rendered-report', [ 'report' => $this->reportState($request, $reviewPack, $review, $tenant), ]); } /** * @return array */ private function reportState( Request $request, ReviewPack $reviewPack, EnvironmentReview $review, ManagedEnvironment $tenant, ): array { $summary = is_array($reviewPack->summary) ? $reviewPack->summary : []; $governancePackage = is_array($summary['governance_package'] ?? null) ? $summary['governance_package'] : []; $controlInterpretation = is_array($summary['control_interpretation'] ?? null) ? $summary['control_interpretation'] : []; $downloadUrl = $this->downloadUrl($request, $reviewPack, $review); $reviewUrl = $this->reviewUrl($request, $review, $tenant); $reviewPackUrl = $this->reviewPackUrl($request, $reviewPack, $tenant); $readiness = ReviewPackOutputResolutionGuidance::readinessForReview($review); $guidance = ReviewPackOutputResolutionGuidance::fromReadiness($readiness, [ 'download' => $downloadUrl, 'review' => $reviewUrl, ]); $decisionSummary = is_array($governancePackage['decision_summary'] ?? null) ? $governancePackage['decision_summary'] : []; $state = (string) ($guidance['state'] ?? ReviewPackOutputResolutionGuidance::STATE_UNKNOWN); $limitations = $this->managementLimitations(is_array($guidance['limitations'] ?? null) ? $guidance['limitations'] : []); $evidenceBasis = $this->evidenceBasisState($review); $profile = $this->profileState($request, $state); $nonCertificationDisclosure = $this->plainText( $controlInterpretation['non_certification_disclosure'] ?? null, __('localization.review.non_certification_disclosure_text'), ); $disclosurePolicy = ReportDisclosurePolicy::evaluate($profile, $readiness, [ 'non_certification_disclosure' => $nonCertificationDisclosure, ]); $sectionAppendix = (bool) ($disclosurePolicy['show_section_appendix'] ?? false) ? $this->sectionAppendix($reviewPack, $review) : []; $technicalDetails = (bool) ($disclosurePolicy['show_technical_details'] ?? false) ? (is_array($guidance['technical_details'] ?? null) ? $guidance['technical_details'] : []) : []; return [ 'title' => __('localization.review.rendered_report'), 'guidance' => $guidance, 'state' => $state, 'hero' => $this->heroState($state, $guidance), 'branding' => $this->brandingState($tenant), 'profile' => $profile, 'disclosure_policy' => $disclosurePolicy, 'source_metadata' => $this->sourceMetadata($request, $review), 'tenant_name' => $tenant->name, 'review_id' => (int) $review->getKey(), 'pack_id' => (int) $reviewPack->getKey(), 'review_status' => (string) $review->status, 'review_completeness_state' => (string) $review->completeness_state, 'review_status_label' => Str::headline((string) $review->status), 'review_completeness_label' => Str::headline((string) $review->completeness_state), 'generated_at' => $reviewPack->generated_at, 'published_at' => $review->published_at, 'download_url' => $downloadUrl, 'download_label' => $this->downloadLabel($state), 'review_url' => $reviewUrl, 'review_pack_url' => $reviewPackUrl, 'html_only' => true, 'executive_summary' => $this->plainText( $governancePackage['executive_summary'] ?? null, __('localization.review.rendered_report_summary_fallback'), ), 'management_summary' => $this->managementSummary( state: $state, guidance: $guidance, limitations: $limitations, storedExecutiveSummary: $governancePackage['executive_summary'] ?? null, ), 'evidence_basis' => $evidenceBasis, 'evidence_basis_summary' => $evidenceBasis['description'], 'limitations' => $limitations, 'top_findings' => is_array($governancePackage['top_findings'] ?? null) ? $governancePackage['top_findings'] : [], 'accepted_risks' => $this->acceptedRiskItems( is_array($governancePackage['accepted_risks'] ?? null) ? $governancePackage['accepted_risks'] : [], $state !== ReviewPackOutputResolutionGuidance::STATE_INTERNAL_ONLY, ), 'decision_summary' => $decisionSummary, 'governance_decisions' => is_array($decisionSummary['entries'] ?? null) ? $decisionSummary['entries'] : (is_array($governancePackage['governance_decisions'] ?? null) ? $governancePackage['governance_decisions'] : []), 'next_actions' => is_array($summary['recommended_next_actions'] ?? null) ? $summary['recommended_next_actions'] : [], 'non_certification_disclosure' => $nonCertificationDisclosure, 'section_appendix' => $sectionAppendix, 'technical_details' => $technicalDetails, 'entrypoint_file' => (string) data_get($summary, 'delivery_bundle.executive_entrypoint_file', ReviewPackService::EXECUTIVE_ENTRYPOINT_FILENAME), 'appendix_files' => is_array(data_get($summary, 'delivery_bundle.appendix_files')) ? data_get($summary, 'delivery_bundle.appendix_files') : [], ]; } /** * @return array{prepared_by:string,prepared_for:string,generated_by:string} */ private function brandingState(ManagedEnvironment $tenant): array { $workspace = $tenant->workspace; return [ 'prepared_by' => $workspace instanceof Workspace && filled($workspace->name) ? (string) $workspace->name : 'TenantPilot', 'prepared_for' => filled($tenant->name) ? (string) $tenant->name : __('localization.review.tenant'), 'generated_by' => 'TenantPilot', ]; } /** * @param array $guidance * @return array{title:string,badge:string,summary:string,warning:?string,color:string,customer_safe:bool} */ private function heroState(string $state, array $guidance): array { $customerSafe = $state === ReviewPackOutputResolutionGuidance::STATE_CUSTOMER_SAFE_READY; return [ 'title' => match ($state) { ReviewPackOutputResolutionGuidance::STATE_CUSTOMER_SAFE_READY => __('localization.review.report_state_customer_safe_ready'), ReviewPackOutputResolutionGuidance::STATE_PUBLISHED_WITH_LIMITATIONS => __('localization.review.report_state_with_limitations'), ReviewPackOutputResolutionGuidance::STATE_INTERNAL_ONLY => __('localization.review.report_state_internal_with_limitations'), default => __('localization.review.report_state_not_customer_ready'), }, 'badge' => $customerSafe ? __('localization.review.customer_safe') : (string) ($guidance['boundary_label'] ?? __('localization.review.requires_review')), 'summary' => match ($state) { ReviewPackOutputResolutionGuidance::STATE_CUSTOMER_SAFE_READY => __('localization.review.report_state_customer_safe_ready_summary'), ReviewPackOutputResolutionGuidance::STATE_INTERNAL_ONLY => __('localization.review.report_state_internal_summary'), ReviewPackOutputResolutionGuidance::STATE_PUBLISHED_WITH_LIMITATIONS => __('localization.review.report_state_limitations_summary'), default => __('localization.review.report_state_not_ready_summary'), }, 'warning' => $customerSafe ? null : __('localization.review.report_external_sharing_warning'), 'color' => $customerSafe ? 'success' : ($state === ReviewPackOutputResolutionGuidance::STATE_PUBLICATION_BLOCKED ? 'danger' : 'warning'), 'customer_safe' => $customerSafe, ]; } /** * @param array $guidance * @param list> $limitations * @return array{overall_state:string,reason:string,impact:string,next_action:string,top_limitations:list} */ private function managementSummary(string $state, array $guidance, array $limitations, mixed $storedExecutiveSummary): array { $topLimitations = collect($limitations) ->pluck('summary') ->filter(static fn (mixed $summary): bool => is_string($summary) && trim($summary) !== '') ->take(3) ->values() ->all(); return [ 'overall_state' => (string) ($this->heroState($state, $guidance)['title']), 'reason' => $this->managementReason($state, $guidance, $storedExecutiveSummary), 'impact' => $this->plainText($guidance['impact'] ?? null, __('localization.review.report_summary_default_impact')), 'next_action' => $this->managementNextAction($state, $limitations), 'top_limitations' => $topLimitations, ]; } /** * @param array $guidance */ private function managementReason(string $state, array $guidance, mixed $storedExecutiveSummary): string { if ($state === ReviewPackOutputResolutionGuidance::STATE_CUSTOMER_SAFE_READY) { return $this->plainText($storedExecutiveSummary, __('localization.review.report_summary_ready_reason')); } return $this->plainText($guidance['primary_reason'] ?? null, __('localization.review.report_summary_limited_reason')); } /** * @param list> $limitations */ private function managementNextAction(string $state, array $limitations): string { $firstKey = $limitations[0]['key'] ?? null; return match (true) { $state === ReviewPackOutputResolutionGuidance::STATE_CUSTOMER_SAFE_READY => __('localization.review.report_next_action_ready'), $state === ReviewPackOutputResolutionGuidance::STATE_INTERNAL_ONLY => __('localization.review.report_next_action_internal'), $firstKey === 'evidence_basis_missing', $firstKey === 'evidence_basis_stale', $firstKey === 'evidence_basis_incomplete' => __('localization.review.report_next_action_evidence'), $firstKey === 'required_sections_incomplete' => __('localization.review.report_next_action_sections'), $firstKey === 'publish_blockers_present' => __('localization.review.report_next_action_blockers'), default => __('localization.review.report_next_action_limited'), }; } /** * @param list> $limitations * @return list */ private function managementLimitations(array $limitations): array { return collect($limitations) ->map(function (array $limitation): array { $key = (string) ($limitation['key'] ?? 'unknown'); return [ 'key' => $key, 'title' => $this->plainText($limitation['label'] ?? null, __('localization.review.output_limitations')), 'summary' => match ($key) { 'evidence_basis_missing', 'evidence_basis_stale', 'evidence_basis_incomplete' => __('localization.review.report_limitation_evidence_summary'), 'required_sections_incomplete' => __('localization.review.report_limitation_sections_summary'), 'contains_pii' => __('localization.review.report_limitation_pii_summary'), 'publish_blockers_present' => __('localization.review.report_limitation_blockers_summary'), 'export_not_ready' => __('localization.review.report_limitation_export_summary'), 'disclosure_missing' => __('localization.review.report_limitation_disclosure_summary'), default => $this->plainText($limitation['reason'] ?? null, __('localization.review.report_limitation_default_summary')), }, 'next_action' => match ($key) { 'evidence_basis_missing', 'evidence_basis_stale', 'evidence_basis_incomplete' => __('localization.review.report_next_action_evidence'), 'required_sections_incomplete' => __('localization.review.report_next_action_sections'), 'contains_pii' => __('localization.review.report_next_action_internal'), 'publish_blockers_present' => __('localization.review.report_next_action_blockers'), default => __('localization.review.report_next_action_limited'), }, 'severity' => (string) ($limitation['severity'] ?? 'warning'), ]; }) ->values() ->all(); } /** * @return array{label:string,description:string,operator_action:string,snapshot_label:string} */ private function evidenceBasisState(EnvironmentReview $review): array { $snapshot = $review->evidenceSnapshot; $state = (string) ($snapshot?->completeness_state ?? 'missing'); $snapshotLabel = $snapshot instanceof \App\Models\EvidenceSnapshot ? __('localization.review.evidence_snapshot_number', ['id' => (int) $snapshot->getKey()]) : __('localization.review.evidence_snapshot_missing'); return [ 'label' => Str::headline($state), 'description' => match ($state) { 'complete' => __('localization.review.report_evidence_complete_description', ['snapshot' => $snapshotLabel]), 'stale' => __('localization.review.report_evidence_stale_description', ['snapshot' => $snapshotLabel]), 'partial' => __('localization.review.report_evidence_partial_description', ['snapshot' => $snapshotLabel]), default => __('localization.review.report_evidence_missing_description'), }, 'operator_action' => match ($state) { 'complete' => __('localization.review.report_evidence_complete_action'), default => __('localization.review.report_next_action_evidence'), }, 'snapshot_label' => $snapshotLabel, ]; } /** * @param list> $acceptedRisks * @return list */ private function acceptedRiskItems(array $acceptedRisks, bool $showOwner): array { return collect($acceptedRisks) ->filter(static fn (mixed $risk): bool => is_array($risk)) ->map(function (array $risk) use ($showOwner): array { $customerSafeSummary = $this->plainText( $risk['customer_safe_summary'] ?? ($risk['customer_summary'] ?? null), '', ); $status = $this->acceptedRiskStatusLabel((string) ($risk['governance_state'] ?? ($risk['status'] ?? 'unknown'))); $owner = $showOwner ? $this->plainText($risk['owner_label'] ?? data_get($risk, 'owner.name'), '') : ''; return [ 'title' => $this->plainText($risk['title'] ?? null, __('localization.review.accepted_risks')), 'status' => $status, 'summary' => $customerSafeSummary !== '' ? $customerSafeSummary : null, 'limitation' => $customerSafeSummary === '' ? __('localization.review.accepted_risk_customer_safe_summary_missing') : null, 'owner' => $owner !== '' ? $owner : null, 'review_state' => $this->acceptedRiskReviewState($risk), ]; }) ->values() ->all(); } private function acceptedRiskStatusLabel(string $state): string { return match ($state) { 'valid_exception' => __('localization.review.accepted_risk_state_current'), 'expiring_exception' => __('localization.review.accepted_risks_expiring_soon'), 'expired_exception' => __('localization.review.accepted_risks_expired'), 'revoked_exception', 'rejected_exception' => __('localization.review.accepted_risks_needs_review'), 'risk_accepted_without_valid_exception' => __('localization.review.accepted_risks_needs_review'), default => Str::headline($state), }; } /** * @param array $risk */ private function acceptedRiskReviewState(array $risk): string { $reviewDueAt = $this->plainText($risk['review_due_at'] ?? null, ''); $expiresAt = $this->plainText($risk['expires_at'] ?? null, ''); if ($reviewDueAt !== '') { return __('localization.review.accepted_risk_review_due_on', ['date' => $reviewDueAt]); } if ($expiresAt !== '') { return __('localization.review.accepted_risk_expires_on', ['date' => $expiresAt]); } return __('localization.review.review_date_not_recorded'); } private function downloadLabel(string $state): string { return match ($state) { ReviewPackOutputResolutionGuidance::STATE_CUSTOMER_SAFE_READY => __('localization.review.download_customer_safe_review_pack'), ReviewPackOutputResolutionGuidance::STATE_INTERNAL_ONLY => __('localization.review.download_internal_review_pack'), default => __('localization.review.download_review_pack_with_limitations'), }; } /** * @return list> */ private function sectionAppendix(ReviewPack $reviewPack, EnvironmentReview $review): array { $includeOperations = (bool) (($reviewPack->options ?? [])['include_operations'] ?? true); return $review->sections ->filter(fn (EnvironmentReviewSection $section): bool => $includeOperations || $section->section_key !== 'operations_health') ->sortBy('sort_order') ->values() ->map(function (EnvironmentReviewSection $section): array { $summaryPayload = is_array($section->summary_payload) ? $section->summary_payload : []; $renderPayload = is_array($section->render_payload) ? $section->render_payload : []; return [ 'title' => (string) $section->title, 'completeness_label' => Str::headline((string) $section->completeness_state), 'summary_items' => collect($summaryPayload) ->map(function (mixed $value, string $key): ?array { if (is_array($value) || $value === null || $value === '') { return null; } return [ 'label' => Str::headline($key), 'value' => (string) $value, ]; }) ->filter() ->take(6) ->values() ->all(), 'highlights' => collect($renderPayload['highlights'] ?? []) ->filter(fn (mixed $value): bool => is_string($value) && trim($value) !== '') ->take(5) ->values() ->all(), 'entries' => $this->sectionEntries($renderPayload, $section->isControlInterpretation()), 'next_actions' => collect($renderPayload['next_actions'] ?? []) ->filter(fn (mixed $value): bool => is_string($value) && trim($value) !== '') ->take(4) ->values() ->all(), 'disclosure' => is_string($renderPayload['disclosure'] ?? null) ? $renderPayload['disclosure'] : null, ]; }) ->all(); } /** * @param array $renderPayload * @return list */ private function sectionEntries(array $renderPayload, bool $isControlInterpretation): array { return collect($renderPayload['entries'] ?? []) ->filter(fn (mixed $entry): bool => is_array($entry)) ->take($isControlInterpretation ? 3 : 4) ->map(function (array $entry) use ($isControlInterpretation): array { if ($isControlInterpretation) { $basisItems = collect($entry['evidence_basis_items'] ?? []) ->filter(fn (mixed $value): bool => is_string($value) && trim($value) !== '') ->take(2) ->values() ->all(); $summary = $this->plainText($entry['explanation_text'] ?? null, ''); if ($summary === '' && $basisItems !== []) { $summary = implode(' ', $basisItems); } return [ 'title' => $this->plainText($entry['control_name'] ?? ($entry['title'] ?? null), __('localization.review.control')), 'summary' => $summary !== '' ? $summary : null, ]; } $detailParts = collect([ $entry['summary'] ?? null, isset($entry['severity']) ? Str::headline((string) $entry['severity']) : null, isset($entry['status']) ? Str::headline((string) $entry['status']) : null, isset($entry['outcome']) ? Str::headline((string) $entry['outcome']) : null, ]) ->filter(fn (mixed $value): bool => is_string($value) && trim($value) !== '') ->values() ->all(); return [ 'title' => $this->plainText($entry['title'] ?? ($entry['displayName'] ?? null), __('localization.review.entry')), 'summary' => $detailParts === [] ? null : implode(' ยท ', $detailParts), ]; }) ->values() ->all(); } private function downloadUrl(Request $request, ReviewPack $reviewPack, EnvironmentReview $review): ?string { if (! filled($reviewPack->file_path) || ! filled($reviewPack->file_disk)) { return null; } return app(ReviewPackService::class)->generateDownloadUrl( $reviewPack, $this->routeParameters($request, $review), ); } private function reviewUrl(Request $request, EnvironmentReview $review, ManagedEnvironment $tenant): string { $url = EnvironmentReviewResource::environmentScopedUrl('view', ['record' => $review], $tenant); if (! $request->boolean(CustomerReviewWorkspace::DETAIL_CONTEXT_QUERY_KEY)) { return $url; } return $this->appendQuery($url, [ CustomerReviewWorkspace::DETAIL_CONTEXT_QUERY_KEY => 1, 'source_surface' => CustomerReviewWorkspace::SOURCE_SURFACE, 'tenant_filter_id' => $request->query('tenant_filter_id'), ]); } private function reviewPackUrl(Request $request, ReviewPack $reviewPack, ManagedEnvironment $tenant): string { $url = ReviewPackResource::getUrl('view', ['record' => $reviewPack], tenant: $tenant, panel: 'admin'); if ((string) $request->query('source_surface') !== CustomerReviewWorkspace::SOURCE_SURFACE) { return $url; } return $this->appendQuery($url, [ 'source_surface' => CustomerReviewWorkspace::SOURCE_SURFACE, 'tenant_filter_id' => $request->query('tenant_filter_id'), ]); } /** * @return array */ private function profileState(Request $request, string $state): array { return ReportProfileRegistry::resolve( is_string($request->query(ReportProfileRegistry::QUERY_PARAMETER)) ? (string) $request->query(ReportProfileRegistry::QUERY_PARAMETER) : null, ReportProfileRegistry::defaultForRenderedReportState( $state, $this->isCustomerWorkspaceReportRequest($request), ), ); } /** * @return array{source_surface:string,customer_workspace_context:bool,requested_profile:?string,interpretation_version:string} */ private function sourceMetadata(Request $request, EnvironmentReview $review): array { return [ 'source_surface' => is_string($request->query('source_surface')) ? (string) $request->query('source_surface') : 'review_pack', 'customer_workspace_context' => $this->isCustomerWorkspaceReportRequest($request), 'requested_profile' => is_string($request->query(ReportProfileRegistry::QUERY_PARAMETER)) ? trim((string) $request->query(ReportProfileRegistry::QUERY_PARAMETER)) : null, 'interpretation_version' => (string) ($request->query('interpretation_version') ?? $review->controlInterpretationVersion()), ]; } /** * @return array */ private function routeParameters(Request $request, EnvironmentReview $review): array { $parameters = [ 'source_surface' => is_string($request->query('source_surface')) ? (string) $request->query('source_surface') : 'review_pack', 'review_id' => (int) $review->getKey(), 'tenant_filter_id' => $request->query('tenant_filter_id'), 'interpretation_version' => $review->controlInterpretationVersion(), ]; if ($request->boolean(CustomerReviewWorkspace::DETAIL_CONTEXT_QUERY_KEY)) { $parameters[CustomerReviewWorkspace::DETAIL_CONTEXT_QUERY_KEY] = 1; } return array_filter( $parameters, static fn (mixed $value): bool => $value !== null && $value !== '', ); } /** * @param array $query */ private function appendQuery(string $url, array $query): string { if ($query === []) { return $url; } return $url.(str_contains($url, '?') ? '&' : '?').http_build_query(array_filter( $query, static fn (mixed $value): bool => $value !== null && $value !== '', )); } private function isCustomerWorkspaceReportRequest(Request $request): bool { return $request->boolean(CustomerReviewWorkspace::DETAIL_CONTEXT_QUERY_KEY) || (string) $request->query('source_surface') === CustomerReviewWorkspace::SOURCE_SURFACE; } private function plainText(mixed $value, string $fallback): string { if (! is_scalar($value) && $value !== null) { return $fallback; } $text = preg_replace('/\s+/', ' ', trim((string) $value)); if (! is_string($text) || $text === '') { return $fallback; } return str_starts_with($text, 'localization.') ? $fallback : $text; } }