From a5b7300ca92dccc151a046be820cb5dfe4934aa9 Mon Sep 17 00:00:00 2001 From: ahmido Date: Mon, 22 Jun 2026 11:03:10 +0000 Subject: [PATCH] feat: reduce receipt page surface depth and simplify evidence summaries (#468) Automated PR created by Codex via Gitea API. Co-authored-by: Ahmed Darrazi Reviewed-on: https://git.cloudarix.de/ahmido/TenantAtlas/pulls/468 --- .../Resources/EvidenceSnapshotResource.php | 10 +- .../Presenters/RestoreRunDetailPresenter.php | 203 +++++++-- .../Filament/Resources/ReviewPackResource.php | 12 +- .../Resources/StoredReportResource.php | 6 +- .../BaselineSnapshotPresenter.php | 5 +- .../baseline-snapshot-groups.blade.php | 26 +- .../baseline-snapshot-summary-table.blade.php | 10 +- .../evidence-dimension-summary.blade.php | 31 -- .../entries/restore-results.blade.php | 121 ++++-- .../partials/locale-switcher.blade.php | 4 + .../Spec277StoredReportsSurfaceSmokeTest.php | 3 +- ...GovernanceArtifactRetargetingSmokeTest.php | 8 +- ...Spec284ArtifactSourceTaxonomySmokeTest.php | 9 +- ...estoreRunDetailProductizationSmokeTest.php | 50 ++- ...2CustomerAuditorSurfaceSafetySmokeTest.php | 6 +- ...76BrowserAuditFixtureCoverageSmokeTest.php | 2 +- .../Spec397ReceiptPageReductionSmokeTest.php | 391 ++++++++++++++++++ .../Evidence/EvidenceSnapshotResourceTest.php | 4 +- .../ArtifactSourceTaxonomySurfaceTest.php | 8 +- ...aselineSnapshotStructuredRenderingTest.php | 58 +++ .../RestoreResultAttentionSurfaceTest.php | 7 +- ...c335RestoreRunDetailProductizationTest.php | 141 ++++++- ...pec372CustomerAuditorSurfaceSafetyTest.php | 8 +- .../Monitoring/HeaderContextBarTest.php | 2 + .../ReviewPack/ReviewPackResourceTest.php | 6 +- .../StoredReportDetailPresentationTest.php | 6 +- .../design-coverage-matrix.md | 12 +- .../page-reports/ui-042-review-pack-detail.md | 14 +- .../ui-046-evidence-snapshot-detail.md | 12 +- .../ui-048-stored-report-detail.md | 12 +- .../ui-ux-enterprise-audit/route-inventory.md | 10 +- .../unresolved-pages.md | 2 +- .../checklists/requirements.md | 84 ++++ .../implementation-report.md | 204 +++++++++ specs/397-receipt-page-reduction/plan.md | 281 +++++++++++++ specs/397-receipt-page-reduction/spec.md | 384 +++++++++++++++++ specs/397-receipt-page-reduction/tasks.md | 179 ++++++++ 37 files changed, 2140 insertions(+), 191 deletions(-) create mode 100644 apps/platform/tests/Browser/Spec397ReceiptPageReductionSmokeTest.php create mode 100644 specs/397-receipt-page-reduction/checklists/requirements.md create mode 100644 specs/397-receipt-page-reduction/implementation-report.md create mode 100644 specs/397-receipt-page-reduction/plan.md create mode 100644 specs/397-receipt-page-reduction/spec.md create mode 100644 specs/397-receipt-page-reduction/tasks.md diff --git a/apps/platform/app/Filament/Resources/EvidenceSnapshotResource.php b/apps/platform/app/Filament/Resources/EvidenceSnapshotResource.php index 0b6cc464..5790449a 100644 --- a/apps/platform/app/Filament/Resources/EvidenceSnapshotResource.php +++ b/apps/platform/app/Filament/Resources/EvidenceSnapshotResource.php @@ -224,7 +224,8 @@ public static function infolist(Schema $schema): Schema ->collapsible() ->collapsed() ->columnSpanFull(), - Section::make('Evidence dimensions') + Section::make('Internal evidence dimensions') + ->description('Dimension-level evidence remains available for audit and support after the receipt outcome is understood.') ->schema([ RepeatableEntry::make('items') ->hiddenLabel() @@ -282,7 +283,10 @@ public static function infolist(Schema $schema): Schema ->columnSpanFull(), ]) ->columns(3), - ]), + ]) + ->collapsible() + ->collapsed() + ->columnSpanFull(), ]); } @@ -489,8 +493,6 @@ private static function dimensionSummaryPresentation(EvidenceSnapshotItem $item) default => static::genericSummaryPresentation($payload), }; - $presentation['artifact_sources'] = [static::artifactSourceSummary($item)]; - return $presentation; } diff --git a/apps/platform/app/Filament/Resources/RestoreRunResource/Presenters/RestoreRunDetailPresenter.php b/apps/platform/app/Filament/Resources/RestoreRunResource/Presenters/RestoreRunDetailPresenter.php index 43f21e37..6c7e9141 100644 --- a/apps/platform/app/Filament/Resources/RestoreRunResource/Presenters/RestoreRunDetailPresenter.php +++ b/apps/platform/app/Filament/Resources/RestoreRunResource/Presenters/RestoreRunDetailPresenter.php @@ -101,65 +101,78 @@ private function decision( $copy = match ($state) { 'not_executed' => [ - 'status_label' => 'Not executed', + 'status_label' => 'Not configured', 'reason' => 'This record proves preview truth, not environment recovery.', 'impact' => 'No execution proof or post-run evidence exists yet.', 'primary_next_action' => 'Review preview', + 'follow_up_summary' => 'Review preview evidence before queueing execution.', 'primary_next_url' => null, 'tone' => 'gray', 'icon' => 'heroicon-m-eye', ], 'in_progress' => [ - 'status_label' => 'Execution in progress', + 'status_label' => 'Running', 'reason' => 'Restore execution is currently running.', 'impact' => 'Results and post-run evidence are not final yet.', - 'primary_next_action' => 'View operation progress', - 'primary_next_url' => $operationProof['url'] ?? null, + 'primary_next_action' => 'Monitor restore progress', + 'primary_next_target' => 'restore-run-technical-details', + 'follow_up_summary' => 'Use technical run details for operation progress and final proof.', + 'primary_next_url' => null, 'tone' => 'info', 'icon' => 'heroicon-m-arrow-path', ], 'completed_with_evidence' => [ - 'status_label' => 'Completed with evidence available', + 'status_label' => 'Ready', 'reason' => 'Execution proof and post-run evidence are available.', 'impact' => 'Review evidence before treating this restore as recovery proof.', - 'primary_next_action' => 'Open evidence', - 'primary_next_url' => $postRunEvidence['url'] ?? null, + 'primary_next_action' => 'Review recovery evidence', + 'primary_next_target' => 'restore-run-technical-details', + 'follow_up_summary' => 'Review the linked evidence before relying on this run as recovery proof.', + 'primary_next_url' => null, 'tone' => 'success', 'icon' => 'heroicon-m-shield-check', ], 'needs_review' => [ - 'status_label' => 'Completed with items needing review', + 'status_label' => 'Needs attention', 'reason' => $attention->summary, 'impact' => 'Review item outcomes before relying on the result.', 'primary_next_action' => 'Review item outcomes', + 'primary_next_target' => 'restore-run-item-outcomes', + 'follow_up_summary' => 'Review item outcomes that require attention.', 'primary_next_url' => null, 'tone' => 'warning', 'icon' => 'heroicon-m-exclamation-triangle', ], 'failed' => [ - 'status_label' => 'Restore failed', + 'status_label' => 'Failed', 'reason' => 'The restore did not complete successfully.', 'impact' => 'Some requested changes may not have been applied.', 'primary_next_action' => 'Review failure details', + 'primary_next_target' => 'restore-run-item-outcomes', + 'follow_up_summary' => 'Review failed items and provider errors before retrying.', 'primary_next_url' => null, 'tone' => 'danger', 'icon' => 'heroicon-m-x-circle', ], 'blocked_or_cancelled' => [ - 'status_label' => 'Restore blocked / cancelled', + 'status_label' => 'Blocked', 'reason' => 'Restore did not execute due to cancellation or blocker.', 'impact' => 'No recovery proof exists.', 'primary_next_action' => 'Review blocker', - 'primary_next_url' => $operationProof['url'] ?? null, + 'primary_next_target' => 'restore-run-technical-details', + 'follow_up_summary' => 'Review the blocker before retrying.', + 'primary_next_url' => null, 'tone' => 'warning', 'icon' => 'heroicon-m-no-symbol', ], default => [ - 'status_label' => 'Completed, recovery proof incomplete', + 'status_label' => 'Needs attention', 'reason' => 'Execution completed, but post-run evidence is not available yet.', 'impact' => 'Do not treat this restore as verified recovery until evidence has been reviewed.', - 'primary_next_action' => filled($operationProof['url'] ?? null) ? 'Open operation proof' : 'Review proof gap', - 'primary_next_url' => $operationProof['url'] ?? null, + 'primary_next_action' => 'View technical run details', + 'primary_next_target' => 'restore-run-technical-details', + 'follow_up_summary' => 'Confirm operation proof and capture post-run evidence before closing this as recovered.', + 'primary_next_url' => null, 'tone' => 'warning', 'icon' => 'heroicon-m-shield-exclamation', ], @@ -170,7 +183,9 @@ private function decision( ...$copy, 'question' => 'Was this restore executed safely, and is recovery proof available?', 'attention_summary' => $attention->summary, - 'primary_cause_family' => RestoreSafetyCopy::primaryCauseFamily($attention->primaryCauseFamily), + 'primary_cause_family' => $attention->primaryCauseFamily === 'none' + ? null + : RestoreSafetyCopy::primaryCauseFamily($attention->primaryCauseFamily), 'result_next_action' => RestoreSafetyCopy::primaryNextAction($attention->primaryNextAction), 'recovery_claim_boundary' => RestoreSafetyCopy::recoveryBoundary($attention->recoveryClaimBoundary), ]; @@ -263,17 +278,22 @@ private function operationProof(?OperationRun $operationRun): array */ private function postRunEvidence(RestoreRun $restoreRun, ?OperationRun $operationRun): array { - if (! $operationRun instanceof OperationRun || ! $this->canViewEvidence($restoreRun)) { - return [ - 'state' => 'unavailable', - 'label' => 'Post-run evidence unavailable', - 'url' => null, - 'status' => null, - 'completeness' => null, - 'status_badge' => $this->statusBadge('gray', 'Unavailable', 'heroicon-m-minus-circle'), - 'completeness_badge' => null, - 'identifier' => null, - ]; + if (! $operationRun instanceof OperationRun) { + return $this->unavailablePostRunEvidence( + 'Post-run evidence requires linked operation proof first. Review operation proof before treating this restore as recovered.', + ); + } + + if (! $this->canViewEvidence($restoreRun)) { + return $this->unavailablePostRunEvidence( + 'Evidence access is required to capture or review post-run evidence. Ask an operator with evidence access before closing this restore as recovered.', + ); + } + + if (in_array((string) $operationRun->status, [OperationRunStatus::Queued->value, OperationRunStatus::Running->value], true)) { + return $this->unavailablePostRunEvidence( + 'Wait until operation proof is completed, then capture a post-run evidence snapshot before closing this restore as recovered.', + ); } $snapshots = EvidenceSnapshot::query() @@ -292,16 +312,13 @@ private function postRunEvidence(RestoreRun $restoreRun, ?OperationRun $operatio ?? $snapshots->first(); if (! $snapshot instanceof EvidenceSnapshot || ! EvidenceSnapshotResource::canView($snapshot)) { - return [ - 'state' => 'unavailable', - 'label' => 'Post-run evidence unavailable', - 'url' => null, - 'status' => null, - 'completeness' => null, - 'status_badge' => $this->statusBadge('gray', 'Unavailable', 'heroicon-m-minus-circle'), - 'completeness_badge' => null, - 'identifier' => null, - ]; + return $this->unavailablePostRunEvidence( + $this->canManageEvidence($restoreRun) + ? 'Open Evidence snapshots and use Create snapshot for this environment. Do not close the restore as recovered until the post-run snapshot is available.' + : 'A post-run snapshot is not linked yet. Ask an operator with evidence management access to create one before closing this restore as recovered.', + $this->evidenceSnapshotIndexUrl($restoreRun), + 'Open evidence snapshots', + ); } $state = $snapshot->status === EvidenceSnapshotStatus::Active->value ? 'available' : 'in_progress'; @@ -310,6 +327,11 @@ private function postRunEvidence(RestoreRun $restoreRun, ?OperationRun $operatio 'state' => $state, 'label' => $state === 'available' ? 'Post-run evidence available' : 'Post-run evidence in progress', 'url' => EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $restoreRun->tenant), + 'guidance' => $state === 'available' + ? 'Review this post-run snapshot before relying on the restore as recovery proof.' + : 'Snapshot generation is still running. Wait until evidence is available before closing the restore as recovered.', + 'next_action_url' => null, + 'next_action_label' => null, 'status' => (string) $snapshot->status, 'completeness' => (string) $snapshot->completeness_state, 'status_badge' => $this->badge(BadgeDomain::EvidenceSnapshotStatus, (string) $snapshot->status), @@ -350,21 +372,71 @@ private function canViewEvidence(RestoreRun $restoreRun): bool && $user->can(Capabilities::EVIDENCE_VIEW, $tenant); } + private function canManageEvidence(RestoreRun $restoreRun): bool + { + $user = auth()->user(); + $tenant = $restoreRun->tenant; + + return $user instanceof User + && $tenant !== null + && $user->can(Capabilities::EVIDENCE_MANAGE, $tenant); + } + + /** + * @return array + */ + private function unavailablePostRunEvidence(string $guidance, ?string $nextActionUrl = null, ?string $nextActionLabel = null): array + { + return [ + 'state' => 'unavailable', + 'label' => 'Post-run evidence unavailable', + 'url' => null, + 'guidance' => $guidance, + 'next_action_url' => $nextActionUrl, + 'next_action_label' => $nextActionLabel, + 'status' => null, + 'completeness' => null, + 'status_badge' => $this->statusBadge('gray', 'Unavailable', 'heroicon-m-minus-circle'), + 'completeness_badge' => null, + 'identifier' => null, + ]; + } + + private function evidenceSnapshotIndexUrl(RestoreRun $restoreRun): ?string + { + if (! $this->canViewEvidence($restoreRun) || $restoreRun->tenant === null) { + return null; + } + + return EvidenceSnapshotResource::getUrl('index', tenant: $restoreRun->tenant); + } + /** * @return array */ private function resultSummary(RestoreRun $restoreRun): array { $metadata = is_array($restoreRun->metadata) ? $restoreRun->metadata : []; + $itemCounts = $this->itemResultCounts($restoreRun); $keys = ['total', 'succeeded', 'failed', 'skipped', 'partial', 'non_applied']; $available = collect($keys)->contains( static fn (string $key): bool => array_key_exists($key, $metadata) && is_numeric($metadata[$key]), ); if (! $available) { + if ($itemCounts['requested'] > 0) { + return [ + 'available' => true, + 'message' => 'Counts derived from recorded item outcomes.', + 'source_label' => 'Recorded outcomes', + 'counts' => $itemCounts, + ]; + } + return [ 'available' => false, - 'message' => 'Result summary unavailable', + 'message' => 'Result counts are not recorded for this run.', + 'source_label' => 'Unavailable', 'counts' => [], ]; } @@ -377,6 +449,16 @@ private function resultSummary(RestoreRun $restoreRun): array 'partial' => $this->numericCount($metadata, 'partial'), 'non_applied' => $this->numericCount($metadata, 'non_applied'), ]; + $completedFromItems = false; + + if ($itemCounts['requested'] > 0) { + foreach (['requested', 'applied', 'failed', 'skipped', 'partial', 'non_applied'] as $key) { + if ($counts[$key] === null) { + $counts[$key] = $itemCounts[$key]; + $completedFromItems = true; + } + } + } $reviewValues = array_filter([ $counts['failed'], @@ -389,11 +471,56 @@ private function resultSummary(RestoreRun $restoreRun): array return [ 'available' => true, - 'message' => 'Repo-backed result counts from restore metadata.', + 'message' => $completedFromItems + ? 'Counts completed from recorded item outcomes.' + : 'Recorded restore counts from stored metadata.', + 'source_label' => $completedFromItems ? 'Recorded counts' : 'Stored metadata', 'counts' => $counts, ]; } + /** + * @return array{requested:int,applied:int,failed:int,skipped:int,partial:int,non_applied:int,needs_review:int} + */ + private function itemResultCounts(RestoreRun $restoreRun): array + { + $results = is_array($restoreRun->results) ? $restoreRun->results : []; + $items = is_array($results['items'] ?? null) ? array_values($results['items']) : []; + $counts = [ + 'requested' => 0, + 'applied' => 0, + 'failed' => 0, + 'skipped' => 0, + 'partial' => 0, + 'non_applied' => 0, + 'needs_review' => 0, + ]; + + foreach ($items as $item) { + if (! is_array($item)) { + continue; + } + + $counts['requested']++; + $status = strtolower((string) ($item['status'] ?? 'unknown')); + + match ($status) { + 'applied', 'completed', 'success', 'succeeded' => $counts['applied']++, + 'failed', 'error' => $counts['failed']++, + 'skipped' => $counts['skipped']++, + 'partial', 'partially_applied', 'manual_required' => $counts['partial']++, + default => $counts['non_applied']++, + }; + } + + $counts['needs_review'] = $counts['failed'] + + $counts['skipped'] + + $counts['partial'] + + $counts['non_applied']; + + return $counts; + } + /** * @param list> $itemOutcomes * @param array $resultSummary diff --git a/apps/platform/app/Filament/Resources/ReviewPackResource.php b/apps/platform/app/Filament/Resources/ReviewPackResource.php index 72ef0f89..08daa5c0 100644 --- a/apps/platform/app/Filament/Resources/ReviewPackResource.php +++ b/apps/platform/app/Filament/Resources/ReviewPackResource.php @@ -196,17 +196,16 @@ public static function infolist(Schema $schema): Schema ->label('Reports included') ->placeholder('—'), TextEntry::make('summary.evidence_resolution.outcome') - ->label('Evidence resolution') + ->label('Evidence basis') ->placeholder('—'), TextEntry::make('evidenceSnapshot.completeness_state') - ->label('View internal evidence details') + ->label('Evidence completeness') ->badge() ->formatStateUsing(BadgeRenderer::label(BadgeDomain::EvidenceCompleteness)) ->color(BadgeRenderer::color(BadgeDomain::EvidenceCompleteness)) ->icon(BadgeRenderer::icon(BadgeDomain::EvidenceCompleteness)) ->iconColor(BadgeRenderer::iconColor(BadgeDomain::EvidenceCompleteness)) - ->placeholder('—') - ->url(fn (ReviewPack $record): ?string => static::evidenceSnapshotUrl($record)), + ->placeholder('—'), TextEntry::make('environmentReview.status') ->label('Released review') ->badge() @@ -246,6 +245,11 @@ public static function infolist(Schema $schema): Schema ->icon(BadgeRenderer::icon(BadgeDomain::EnvironmentReviewStatus)) ->iconColor(BadgeRenderer::iconColor(BadgeDomain::EnvironmentReviewStatus)) ->placeholder('—'), + TextEntry::make('internal_evidence_snapshot') + ->label('Internal evidence details') + ->state(fn (ReviewPack $record): string => $record->evidence_snapshot_id !== null ? 'Open evidence snapshot' : 'No evidence snapshot linked') + ->url(fn (ReviewPack $record): ?string => static::evidenceSnapshotUrl($record)) + ->hidden(fn (): bool => static::isCustomerWorkspaceFlow()), TextEntry::make('operationRun.id') ->label('Operation') ->url(function (ReviewPack $record): ?string { diff --git a/apps/platform/app/Filament/Resources/StoredReportResource.php b/apps/platform/app/Filament/Resources/StoredReportResource.php index d0740325..041f07c4 100644 --- a/apps/platform/app/Filament/Resources/StoredReportResource.php +++ b/apps/platform/app/Filament/Resources/StoredReportResource.php @@ -259,7 +259,8 @@ public static function infolist(Schema $schema): Schema ->visible(fn (StoredReport $record): bool => $record->report_type === StoredReport::REPORT_TYPE_ENTRA_ADMIN_ROLES) ->columnSpanFull(), - Section::make('Technical report details') + Section::make('Internal report artifact') + ->description('Raw artifact descriptors, fingerprints, and renderer metadata are secondary support details.') ->schema([ TextEntry::make('display_reference') ->label('Artifact reference') @@ -308,7 +309,8 @@ public static function infolist(Schema $schema): Schema ->collapsed() ->columnSpanFull(), - Section::make('Raw payload') + Section::make('Internal payload details') + ->description('Stored payload JSON is technical evidence and stays out of the default receipt summary.') ->schema([ ViewEntry::make('payload') ->hiddenLabel() diff --git a/apps/platform/app/Services/Baselines/SnapshotRendering/BaselineSnapshotPresenter.php b/apps/platform/app/Services/Baselines/SnapshotRendering/BaselineSnapshotPresenter.php index 1a3c5055..561e3934 100644 --- a/apps/platform/app/Services/Baselines/SnapshotRendering/BaselineSnapshotPresenter.php +++ b/apps/platform/app/Services/Baselines/SnapshotRendering/BaselineSnapshotPresenter.php @@ -15,9 +15,9 @@ use App\Support\Ui\EnterpriseDetail\EnterpriseDetailPageData; use App\Support\Ui\EnterpriseDetail\EnterpriseDetailSectionFactory; use App\Support\Ui\EnterpriseDetail\SummaryHeaderData; -use App\Support\Ui\GovernanceArtifactTruth\SurfaceCompressionContext; use App\Support\Ui\GovernanceArtifactTruth\ArtifactTruthEnvelope; use App\Support\Ui\GovernanceArtifactTruth\ArtifactTruthPresenter; +use App\Support\Ui\GovernanceArtifactTruth\SurfaceCompressionContext; use Illuminate\Database\Eloquent\Collection as EloquentCollection; use Illuminate\Support\Carbon; use Illuminate\Support\Collection; @@ -246,6 +246,9 @@ public function presentEnterpriseDetail(BaselineSnapshot $snapshot, array $relat $rendered->groups, )], emptyState: $factory->emptyState('No snapshot items were captured for this baseline snapshot.'), + description: 'Detailed governed-subject inventory is secondary to the receipt summary and remains collapsed by default.', + collapsible: true, + collapsed: true, ), ) ->addSupportingCard( diff --git a/apps/platform/resources/views/filament/infolists/entries/baseline-snapshot-groups.blade.php b/apps/platform/resources/views/filament/infolists/entries/baseline-snapshot-groups.blade.php index aa2367fe..18e731da 100644 --- a/apps/platform/resources/views/filament/infolists/entries/baseline-snapshot-groups.blade.php +++ b/apps/platform/resources/views/filament/infolists/entries/baseline-snapshot-groups.blade.php @@ -5,6 +5,8 @@ $groups = isset($groups) ? $groups : (isset($getState) ? $getState() : []); $groups = is_array($groups) ? $groups : []; + $visibleGroups = array_slice($groups, 0, 8); + $hiddenGroupCount = max(0, count($groups) - count($visibleGroups)); $formatTimestamp = static function (?string $value): string { if (! is_string($value) || trim($value) === '') { @@ -25,7 +27,7 @@ No snapshot items were captured for this baseline snapshot. @else - @foreach ($groups as $group) + @foreach ($visibleGroups as $group) @php $fidelitySpec = BadgeRenderer::spec(BadgeDomain::BaselineSnapshotFidelity, $group['fidelity'] ?? null); $gapSpec = BadgeRenderer::spec(BadgeDomain::BaselineSnapshotGapStatus, data_get($group, 'gapSummary.badge_state')); @@ -70,8 +72,14 @@ @endif -
- @foreach (($group['items'] ?? []) as $item) +
+ @php + $items = is_array($group['items'] ?? null) ? $group['items'] : []; + $visibleItems = array_slice($items, 0, 8); + $hiddenItemCount = max(0, count($items) - count($visibleItems)); + @endphp + + @foreach ($visibleItems as $item) @php $itemFidelitySpec = BadgeRenderer::spec(BadgeDomain::BaselineSnapshotFidelity, $item['fidelity'] ?? null); $itemGapSpec = BadgeRenderer::spec(BadgeDomain::BaselineSnapshotGapStatus, data_get($item, 'gapSummary.badge_state')); @@ -138,9 +146,21 @@ @endif
@endforeach + + @if ($hiddenItemCount > 0) +
+ Showing the first 8 captured items for this governed subject. {{ $hiddenItemCount }} additional {{ \Illuminate\Support\Str::plural('item', $hiddenItemCount) }} stay in internal detail. +
+ @endif
@endforeach + + @if ($hiddenGroupCount > 0) +
+ Showing the first 8 governed subjects on the receipt. {{ $hiddenGroupCount }} additional {{ \Illuminate\Support\Str::plural('subject', $hiddenGroupCount) }} stay in internal detail. +
+ @endif @endif diff --git a/apps/platform/resources/views/filament/infolists/entries/baseline-snapshot-summary-table.blade.php b/apps/platform/resources/views/filament/infolists/entries/baseline-snapshot-summary-table.blade.php index 8035af6f..2b96d78d 100644 --- a/apps/platform/resources/views/filament/infolists/entries/baseline-snapshot-summary-table.blade.php +++ b/apps/platform/resources/views/filament/infolists/entries/baseline-snapshot-summary-table.blade.php @@ -5,6 +5,8 @@ $rows = isset($rows) ? $rows : (isset($getState) ? $getState() : []); $rows = is_array($rows) ? $rows : []; + $visibleRows = array_slice($rows, 0, 8); + $hiddenRowCount = max(0, count($rows) - count($visibleRows)); $formatTimestamp = static function (?string $value): string { if (! is_string($value) || trim($value) === '') { @@ -38,7 +40,7 @@ - @foreach ($rows as $row) + @foreach ($visibleRows as $row) @php $fidelitySpec = BadgeRenderer::spec(BadgeDomain::BaselineSnapshotFidelity, $row['fidelity'] ?? null); $gapState = (($row['gapCount'] ?? 0) > 0) ? 'gaps_present' : 'clear'; @@ -78,5 +80,11 @@ + + @if ($hiddenRowCount > 0) +
+ Showing the first 8 governed subjects for receipt review. {{ $hiddenRowCount }} additional {{ \Illuminate\Support\Str::plural('subject', $hiddenRowCount) }} stay in the internal detail path. +
+ @endif @endif diff --git a/apps/platform/resources/views/filament/infolists/entries/evidence-dimension-summary.blade.php b/apps/platform/resources/views/filament/infolists/entries/evidence-dimension-summary.blade.php index 5dc6d554..f31ddeab 100644 --- a/apps/platform/resources/views/filament/infolists/entries/evidence-dimension-summary.blade.php +++ b/apps/platform/resources/views/filament/infolists/entries/evidence-dimension-summary.blade.php @@ -3,7 +3,6 @@ $state = is_array($state) ? $state : []; $summary = is_string($state['summary'] ?? null) ? $state['summary'] : null; - $artifactSources = is_array($state['artifact_sources'] ?? null) ? $state['artifact_sources'] : []; $highlights = is_array($state['highlights'] ?? null) ? $state['highlights'] : []; $items = is_array($state['items'] ?? null) ? $state['items'] : []; @endphp @@ -13,36 +12,6 @@
{{ $summary }}
@endif - @if ($artifactSources !== []) -
-
Artifact source
-
- @foreach ($artifactSources as $source) - @continue(! is_array($source)) - - @foreach ([ - 'Source family' => $source['source_family'] ?? null, - 'Source kind' => $source['source_kind'] ?? null, - 'Source target' => $source['source_target_kind'] ?? null, - 'Control' => $source['control_key'] ?? null, - 'Detector' => $source['detector_key'] ?? null, - ] as $label => $value) - @php - $value = is_string($value) && trim($value) !== '' ? \Illuminate\Support\Str::headline($value) : null; - @endphp - - @continue($value === null) - -
-
{{ $label }}
-
{{ $value }}
-
- @endforeach - @endforeach -
-
- @endif - @if ($highlights !== [])
@foreach ($highlights as $highlight) diff --git a/apps/platform/resources/views/filament/infolists/entries/restore-results.blade.php b/apps/platform/resources/views/filament/infolists/entries/restore-results.blade.php index 9a33463b..c4e9e592 100644 --- a/apps/platform/resources/views/filament/infolists/entries/restore-results.blade.php +++ b/apps/platform/resources/views/filament/infolists/entries/restore-results.blade.php @@ -10,25 +10,22 @@ $summaryCounts = is_array($resultSummary['counts'] ?? null) ? $resultSummary['counts'] : []; $itemOutcomeEvidence = is_array($surface['itemOutcomeEvidence'] ?? null) ? $surface['itemOutcomeEvidence'] : []; $itemOutcomes = collect(is_array($surface['itemOutcomes'] ?? null) ? $surface['itemOutcomes'] : []); + $visibleItemOutcomes = $itemOutcomes->take(8); + $hiddenItemOutcomeCount = max(0, $itemOutcomes->count() - $visibleItemOutcomes->count()); $foundationOutcomes = collect(is_array($surface['foundationOutcomes'] ?? null) ? $surface['foundationOutcomes'] : []); + $visibleFoundationOutcomes = $foundationOutcomes->take(8); + $hiddenFoundationOutcomeCount = max(0, $foundationOutcomes->count() - $visibleFoundationOutcomes->count()); $diagnostics = is_array($surface['diagnostics'] ?? null) ? $surface['diagnostics'] : []; $runContext = is_array($surface['runContext'] ?? null) ? $surface['runContext'] : []; - $resultAttention = is_array($surface['resultAttention'] ?? null) ? $surface['resultAttention'] : []; - - $attentionSpec = \App\Support\Badges\BadgeRenderer::spec( - \App\Support\Badges\BadgeDomain::RestoreResultStatus, - $resultAttention['state'] ?? ($decision['state'] ?? 'not_executed') - ); - + $primaryNextTarget = is_string($decision['primary_next_target'] ?? null) ? $decision['primary_next_target'] : null; $summaryCards = [ 'requested' => 'Requested', 'applied' => 'Applied', 'failed' => 'Failed', - 'skipped' => 'Skipped', 'needs_review' => 'Needs review', ]; - $formatCount = static fn (mixed $value): string => is_numeric($value) ? number_format((int) $value) : '—'; + $formatCount = static fn (mixed $value): string => is_numeric($value) ? number_format((int) $value) : 'Not recorded'; @endphp
@@ -46,9 +43,6 @@ class="rounded-xl border border-slate-200 bg-white p-5 shadow-sm dark:border-whi {{ $decision['status_label'] ?? 'Restore result unavailable' }} - - {{ $attentionSpec->label }} -
@@ -73,39 +67,54 @@ class="rounded-xl border border-slate-200 bg-white p-5 shadow-sm dark:border-whi

{{ $decision['impact'] ?? 'Review restore details before relying on this record.' }}

-

Primary next action

-

{{ $decision['primary_next_action'] ?? 'Review restore details' }}

+

Trust boundary

+

{{ $decision['recovery_claim_boundary'] ?? 'Target environment recovery is not proven.' }}

- -
-
-

Restore readiness

- - {{ $readinessGuidance['stateLabel'] ?? 'Restore readiness unavailable.' }} - -
-

{{ $readinessGuidance['reasonSummary'] ?? 'This guidance is based on the current restore scope and preview state.' }}

-

Next safe action: {{ $readinessGuidance['nextActionLabel'] ?? 'Review restore details' }}

-

{{ $readinessGuidance['actionSafetyCopy'] ?? 'This guidance does not execute the restore.' }}

-
-

Dominant action

+

Next action

@if (filled($decision['primary_next_url'] ?? null)) {{ $decision['primary_next_action'] ?? 'Open detail' }} + @elseif (filled($primaryNextTarget)) + + {{ $decision['primary_next_action'] ?? 'Review restore details' }} + @else
{{ $decision['primary_next_action'] ?? 'Review restore details' }}
@endif
-

Result next step: {{ $decision['result_next_action'] ?? 'Review the completed restore details.' }}

-

Main follow-up driver: {{ $decision['primary_cause_family'] ?? 'No dominant cause recorded' }}

-

Boundary: {{ $decision['recovery_claim_boundary'] ?? 'Target environment recovery is not proven.' }}

+ @if (filled($decision['follow_up_summary'] ?? null)) +

Follow-up: {{ $decision['follow_up_summary'] }}

+ @endif + @if (filled($decision['primary_cause_family'] ?? null)) +

Primary driver: {{ $decision['primary_cause_family'] }}

+ @endif
@@ -122,7 +131,7 @@ class="grid gap-6 lg:grid-cols-[minmax(0,1fr)_22rem]"

Restore result summary

{{ $resultSummary['message'] ?? 'Result summary unavailable' }}

- Repo-backed + {{ $resultSummary['source_label'] ?? 'Recorded' }} @if (($resultSummary['available'] ?? false) === true) @@ -136,16 +145,16 @@ class="grid gap-6 lg:grid-cols-[minmax(0,1fr)_22rem]" @else
- Result summary unavailable. No fake zero counts are shown. + Result counts are not recorded for this run. No fake zero counts are shown.
@endif -
+

Item outcomes

-

Per-item results are table-first. Item diagnostics stay behind row disclosure.

+

Recorded restore item outcomes. Row diagnostics remain secondary when present.

{{ $itemOutcomeEvidence['label'] ?? ($itemOutcomes->count().' items') }} @@ -170,7 +179,7 @@ class="grid gap-6 lg:grid-cols-[minmax(0,1fr)_22rem]" - @foreach ($itemOutcomes as $item) + @foreach ($visibleItemOutcomes as $item) @php $statusBadge = is_array($item['status_badge'] ?? null) ? $item['status_badge'] : []; $diagnostics = collect(is_array($item['diagnostics'] ?? null) ? $item['diagnostics'] : []); @@ -214,6 +223,12 @@ class="grid gap-6 lg:grid-cols-[minmax(0,1fr)_22rem]"
+ + @if ($hiddenItemOutcomeCount > 0) +
+ Showing the first 8 item outcomes for receipt review. {{ $hiddenItemOutcomeCount }} additional {{ \Illuminate\Support\Str::plural('item', $hiddenItemOutcomeCount) }} stay in internal detail. +
+ @endif @endif
@@ -231,7 +246,7 @@ class="grid gap-6 lg:grid-cols-[minmax(0,1fr)_22rem]" - @foreach ($foundationOutcomes as $foundation) + @foreach ($visibleFoundationOutcomes as $foundation) @php $decisionBadge = is_array($foundation['decision_badge'] ?? null) ? $foundation['decision_badge'] : []; @endphp @@ -257,13 +272,21 @@ class="grid gap-6 lg:grid-cols-[minmax(0,1fr)_22rem]" + + @if ($hiddenFoundationOutcomeCount > 0) +
+ Showing the first 8 foundation outcomes for receipt review. {{ $hiddenFoundationOutcomeCount }} additional {{ \Illuminate\Support\Str::plural('foundation', $hiddenFoundationOutcomeCount) }} stay in internal detail. +
+ @endif
@endif