TenantAtlas/apps/platform/app/Filament/Pages/Reviews/CustomerReviewWorkspace.php
Ahmed Darrazi acdb205e1b
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 3m42s
feat: customer review workspace output resolution guidance (spec 349)
Implemented the output resolution guidance for the customer review workspace and internal views. Added ReviewPackOutputResolutionGuidance, updated CustomerReviewWorkspace and EnvironmentReviewResource, and added related blade views and tests.
2026-06-03 03:31:29 +02:00

2996 lines
123 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Filament\Pages\Reviews;
use App\Filament\Concerns\CleansAdminTenantQueryParameter;
use App\Filament\Concerns\ClearsWorkspaceHubEnvironmentFilterState;
use App\Filament\Resources\EnvironmentReviewResource;
use App\Filament\Resources\EvidenceSnapshotResource;
use App\Models\EnvironmentReview;
use App\Models\EnvironmentReviewAcknowledgement;
use App\Models\EvidenceSnapshot;
use App\Models\Finding;
use App\Models\FindingException;
use App\Models\ManagedEnvironment;
use App\Models\OperationRun;
use App\Models\ReviewPack;
use App\Models\User;
use App\Models\Workspace;
use App\Services\Audit\WorkspaceAuditLogger;
use App\Services\EnvironmentReviews\EnvironmentReviewAcknowledgementService;
use App\Services\EnvironmentReviews\EnvironmentReviewRegisterService;
use App\Services\ReviewPackService;
use App\Support\Audit\AuditActionId;
use App\Support\Auth\Capabilities;
use App\Support\EnvironmentReviewCompletenessState;
use App\Support\Filament\TablePaginationProfiles;
use App\Support\Findings\FindingOutcomeSemantics;
use App\Support\Governance\Controls\ComplianceEvidenceMappingV1;
use App\Support\Navigation\CanonicalNavigationContext;
use App\Support\Navigation\WorkspaceHubEnvironmentFilter;
use App\Support\Navigation\WorkspaceHubNavigation;
use App\Support\OperationRunLinks;
use App\Support\ReviewPackStatus;
use App\Support\ReviewPacks\ReviewPackOutputReadiness;
use App\Support\ReviewPacks\ReviewPackOutputResolutionGuidance;
use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceInspectAffordance;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceProfile;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceSlot;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceType;
use App\Support\Ui\GovernanceArtifactTruth\ArtifactTruthEnvelope;
use App\Support\Ui\GovernanceArtifactTruth\ArtifactTruthPresenter;
use App\Support\Ui\GovernanceArtifactTruth\CompressedGovernanceOutcome;
use App\Support\Ui\GovernanceArtifactTruth\SurfaceCompressionContext;
use App\Support\Workspaces\WorkspaceContext;
use BackedEnum;
use Filament\Actions\Action;
use Filament\Forms\Components\Textarea;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Concerns\InteractsWithTable;
use Filament\Tables\Contracts\HasTable;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use UnitEnum;
class CustomerReviewWorkspace extends Page implements HasTable
{
use CleansAdminTenantQueryParameter;
use ClearsWorkspaceHubEnvironmentFilterState;
use InteractsWithTable;
public const string DETAIL_CONTEXT_QUERY_KEY = 'customer_workspace';
public const string SOURCE_SURFACE = 'customer_review_workspace';
private const array ACCEPTED_RISK_FOLLOW_UP_STATES = [
'expiring_exception',
'expired_exception',
'revoked_exception',
'risk_accepted_without_valid_exception',
'pending_exception',
];
protected static bool $isDiscovered = false;
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-document-text';
protected static string|UnitEnum|null $navigationGroup = 'Reporting';
protected static ?string $navigationLabel = 'Customer reviews';
protected static ?int $navigationSort = 44;
protected static ?string $title = 'Customer Review Workspace';
protected static ?string $slug = 'reviews/workspace';
protected string $view = 'filament.pages.reviews.customer-review-workspace';
public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
{
return ActionSurfaceDeclaration::forPage(ActionSurfaceProfile::RunLog, ActionSurfaceType::ReadOnlyRegistryReport)
->satisfy(ActionSurfaceSlot::ListHeader, 'Header actions provide a single Clear filters action for the customer review workspace.')
->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::PrimaryLinkColumn->value)
->withPrimaryLinkColumnReason('Only the dedicated review-open column should navigate away; the rest of the row stays comparative workspace context.')
->exempt(ActionSurfaceSlot::ListBulkMoreGroup, 'The customer review workspace remains scan-first and does not expose bulk actions.')
->satisfy(ActionSurfaceSlot::ListEmptyState, 'The empty state keeps exactly one Clear filters CTA when filters are active.')
->exempt(ActionSurfaceSlot::DetailHeader, 'The dedicated open link column opens the latest published review detail instead of an inline canonical detail panel.');
}
public static function getNavigationGroup(): string
{
return WorkspaceHubNavigation::workspaceWideGroup(__('localization.review.reporting'));
}
public static function getNavigationLabel(): string
{
return __('localization.review.customer_reviews');
}
public static function getNavigationUrl(): string
{
return WorkspaceHubNavigation::environmentFilteredUrl(static::getUrl(panel: 'admin'));
}
public function getTitle(): string
{
return __('localization.review.customer_review_workspace');
}
public static function environmentFilterUrl(ManagedEnvironment $tenant): string
{
return static::getUrl(panel: 'admin').'?'.http_build_query([
'environment_id' => (int) $tenant->getKey(),
]);
}
/**
* @var array<int, ManagedEnvironment>|null
*/
private ?array $authorizedTenants = null;
public function mount(): void
{
$this->authorizePageAccess();
$this->resetWorkspaceHubEnvironmentFilterStateForCleanEntry(request());
$this->applyRequestedTenantPrefilter();
$this->mountInteractsWithTable();
$this->resetWorkspaceHubEnvironmentFilterStateForCleanEntry(request());
$this->auditWorkspaceOpen();
}
protected function getHeaderActions(): array
{
$actions = [];
$governanceContext = $this->incomingGovernanceContext();
if ($governanceContext?->backLinkUrl !== null) {
$actions[] = Action::make('return_to_governance_inbox')
->label($governanceContext->backLinkLabel ?? 'Back to governance inbox')
->icon('heroicon-o-arrow-left')
->color('gray')
->url($governanceContext->backLinkUrl);
}
$actions[] = Action::make('clear_filters')
->label(__('localization.review.clear_filters'))
->icon('heroicon-o-x-mark')
->color('gray')
->visible(fn (): bool => $this->hasActiveFilters())
->action(function (): void {
$this->clearWorkspaceFilters();
});
return $actions;
}
public function acknowledgeReviewAction(): Action
{
return Action::make('acknowledgeReview')
->label(__('localization.review.acknowledge_review'))
->icon('heroicon-o-check-badge')
->color('primary')
->record(fn (): ?ManagedEnvironment => $this->latestReleasedTenant())
->requiresConfirmation()
->modalHeading(__('localization.review.acknowledge_review_heading'))
->modalDescription(__('localization.review.acknowledge_review_description'))
->modalSubmitActionLabel(__('localization.review.acknowledge_review_confirm'))
->form([
Textarea::make('comment')
->label(__('localization.review.acknowledge_review_comment'))
->rows(4)
->maxLength(2000),
])
->action(function (array $data): void {
$actor = auth()->user();
$tenant = $this->latestReleasedTenant();
$review = $tenant instanceof ManagedEnvironment
? $this->latestPublishedReview($tenant)
: null;
if (! $actor instanceof User || ! $tenant instanceof ManagedEnvironment || ! $review instanceof EnvironmentReview) {
Notification::make()
->title(__('localization.review.acknowledge_review_unavailable'))
->danger()
->send();
return;
}
try {
app(EnvironmentReviewAcknowledgementService::class)->acknowledge(
tenant: $tenant,
review: $review,
actor: $actor,
comment: is_string($data['comment'] ?? null) ? (string) $data['comment'] : null,
);
} catch (\Throwable $throwable) {
Notification::make()
->title(__('localization.review.acknowledge_review_failed'))
->body($throwable->getMessage())
->danger()
->send();
return;
}
Notification::make()
->title(__('localization.review.review_acknowledged'))
->success()
->send();
});
}
public function table(Table $table): Table
{
return $table
->query(fn (): Builder => $this->workspaceQuery())
->defaultSort('name')
->paginated(TablePaginationProfiles::customPage())
->persistFiltersInSession()
->persistSearchInSession()
->persistSortInSession()
->recordUrl(null)
->columns([
TextColumn::make('name')->label('Environment')->searchable(),
TextColumn::make('package_availability')
->label(__('localization.review.governance_package'))
->width('9rem')
->extraHeaderAttributes(['class' => 'whitespace-normal'])
->badge()
->getStateUsing(fn (ManagedEnvironment $record): string => $this->governancePackageAvailabilityLabel($record))
->color(fn (ManagedEnvironment $record): string => $this->governancePackageAvailabilityColor($record))
->tooltip(fn (ManagedEnvironment $record): string => $this->governancePackageAvailability($record)['description']),
TextColumn::make('latest_review')
->label(__('localization.review.status'))
->width('9rem')
->badge()
->getStateUsing(fn (ManagedEnvironment $record): string => $this->latestReviewStateLabel($record))
->color(fn (ManagedEnvironment $record): string => $this->latestReviewStateColor($record)),
TextColumn::make('evidence_proof_state')
->label(__('localization.review.evidence_status'))
->width('8rem')
->badge()
->getStateUsing(fn (ManagedEnvironment $record): string => $this->evidenceStatusLabel($record))
->color(fn (ManagedEnvironment $record): string => $this->evidenceStatusColor($record)),
TextColumn::make('recommended_next_action')
->label(__('localization.review.next_step'))
->width('10rem')
->extraHeaderAttributes(['class' => 'whitespace-normal'])
->getStateUsing(fn (ManagedEnvironment $record): string => $this->controlRecommendedNextAction($record))
->wrap(),
TextColumn::make('open_review')
->label(__('localization.review.open'))
->width('8rem')
->getStateUsing(fn (): string => __('localization.review.open_review'))
->url(fn (ManagedEnvironment $record): ?string => $this->latestReviewUrl($record))
->color('primary'),
])
->filters([
SelectFilter::make('managed_environment_id')
->label('Environment')
->options(fn (): array => $this->tenantFilterOptions())
->default(fn (): ?string => $this->defaultTenantFilter())
->query(function (Builder $query, array $data): Builder {
$tenantId = $data['value'] ?? null;
return is_numeric($tenantId)
? $query->whereKey((int) $tenantId)
: $query;
})
->searchable(),
])
->actions([])
->bulkActions([])
->emptyStateHeading(fn (): string => $this->workspaceEmptyStateHeading())
->emptyStateDescription(fn (): string => $this->workspaceEmptyStateDescription())
->emptyStateActions([
Action::make('clear_filters_empty')
->label(__('localization.review.clear_filters'))
->icon('heroicon-o-x-mark')
->color('gray')
->visible(fn (): bool => $this->hasActiveFilters())
->action(fn (): mixed => $this->clearWorkspaceFilters()),
]);
}
/**
* @return array<int, ManagedEnvironment>
*/
public function authorizedTenants(): array
{
if ($this->authorizedTenants !== null) {
return $this->authorizedTenants;
}
$user = auth()->user();
$workspace = $this->workspace();
if (! $user instanceof User || ! $workspace instanceof Workspace) {
return $this->authorizedTenants = [];
}
return $this->authorizedTenants = app(EnvironmentReviewRegisterService::class)->authorizedTenants($user, $workspace);
}
public function activeEnvironmentFilterLabel(): ?string
{
return $this->filteredTenant()?->name;
}
/**
* @return array{label: string, clear_url: string}|null
*/
public function environmentFilterChip(): ?array
{
$tenant = $this->filteredTenant();
if (! $tenant instanceof ManagedEnvironment) {
return null;
}
return [
'label' => (string) $tenant->name,
'clear_url' => $this->cleanWorkspaceHubUrl(static::getUrl(panel: 'admin')),
];
}
/**
* @return array<string, mixed>|null
*/
public function latestReviewConsumptionPayload(): ?array
{
$tenant = $this->latestReleasedTenant();
if (! $tenant instanceof ManagedEnvironment) {
return null;
}
$review = $this->latestPublishedReview($tenant);
if (! $review instanceof EnvironmentReview) {
return null;
}
$review->loadMissing([
'currentExportReviewPack.operationRun',
'evidenceSnapshot.operationRun',
'operationRun',
]);
$publishedAt = $review->published_at ?? $review->generated_at ?? $review->created_at;
$packageAvailability = $this->governancePackageAvailability($tenant);
$downloadUrl = $this->reviewPackDownloadUrl($review, $tenant);
$reviewUrl = $this->latestReviewUrl($tenant);
$evidenceUrl = $this->evidenceSnapshotUrlForReview($review, $tenant);
$outputReadiness = $this->reviewPackOutputReadinessForReview($review);
$outputGuidance = $this->reviewOutputGuidanceForReview(
review: $review,
downloadUrl: $downloadUrl,
reviewUrl: $reviewUrl,
evidenceUrl: $evidenceUrl,
);
$decision = $this->decisionSummaryForReview($review);
$acceptedRisks = $this->acceptedRisksForReview($review);
$hasAcceptedRiskFollowUp = $this->acceptedRiskFollowUpRequiredForReview($review);
$findingPanel = $this->findingPanelForReview($tenant);
$evidencePath = $this->evidencePathForReview($review, $tenant, $packageAvailability, $downloadUrl, $decision, $acceptedRisks);
$readiness = $this->reviewReadinessForTenant(
tenant: $tenant,
review: $review,
packageAvailability: $packageAvailability,
outputReadiness: $outputReadiness,
outputGuidance: $outputGuidance,
downloadUrl: $downloadUrl,
reviewUrl: $reviewUrl,
evidenceUrl: $evidenceUrl,
);
return [
'scope' => $this->reviewScopePayload($tenant),
'latest' => [
'review_label' => __('localization.review.released_review_for_environment', [
'environment' => $tenant->name,
]),
'environment_label' => $tenant->name,
'status_label' => $this->latestReviewStateLabel($tenant),
'status_color' => $this->latestReviewStateColor($tenant),
'published_label' => $publishedAt instanceof \DateTimeInterface
? $publishedAt->format('M j, Y H:i')
: __('localization.review.unavailable'),
'package_label' => $packageAvailability['label'],
'package_badge_label' => $this->governancePackageAvailabilityLabel($tenant),
'package_color' => $this->governancePackageAvailabilityColor($tenant),
'package_description' => $packageAvailability['description'],
'primary_action_label' => $downloadUrl !== null
? $outputGuidance['qualified_download_label']
: __('localization.review.open_latest_review'),
'primary_action_url' => $downloadUrl ?? $reviewUrl,
'primary_action_icon' => $downloadUrl !== null
? 'heroicon-o-arrow-down-tray'
: 'heroicon-o-arrow-top-right-on-square',
'secondary_action_label' => $readiness['secondary_action_label'],
'secondary_action_url' => $readiness['secondary_action_url'],
'secondary_action_icon' => 'heroicon-o-arrow-top-right-on-square',
],
'readiness' => $readiness,
'readiness_flow' => $this->reviewConsumptionFlowForReview($tenant, $review, $packageAvailability, $downloadUrl, $outputReadiness),
'finding_panel' => $findingPanel,
'acknowledgement' => $this->reviewAcknowledgementPayloadForReview($tenant, $review, $packageAvailability, $downloadUrl),
'decision' => $decision,
'accepted_risks' => $acceptedRisks,
'accepted_risk_panel' => $this->acceptedRiskPanelForReview($review, $tenant),
'evidence_basis' => $this->evidenceBasisForReview($review, $packageAvailability, $outputReadiness),
'evidence_path' => $evidencePath,
'aside_evidence_path' => $this->asideEvidencePath($evidencePath),
'review_pack_panel' => $this->reviewPackPanelForReview($review, $tenant, $packageAvailability, $downloadUrl, $outputReadiness),
'follow_ups' => $this->customerSafeFollowUpsForReview($decision),
'diagnostics' => $this->diagnosticsDisclosureForReview(),
'disclosure_rules' => $this->disclosureRuleRows(),
];
}
/**
* @param array{state:string,label:string,description:string} $packageAvailability
* @return array{
* state: 'not_available'|'required'|'acknowledged'|'re_ack_required',
* status_label: string,
* status_color: string,
* reason: string,
* impact: string,
* action_name: string|null,
* action_label: string,
* action_color: string,
* action_disabled: bool,
* action_helper: string|null,
* acknowledged_at_label: string|null,
* acknowledged_by_label: string|null,
* comment: string|null,
* basis: list<array{label:string,value:string,color:string}>
* }
*/
private function reviewAcknowledgementPayloadForReview(
ManagedEnvironment $tenant,
EnvironmentReview $review,
array $packageAvailability,
?string $downloadUrl,
): array {
$actor = auth()->user();
$canAcknowledge = $actor instanceof User
&& $actor->canAccessTenant($tenant)
&& $actor->can(Capabilities::ENVIRONMENT_REVIEW_ACKNOWLEDGE, $tenant);
$ack = EnvironmentReviewAcknowledgement::query()
->with(['acknowledgedByUser'])
->where('environment_review_id', (int) $review->getKey())
->where('managed_environment_id', (int) $review->managed_environment_id)
->where('workspace_id', (int) $review->workspace_id)
->first();
$currentReviewPackId = is_numeric($review->current_export_review_pack_id)
? (int) $review->current_export_review_pack_id
: null;
$currentEvidenceSnapshotId = is_numeric($review->evidence_snapshot_id)
? (int) $review->evidence_snapshot_id
: null;
$reviewPackProof = $this->reviewPackProofForReview($packageAvailability, $downloadUrl);
$evidenceState = $this->evidenceStatusState($tenant);
$basis = [
[
'label' => __('localization.review.review_pack'),
'value' => $reviewPackProof['label'],
'color' => $reviewPackProof['color'],
],
[
'label' => __('localization.review.evidence'),
'value' => $this->evidenceStatusLabelForState($evidenceState),
'color' => $this->evidenceStatusColorForState($evidenceState),
],
];
if (! $review->isPublished()) {
return [
'state' => 'not_available',
'status_label' => __('localization.review.acknowledgement_not_available'),
'status_color' => 'gray',
'reason' => __('localization.review.acknowledgement_not_available_reason'),
'impact' => __('localization.review.acknowledgement_not_available_impact'),
'action_name' => null,
'action_label' => __('localization.review.review_accepted_risks'),
'action_color' => 'gray',
'action_disabled' => true,
'action_helper' => null,
'acknowledged_at_label' => null,
'acknowledged_by_label' => null,
'comment' => null,
'basis' => $basis,
];
}
if (! $ack instanceof EnvironmentReviewAcknowledgement) {
return [
'state' => 'required',
'status_label' => __('localization.review.acknowledgement_required'),
'status_color' => 'warning',
'reason' => __('localization.review.acknowledgement_required_reason'),
'impact' => __('localization.review.acknowledgement_required_impact'),
'action_name' => 'acknowledgeReview',
'action_label' => __('localization.review.acknowledge_review'),
'action_color' => 'primary',
'action_disabled' => ! $canAcknowledge,
'action_helper' => ! $canAcknowledge
? __('localization.review.acknowledgement_requires_permission')
: null,
'acknowledged_at_label' => null,
'acknowledged_by_label' => null,
'comment' => null,
'basis' => $basis,
];
}
$basisDriftDetected = false;
if (is_numeric($ack->review_pack_id) && is_int($currentReviewPackId) && (int) $ack->review_pack_id !== $currentReviewPackId) {
$basisDriftDetected = true;
}
if (is_numeric($ack->evidence_snapshot_id) && is_int($currentEvidenceSnapshotId) && (int) $ack->evidence_snapshot_id !== $currentEvidenceSnapshotId) {
$basisDriftDetected = true;
}
$acknowledgedAtLabel = $ack->acknowledged_at instanceof \DateTimeInterface
? $ack->acknowledged_at->format('M j, Y H:i')
: null;
$acknowledgedByLabel = $ack->acknowledgedByUser instanceof User
? (string) $ack->acknowledgedByUser->name
: null;
$comment = is_string($ack->comment) ? trim($ack->comment) : null;
$comment = filled($comment) ? $comment : null;
if ($basisDriftDetected) {
return [
'state' => 're_ack_required',
'status_label' => __('localization.review.acknowledgement_re_ack_required'),
'status_color' => 'warning',
'reason' => __('localization.review.acknowledgement_re_ack_required_reason'),
'impact' => __('localization.review.acknowledgement_re_ack_required_impact'),
'action_name' => 'acknowledgeReview',
'action_label' => __('localization.review.re_acknowledge_review'),
'action_color' => 'warning',
'action_disabled' => ! $canAcknowledge,
'action_helper' => ! $canAcknowledge
? __('localization.review.acknowledgement_requires_permission')
: null,
'acknowledged_at_label' => $acknowledgedAtLabel,
'acknowledged_by_label' => $acknowledgedByLabel,
'comment' => $comment,
'basis' => $basis,
];
}
return [
'state' => 'acknowledged',
'status_label' => __('localization.review.review_acknowledged'),
'status_color' => 'success',
'reason' => __('localization.review.acknowledgement_recorded_reason'),
'impact' => __('localization.review.acknowledgement_recorded_impact'),
'action_name' => null,
'action_label' => __('localization.review.review_accepted_risks'),
'action_color' => 'gray',
'action_disabled' => true,
'action_helper' => null,
'acknowledged_at_label' => $acknowledgedAtLabel,
'acknowledged_by_label' => $acknowledgedByLabel,
'comment' => $comment,
'basis' => $basis,
];
}
/**
* @return array{label:string,description:string,is_filtered:bool}
*/
private function reviewScopePayload(ManagedEnvironment $tenant): array
{
$filteredTenant = $this->filteredTenant();
if ($filteredTenant instanceof ManagedEnvironment) {
return [
'label' => __('localization.review.customer_workspace_scope_environment_filtered', [
'environment' => $filteredTenant->name,
]),
'description' => __('localization.review.customer_workspace_scope_environment_filtered_description'),
'is_filtered' => true,
];
}
return [
'label' => __('localization.review.customer_workspace_scope_workspace_wide'),
'description' => __('localization.review.customer_workspace_scope_workspace_wide_description', [
'environment' => $tenant->name,
]),
'is_filtered' => false,
];
}
/**
* @param array{state:string,label:string,description:string} $packageAvailability
* @param array<string, mixed> $outputReadiness
* @param array<string, mixed> $outputGuidance
* @return array{
* question:string,
* label:string,
* color:string,
* boundary_label:string,
* boundary_color:string,
* reason:string,
* impact:string,
* primary_action_label:string,
* primary_action_url:?string,
* primary_action_icon:string,
* secondary_action_label:?string,
* secondary_action_url:?string,
* secondary_actions:list<array{label:string,url:?string,kind:string,icon:string}>,
* output_guidance:array<string, mixed>
* }
*/
private function reviewReadinessForTenant(
ManagedEnvironment $tenant,
EnvironmentReview $review,
array $packageAvailability,
array $outputReadiness,
array $outputGuidance,
?string $downloadUrl,
?string $reviewUrl,
?string $evidenceUrl,
): array {
$hasAcceptedRiskFollowUp = $this->acceptedRiskFollowUpRequiredForReview($review);
$findingPanel = $this->findingPanelForReview($tenant);
$hasFindingFollowUp = $findingPanel['open_count'] > 0;
$effectiveState = $this->effectiveWorkspaceReadinessState(
$outputReadiness,
$hasFindingFollowUp,
$hasAcceptedRiskFollowUp,
);
$reasonCode = $hasFindingFollowUp
? 'findings_follow_up_required'
: ($hasAcceptedRiskFollowUp ? 'accepted_risk_follow_up_required' : (string) ($outputReadiness['primary_reason'] ?? 'customer_safe_ready'));
$actions = $this->workspaceReadinessActions(
state: $effectiveState,
reasonCode: $reasonCode,
downloadUrl: $downloadUrl,
reviewUrl: $reviewUrl,
evidenceUrl: $evidenceUrl,
);
$followUpOverride = in_array($reasonCode, ['findings_follow_up_required', 'accepted_risk_follow_up_required'], true);
$secondaryActions = $followUpOverride
? collect([
$actions['secondary_url'] !== null && $actions['secondary_label'] !== null
? [
'label' => $actions['secondary_label'],
'url' => $actions['secondary_url'],
'kind' => 'environment_link',
'icon' => 'heroicon-o-arrow-top-right-on-square',
]
: null,
])->filter()->values()->all()
: (is_array($outputGuidance['secondary_actions'] ?? null) ? $outputGuidance['secondary_actions'] : []);
$primaryAction = $followUpOverride
? [
'label' => $actions['primary_label'],
'url' => $actions['primary_url'],
'icon' => $actions['primary_icon'],
]
: (is_array($outputGuidance['primary_action'] ?? null) ? $outputGuidance['primary_action'] : null);
return [
'question' => __('localization.review.review_pack_output_status'),
'label' => $followUpOverride
? $this->workspaceReadinessLabel($effectiveState)
: (string) ($outputGuidance['label'] ?? $this->workspaceReadinessLabel($effectiveState)),
'color' => $followUpOverride
? $this->workspaceReadinessColor($effectiveState)
: (string) ($outputGuidance['color'] ?? $this->workspaceReadinessColor($effectiveState)),
'boundary_label' => $followUpOverride
? $this->workspaceBoundaryLabel((string) ($outputReadiness['customer_safe_state'] ?? 'requires_review'))
: (string) ($outputGuidance['boundary_label'] ?? $this->workspaceBoundaryLabel((string) ($outputReadiness['customer_safe_state'] ?? 'requires_review'))),
'boundary_color' => $followUpOverride
? $this->workspaceBoundaryColor((string) ($outputReadiness['customer_safe_state'] ?? 'requires_review'))
: (string) ($outputGuidance['boundary_color'] ?? $this->workspaceBoundaryColor((string) ($outputReadiness['customer_safe_state'] ?? 'requires_review'))),
'reason' => $followUpOverride
? $this->workspaceReadinessReason(
reasonCode: $reasonCode,
outputReadiness: $outputReadiness,
findingPanel: $findingPanel,
packageAvailability: $packageAvailability,
)
: (string) ($outputGuidance['primary_reason'] ?? $packageAvailability['description']),
'impact' => $followUpOverride
? $this->workspaceReadinessImpact(
state: $effectiveState,
reasonCode: $reasonCode,
)
: (string) ($outputGuidance['impact'] ?? $this->workspaceReadinessImpact(state: $effectiveState, reasonCode: $reasonCode)),
'primary_action_label' => (string) ($primaryAction['label'] ?? $actions['primary_label']),
'primary_action_url' => $primaryAction['url'] ?? $actions['primary_url'],
'primary_action_icon' => (string) ($primaryAction['icon'] ?? $actions['primary_icon']),
'secondary_action_label' => $secondaryActions[0]['label'] ?? null,
'secondary_action_url' => $secondaryActions[0]['url'] ?? null,
'secondary_actions' => $secondaryActions,
'output_guidance' => $outputGuidance,
];
}
/**
* @param array{state:string,label:string,description:string} $packageAvailability
* @param array<string, mixed> $outputReadiness
* @return list<array{title:string,label:string,color:string,description:string,is_current:bool}>
*/
private function reviewConsumptionFlowForReview(
ManagedEnvironment $tenant,
EnvironmentReview $review,
array $packageAvailability,
?string $downloadUrl,
array $outputReadiness,
): array {
$evidenceState = $this->evidenceStatusState($tenant);
$findingPanel = $this->findingPanelForReview($tenant);
$acceptedRisk = $this->acceptedRiskDimensionForReview($review, $tenant);
$hasAcceptedRiskFollowUp = $this->acceptedRiskFollowUpRequiredForReview($review);
$hasReadyPackage = $packageAvailability['state'] === 'available' && $downloadUrl !== null;
$hasMappedReviewData = $this->primaryControlSummary($tenant) !== null;
$workspaceState = $this->effectiveWorkspaceReadinessState(
$outputReadiness,
$findingPanel['open_count'] > 0,
$hasAcceptedRiskFollowUp,
);
$hasBlockingAttention = $findingPanel['open_count'] > 0
|| $hasAcceptedRiskFollowUp
|| $evidenceState !== 'available'
|| ! $hasMappedReviewData
|| $workspaceState !== ReviewPackOutputReadiness::STATE_CUSTOMER_SAFE_READY;
$customerOutputLabel = match (true) {
$hasReadyPackage && ! $hasBlockingAttention => __('localization.review.ready'),
$workspaceState === ReviewPackOutputReadiness::STATE_EXPORT_NOT_READY || ! $hasReadyPackage => __('localization.review.not_ready'),
$hasReadyPackage => __('localization.review.needs_review'),
default => __('localization.review.not_ready'),
};
$customerOutputColor = match (true) {
$hasReadyPackage && ! $hasBlockingAttention => 'success',
$workspaceState === ReviewPackOutputReadiness::STATE_EXPORT_NOT_READY || ! $hasReadyPackage => 'gray',
$hasReadyPackage => 'warning',
default => 'gray',
};
$customerOutputDescription = match (true) {
$hasReadyPackage && ! $hasBlockingAttention => __('localization.review.customer_output_ready_description'),
$hasReadyPackage => __('localization.review.customer_output_needs_review_description'),
default => __('localization.review.customer_output_not_ready_description'),
};
return [
[
'title' => __('localization.review.review_data'),
'label' => __('localization.review.available'),
'color' => 'success',
'description' => __('localization.review.review_data_available_description'),
'is_current' => false,
],
[
'title' => __('localization.review.evidence'),
'label' => $this->evidenceStatusLabelForState($evidenceState),
'color' => $this->evidenceStatusColorForState($evidenceState),
'description' => $this->evidenceDimensionDescription($evidenceState),
'is_current' => $evidenceState !== 'available',
],
[
'title' => __('localization.review.findings_triaged'),
'label' => $findingPanel['status_label'],
'color' => $findingPanel['status_color'],
'description' => $findingPanel['summary'],
'is_current' => $findingPanel['open_count'] > 0,
],
[
'title' => __('localization.review.accepted_risks_reviewed'),
'label' => $acceptedRisk['label'],
'color' => $acceptedRisk['color'],
'description' => $acceptedRisk['description'],
'is_current' => $this->acceptedRiskFollowUpRequiredForReview($review),
],
[
'title' => __('localization.review.review_pack'),
'label' => $packageAvailability['label'],
'color' => $this->governancePackageAvailabilityColor($tenant),
'description' => $this->reviewPackDimensionDescription($packageAvailability),
'is_current' => $packageAvailability['state'] !== 'available',
],
[
'title' => __('localization.review.customer_output'),
'label' => $customerOutputLabel,
'color' => $customerOutputColor,
'description' => $customerOutputDescription,
'is_current' => ! ($hasReadyPackage && ! $hasBlockingAttention),
],
];
}
private function evidenceDimensionDescription(string $state): string
{
return match ($state) {
'available' => __('localization.review.evidence_dimension_available_description'),
'expired' => __('localization.review.evidence_dimension_expired_description'),
'restricted' => __('localization.review.evidence_dimension_restricted_description'),
default => __('localization.review.evidence_dimension_unavailable_description'),
};
}
/**
* @param array{state:string,label:string,description:string} $packageAvailability
*/
private function reviewPackDimensionDescription(array $packageAvailability): string
{
return match ($packageAvailability['state']) {
'available' => __('localization.review.review_pack_dimension_available_description'),
'not_available' => __('localization.review.review_pack_dimension_not_generated_description'),
'preparing' => __('localization.review.review_pack_dimension_preparing_description'),
'expired' => __('localization.review.review_pack_dimension_expired_description'),
default => __('localization.review.review_pack_dimension_unavailable_description'),
};
}
/**
* @return array{label:string,color:string,description:string}
*/
private function acceptedRiskDimensionForReview(EnvironmentReview $review, ManagedEnvironment $tenant): array
{
$acceptedRisks = $this->acceptedRisksForReview($review);
$exceptionCount = $this->acceptedRiskExceptionsForTenant($tenant)->count();
$acceptedRiskCount = max($acceptedRisks['count'], $exceptionCount);
$hasFollowUp = $this->acceptedRiskFollowUpRequiredForReview($review);
if ($hasFollowUp) {
return [
'label' => __('localization.review.accepted_risk_follow_up'),
'color' => 'warning',
'description' => __('localization.review.accepted_risk_dimension_follow_up_description'),
];
}
if ($acceptedRiskCount === 0) {
return [
'label' => __('localization.review.accepted_risk_no_action_needed'),
'color' => 'gray',
'description' => __('localization.review.accepted_risk_dimension_no_action_description'),
];
}
return [
'label' => __('localization.review.accepted_risk_on_record', ['count' => $acceptedRiskCount]),
'color' => 'info',
'description' => __('localization.review.accepted_risk_dimension_on_record_description'),
];
}
private function acceptedRiskFollowUpRequiredForReview(EnvironmentReview $review): bool
{
$package = $this->governancePackageSummaryForReview($review);
$decisionSummary = is_array($package['decision_summary'] ?? null) ? $package['decision_summary'] : [];
if ((string) ($decisionSummary['status'] ?? '') === 'requires_awareness') {
return true;
}
$acceptedEntries = collect($package['accepted_risks'] ?? [])
->filter(static fn (mixed $entry): bool => is_array($entry));
$decisionEntries = collect($package['governance_decisions'] ?? [])
->filter(static fn (mixed $entry): bool => is_array($entry));
if ($acceptedEntries
->merge($decisionEntries)
->contains(static fn (array $entry): bool => in_array(
(string) ($entry['governance_state'] ?? ''),
self::ACCEPTED_RISK_FOLLOW_UP_STATES,
true,
))) {
return true;
}
return FindingException::query()
->where('workspace_id', (int) $review->workspace_id)
->where('managed_environment_id', (int) $review->managed_environment_id)
->current()
->whereIn('current_validity_state', [
FindingException::VALIDITY_EXPIRING,
FindingException::VALIDITY_EXPIRED,
FindingException::VALIDITY_REVOKED,
FindingException::VALIDITY_MISSING_SUPPORT,
])
->exists();
}
/**
* @param array{state:string,label:string,description:string} $packageAvailability
* @param array<string, mixed> $decision
* @param array{count:int,entries:list<array<string, string>>,empty_state:string} $acceptedRisks
* @return list<array{key:string,title:string,label:string,color:string,description:string,action_label:?string,action_url:?string}>
*/
private function evidencePathForReview(
EnvironmentReview $review,
ManagedEnvironment $tenant,
array $packageAvailability,
?string $downloadUrl,
array $decision,
array $acceptedRisks,
): array {
return [
$this->evidenceSnapshotProofForReview($review, $tenant),
$this->reviewPackProofForReview($packageAvailability, $downloadUrl),
$this->decisionTrailProofForReview($decision),
$this->acceptedRiskProofForReview($acceptedRisks),
$this->operationProofForReview($review, $tenant),
$this->exportArtifactProofForReview($packageAvailability, $downloadUrl),
];
}
/**
* @param list<array{key:string,title:string,label:string,color:string,description:string,action_label:?string,action_url:?string}> $evidencePath
* @return list<array{key:string,title:string,label:string,color:string,description:string,detail:string,action_label:?string,action_url:?string}>
*/
private function asideEvidencePath(array $evidencePath): array
{
$asideKeys = [
'evidence_snapshot',
'review_pack',
'decision_trail',
'operation_proof',
];
return collect($evidencePath)
->filter(static fn (array $proof): bool => in_array($proof['key'], $asideKeys, true))
->map(fn (array $proof): array => array_replace($proof, [
'label' => $this->asideEvidencePathLabel($proof),
'detail' => $this->asideEvidencePathDetail($proof),
]))
->values()
->all();
}
/**
* @param array{key:string,label:string,color:string} $proof
*/
private function asideEvidencePathLabel(array $proof): string
{
if ($proof['key'] !== 'decision_trail') {
return $proof['label'];
}
return match ($proof['color']) {
'success', 'info' => __('localization.review.available'),
'warning' => __('localization.review.limited'),
default => __('localization.review.unavailable'),
};
}
/**
* @param array{key:string,title:string,description:string} $proof
*/
private function asideEvidencePathDetail(array $proof): string
{
$description = (string) $proof['description'];
$titlePrefix = trim((string) $proof['title']).' ';
if (str_starts_with($description, $titlePrefix)) {
return Str::ucfirst(Str::replaceStart($titlePrefix, '', $description));
}
return $description;
}
/**
* @return array{key:string,title:string,label:string,color:string,description:string,action_label:?string,action_url:?string}
*/
private function evidenceSnapshotProofForReview(EnvironmentReview $review, ManagedEnvironment $tenant): array
{
$snapshot = $review->evidenceSnapshot;
$state = $this->evidenceStatusState($tenant);
$url = $this->evidenceSnapshotUrlForReview($review, $tenant);
return [
'key' => 'evidence_snapshot',
'title' => __('localization.review.evidence_snapshot'),
'label' => $this->evidenceStatusLabelForState($state),
'color' => $this->evidenceStatusColorForState($state),
'description' => $snapshot instanceof EvidenceSnapshot && $snapshot->generated_at !== null
? __('localization.review.evidence_snapshot_available_description', [
'date' => $snapshot->generated_at->format('M j, Y H:i'),
])
: __('localization.review.evidence_proof_absent'),
'action_label' => $url !== null ? __('localization.review.view_evidence_snapshot') : null,
'action_url' => $url,
];
}
/**
* @param array{state:string,label:string,description:string} $packageAvailability
* @return array{key:string,title:string,label:string,color:string,description:string,action_label:?string,action_url:?string}
*/
private function reviewPackProofForReview(array $packageAvailability, ?string $downloadUrl): array
{
return [
'key' => 'review_pack',
'title' => __('localization.review.review_pack'),
'label' => $packageAvailability['label'],
'color' => match ($packageAvailability['state']) {
'available' => 'success',
'preparing' => 'warning',
'expired', 'unavailable' => 'danger',
default => 'gray',
},
'description' => $packageAvailability['description'],
'action_label' => null,
'action_url' => null,
];
}
/**
* @param array<string, mixed> $decision
* @return array{key:string,title:string,label:string,color:string,description:string,action_label:?string,action_url:?string}
*/
private function decisionTrailProofForReview(array $decision): array
{
return [
'key' => 'decision_trail',
'title' => __('localization.review.decision_trail'),
'label' => (string) $decision['label'],
'color' => (string) $decision['color'],
'description' => (string) $decision['summary'],
'action_label' => null,
'action_url' => null,
];
}
/**
* @param array{count:int,entries:list<array<string, string>>,empty_state:string} $acceptedRisks
* @return array{key:string,title:string,label:string,color:string,description:string,action_label:?string,action_url:?string}
*/
private function acceptedRiskProofForReview(array $acceptedRisks): array
{
return [
'key' => 'accepted_risk_records',
'title' => __('localization.review.accepted_risk_records'),
'label' => $acceptedRisks['count'] === 0
? __('localization.review.accepted_risk_none')
: __('localization.review.accepted_risk_on_record', ['count' => $acceptedRisks['count']]),
'color' => $acceptedRisks['count'] === 0 ? 'success' : 'info',
'description' => $acceptedRisks['count'] === 0
? $acceptedRisks['empty_state']
: __('localization.review.accepted_risk_records_description'),
'action_label' => null,
'action_url' => null,
];
}
/**
* @return array{key:string,title:string,label:string,color:string,description:string,action_label:?string,action_url:?string}
*/
private function operationProofForReview(EnvironmentReview $review, ManagedEnvironment $tenant): array
{
$run = collect([
$review->operationRun,
$review->evidenceSnapshot?->operationRun,
$review->currentExportReviewPack?->operationRun,
])->first(fn (mixed $candidate): bool => $candidate instanceof OperationRun);
if ($run instanceof OperationRun) {
$initiator = is_string($run->initiator_name) && trim($run->initiator_name) !== ''
? trim($run->initiator_name)
: null;
return [
'key' => 'operation_proof',
'title' => __('localization.review.operation_proof'),
'label' => __('localization.review.available'),
'color' => 'info',
'description' => $initiator === null
? __('localization.review.operation_proof_available_description')
: __('localization.review.operation_proof_available_with_initiator_description', ['initiator' => $initiator]),
'action_label' => OperationRunLinks::openLabel(),
'action_url' => OperationRunLinks::tenantlessView($run),
];
}
return [
'key' => 'operation_proof',
'title' => __('localization.review.operation_proof'),
'label' => __('localization.review.unavailable'),
'color' => 'gray',
'description' => __('localization.review.operation_proof_unavailable_description'),
'action_label' => null,
'action_url' => null,
];
}
/**
* @param array{state:string,label:string,description:string} $packageAvailability
* @return array{key:string,title:string,label:string,color:string,description:string,action_label:?string,action_url:?string}
*/
private function exportArtifactProofForReview(array $packageAvailability, ?string $downloadUrl): array
{
return [
'key' => 'export_artifact',
'title' => __('localization.review.export_artifact'),
'label' => $downloadUrl !== null
? __('localization.review.available')
: $packageAvailability['label'],
'color' => $downloadUrl !== null ? 'success' : 'gray',
'description' => $downloadUrl !== null
? __('localization.review.export_artifact_available_description')
: __('localization.review.export_artifact_unavailable_description'),
'action_label' => null,
'action_url' => null,
];
}
/**
* @return array{status_label:string,status_color:string,summary:string,total_count:int,open_count:int,high_impact_count:int,items:list<array{label:string,value:string,color:string}>}
*/
private function findingPanelForReview(ManagedEnvironment $tenant): array
{
$baseQuery = Finding::query()
->where('workspace_id', (int) $tenant->workspace_id)
->where('managed_environment_id', (int) $tenant->getKey());
$total = (clone $baseQuery)->count();
$open = (clone $baseQuery)
->whereIn('status', Finding::openStatusesForQuery())
->count();
$highImpact = (clone $baseQuery)
->whereIn('status', Finding::openStatusesForQuery())
->whereIn('severity', Finding::highSeverityValues())
->count();
$accepted = (clone $baseQuery)
->where('status', Finding::STATUS_RISK_ACCEPTED)
->count();
$summary = match (true) {
$open > 0 && $highImpact > 0 => __('localization.review.findings_high_impact_summary', [
'open' => trans_choice('localization.review.findings_open_attention_count', $open, ['count' => $open]),
'high' => trans_choice('localization.review.findings_high_impact_count_summary', $highImpact, ['count' => $highImpact]),
]),
$open > 0 => trans_choice('localization.review.findings_open_summary', $open, ['count' => $open]),
$total > 0 => __('localization.review.findings_no_open_summary', ['total' => $total]),
default => __('localization.review.findings_none_action_summary'),
};
return [
'status_label' => $open > 0
? __('localization.review.needs_review')
: __('localization.review.no_action_needed'),
'status_color' => match (true) {
$highImpact > 0 => 'danger',
$open > 0 => 'warning',
default => 'success',
},
'summary' => $summary,
'total_count' => $total,
'open_count' => $open,
'high_impact_count' => $highImpact,
'items' => [
[
'label' => __('localization.review.findings_total'),
'value' => (string) $total,
'color' => $total > 0 ? 'info' : 'gray',
],
[
'label' => __('localization.review.findings_open'),
'value' => (string) $open,
'color' => $open > 0 ? 'warning' : 'gray',
],
[
'label' => __('localization.review.findings_high_impact'),
'value' => (string) $highImpact,
'color' => $highImpact > 0 ? 'danger' : 'gray',
],
[
'label' => __('localization.review.accepted_risks'),
'value' => (string) $accepted,
'color' => $accepted > 0 ? 'info' : 'gray',
],
],
];
}
/**
* @return Collection<int, FindingException>
*/
private function acceptedRiskExceptionsForTenant(ManagedEnvironment $tenant): Collection
{
$user = auth()->user();
if (! $user instanceof User || ! $user->can(Capabilities::FINDING_EXCEPTION_VIEW, $tenant)) {
return collect();
}
return FindingException::query()
->with(['owner', 'approver', 'currentDecision'])
->where('workspace_id', (int) $tenant->workspace_id)
->where('managed_environment_id', (int) $tenant->getKey())
->current()
->orderByRaw("case when current_validity_state in ('expiring', 'expired', 'missing_support') then 0 else 1 end")
->latest('approved_at')
->latest('requested_at')
->latest('id')
->get();
}
/**
* @param Collection<int, FindingException> $exceptions
* @return list<array{label:string,value:string,color:string}>
*/
private function acceptedRiskDetailRows(Collection $exceptions): array
{
if ($exceptions->isEmpty()) {
return [];
}
$owner = $exceptions
->map(static fn (FindingException $exception): ?string => $exception->owner?->name ?? $exception->approver?->name)
->filter(static fn (?string $name): bool => is_string($name) && trim($name) !== '')
->first();
$ownedException = $exceptions
->first(static fn (FindingException $exception): bool => $exception->owner?->name !== null || $exception->approver?->name !== null);
$reviewDate = $exceptions
->map(static fn (FindingException $exception): mixed => $exception->review_due_at ?? $exception->expires_at)
->first(static fn (mixed $date): bool => $date instanceof \DateTimeInterface);
$hasMissingReviewDate = $exceptions
->contains(static fn (FindingException $exception): bool => $exception->review_due_at === null && $exception->expires_at === null);
$reason = $ownedException instanceof FindingException && is_string($ownedException->request_reason) && trim($ownedException->request_reason) !== ''
? trim($ownedException->request_reason)
: $exceptions
->map(static fn (FindingException $exception): ?string => is_string($exception->request_reason) ? trim($exception->request_reason) : null)
->filter(static fn (?string $value): bool => is_string($value) && $value !== '')
->first();
$rows = [
[
'label' => __('localization.review.accepted_risk_owner'),
'value' => is_string($owner) ? $owner : __('localization.review.not_recorded'),
'color' => is_string($owner) ? 'info' : 'warning',
],
];
if ($reviewDate instanceof \DateTimeInterface) {
$rows[] = [
'label' => __('localization.review.accepted_risk_next_review'),
'value' => $reviewDate->format('Y-m-d'),
'color' => 'info',
];
}
if ($hasMissingReviewDate) {
$rows[] = [
'label' => __('localization.review.accepted_risk_next_review'),
'value' => __('localization.review.review_date_not_recorded'),
'color' => 'warning',
];
}
$rows[] = [
'label' => __('localization.review.accepted_risk_rationale'),
'value' => is_string($reason) ? Str::limit($reason, 160) : __('localization.review.not_recorded'),
'color' => is_string($reason) ? 'info' : 'warning',
];
return $rows;
}
/**
* @return array{summary_label:string,summary_color:string,items:list<array{label:string,value:string,color:string}>,detail_rows:list<array{label:string,value:string,color:string}>}
*/
private function acceptedRiskPanelForReview(EnvironmentReview $review, ManagedEnvironment $tenant): array
{
$package = $this->governancePackageSummaryForReview($review);
$hasFollowUp = $this->acceptedRiskFollowUpRequiredForReview($review);
$exceptions = $this->acceptedRiskExceptionsForTenant($tenant);
$acceptedEntries = collect($package['accepted_risks'] ?? [])
->filter(static fn (mixed $entry): bool => is_array($entry));
$decisionEntries = collect($package['governance_decisions'] ?? [])
->filter(static fn (mixed $entry): bool => is_array($entry));
$allEntries = $acceptedEntries->merge($decisionEntries);
$total = max($allEntries->count(), $exceptions->count());
$expiring = max(
$allEntries->where('governance_state', 'expiring_exception')->count(),
$exceptions->where('current_validity_state', FindingException::VALIDITY_EXPIRING)->count(),
);
$expired = max(
$allEntries->where('governance_state', 'expired_exception')->count(),
$exceptions->where('current_validity_state', FindingException::VALIDITY_EXPIRED)->count(),
);
$pending = max(
$allEntries->where('governance_state', 'pending_exception')->count(),
$exceptions->where('status', FindingException::STATUS_PENDING)->count(),
);
$needsReview = $allEntries
->filter(static fn (array $entry): bool => in_array(
(string) ($entry['governance_state'] ?? ''),
self::ACCEPTED_RISK_FOLLOW_UP_STATES,
true,
))
->count();
$needsReview = max($needsReview, $exceptions
->filter(static fn (FindingException $exception): bool => in_array(
(string) $exception->current_validity_state,
[
FindingException::VALIDITY_EXPIRING,
FindingException::VALIDITY_EXPIRED,
FindingException::VALIDITY_REVOKED,
FindingException::VALIDITY_MISSING_SUPPORT,
],
true,
))
->count());
return [
'summary_label' => $hasFollowUp
? __('localization.review.accepted_risk_follow_up')
: __('localization.review.accepted_risk_no_action_needed'),
'summary_color' => $hasFollowUp ? ($expired > 0 ? 'danger' : 'warning') : 'gray',
'items' => [
[
'label' => __('localization.review.accepted_risks'),
'value' => (string) $total,
'color' => $total > 0 ? 'info' : 'gray',
],
[
'label' => __('localization.review.accepted_risks_expiring_soon'),
'value' => (string) $expiring,
'color' => $expiring > 0 ? 'warning' : 'gray',
],
[
'label' => __('localization.review.accepted_risks_expired'),
'value' => (string) $expired,
'color' => $expired > 0 ? 'danger' : 'gray',
],
[
'label' => __('localization.review.accepted_risks_pending_approval'),
'value' => (string) $pending,
'color' => $pending > 0 ? 'warning' : 'gray',
],
[
'label' => __('localization.review.accepted_risks_needs_review'),
'value' => (string) $needsReview,
'color' => $needsReview > 0 ? 'warning' : 'gray',
],
],
'detail_rows' => $this->acceptedRiskDetailRows($exceptions),
];
}
/**
* @param array{state:string,label:string,description:string} $packageAvailability
* @param array<string, mixed> $outputReadiness
* @return array{
* status_label:string,
* status_color:string,
* description:string,
* detail_rows:list<array{label:string,value:string,color:string}>,
* download_url:?string
* }
*/
private function reviewPackPanelForReview(
EnvironmentReview $review,
ManagedEnvironment $tenant,
array $packageAvailability,
?string $downloadUrl,
array $outputReadiness,
): array {
$pack = $review->currentExportReviewPack;
$snapshot = $review->evidenceSnapshot;
$evidenceBasis = $this->evidenceBasisForReview($review, $packageAvailability, $outputReadiness);
$sectionSummary = is_array($outputReadiness['section_summary'] ?? null) ? $outputReadiness['section_summary'] : [];
return [
'status_label' => $packageAvailability['label'],
'status_color' => $this->governancePackageAvailabilityColor($tenant),
'description' => $this->reviewPackPanelDescription($packageAvailability, $outputReadiness),
'detail_rows' => [
[
'label' => __('localization.review.last_generated'),
'value' => $pack instanceof ReviewPack && $pack->generated_at !== null
? $pack->generated_at->format('M j, Y H:i')
: __('localization.review.unavailable'),
'color' => 'gray',
],
[
'label' => __('localization.review.evidence_source'),
'value' => $snapshot instanceof EvidenceSnapshot && $snapshot->generated_at !== null
? $snapshot->generated_at->format('M j, Y H:i')
: __('localization.review.unavailable'),
'color' => 'gray',
],
[
'label' => __('localization.review.export_availability'),
'value' => $downloadUrl !== null
? __('localization.review.export_ready')
: __('localization.review.export_not_ready'),
'color' => $downloadUrl !== null ? 'success' : 'gray',
],
[
'label' => __('localization.review.evidence_basis_state'),
'value' => $evidenceBasis['label'],
'color' => $evidenceBasis['color'],
],
[
'label' => __('localization.review.section_completeness'),
'value' => $this->sectionCompletenessLabel($sectionSummary),
'color' => ((int) ($sectionSummary['required_limited'] ?? 0)) > 0 ? 'warning' : 'success',
],
[
'label' => __('localization.review.sharing_boundary'),
'value' => $this->workspaceBoundaryLabel((string) ($outputReadiness['customer_safe_state'] ?? 'requires_review')),
'color' => $this->workspaceBoundaryColor((string) ($outputReadiness['customer_safe_state'] ?? 'requires_review')),
],
[
'label' => __('localization.review.pii_state'),
'value' => (bool) ($outputReadiness['contains_pii'] ?? false)
? __('localization.review.contains_pii')
: __('localization.review.pii_excluded'),
'color' => (bool) ($outputReadiness['contains_pii'] ?? false) ? 'warning' : 'success',
],
[
'label' => __('localization.review.protected_values'),
'value' => (bool) ($outputReadiness['protected_values_hidden'] ?? true)
? __('localization.review.protected_values_hidden')
: __('localization.review.unavailable'),
'color' => (bool) ($outputReadiness['protected_values_hidden'] ?? true) ? 'success' : 'warning',
],
[
'label' => __('localization.review.disclosure'),
'value' => (bool) ($outputReadiness['disclosure_present'] ?? false)
? __('localization.review.disclosure_present')
: __('localization.review.unavailable'),
'color' => (bool) ($outputReadiness['disclosure_present'] ?? false) ? 'success' : 'warning',
],
[
'label' => __('localization.review.operation_proof'),
'value' => $pack instanceof ReviewPack && $pack->operationRun instanceof OperationRun
? OperationRunLinks::identifier($pack->operationRun)
: __('localization.review.operation_proof_unavailable'),
'color' => $pack instanceof ReviewPack && $pack->operationRun instanceof OperationRun ? 'info' : 'gray',
],
],
'download_url' => $downloadUrl,
];
}
/**
* @param array<string, mixed> $decision
* @return array{entries:list<array<string,string>>,empty_state:string}
*/
private function customerSafeFollowUpsForReview(array $decision): array
{
$entries = collect($decision['entries'] ?? [])
->filter(static fn (mixed $entry): bool => is_array($entry))
->map(static fn (array $entry): array => [
'title' => (string) ($entry['title'] ?? __('localization.review.follow_up')),
'priority' => (string) ($decision['label'] ?? __('localization.review.follow_up')),
'proof' => __('localization.review.decision_trail'),
'summary' => (string) ($entry['summary'] ?? __('localization.review.decision_entry_customer_safe_summary')),
'next_action' => (string) ($entry['next_action'] ?? __('localization.review.decision_summary_requires_awareness_next_action')),
])
->take(3)
->values()
->all();
return [
'entries' => $entries,
'empty_state' => __('localization.review.customer_safe_follow_ups_empty'),
];
}
/**
* @return array{label:string,summary:string}
*/
private function diagnosticsDisclosureForReview(): array
{
return [
'label' => __('localization.review.diagnostics'),
'summary' => __('localization.review.diagnostics_customer_workspace_default_hidden'),
];
}
/**
* @return list<array{label:string,value:string,color:string}>
*/
private function disclosureRuleRows(): array
{
return [
[
'label' => __('localization.review.disclosure_decision'),
'value' => __('localization.review.disclosure_visible'),
'color' => 'info',
],
[
'label' => __('localization.review.disclosure_evidence'),
'value' => __('localization.review.disclosure_visible'),
'color' => 'info',
],
[
'label' => __('localization.review.disclosure_diagnostics'),
'value' => __('localization.review.disclosure_collapsed'),
'color' => 'gray',
],
[
'label' => __('localization.review.disclosure_raw_support'),
'value' => __('localization.review.disclosure_hidden'),
'color' => 'gray',
],
];
}
private function authorizePageAccess(): void
{
$user = auth()->user();
$workspace = $this->workspace();
if (! $user instanceof User) {
abort(403);
}
if (! $workspace instanceof Workspace) {
throw new NotFoundHttpException;
}
$service = app(EnvironmentReviewRegisterService::class);
if (! $service->canAccessWorkspace($user, $workspace)) {
throw new NotFoundHttpException;
}
if ($this->authorizedTenants() === []) {
throw new NotFoundHttpException;
}
}
private function auditWorkspaceOpen(): void
{
$user = auth()->user();
$workspace = $this->workspace();
if (! $user instanceof User || ! $workspace instanceof Workspace) {
return;
}
app(WorkspaceAuditLogger::class)->log(
workspace: $workspace,
action: AuditActionId::CustomerReviewWorkspaceOpened,
context: [
'metadata' => [
'source_surface' => self::SOURCE_SURFACE,
'tenant_filter_id' => $this->currentTenantFilterId(),
'entitled_tenant_count' => count($this->authorizedTenants()),
'interpretation_version' => $this->currentTenantFilterInterpretationVersion(),
'interpretation_versions' => $this->visibleInterpretationVersions(),
],
],
actor: $user,
resourceType: 'customer_review_workspace',
resourceId: (string) $workspace->getKey(),
targetLabel: __('localization.review.customer_review_workspace'),
);
}
private function workspaceQuery(): Builder
{
$user = auth()->user();
$workspace = $this->workspace();
if (! $user instanceof User || ! $workspace instanceof Workspace) {
return ManagedEnvironment::query()->whereRaw('1 = 0');
}
return app(EnvironmentReviewRegisterService::class)->customerWorkspaceTenantQuery($user, $workspace);
}
/**
* @return array<string, string>
*/
private function tenantFilterOptions(): array
{
return collect($this->authorizedTenants())
->mapWithKeys(static fn (ManagedEnvironment $tenant): array => [
(string) $tenant->getKey() => $tenant->name,
])
->all();
}
private function defaultTenantFilter(): ?string
{
return null;
}
private function applyRequestedTenantPrefilter(): void
{
$workspace = $this->workspace();
if (! $workspace instanceof Workspace) {
return;
}
$filter = WorkspaceHubEnvironmentFilter::fromRequest(request(), $workspace);
if (! $filter instanceof WorkspaceHubEnvironmentFilter) {
return;
}
$environmentId = $filter->environmentId();
foreach ($this->authorizedTenants() as $tenant) {
if ((int) $tenant->getKey() === $environmentId) {
$this->tableFilters['managed_environment_id']['value'] = (string) $environmentId;
$this->tableDeferredFilters['managed_environment_id']['value'] = (string) $environmentId;
return;
}
}
throw new NotFoundHttpException;
}
private function hasActiveFilters(): bool
{
return $this->currentTenantFilterId() !== null;
}
private function clearWorkspaceFilters(): void
{
$hadEnvironmentFilter = $this->currentTenantFilterId() !== null;
$this->removeTableFilters();
$this->clearWorkspaceHubEnvironmentFilterState(request());
if ($hadEnvironmentFilter) {
$this->redirectToCleanWorkspaceHubUrl(static::getUrl(panel: 'admin'), request());
}
}
private function workspaceEmptyStateHeading(): string
{
return $this->filteredViewHasNoReleasedReviewsButWorkspaceHasMatches()
? __('localization.review.filtered_no_released_customer_reviews')
: __('localization.review.no_released_customer_reviews');
}
private function workspaceEmptyStateDescription(): string
{
if ($this->filteredViewHasNoReleasedReviewsButWorkspaceHasMatches()) {
return __('localization.review.filtered_no_released_customer_reviews_description');
}
return __('localization.review.no_released_customer_reviews_description');
}
private function filteredViewHasNoReleasedReviewsButWorkspaceHasMatches(): bool
{
$tenantFilterId = $this->currentTenantFilterId();
$user = auth()->user();
$workspace = $this->workspace();
if ($tenantFilterId === null || ! $user instanceof User || ! $workspace instanceof Workspace) {
return false;
}
$selectedTenantHasReleasedReview = EnvironmentReview::query()
->forWorkspace((int) $workspace->getKey())
->where('managed_environment_id', $tenantFilterId)
->published()
->exists();
if ($selectedTenantHasReleasedReview) {
return false;
}
return app(EnvironmentReviewRegisterService::class)
->latestPublishedQuery($user, $workspace)
->exists();
}
private function currentTenantFilterId(): ?int
{
$tenantFilter = data_get($this->tableFilters, 'managed_environment_id.value');
if (! is_numeric($tenantFilter)) {
$tenantFilter = data_get(session()->get($this->getTableFiltersSessionKey(), []), 'managed_environment_id.value');
}
return is_numeric($tenantFilter) ? (int) $tenantFilter : null;
}
private function filteredTenant(): ?ManagedEnvironment
{
$tenantId = $this->currentTenantFilterId();
if (! is_int($tenantId)) {
return null;
}
foreach ($this->authorizedTenants() as $tenant) {
if ((int) $tenant->getKey() === $tenantId) {
return $tenant;
}
}
return null;
}
private function workspace(): ?Workspace
{
$workspaceId = app(WorkspaceContext::class)->currentWorkspaceId(request());
return is_numeric($workspaceId)
? Workspace::query()->whereKey((int) $workspaceId)->first()
: null;
}
private function latestReleasedTenant(): ?ManagedEnvironment
{
$user = auth()->user();
$workspace = $this->workspace();
if (! $user instanceof User || ! $workspace instanceof Workspace) {
return null;
}
$query = app(EnvironmentReviewRegisterService::class)->latestPublishedQuery($user, $workspace);
$tenantFilterId = $this->currentTenantFilterId();
if ($tenantFilterId !== null) {
$query->where('managed_environment_id', $tenantFilterId);
}
$review = $query->first();
if (! $review instanceof EnvironmentReview || ! $review->tenant instanceof ManagedEnvironment) {
return null;
}
$tenant = $review->tenant;
$tenant->setRelation('environmentReviews', $review->newCollection([$review]));
return $tenant;
}
private function latestPublishedReview(ManagedEnvironment $tenant): ?EnvironmentReview
{
$review = $tenant->environmentReviews->first();
return $review instanceof EnvironmentReview ? $review : null;
}
private function latestReviewUrl(ManagedEnvironment $tenant): ?string
{
$review = $this->latestPublishedReview($tenant);
if (! $review instanceof EnvironmentReview) {
return null;
}
$query = array_filter(
array_replace(
[self::DETAIL_CONTEXT_QUERY_KEY => 1],
[
'source_surface' => self::SOURCE_SURFACE,
'tenant_filter_id' => $this->currentTenantFilterId(),
],
$this->navigationContext()?->toQuery() ?? [],
),
static fn (mixed $value): bool => $value !== null && $value !== '',
);
return $this->appendQuery(EnvironmentReviewResource::environmentScopedUrl('view', ['record' => $review], $tenant), $query);
}
private function reviewPackDownloadUrl(EnvironmentReview $review, ManagedEnvironment $tenant): ?string
{
$pack = $review->currentExportReviewPack;
$user = auth()->user();
if (! $pack instanceof ReviewPack || ! $user instanceof User) {
return null;
}
if ($this->governancePackageAvailability($tenant)['state'] !== 'available') {
return null;
}
if (! $user->can(Capabilities::REVIEW_PACK_VIEW, $tenant)) {
return null;
}
if ($pack->status !== ReviewPackStatus::Ready->value) {
return null;
}
if ($pack->expires_at !== null && $pack->expires_at->isPast()) {
return null;
}
if (! filled($pack->file_path) || ! filled($pack->file_disk)) {
return null;
}
return app(ReviewPackService::class)->generateDownloadUrl($pack, [
'source_surface' => self::SOURCE_SURFACE,
'review_id' => (int) $review->getKey(),
'tenant_filter_id' => (string) ($this->currentTenantFilterId() ?? $tenant->getKey()),
'interpretation_version' => $review->controlInterpretationVersion(),
]);
}
private function latestPublishedAt(ManagedEnvironment $tenant): ?\Illuminate\Support\Carbon
{
return $this->latestPublishedReview($tenant)?->published_at;
}
private function reviewTruth(ManagedEnvironment $tenant): ?ArtifactTruthEnvelope
{
$review = $this->latestPublishedReview($tenant);
return $review instanceof EnvironmentReview
? app(ArtifactTruthPresenter::class)->forEnvironmentReview($review)
: null;
}
private function reviewOutcome(ManagedEnvironment $tenant): ?CompressedGovernanceOutcome
{
$presenter = app(ArtifactTruthPresenter::class);
$review = $this->latestPublishedReview($tenant);
$truth = $this->reviewTruth($tenant);
if (! $review instanceof EnvironmentReview || ! $truth instanceof ArtifactTruthEnvelope) {
return null;
}
return $presenter->compressedOutcomeFor($review, SurfaceCompressionContext::reviewRegister())
?? $presenter->compressedOutcomeFromEnvelope($truth, SurfaceCompressionContext::reviewRegister());
}
private function latestReviewStateLabel(ManagedEnvironment $tenant): string
{
$review = $this->latestPublishedReview($tenant);
if (! $review instanceof EnvironmentReview) {
return __('localization.review.no_published_review');
}
return match ($this->workspaceCustomerOutputState($tenant)) {
'ready' => __('localization.review.ready'),
'not_ready' => __('localization.review.not_ready'),
default => __('localization.review.needs_review'),
};
}
private function latestReviewStateColor(ManagedEnvironment $tenant): string
{
$review = $this->latestPublishedReview($tenant);
if (! $review instanceof EnvironmentReview) {
return 'gray';
}
return match ($this->workspaceCustomerOutputState($tenant)) {
'ready' => 'success',
'not_ready' => in_array($this->governancePackageAvailability($tenant)['state'], ['expired', 'unavailable'], true)
? 'danger'
: 'gray',
default => 'warning',
};
}
private function latestReviewStateIcon(ManagedEnvironment $tenant): ?string
{
return $this->reviewOutcome($tenant)?->primaryBadge->icon;
}
private function latestReviewStateIconColor(ManagedEnvironment $tenant): ?string
{
return $this->reviewOutcome($tenant)?->primaryBadge->iconColor;
}
private function reviewOutcomeDescription(ManagedEnvironment $tenant): ?string
{
$review = $this->latestPublishedReview($tenant);
if (! $review instanceof EnvironmentReview) {
return __('localization.review.no_published_review_available');
}
$primaryReason = $this->reviewOutcome($tenant)?->primaryReason;
$summary = is_array($review->summary) ? $review->summary : [];
$findingOutcomes = $summary['finding_outcomes'] ?? null;
if (! is_array($findingOutcomes)) {
return $primaryReason;
}
$findingOutcomeSummary = app(FindingOutcomeSemantics::class)->compactOutcomeSummary($findingOutcomes);
if ($findingOutcomeSummary === null) {
return $primaryReason;
}
return trim($primaryReason.' '.__('localization.review.terminal_outcomes').': '.$findingOutcomeSummary.'.');
}
private function controlReadinessLabel(ManagedEnvironment $tenant): string
{
$control = $this->primaryControlSummary($tenant);
if ($control === null) {
return __('localization.review.control_readiness_unmapped');
}
$label = $control['readiness_label'] ?? null;
return is_string($label) && trim($label) !== ''
? $label
: ComplianceEvidenceMappingV1::readinessLabel((string) ($control['readiness_bucket'] ?? 'review_recommended'));
}
/**
* @return array<string, mixed>
*/
private function governancePackageSummary(ManagedEnvironment $tenant): array
{
$review = $this->latestPublishedReview($tenant);
if (! $review instanceof EnvironmentReview) {
return [];
}
return $this->governancePackageSummaryForReview($review);
}
/**
* @return array<string, mixed>
*/
private function governancePackageSummaryForReview(EnvironmentReview $review): array
{
$summary = is_array($review->summary) ? $review->summary : [];
$package = is_array($summary['governance_package'] ?? null) ? $summary['governance_package'] : [];
return $package;
}
/**
* @return array{state:string,label:string,description:string}
*/
private function governancePackageAvailability(ManagedEnvironment $tenant): array
{
$review = $this->latestPublishedReview($tenant);
if (! $review instanceof EnvironmentReview) {
return [
'state' => 'unavailable',
'label' => __('localization.review.governance_package_unavailable'),
'description' => __('localization.review.no_published_review_available'),
];
}
$pack = $review->currentExportReviewPack;
$user = auth()->user();
if (! $pack instanceof ReviewPack) {
return [
'state' => 'not_available',
'label' => __('localization.review.review_pack_not_available_yet'),
'description' => __('localization.review.review_pack_not_available_yet_description'),
];
}
if (! $user instanceof User || ! $user->can(Capabilities::REVIEW_PACK_VIEW, $tenant)) {
return [
'state' => 'unavailable',
'label' => __('localization.review.unavailable'),
'description' => __('localization.review.review_pack_unavailable_customer_description'),
];
}
if ($pack->status === ReviewPackStatus::Expired->value || ($pack->expires_at !== null && $pack->expires_at->isPast())) {
return [
'state' => 'expired',
'label' => __('localization.review.governance_package_expired'),
'description' => __('localization.review.governance_package_expired_description'),
];
}
if (in_array($pack->status, [ReviewPackStatus::Queued->value, ReviewPackStatus::Generating->value], true)) {
return [
'state' => 'preparing',
'label' => __('localization.review.review_pack_preparing'),
'description' => __('localization.review.review_pack_preparing_description'),
];
}
if ($pack->status !== ReviewPackStatus::Ready->value) {
return [
'state' => 'unavailable',
'label' => __('localization.review.unavailable'),
'description' => __('localization.review.review_pack_unavailable_customer_description'),
];
}
if (! filled($pack->file_path) || ! filled($pack->file_disk)) {
return [
'state' => 'not_available',
'label' => __('localization.review.review_pack_not_available_yet'),
'description' => __('localization.review.review_pack_not_available_yet_description'),
];
}
return [
'state' => 'available',
'label' => __('localization.review.available'),
'description' => __('localization.review.review_pack_available_customer_description'),
];
}
private function governancePackageAvailabilityLabel(ManagedEnvironment $tenant): string
{
return match ($this->governancePackageAvailability($tenant)['state']) {
'available' => __('localization.review.available'),
'not_available' => __('localization.review.review_pack_not_available_yet'),
'preparing' => __('localization.review.review_pack_preparing'),
'expired' => __('localization.review.expired'),
default => __('localization.review.unavailable'),
};
}
private function governancePackageAvailabilityColor(ManagedEnvironment $tenant): string
{
return match ($this->governancePackageAvailability($tenant)['state']) {
'available' => 'success',
'preparing' => 'warning',
'expired', 'unavailable' => 'danger',
default => 'gray',
};
}
private function governancePackageTeaser(ManagedEnvironment $tenant): string
{
$package = $this->governancePackageSummary($tenant);
$executiveSummary = $package['executive_summary'] ?? null;
if (is_string($executiveSummary) && trim($executiveSummary) !== '') {
return $executiveSummary;
}
return $this->governancePackageAvailability($tenant)['description'];
}
/**
* @return array<string, mixed>
*/
private function decisionSummaryForReview(EnvironmentReview $review): array
{
$package = $this->governancePackageSummaryForReview($review);
$decisionSummary = is_array($package['decision_summary'] ?? null) ? $package['decision_summary'] : [];
if ($decisionSummary === []) {
return [
'status' => 'unavailable',
'label' => __('localization.review.decision_evidence_unavailable'),
'color' => 'warning',
'total_count' => 0,
'summary' => __('localization.review.decision_summary_unavailable_description'),
'next_action' => __('localization.review.decision_summary_unavailable_next_action'),
'entries' => [],
];
}
$status = (string) ($decisionSummary['status'] ?? 'unavailable');
if (! in_array($status, ['none', 'requires_awareness', 'unavailable', 'incomplete'], true)) {
$status = 'unavailable';
}
$entries = collect($decisionSummary['entries'] ?? [])
->filter(static fn (mixed $entry): bool => is_array($entry))
->map(fn (array $entry): array => [
'title' => $this->customerSafeText($entry['title'] ?? null, __('localization.review.governance_decisions')),
'summary' => $this->customerSafeText($entry['summary'] ?? null, __('localization.review.decision_entry_customer_safe_summary')),
'next_action' => $this->customerSafeText($entry['next_action'] ?? null, __('localization.review.decision_summary_requires_awareness_next_action')),
])
->take(3)
->values()
->all();
return [
'status' => $status,
'label' => $this->decisionSummaryLabel($status),
'color' => $this->decisionSummaryColor($status),
'total_count' => (int) ($decisionSummary['total_count'] ?? count($entries)),
'summary' => $this->customerSafeText(
$decisionSummary['summary'] ?? null,
$this->decisionSummaryFallbackText($status),
),
'next_action' => $this->customerSafeText(
$decisionSummary['next_action'] ?? null,
$this->decisionSummaryFallbackNextAction($status),
),
'entries' => $entries,
];
}
private function decisionSummaryLabel(string $status): string
{
return match ($status) {
'requires_awareness' => __('localization.review.governance_decisions_requiring_awareness'),
'none' => __('localization.review.no_decisions_require_awareness'),
'incomplete' => __('localization.review.decision_evidence_incomplete'),
default => __('localization.review.decision_evidence_unavailable'),
};
}
private function decisionSummaryColor(string $status): string
{
return match ($status) {
'requires_awareness' => 'warning',
'none' => 'success',
default => 'gray',
};
}
private function decisionSummaryFallbackText(string $status): string
{
return match ($status) {
'requires_awareness' => __('localization.review.decision_summary_requires_awareness_description'),
'none' => __('localization.review.no_decisions_require_awareness_description'),
'incomplete' => __('localization.review.decision_summary_incomplete_description'),
default => __('localization.review.decision_summary_unavailable_description'),
};
}
private function decisionSummaryFallbackNextAction(string $status): string
{
return match ($status) {
'requires_awareness' => __('localization.review.decision_summary_requires_awareness_next_action'),
'none' => __('localization.review.no_decisions_require_awareness_next_action'),
'incomplete' => __('localization.review.decision_summary_incomplete_next_action'),
default => __('localization.review.decision_summary_unavailable_next_action'),
};
}
/**
* @return array{count:int,entries:list<array<string, string>>,empty_state:string}
*/
private function acceptedRisksForReview(EnvironmentReview $review): array
{
$package = $this->governancePackageSummaryForReview($review);
$entries = collect($package['accepted_risks'] ?? [])
->filter(static fn (mixed $entry): bool => is_array($entry))
->map(fn (array $entry): array => [
'title' => $this->customerSafeText($entry['title'] ?? null, __('localization.review.accepted_risk_state_on_record')),
'state_label' => $this->acceptedRiskStateLabel(is_string($entry['governance_state'] ?? null) ? $entry['governance_state'] : null),
'summary' => $this->customerSafeText(
$entry['customer_summary'] ?? null,
__('localization.review.accepted_risk_customer_safe_summary'),
),
])
->values();
return [
'count' => $entries->count(),
'entries' => $entries->take(3)->all(),
'empty_state' => __('localization.review.no_accepted_risks_recorded'),
];
}
private function acceptedRiskStateLabel(?string $state): string
{
return match ($state) {
'valid_exception' => __('localization.review.accepted_risk_state_current'),
'expiring_exception' => __('localization.review.accepted_risk_state_review_due'),
default => __('localization.review.accepted_risk_state_on_record'),
};
}
/**
* @param array{state:string,label:string,description:string} $packageAvailability
* @param array<string, mixed> $outputReadiness
* @return array<string, string>
*/
private function evidenceBasisForReview(EnvironmentReview $review, array $packageAvailability, array $outputReadiness): array
{
$package = $this->governancePackageSummaryForReview($review);
$decision = $this->decisionSummaryForReview($review);
$pack = $review->currentExportReviewPack;
$evidenceState = (string) ($outputReadiness['evidence_completeness_state'] ?? '');
$state = match (true) {
$package === [] => 'unavailable',
! $pack instanceof ReviewPack => 'not_generated',
$evidenceState === EnvironmentReviewCompletenessState::Missing->value => 'missing',
$evidenceState === EnvironmentReviewCompletenessState::Stale->value => 'stale',
$evidenceState === EnvironmentReviewCompletenessState::Partial->value || $decision['status'] === 'incomplete' => 'incomplete',
$decision['status'] === 'unavailable' => 'unavailable',
$decision['status'] === 'none' => 'no_awareness_required',
default => 'complete',
};
return [
'state' => $state,
'label' => $this->evidenceBasisLabel($state),
'summary' => $this->evidenceBasisSummary($state),
'color' => $this->evidenceBasisColor($state),
];
}
private function evidenceBasisLabel(string $state): string
{
return match ($state) {
'complete' => __('localization.review.evidence_basis_complete'),
'no_awareness_required' => __('localization.review.evidence_basis_no_awareness_required'),
'missing' => __('localization.review.evidence_basis_missing'),
'stale' => __('localization.review.evidence_basis_stale'),
'incomplete' => __('localization.review.evidence_basis_incomplete'),
'not_generated' => __('localization.review.evidence_basis_not_generated'),
default => __('localization.review.evidence_basis_unavailable'),
};
}
private function evidenceBasisSummary(string $state): string
{
return match ($state) {
'complete' => __('localization.review.evidence_basis_complete_description'),
'no_awareness_required' => __('localization.review.evidence_basis_no_awareness_required_description'),
'missing' => __('localization.review.evidence_basis_missing_description'),
'stale' => __('localization.review.evidence_basis_stale_description'),
'incomplete' => __('localization.review.evidence_basis_incomplete_description'),
'not_generated' => __('localization.review.evidence_basis_not_generated_description'),
default => __('localization.review.evidence_basis_unavailable_description'),
};
}
private function evidenceBasisColor(string $state): string
{
return match ($state) {
'complete', 'no_awareness_required' => 'success',
'missing', 'stale', 'incomplete' => 'warning',
default => 'gray',
};
}
private function evidenceSnapshotUrlForReview(EnvironmentReview $review, ManagedEnvironment $tenant): ?string
{
$snapshot = $review->evidenceSnapshot;
$user = auth()->user();
if (! $snapshot instanceof EvidenceSnapshot || ! $user instanceof User || ! $user->can(Capabilities::EVIDENCE_VIEW, $tenant)) {
return null;
}
return EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $tenant, panel: 'admin');
}
/**
* @return array<string, mixed>
*/
private function reviewPackOutputReadinessForReview(EnvironmentReview $review): array
{
return ReviewPackOutputResolutionGuidance::readinessForReview($review);
}
/**
* @return array<string, mixed>
*/
private function reviewOutputGuidanceForReview(
EnvironmentReview $review,
?string $downloadUrl,
?string $reviewUrl,
?string $evidenceUrl,
): array {
$operationUrl = null;
$operationRun = $review->currentExportReviewPack?->operationRun ?? $review->operationRun;
if ($operationRun instanceof OperationRun) {
$operationUrl = OperationRunLinks::tenantlessView((int) $operationRun->getKey());
}
return ReviewPackOutputResolutionGuidance::fromReadiness(
$this->reviewPackOutputReadinessForReview($review),
[
'download' => $downloadUrl,
'review' => $reviewUrl,
'evidence' => $evidenceUrl,
'operation' => $operationUrl,
],
);
}
private function reviewPackHasReadyExport(?ReviewPack $pack): bool
{
if (! $pack instanceof ReviewPack) {
return false;
}
if ($pack->status !== ReviewPackStatus::Ready->value) {
return false;
}
if ($pack->expires_at !== null && $pack->expires_at->isPast()) {
return false;
}
return filled($pack->file_path) && filled($pack->file_disk);
}
/**
* @param array<string, mixed> $outputReadiness
*/
private function effectiveWorkspaceReadinessState(array $outputReadiness, bool $hasFindingFollowUp, bool $hasAcceptedRiskFollowUp): string
{
$state = (string) ($outputReadiness['readiness_state'] ?? ReviewPackOutputReadiness::STATE_EXPORT_NOT_READY);
if ($state === ReviewPackOutputReadiness::STATE_CUSTOMER_SAFE_READY && ($hasFindingFollowUp || $hasAcceptedRiskFollowUp)) {
return ReviewPackOutputReadiness::STATE_PUBLISHED_WITH_LIMITATIONS;
}
return $state;
}
private function workspaceReadinessLabel(string $state): string
{
return match ($state) {
ReviewPackOutputReadiness::STATE_CUSTOMER_SAFE_READY => __('localization.review.customer_safe_review_pack_ready'),
ReviewPackOutputReadiness::STATE_INTERNAL_REVIEW_PACKAGE_AVAILABLE => __('localization.review.internal_review_package_available'),
ReviewPackOutputReadiness::STATE_EXPORT_NOT_READY => __('localization.review.export_not_ready'),
default => __('localization.review.published_with_limitations'),
};
}
private function workspaceReadinessColor(string $state): string
{
return match ($state) {
ReviewPackOutputReadiness::STATE_CUSTOMER_SAFE_READY => 'success',
ReviewPackOutputReadiness::STATE_EXPORT_NOT_READY => 'gray',
default => 'warning',
};
}
private function workspaceBoundaryLabel(string $state): string
{
return match ($state) {
'customer_safe_ready' => __('localization.review.customer_safe'),
'internal_only' => __('localization.review.internal_only'),
'not_ready' => __('localization.review.not_ready'),
default => __('localization.review.requires_review'),
};
}
private function workspaceBoundaryColor(string $state): string
{
return match ($state) {
'customer_safe_ready' => 'success',
'internal_only', 'requires_review' => 'warning',
default => 'gray',
};
}
/**
* @param array<string, mixed> $outputReadiness
* @param array{state:string,label:string,description:string} $packageAvailability
* @param array{summary:string} $findingPanel
*/
private function workspaceReadinessReason(
string $reasonCode,
array $outputReadiness,
array $findingPanel,
array $packageAvailability,
): string {
return match ($reasonCode) {
'findings_follow_up_required' => __('localization.review.findings_follow_up_required_reason', [
'summary' => $findingPanel['summary'],
]),
'accepted_risk_follow_up_required' => __('localization.review.accepted_risk_follow_up_required_reason'),
'export_not_ready' => __('localization.review.export_not_ready_reason'),
'evidence_basis_missing' => __('localization.review.evidence_basis_missing_reason'),
'evidence_basis_stale' => __('localization.review.evidence_basis_stale_reason'),
'evidence_basis_incomplete' => __('localization.review.evidence_basis_incomplete_reason'),
'required_sections_incomplete' => __('localization.review.required_sections_incomplete_reason', [
'complete' => (int) data_get($outputReadiness, 'section_summary.required_complete', 0),
'total' => (int) data_get($outputReadiness, 'section_summary.required_total', 0),
'limited' => (int) data_get($outputReadiness, 'section_summary.required_limited', 0),
]),
'publish_blockers_present' => __('localization.review.publish_blockers_present_reason'),
'contains_pii' => __('localization.review.contains_pii_reason'),
'customer_safe_ready' => __('localization.review.customer_safe_review_pack_ready_reason'),
default => $packageAvailability['description'],
};
}
private function workspaceReadinessImpact(string $state, string $reasonCode): string
{
return match ($reasonCode) {
'findings_follow_up_required' => __('localization.review.findings_follow_up_required_impact'),
'accepted_risk_follow_up_required' => __('localization.review.accepted_risk_follow_up_required_impact'),
default => match ($state) {
ReviewPackOutputReadiness::STATE_CUSTOMER_SAFE_READY => __('localization.review.customer_safe_review_pack_ready_impact'),
ReviewPackOutputReadiness::STATE_INTERNAL_REVIEW_PACKAGE_AVAILABLE => __('localization.review.internal_review_package_available_impact'),
ReviewPackOutputReadiness::STATE_EXPORT_NOT_READY => __('localization.review.export_not_ready_impact'),
default => __('localization.review.published_with_limitations_impact'),
},
};
}
/**
* @return array{
* primary_label:string,
* primary_url:?string,
* primary_icon:string,
* secondary_label:?string,
* secondary_url:?string
* }
*/
private function workspaceReadinessActions(
string $state,
string $reasonCode,
?string $downloadUrl,
?string $reviewUrl,
?string $evidenceUrl,
): array {
if (in_array($reasonCode, ['findings_follow_up_required', 'accepted_risk_follow_up_required'], true)) {
return [
'primary_label' => __('localization.review.open_review'),
'primary_url' => $reviewUrl ?? $evidenceUrl ?? $downloadUrl,
'primary_icon' => 'heroicon-o-arrow-top-right-on-square',
'secondary_label' => match ($state) {
ReviewPackOutputReadiness::STATE_INTERNAL_REVIEW_PACKAGE_AVAILABLE => $downloadUrl !== null
? __('localization.review.download_internal_review_pack')
: null,
default => $downloadUrl !== null
? __('localization.review.download_review_pack_with_limitations')
: null,
},
'secondary_url' => $downloadUrl,
];
}
return match ($state) {
ReviewPackOutputReadiness::STATE_CUSTOMER_SAFE_READY => [
'primary_label' => $downloadUrl !== null
? __('localization.review.download_customer_safe_review_pack')
: __('localization.review.open_latest_review'),
'primary_url' => $downloadUrl ?? $reviewUrl,
'primary_icon' => $downloadUrl !== null
? 'heroicon-o-arrow-down-tray'
: 'heroicon-o-arrow-top-right-on-square',
'secondary_label' => $downloadUrl !== null && $reviewUrl !== null
? __('localization.review.open_review')
: null,
'secondary_url' => $downloadUrl !== null ? $reviewUrl : null,
],
ReviewPackOutputReadiness::STATE_INTERNAL_REVIEW_PACKAGE_AVAILABLE => [
'primary_label' => __('localization.review.review_package_contents'),
'primary_url' => $reviewUrl ?? $downloadUrl,
'primary_icon' => 'heroicon-o-arrow-top-right-on-square',
'secondary_label' => $downloadUrl !== null
? __('localization.review.download_internal_review_pack')
: null,
'secondary_url' => $downloadUrl,
],
ReviewPackOutputReadiness::STATE_EXPORT_NOT_READY => [
'primary_label' => __('localization.review.open_evidence_basis'),
'primary_url' => $evidenceUrl ?? $reviewUrl,
'primary_icon' => 'heroicon-o-arrow-top-right-on-square',
'secondary_label' => $reviewUrl !== null && $reviewUrl !== $evidenceUrl
? __('localization.review.open_review')
: null,
'secondary_url' => $reviewUrl !== $evidenceUrl ? $reviewUrl : null,
],
default => [
'primary_label' => __('localization.review.review_output_limitations'),
'primary_url' => $reviewUrl ?? $evidenceUrl ?? $downloadUrl,
'primary_icon' => 'heroicon-o-arrow-top-right-on-square',
'secondary_label' => $downloadUrl !== null
? __('localization.review.download_review_pack_with_limitations')
: null,
'secondary_url' => $downloadUrl,
],
};
}
/**
* @param array{state:string,label:string,description:string} $packageAvailability
* @param array<string, mixed> $outputReadiness
*/
private function reviewPackPanelDescription(array $packageAvailability, array $outputReadiness): string
{
if ($packageAvailability['state'] !== 'available') {
return $packageAvailability['description'];
}
return match ((string) ($outputReadiness['readiness_state'] ?? ReviewPackOutputReadiness::STATE_EXPORT_NOT_READY)) {
ReviewPackOutputReadiness::STATE_CUSTOMER_SAFE_READY => __('localization.review.review_pack_customer_safe_ready_description'),
ReviewPackOutputReadiness::STATE_INTERNAL_REVIEW_PACKAGE_AVAILABLE => __('localization.review.review_pack_internal_review_description'),
ReviewPackOutputReadiness::STATE_EXPORT_NOT_READY => __('localization.review.review_pack_export_not_ready_description'),
default => __('localization.review.review_pack_with_limitations_description'),
};
}
/**
* @param array<string, mixed> $sectionSummary
*/
private function sectionCompletenessLabel(array $sectionSummary): string
{
$requiredTotal = (int) ($sectionSummary['required_total'] ?? 0);
$requiredComplete = (int) ($sectionSummary['required_complete'] ?? 0);
$requiredLimited = (int) ($sectionSummary['required_limited'] ?? 0);
if ($requiredTotal <= 0) {
return __('localization.review.unavailable');
}
if ($requiredLimited > 0) {
return __('localization.review.section_completeness_limited', [
'complete' => $requiredComplete,
'total' => $requiredTotal,
'limited' => $requiredLimited,
]);
}
return __('localization.review.section_completeness_complete', [
'complete' => $requiredComplete,
'total' => $requiredTotal,
]);
}
private function workspaceCustomerOutputState(ManagedEnvironment $tenant): string
{
$review = $this->latestPublishedReview($tenant);
if (! $review instanceof EnvironmentReview) {
return 'not_ready';
}
if ($this->primaryControlSummary($tenant) === null || $this->evidenceStatusState($tenant) !== 'available') {
return 'not_ready';
}
$effectiveState = $this->effectiveWorkspaceReadinessState(
$this->reviewPackOutputReadinessForReview($review),
$this->findingPanelForReview($tenant)['open_count'] > 0,
$this->acceptedRiskFollowUpRequiredForReview($review),
);
return match ($effectiveState) {
ReviewPackOutputReadiness::STATE_CUSTOMER_SAFE_READY => 'ready',
ReviewPackOutputReadiness::STATE_EXPORT_NOT_READY => 'not_ready',
default => 'needs_review',
};
}
private function customerSafeText(mixed $value, string $fallback, int $limit = 220): string
{
if (! is_string($value) || trim($value) === '') {
return $fallback;
}
return Str::limit(trim($value), $limit);
}
private function controlReadinessColor(ManagedEnvironment $tenant): string
{
return match ((string) ($this->primaryControlSummary($tenant)['readiness_bucket'] ?? 'unmapped')) {
'follow_up_required' => 'warning',
'review_recommended' => 'info',
'evidence_on_record' => 'success',
default => 'gray',
};
}
private function controlReadinessDescription(ManagedEnvironment $tenant): string
{
$review = $this->latestPublishedReview($tenant);
if (! $review instanceof EnvironmentReview) {
return __('localization.review.no_published_review_available');
}
$controls = $review->controlInterpretationControls();
if ($controls === []) {
return __('localization.review.control_readiness_unmapped_description');
}
$summary = collect($controls)
->take(2)
->map(function (array $control): string {
$name = is_string($control['control_name'] ?? null) ? $control['control_name'] : __('localization.review.control');
$label = is_string($control['readiness_label'] ?? null)
? $control['readiness_label']
: ComplianceEvidenceMappingV1::readinessLabel((string) ($control['readiness_bucket'] ?? 'review_recommended'));
return $name.': '.$label;
})
->implode(' · ');
$remaining = count($controls) - 2;
if ($remaining > 0) {
$summary .= ' · '.__('localization.review.additional_controls', ['count' => $remaining]);
}
$limitations = $this->controlLimitationSummary($review);
return trim($summary.($limitations !== null ? ' '.$limitations : ''));
}
private function controlEvidenceBasisSummary(ManagedEnvironment $tenant): string
{
$control = $this->primaryControlSummary($tenant);
if ($control === null) {
return __('localization.review.control_evidence_unmapped');
}
$summary = $control['evidence_basis_summary'] ?? null;
return is_string($summary) && trim($summary) !== ''
? $summary
: __('localization.review.control_evidence_unavailable');
}
private function controlRecommendedNextAction(ManagedEnvironment $tenant): string
{
if ($this->primaryControlSummary($tenant) === null) {
return __('localization.review.workspace_next_step_control_mapping');
}
if ($this->evidenceStatusState($tenant) !== 'available') {
return __('localization.review.workspace_next_step_evidence_review');
}
$review = $this->latestPublishedReview($tenant);
if (! $review instanceof EnvironmentReview) {
return __('localization.review.workspace_next_step_review_open');
}
$readinessState = $this->effectiveWorkspaceReadinessState(
$this->reviewPackOutputReadinessForReview($review),
$this->findingPanelForReview($tenant)['open_count'] > 0,
$this->acceptedRiskFollowUpRequiredForReview($review),
);
return match (true) {
$readinessState === ReviewPackOutputReadiness::STATE_EXPORT_NOT_READY => __('localization.review.workspace_next_step_evidence_review'),
$this->governancePackageAvailability($tenant)['state'] === 'available' => __('localization.review.workspace_next_step_package_review'),
default => __('localization.review.workspace_next_step_review_open'),
};
}
private function workspaceReviewNeedsAttention(ManagedEnvironment $tenant): bool
{
return $this->workspaceCustomerOutputState($tenant) !== 'ready';
}
private function evidenceStatusState(ManagedEnvironment $tenant): string
{
$review = $this->latestPublishedReview($tenant);
if (! $review instanceof EnvironmentReview) {
return 'pending';
}
$snapshot = $review->evidenceSnapshot;
$user = auth()->user();
if (! $snapshot instanceof EvidenceSnapshot) {
return 'pending';
}
if (! $user instanceof User || ! $user->can(Capabilities::EVIDENCE_VIEW, $tenant)) {
return 'restricted';
}
if ((string) $snapshot->status === 'expired' || ($snapshot->expires_at !== null && $snapshot->expires_at->isPast())) {
return 'expired';
}
return 'available';
}
private function evidenceStatusLabelForState(string $state): string
{
return match ($state) {
'available' => __('localization.review.available'),
'restricted' => __('localization.review.restricted'),
'expired' => __('localization.review.expired'),
default => __('localization.review.pending'),
};
}
private function evidenceStatusColorForState(string $state): string
{
return match ($state) {
'available' => 'success',
'restricted', 'expired' => 'danger',
default => 'gray',
};
}
private function controlRecommendedNextActionDescription(ManagedEnvironment $tenant): string
{
$control = $this->primaryControlSummary($tenant);
if ($control === null) {
return __('localization.review.control_recommendation_unmapped');
}
$action = $control['recommended_next_action'] ?? null;
return is_string($action) && trim($action) !== ''
? $action
: __('localization.review.no_action_needed');
}
/**
* @return array<string, mixed>|null
*/
private function primaryControlSummary(ManagedEnvironment $tenant): ?array
{
$review = $this->latestPublishedReview($tenant);
if (! $review instanceof EnvironmentReview) {
return null;
}
$controls = collect($review->controlInterpretationControls());
return $controls
->sortBy(static fn (array $control): int => match ((string) ($control['readiness_bucket'] ?? '')) {
'follow_up_required' => 0,
'review_recommended' => 1,
'evidence_on_record' => 2,
default => 3,
})
->first();
}
private function controlLimitationSummary(EnvironmentReview $review): ?string
{
$counts = $review->controlInterpretationLimitationCounts();
if ($counts === []) {
return null;
}
$labels = collect($counts)
->filter(static fn (int $count): bool => $count > 0)
->keys()
->map(static fn (string $flag): string => ComplianceEvidenceMappingV1::limitationLabel($flag))
->values()
->all();
return $labels === []
? null
: __('localization.review.control_limitations_summary', ['limitations' => implode(', ', $labels)]);
}
private function findingSummary(ManagedEnvironment $tenant): string
{
$review = $this->latestPublishedReview($tenant);
if (! $review instanceof EnvironmentReview) {
return __('localization.review.no_published_review_available');
}
$summary = is_array($review->summary) ? $review->summary : [];
$findingCount = (int) ($summary['finding_count'] ?? 0);
$findingOutcomes = is_array($summary['finding_outcomes'] ?? null) ? $summary['finding_outcomes'] : [];
$terminalOutcomes = app(FindingOutcomeSemantics::class)->compactOutcomeSummary($findingOutcomes);
if ($findingCount === 0) {
return __('localization.review.no_findings_recorded');
}
if ($terminalOutcomes === null) {
return __('localization.review.findings_count_summary', ['count' => $findingCount]);
}
return __('localization.review.findings_count_with_outcomes', [
'count' => $findingCount,
'outcomes' => $terminalOutcomes,
]);
}
private function acceptedRiskSummary(ManagedEnvironment $tenant): string
{
$review = $this->latestPublishedReview($tenant);
if (! $review instanceof EnvironmentReview) {
return __('localization.review.no_published_review_available');
}
$summary = is_array($review->summary) ? $review->summary : [];
$riskAcceptance = is_array($summary['risk_acceptance'] ?? null) ? $summary['risk_acceptance'] : [];
$statusMarkedCount = (int) ($riskAcceptance['status_marked_count'] ?? 0);
$validGovernedCount = (int) ($riskAcceptance['valid_governed_count'] ?? 0);
$warningCount = (int) ($riskAcceptance['warning_count'] ?? 0);
$countSummary = match (true) {
$statusMarkedCount === 0 => __('localization.review.no_accepted_risks_recorded'),
$warningCount > 0 => __('localization.review.accepted_risks_need_follow_up', ['warnings' => $warningCount, 'total' => $statusMarkedCount]),
$validGovernedCount > 0 => __('localization.review.accepted_risks_governed', ['count' => $validGovernedCount]),
default => __('localization.review.accepted_risks_on_record', ['count' => $statusMarkedCount]),
};
$accountability = $this->acceptedRiskAccountability($tenant);
return $accountability === null
? $countSummary
: $countSummary.' '.$accountability;
}
private function evidenceProofAvailability(ManagedEnvironment $tenant): string
{
$review = $this->latestPublishedReview($tenant);
if (! $review instanceof EnvironmentReview) {
return __('localization.review.no_published_review_available');
}
$snapshot = $review->evidenceSnapshot;
$user = auth()->user();
if (! $snapshot instanceof EvidenceSnapshot) {
return __('localization.review.evidence_proof_absent');
}
if (! $user instanceof User || ! $user->can(Capabilities::EVIDENCE_VIEW, $tenant)) {
return __('localization.review.evidence_proof_access_unavailable');
}
if ((string) $snapshot->status === 'expired' || ($snapshot->expires_at !== null && $snapshot->expires_at->isPast())) {
return __('localization.review.evidence_proof_expired');
}
return __('localization.review.evidence_proof_available');
}
private function evidenceStatusLabel(ManagedEnvironment $tenant): string
{
return $this->evidenceStatusLabelForState($this->evidenceStatusState($tenant));
}
private function evidenceStatusColor(ManagedEnvironment $tenant): string
{
return $this->evidenceStatusColorForState($this->evidenceStatusState($tenant));
}
/**
* @return list<string>
*/
private function visibleInterpretationVersions(): array
{
$user = auth()->user();
$workspace = $this->workspace();
if (! $user instanceof User || ! $workspace instanceof Workspace) {
return [];
}
return app(EnvironmentReviewRegisterService::class)
->latestPublishedQuery($user, $workspace)
->get()
->map(static fn (EnvironmentReview $review): ?string => $review->controlInterpretationVersion())
->filter()
->unique()
->values()
->all();
}
private function currentTenantFilterInterpretationVersion(): ?string
{
$tenant = $this->filteredTenant();
if (! $tenant instanceof ManagedEnvironment) {
return null;
}
return $tenant->environmentReviews()->published()
->latest('published_at')
->latest('generated_at')
->latest('id')
->first()
?->controlInterpretationVersion();
}
private function acceptedRiskAccountability(ManagedEnvironment $tenant): ?string
{
$exception = FindingException::query()
->with(['owner', 'approver', 'currentDecision'])
->where('workspace_id', (int) $tenant->workspace_id)
->where('managed_environment_id', (int) $tenant->getKey())
->current()
->orderByRaw("case when current_validity_state in ('valid', 'expiring') then 0 else 1 end")
->latest('approved_at')
->latest('requested_at')
->latest('id')
->first();
if (! $exception instanceof FindingException) {
return null;
}
$accountable = $exception->owner?->name
?? $exception->approver?->name;
$decisionType = $exception->currentDecision?->decision_type;
$reviewDue = $exception->review_due_at ?? $exception->expires_at;
$reason = is_string($exception->request_reason) ? trim($exception->request_reason) : '';
$parts = [];
if (is_string($accountable) && trim($accountable) !== '') {
$parts[] = $reviewDue === null
? __('localization.review.accepted_risk_accountable', ['name' => $accountable])
: __('localization.review.accepted_risk_accountable_until', [
'name' => $accountable,
'date' => $reviewDue->toDateString(),
]);
} elseif (is_string($decisionType) && trim($decisionType) !== '') {
$parts[] = __('localization.review.accepted_risk_partial_accountability');
}
if ($reason !== '') {
$parts[] = __('localization.review.accepted_risk_reason', [
'reason' => Str::limit($reason, 160),
]);
}
return $parts === [] ? null : implode(' ', $parts);
}
private function navigationContext(): ?CanonicalNavigationContext
{
return CanonicalNavigationContext::fromRequest(request());
}
private function incomingGovernanceContext(): ?CanonicalNavigationContext
{
$context = $this->navigationContext();
return $context?->sourceSurface === 'governance.inbox'
? $context
: null;
}
/**
* @param array<string, mixed> $query
*/
private function appendQuery(string $url, array $query): string
{
if ($query === []) {
return $url;
}
return $url.(str_contains($url, '?') ? '&' : '?').http_build_query($query);
}
}