TenantAtlas/apps/platform/app/Filament/Pages/Reviews/CustomerReviewWorkspace.php
ahmido c8224843b3 Spec 326: productize customer review workspace (#386)
## Summary
- productizes the Customer Review Workspace into a more decision-first, customer-safe review surface
- updates the page class, Blade view, and localized copy for the new workspace presentation
- expands feature and browser coverage for workspace behavior, localization, and access rules
- adds the Spec 326 artifact package for this implementation

## Testing
- not run in this session

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #386
2026-05-18 13:30:38 +00:00

2098 lines
83 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\EvidenceSnapshot;
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\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\OperationRunLinks;
use App\Support\ReviewPackStatus;
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\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\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 __('localization.review.reporting');
}
public static function getNavigationLabel(): string
{
return __('localization.review.customer_reviews');
}
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 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);
$decision = $this->decisionSummaryForReview($review);
$acceptedRisks = $this->acceptedRisksForReview($review);
$hasAcceptedRiskFollowUp = $this->acceptedRiskFollowUpRequiredForReview($review);
$evidencePath = $this->evidencePathForReview($review, $tenant, $packageAvailability, $downloadUrl, $decision, $acceptedRisks);
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
? __('localization.review.download_review_pack')
: __('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' => $downloadUrl !== null
? ($hasAcceptedRiskFollowUp ? __('localization.review.download_review_pack') : __('localization.review.open_review'))
: null,
'secondary_action_url' => $downloadUrl !== null
? ($hasAcceptedRiskFollowUp ? $downloadUrl : $reviewUrl)
: null,
'secondary_action_icon' => $downloadUrl !== null && $hasAcceptedRiskFollowUp
? 'heroicon-o-arrow-down-tray'
: 'heroicon-o-arrow-top-right-on-square',
],
'readiness' => $this->reviewReadinessForTenant($tenant, $review, $packageAvailability, $downloadUrl, $reviewUrl),
'readiness_dimensions' => $this->readinessDimensionPayloads($tenant, $review, $packageAvailability),
'decision' => $decision,
'accepted_risks' => $acceptedRisks,
'accepted_risk_panel' => $this->acceptedRiskPanelForReview($review),
'evidence_basis' => $this->evidenceBasisForReview($review, $packageAvailability),
'evidence_path' => $evidencePath,
'aside_evidence_path' => $this->asideEvidencePath($evidencePath),
'review_pack_panel' => $this->reviewPackPanelForReview($review, $tenant, $packageAvailability, $downloadUrl),
'follow_ups' => $this->customerSafeFollowUpsForReview($decision),
'diagnostics' => $this->diagnosticsDisclosureForReview(),
'disclosure_rules' => $this->disclosureRuleRows(),
];
}
/**
* @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
* @return array{question:string,label:string,color:string,reason:string,impact:string,primary_action_label:string,primary_action_url:?string,primary_action_icon:string}
*/
private function reviewReadinessForTenant(
ManagedEnvironment $tenant,
EnvironmentReview $review,
array $packageAvailability,
?string $downloadUrl,
?string $reviewUrl,
): array {
$hasAcceptedRiskFollowUp = $this->acceptedRiskFollowUpRequiredForReview($review);
$hasReadyPackage = $packageAvailability['state'] === 'available' && $downloadUrl !== null;
$hasCustomerSafeProof = $this->primaryControlSummary($tenant) !== null
&& $this->evidenceStatusState($tenant) === 'available';
$isReadyToShare = ! $hasAcceptedRiskFollowUp
&& ! $this->workspaceReviewNeedsAttention($tenant)
&& $hasReadyPackage;
$isShareableWithFollowUp = $hasAcceptedRiskFollowUp && $hasReadyPackage && $hasCustomerSafeProof;
return [
'question' => __('localization.review.is_review_ready_to_share'),
'label' => match (true) {
$isReadyToShare => __('localization.review.ready_to_share'),
$isShareableWithFollowUp => __('localization.review.shareable_with_follow_up'),
default => __('localization.review.follow_up_required_before_sharing'),
},
'color' => match (true) {
$isReadyToShare => 'success',
$isShareableWithFollowUp => 'warning',
default => $this->latestReviewStateColor($tenant),
},
'reason' => match (true) {
$isReadyToShare => __('localization.review.ready_to_share_reason'),
$isShareableWithFollowUp => __('localization.review.shareable_with_follow_up_reason'),
default => $this->customerSafeText(
$this->reviewOutcomeDescription($tenant) ?? $packageAvailability['description'],
__('localization.review.follow_up_required_before_sharing_reason'),
),
},
'impact' => match (true) {
$isReadyToShare => __('localization.review.ready_to_share_impact'),
$isShareableWithFollowUp => __('localization.review.shareable_with_follow_up_impact'),
default => __('localization.review.follow_up_required_before_sharing_impact'),
},
'primary_action_label' => $isShareableWithFollowUp
? __('localization.review.open_review')
: ($downloadUrl !== null ? __('localization.review.download_review_pack') : __('localization.review.open_latest_review')),
'primary_action_url' => $isShareableWithFollowUp
? ($reviewUrl ?? $downloadUrl)
: ($downloadUrl ?? $reviewUrl),
'primary_action_icon' => $isShareableWithFollowUp || $downloadUrl === null
? 'heroicon-o-arrow-top-right-on-square'
: 'heroicon-o-arrow-down-tray',
];
}
/**
* @param array{state:string,label:string,description:string} $packageAvailability
* @return list<array{title:string,label:string,color:string,description:string}>
*/
private function readinessDimensionPayloads(
ManagedEnvironment $tenant,
EnvironmentReview $review,
array $packageAvailability,
): array {
$acceptedRisk = $this->acceptedRiskDimensionForReview($review);
$evidenceState = $this->evidenceStatusState($tenant);
return [
[
'title' => __('localization.review.readiness'),
'label' => $this->latestReviewStateLabel($tenant),
'color' => $this->latestReviewStateColor($tenant),
'description' => $this->workspaceReviewNeedsAttention($tenant)
? __('localization.review.readiness_dimension_follow_up_description')
: __('localization.review.readiness_dimension_ready_description'),
],
[
'title' => __('localization.review.evidence'),
'label' => $this->evidenceStatusLabelForState($evidenceState),
'color' => $this->evidenceStatusColorForState($evidenceState),
'description' => $this->evidenceDimensionDescription($evidenceState),
],
[
'title' => __('localization.review.accepted_risk_status'),
'label' => $acceptedRisk['label'],
'color' => $acceptedRisk['color'],
'description' => $acceptedRisk['description'],
],
[
'title' => __('localization.review.review_pack'),
'label' => $packageAvailability['label'],
'color' => $this->governancePackageAvailabilityColor($tenant),
'description' => $this->reviewPackDimensionDescription($packageAvailability),
],
];
}
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'),
'evidence_incomplete' => __('localization.review.review_pack_dimension_needs_refresh_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): array
{
$acceptedRisks = $this->acceptedRisksForReview($review);
$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 ($acceptedRisks['count'] === 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' => $acceptedRisks['count']]),
'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));
return $acceptedEntries
->merge($decisionEntries)
->contains(static fn (array $entry): bool => in_array(
(string) ($entry['governance_state'] ?? ''),
self::ACCEPTED_RISK_FOLLOW_UP_STATES,
true,
));
}
/**
* @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);
$user = auth()->user();
$url = $snapshot instanceof EvidenceSnapshot && $user instanceof User && $user->can(Capabilities::EVIDENCE_VIEW, $tenant)
? EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $tenant, panel: 'admin')
: null;
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',
'evidence_incomplete', 'preparing' => 'warning',
'expired', 'unavailable' => 'danger',
default => 'gray',
},
'description' => $packageAvailability['description'],
'action_label' => $downloadUrl !== null ? __('localization.review.download_review_pack') : null,
'action_url' => $downloadUrl,
];
}
/**
* @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) {
return [
'key' => 'operation_proof',
'title' => __('localization.review.operation_proof'),
'label' => __('localization.review.available'),
'color' => 'info',
'description' => __('localization.review.operation_proof_available_description'),
'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' => $downloadUrl !== null ? __('localization.review.download_review_pack') : null,
'action_url' => $downloadUrl,
];
}
/**
* @return array{summary_label:string,summary_color:string,items:list<array{label:string,value:string,color:string}>}
*/
private function acceptedRiskPanelForReview(EnvironmentReview $review): array
{
$package = $this->governancePackageSummaryForReview($review);
$hasFollowUp = $this->acceptedRiskFollowUpRequiredForReview($review);
$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 = $allEntries->count();
$expiring = $allEntries->where('governance_state', 'expiring_exception')->count();
$expired = $allEntries->where('governance_state', 'expired_exception')->count();
$pending = $allEntries->where('governance_state', 'pending_exception')->count();
$needsReview = $allEntries
->filter(static fn (array $entry): bool => in_array(
(string) ($entry['governance_state'] ?? ''),
self::ACCEPTED_RISK_FOLLOW_UP_STATES,
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',
],
],
];
}
/**
* @param array{state:string,label:string,description:string} $packageAvailability
* @return array{status_label:string,status_color:string,description:string,last_generated_label:string,evidence_snapshot_label:string,export_label:string,operation_label:string,download_url:?string}
*/
private function reviewPackPanelForReview(
EnvironmentReview $review,
ManagedEnvironment $tenant,
array $packageAvailability,
?string $downloadUrl,
): array {
$pack = $review->currentExportReviewPack;
$snapshot = $review->evidenceSnapshot;
return [
'status_label' => $packageAvailability['label'],
'status_color' => $this->governancePackageAvailabilityColor($tenant),
'description' => $packageAvailability['description'],
'last_generated_label' => $pack instanceof ReviewPack && $pack->generated_at !== null
? $pack->generated_at->format('M j, Y H:i')
: __('localization.review.unavailable'),
'evidence_snapshot_label' => $snapshot instanceof EvidenceSnapshot && $snapshot->generated_at !== null
? $snapshot->generated_at->format('M j, Y H:i')
: __('localization.review.unavailable'),
'export_label' => $downloadUrl !== null
? __('localization.review.export_ready')
: __('localization.review.export_not_ready'),
'operation_label' => $pack instanceof ReviewPack && $pack->operationRun instanceof OperationRun
? OperationRunLinks::identifier($pack->operationRun)
: __('localization.review.operation_proof_unavailable'),
'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 $this->workspaceReviewNeedsAttention($tenant)
? __('localization.review.review_needed')
: __('localization.review.ready_to_share');
}
private function latestReviewStateColor(ManagedEnvironment $tenant): string
{
$review = $this->latestPublishedReview($tenant);
if (! $review instanceof EnvironmentReview) {
return 'gray';
}
$packageState = $this->governancePackageAvailability($tenant)['state'];
if (! $this->workspaceReviewNeedsAttention($tenant)) {
return 'success';
}
return in_array($packageState, ['expired', 'unavailable'], true)
? 'danger'
: '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();
$decisionSummary = data_get($this->governancePackageSummaryForReview($review), 'decision_summary');
$isPartialReview = is_array($decisionSummary) && (
in_array((string) ($decisionSummary['status'] ?? ''), ['unavailable', 'incomplete'], true)
|| (string) ($decisionSummary['decision_data_state'] ?? '') === 'incomplete'
|| in_array((string) ($decisionSummary['evidence_state'] ?? ''), [
EnvironmentReviewCompletenessState::Partial->value,
EnvironmentReviewCompletenessState::Stale->value,
EnvironmentReviewCompletenessState::Missing->value,
], true)
);
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'),
];
}
if ($isPartialReview) {
return [
'state' => 'evidence_incomplete',
'label' => __('localization.review.review_pack_evidence_incomplete'),
'description' => __('localization.review.review_pack_evidence_incomplete_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'),
'evidence_incomplete' => __('localization.review.review_pack_evidence_incomplete'),
'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',
'evidence_incomplete', '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
* @return array<string, string>
*/
private function evidenceBasisForReview(EnvironmentReview $review, array $packageAvailability): array
{
$package = $this->governancePackageSummaryForReview($review);
$decision = $this->decisionSummaryForReview($review);
$pack = $review->currentExportReviewPack;
$state = match (true) {
$package === [] => 'unavailable',
! $pack instanceof ReviewPack => 'not_generated',
$packageAvailability['state'] === 'evidence_incomplete' || $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'),
'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'),
'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',
'incomplete' => 'warning',
default => 'gray',
};
}
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');
}
return match ($this->governancePackageAvailability($tenant)['state']) {
'available' => __('localization.review.workspace_next_step_package_review'),
'evidence_incomplete' => __('localization.review.workspace_next_step_evidence_review'),
default => __('localization.review.workspace_next_step_review_open'),
};
}
private function workspaceReviewNeedsAttention(ManagedEnvironment $tenant): bool
{
$review = $this->latestPublishedReview($tenant);
if (! $review instanceof EnvironmentReview) {
return true;
}
if ($this->primaryControlSummary($tenant) === null) {
return true;
}
if ($this->evidenceStatusState($tenant) !== 'available') {
return true;
}
if ($this->acceptedRiskFollowUpRequiredForReview($review)) {
return true;
}
return $this->governancePackageAvailability($tenant)['state'] !== 'available';
}
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);
}
}