withDefaults(new ActionSurfaceDefaults( moreGroupLabel: 'More', exportIsDefaultBulkActionForReadOnly: false, )) ->exempt( ActionSurfaceSlot::ListHeader, 'Run-log list intentionally has no list-header actions; navigation actions are provided by Monitoring shell pages.', ) ->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ViewAction->value) ->exempt( ActionSurfaceSlot::ListBulkMoreGroup, 'Operation runs are immutable records; bulk export is deferred and tracked outside this retrofit.', ) ->exempt( ActionSurfaceSlot::ListEmptyState, 'Empty-state action is intentionally omitted; users can adjust filters/date range in-page.', ) ->satisfy( ActionSurfaceSlot::DetailHeader, 'Tenantless detail view keeps back-navigation, refresh, related links, and resumable operation actions in the header.', ); } public static function getEloquentQuery(): Builder { $workspaceId = app(WorkspaceContext::class)->currentWorkspaceId(); return parent::getEloquentQuery() ->with('user') ->latest('id') ->when($workspaceId, fn (Builder $query) => $query->where('workspace_id', (int) $workspaceId)) ->when(! $workspaceId, fn (Builder $query) => $query->whereRaw('1 = 0')); } public static function form(Schema $schema): Schema { return $schema; } public static function infolist(Schema $schema): Schema { return $schema ->schema([ ViewEntry::make('enterprise_detail') ->label('') ->view('filament.infolists.entries.enterprise-detail.layout') ->state(fn (OperationRun $record): array => static::enterpriseDetailPage($record)->toArray()) ->columnSpanFull(), ]); } public static function table(Table $table): Table { return $table ->defaultSort('created_at', 'desc') ->paginated(TablePaginationProfiles::resource()) ->persistFiltersInSession() ->persistSearchInSession() ->persistSortInSession() ->columns([ Tables\Columns\TextColumn::make('status') ->badge() ->formatStateUsing(fn (mixed $state, OperationRun $record): string => BadgeRenderer::spec(BadgeDomain::OperationRunStatus, static::statusBadgeState($record))->label) ->color(fn (mixed $state, OperationRun $record): string => BadgeRenderer::spec(BadgeDomain::OperationRunStatus, static::statusBadgeState($record))->color) ->icon(fn (mixed $state, OperationRun $record): ?string => BadgeRenderer::spec(BadgeDomain::OperationRunStatus, static::statusBadgeState($record))->icon) ->iconColor(fn (mixed $state, OperationRun $record): ?string => BadgeRenderer::spec(BadgeDomain::OperationRunStatus, static::statusBadgeState($record))->iconColor) ->description(fn (OperationRun $record): ?string => OperationUxPresenter::lifecycleAttentionSummary($record)), Tables\Columns\TextColumn::make('type') ->label('Operation') ->formatStateUsing(fn (?string $state): string => OperationCatalog::label((string) $state)) ->searchable() ->sortable(), Tables\Columns\TextColumn::make('initiator_name') ->label('Initiator') ->searchable(), Tables\Columns\TextColumn::make('created_at') ->label('Started') ->since() ->sortable(), Tables\Columns\TextColumn::make('duration') ->getStateUsing(function (OperationRun $record): string { if ($record->started_at && $record->completed_at) { return $record->completed_at->diffForHumans($record->started_at, true); } return '—'; }), Tables\Columns\TextColumn::make('outcome') ->badge() ->formatStateUsing(fn (mixed $state, OperationRun $record): string => BadgeRenderer::spec(BadgeDomain::OperationRunOutcome, static::outcomeBadgeState($record))->label) ->color(fn (mixed $state, OperationRun $record): string => BadgeRenderer::spec(BadgeDomain::OperationRunOutcome, static::outcomeBadgeState($record))->color) ->icon(fn (mixed $state, OperationRun $record): ?string => BadgeRenderer::spec(BadgeDomain::OperationRunOutcome, static::outcomeBadgeState($record))->icon) ->iconColor(fn (mixed $state, OperationRun $record): ?string => BadgeRenderer::spec(BadgeDomain::OperationRunOutcome, static::outcomeBadgeState($record))->iconColor) ->description(fn (OperationRun $record): ?string => OperationUxPresenter::surfaceGuidance($record)), ]) ->filters([ Tables\Filters\SelectFilter::make('tenant_id') ->label('Tenant') ->options(function (): array { $user = auth()->user(); if (! $user instanceof User) { return []; } return collect($user->getTenants(Filament::getCurrentOrDefaultPanel())) ->mapWithKeys(static fn (Tenant $tenant): array => [ (string) $tenant->getKey() => $tenant->getFilamentName(), ]) ->all(); }) ->default(function (): ?string { $activeTenant = app(OperateHubShell::class)->activeEntitledTenant(request()); if (! $activeTenant instanceof Tenant) { return null; } $workspaceId = app(WorkspaceContext::class)->currentWorkspaceId(); if ($workspaceId === null || (int) $activeTenant->workspace_id !== (int) $workspaceId) { return null; } return (string) $activeTenant->getKey(); }) ->searchable(), Tables\Filters\SelectFilter::make('type') ->options(function (): array { $workspaceId = app(WorkspaceContext::class)->currentWorkspaceId(); if ($workspaceId === null) { return []; } $types = OperationRun::query() ->where('workspace_id', (int) $workspaceId) ->select('type') ->distinct() ->orderBy('type') ->pluck('type', 'type') ->all(); return FilterOptionCatalog::operationTypes(array_keys($types)); }), Tables\Filters\SelectFilter::make('status') ->options(BadgeCatalog::options(BadgeDomain::OperationRunStatus, OperationRunStatus::values())), Tables\Filters\SelectFilter::make('outcome') ->options(BadgeCatalog::options(BadgeDomain::OperationRunOutcome, OperationRunOutcome::values(includeReserved: false))), Tables\Filters\SelectFilter::make('initiator_name') ->label('Initiator') ->options(function (): array { $workspaceId = app(WorkspaceContext::class)->currentWorkspaceId(); if ($workspaceId === null) { return []; } return OperationRun::query() ->where('workspace_id', (int) $workspaceId) ->whereNotNull('initiator_name') ->select('initiator_name') ->distinct() ->orderBy('initiator_name') ->pluck('initiator_name', 'initiator_name') ->all(); }) ->searchable(), FilterPresets::dateRange('created_at', 'Created', 'created_at', [ 'from' => now()->subDays(30)->toDateString(), 'until' => now()->toDateString(), ]), ]) ->actions([ Actions\ViewAction::make() ->label('View run') ->url(fn (OperationRun $record): string => OperationRunLinks::tenantlessView($record)), ]) ->bulkActions([]) ->emptyStateHeading('No operation runs found') ->emptyStateDescription('Queued, running, and completed operations will appear here when work is triggered in this scope.') ->emptyStateIcon('heroicon-o-queue-list'); } private static function enterpriseDetailPage(OperationRun $record): \App\Support\Ui\EnterpriseDetail\EnterpriseDetailPageData { $factory = new \App\Support\Ui\EnterpriseDetail\EnterpriseDetailSectionFactory; $statusSpec = BadgeRenderer::spec(BadgeDomain::OperationRunStatus, static::statusBadgeState($record)); $outcomeSpec = BadgeRenderer::spec(BadgeDomain::OperationRunOutcome, static::outcomeBadgeState($record)); $targetScope = static::targetScopeDisplay($record) ?? 'No target scope details were recorded for this run.'; $summaryLine = \App\Support\OpsUx\SummaryCountsNormalizer::renderSummaryLine(is_array($record->summary_counts) ? $record->summary_counts : []); $referencedTenantLifecycle = $record->tenant instanceof Tenant ? ReferencedTenantLifecyclePresentation::forOperationRun($record->tenant) : null; $artifactTruth = $record->supportsOperatorExplanation() ? app(ArtifactTruthPresenter::class)->forOperationRun($record) : null; $operatorExplanation = $artifactTruth?->operatorExplanation; $primaryNextStep = static::resolvePrimaryNextStep($record, $artifactTruth, $operatorExplanation); $supportingGroups = static::supportingGroups( record: $record, factory: $factory, referencedTenantLifecycle: $referencedTenantLifecycle, operatorExplanation: $operatorExplanation, primaryNextStep: $primaryNextStep, ); $builder = \App\Support\Ui\EnterpriseDetail\EnterpriseDetailBuilder::make('operation_run', 'workspace-context') ->header(new \App\Support\Ui\EnterpriseDetail\SummaryHeaderData( title: OperationCatalog::label((string) $record->type), subtitle: 'Run #'.$record->getKey(), statusBadges: [ $factory->statusBadge($statusSpec->label, $statusSpec->color, $statusSpec->icon, $statusSpec->iconColor), $factory->statusBadge($outcomeSpec->label, $outcomeSpec->color, $outcomeSpec->icon, $outcomeSpec->iconColor), ], keyFacts: [ $factory->keyFact('Target', $targetScope), $factory->keyFact('Elapsed', RunDurationInsights::elapsedHuman($record)), ], descriptionHint: 'Decision guidance and high-signal context stay ahead of diagnostic payloads and raw JSON.', )) ->decisionZone($factory->decisionZone( facts: array_values(array_filter([ $factory->keyFact( 'Execution state', $statusSpec->label, badge: $factory->statusBadge($statusSpec->label, $statusSpec->color, $statusSpec->icon, $statusSpec->iconColor), ), $factory->keyFact( 'Outcome', $outcomeSpec->label, badge: $factory->statusBadge($outcomeSpec->label, $outcomeSpec->color, $outcomeSpec->icon, $outcomeSpec->iconColor), ), static::artifactTruthFact($factory, $artifactTruth), $operatorExplanation instanceof OperatorExplanationPattern ? $factory->keyFact( 'Result meaning', $operatorExplanation->evaluationResultLabel(), $operatorExplanation->headline, ) : null, $operatorExplanation instanceof OperatorExplanationPattern ? $factory->keyFact( 'Result trust', $operatorExplanation->trustworthinessLabel(), static::detailHintUnlessDuplicate( $operatorExplanation->reliabilityStatement, $artifactTruth?->primaryExplanation, ), ) : null, ])), primaryNextStep: $factory->primaryNextStep( $primaryNextStep['text'], $primaryNextStep['source'], $primaryNextStep['secondaryGuidance'], ), description: 'Start here to see how the run ended, whether the result is trustworthy enough to use, and the one primary next step.', compactCounts: $summaryLine !== null ? $factory->countPresentation(summaryLine: $summaryLine) : null, attentionNote: static::decisionAttentionNote($record), )); if ($supportingGroups !== []) { $builder->addSupportingGroup(...$supportingGroups); } $builder->addSection( $factory->viewSection( id: 'related_context', kind: 'related_context', title: 'Related context', view: 'filament.infolists.entries.related-context', viewData: ['entries' => app(RelatedNavigationResolver::class) ->detailEntries(CrossResourceNavigationMatrix::SOURCE_OPERATION_RUN, $record)], emptyState: $factory->emptyState('No related context is available for this record.'), ), $factory->viewSection( id: 'artifact_truth', kind: 'supporting_detail', title: 'Artifact truth details', view: 'filament.infolists.entries.governance-artifact-truth', viewData: [ 'artifactTruthState' => $artifactTruth?->toArray(), 'surface' => 'expanded', ], visible: $artifactTruth !== null, description: 'Detailed artifact-truth context explains evidence quality and caveats without repeating the top decision summary.', collapsible: true, collapsed: true, ), ); $counts = static::summaryCountFacts($record, $factory); if ($counts !== []) { $builder->addTechnicalSection( $factory->technicalDetail( title: 'Count diagnostics', entries: $counts, description: 'Normalized run counters remain available for deeper inspection without competing with the primary decision.', collapsible: true, collapsed: true, variant: 'diagnostic', ), ); } if (! empty($record->failure_summary)) { $builder->addTechnicalSection( $factory->technicalDetail( title: (string) $record->outcome === OperationRunOutcome::Blocked->value ? 'Blocked execution details' : 'Failures', description: 'Detailed failure evidence stays available for investigation after the decision and supporting context.', view: 'filament.infolists.entries.snapshot-json', viewData: ['payload' => $record->failure_summary ?? []], collapsible: true, collapsed: false, ), ); } if (static::reconciliationPayload($record) !== []) { $builder->addTechnicalSection( $factory->technicalDetail( title: 'Lifecycle reconciliation', description: 'Lifecycle reconciliation is diagnostic evidence showing when TenantPilot force-resolved the run.', view: 'filament.infolists.entries.snapshot-json', viewData: ['payload' => static::reconciliationPayload($record)], collapsible: true, collapsed: true, ), ); } if ((string) $record->type === 'baseline_compare') { $baselineCompareFacts = static::baselineCompareFacts($record, $factory); $baselineCompareEvidence = static::baselineCompareEvidencePayload($record); $gapDetails = BaselineCompareEvidenceGapDetails::fromOperationRun($record); $gapSummary = is_array($gapDetails['summary'] ?? null) ? $gapDetails['summary'] : []; $gapBuckets = is_array($gapDetails['buckets'] ?? null) ? $gapDetails['buckets'] : []; if ($baselineCompareFacts !== []) { $builder->addSection( $factory->factsSection( id: 'baseline_compare', kind: 'type_specific_detail', title: 'Baseline compare', items: $baselineCompareFacts, description: 'Type-specific comparison detail stays below the canonical decision and supporting layers.', collapsible: true, collapsed: true, ), ); } if (($gapSummary['detail_state'] ?? 'no_gaps') !== 'no_gaps') { $builder->addSection( $factory->viewSection( id: 'baseline_compare_gap_details', kind: 'type_specific_detail', title: 'Evidence gap details', description: 'Policies affected by evidence gaps, grouped by reason and searchable by reason, policy type, or subject key.', view: 'filament.infolists.entries.evidence-gap-subjects', viewData: [ 'summary' => $gapSummary, 'buckets' => $gapBuckets, 'searchId' => 'baseline-compare-gap-search-'.$record->getKey(), ], collapsible: true, collapsed: true, ), ); } if ($baselineCompareEvidence !== []) { $builder->addSection( $factory->viewSection( id: 'baseline_compare_evidence', kind: 'type_specific_detail', title: 'Baseline compare evidence', view: 'filament.infolists.entries.snapshot-json', viewData: ['payload' => $baselineCompareEvidence], collapsible: true, collapsed: true, ), ); } } if ((string) $record->type === 'baseline_capture') { $baselineCaptureEvidence = static::baselineCaptureEvidencePayload($record); if ($baselineCaptureEvidence !== []) { $builder->addSection( $factory->viewSection( id: 'baseline_capture_evidence', kind: 'type_specific_detail', title: 'Baseline capture evidence', view: 'filament.infolists.entries.snapshot-json', viewData: ['payload' => $baselineCaptureEvidence], collapsible: true, collapsed: true, ), ); } } if (VerificationReportViewer::shouldRenderForRun($record)) { $builder->addSection( $factory->viewSection( id: 'verification_report', kind: 'type_specific_detail', title: 'Verification report', view: 'filament.components.verification-report-viewer', viewData: static::verificationReportViewData($record), ), ); } $builder->addTechnicalSection( $factory->technicalDetail( title: 'Context', entries: [ $factory->keyFact('Identity hash', $record->run_identity_hash, mono: true), $factory->keyFact('Workspace scope', $record->workspace_id), $factory->keyFact('Tenant scope', $record->tenant_id), ], description: 'Stored run context stays available for debugging without dominating the default reading path.', view: 'filament.infolists.entries.snapshot-json', viewData: ['payload' => static::contextPayload($record)], ), ); return $builder->build(); } /** * @return list<\App\Support\Ui\EnterpriseDetail\SupportingCardData> */ private static function supportingGroups( OperationRun $record, \App\Support\Ui\EnterpriseDetail\EnterpriseDetailSectionFactory $factory, ?ReferencedTenantLifecyclePresentation $referencedTenantLifecycle, ?OperatorExplanationPattern $operatorExplanation, array $primaryNextStep, ): array { $groups = []; $hasElevatedLifecycleState = OperationUxPresenter::lifecycleAttentionSummary($record) !== null; $guidanceItems = array_values(array_filter([ $operatorExplanation instanceof OperatorExplanationPattern && $operatorExplanation->coverageStatement !== null ? $factory->keyFact('Coverage', $operatorExplanation->coverageStatement) : null, $operatorExplanation instanceof OperatorExplanationPattern && $operatorExplanation->diagnosticsSummary !== null ? $factory->keyFact('Diagnostics summary', $operatorExplanation->diagnosticsSummary) : null, ...array_map( static fn (array $guidance): array => $factory->keyFact($guidance['label'], $guidance['text']), array_values(array_filter( $primaryNextStep['secondaryGuidance'] ?? [], static fn (mixed $guidance): bool => is_array($guidance), )), ), static::blockedExecutionReasonCode($record) !== null ? $factory->keyFact('Blocked reason', static::blockedExecutionReasonCode($record)) : null, static::blockedExecutionDetail($record) !== null ? $factory->keyFact('Blocked detail', static::blockedExecutionDetail($record)) : null, static::blockedExecutionSource($record) !== null ? $factory->keyFact('Blocked by', static::blockedExecutionSource($record)) : null, RunDurationInsights::stuckGuidance($record) !== null ? $factory->keyFact('Queue guidance', RunDurationInsights::stuckGuidance($record)) : null, ])); if ($guidanceItems !== []) { $groups[] = $factory->supportingFactsCard( kind: 'guidance', title: 'Guidance', items: $guidanceItems, description: 'Secondary guidance explains caveats and context without competing with the primary next step.', ); } $lifecycleItems = array_values(array_filter([ $referencedTenantLifecycle !== null ? $factory->keyFact( 'Tenant lifecycle', $referencedTenantLifecycle->presentation->label, badge: $factory->statusBadge( $referencedTenantLifecycle->presentation->label, $referencedTenantLifecycle->presentation->badgeColor, $referencedTenantLifecycle->presentation->badgeIcon, $referencedTenantLifecycle->presentation->badgeIconColor, ), ) : null, $referencedTenantLifecycle?->selectorAvailabilityMessage() !== null ? $factory->keyFact('Tenant selector context', $referencedTenantLifecycle->selectorAvailabilityMessage()) : null, $referencedTenantLifecycle?->contextNote !== null ? $factory->keyFact('Viewer context', $referencedTenantLifecycle->contextNote) : null, ! $hasElevatedLifecycleState && static::freshnessLabel($record) !== null ? $factory->keyFact('Freshness', (string) static::freshnessLabel($record)) : null, ! $hasElevatedLifecycleState && static::reconciliationHeadline($record) !== null ? $factory->keyFact('Lifecycle truth', (string) static::reconciliationHeadline($record)) : null, static::reconciledAtLabel($record) !== null ? $factory->keyFact('Reconciled at', (string) static::reconciledAtLabel($record)) : null, static::reconciliationSourceLabel($record) !== null ? $factory->keyFact('Reconciled by', (string) static::reconciliationSourceLabel($record)) : null, ])); if ($lifecycleItems !== []) { $groups[] = $factory->supportingFactsCard( kind: 'lifecycle', title: 'Lifecycle', items: $lifecycleItems, description: 'Lifecycle context explains freshness, reconciliation, and tenant-scoped caveats.', ); } $timingItems = [ $factory->keyFact('Created', static::formatDetailTimestamp($record->created_at)), $factory->keyFact('Started', static::formatDetailTimestamp($record->started_at)), $factory->keyFact('Completed', static::formatDetailTimestamp($record->completed_at)), $factory->keyFact('Elapsed', RunDurationInsights::elapsedHuman($record)), ]; $groups[] = $factory->supportingFactsCard( kind: 'timing', title: 'Timing', items: $timingItems, ); $metadataItems = array_values(array_filter([ $factory->keyFact('Initiator', $record->initiator_name), RunDurationInsights::expectedHuman($record) !== null ? $factory->keyFact('Expected duration', RunDurationInsights::expectedHuman($record)) : null, ])); if ($metadataItems !== []) { $groups[] = $factory->supportingFactsCard( kind: 'metadata', title: 'Metadata', items: $metadataItems, description: 'Secondary metadata remains visible without crowding the top decision surface.', ); } return $groups; } /** * @return array{ * text: string, * source: string, * secondaryGuidance: list * } */ private static function resolvePrimaryNextStep( OperationRun $record, ?ArtifactTruthEnvelope $artifactTruth, ?OperatorExplanationPattern $operatorExplanation, ): array { $candidates = []; static::pushNextStepCandidate($candidates, $operatorExplanation?->nextActionText, 'operator_explanation'); static::pushNextStepCandidate($candidates, $artifactTruth?->nextStepText(), 'artifact_truth'); $opsUxSource = match (true) { (string) $record->outcome === OperationRunOutcome::Blocked->value => 'blocked_reason', OperationUxPresenter::lifecycleAttentionSummary($record) !== null => 'lifecycle_attention', default => 'ops_ux', }; static::pushNextStepCandidate($candidates, OperationUxPresenter::surfaceGuidance($record), $opsUxSource); if ($candidates === []) { return [ 'text' => 'No action needed.', 'source' => 'none_required', 'secondaryGuidance' => [], ]; } $primary = $candidates[0]; $primarySource = static::normalizeGuidance($primary['text']) === 'no action needed' ? 'none_required' : $primary['source']; $secondaryGuidance = array_map( static fn (array $candidate): array => [ 'label' => static::guidanceLabel($candidate['source']), 'text' => $candidate['text'], 'source' => $candidate['source'], ], array_slice($candidates, 1), ); return [ 'text' => $primary['text'], 'source' => $primarySource, 'secondaryGuidance' => $secondaryGuidance, ]; } /** * @param array $candidates */ private static function pushNextStepCandidate(array &$candidates, ?string $text, string $source): void { $formattedText = static::formatGuidanceText($text); if ($formattedText === null) { return; } $normalized = static::normalizeGuidance($formattedText); foreach ($candidates as $candidate) { if (($candidate['normalized'] ?? null) === $normalized) { return; } } $candidates[] = [ 'text' => $formattedText, 'source' => $source, 'normalized' => $normalized, ]; } private static function formatGuidanceText(?string $text): ?string { if (! is_string($text)) { return null; } $text = trim($text); if ($text === '') { return null; } if (preg_match('/[.!?]$/', $text) === 1) { return $text; } return $text.'.'; } private static function normalizeGuidance(string $text): string { $normalized = mb_strtolower(trim($text)); $normalized = preg_replace('/^next step:\s*/', '', $normalized) ?? $normalized; return trim($normalized, " \t\n\r\0\x0B.!?"); } private static function guidanceLabel(string $source): string { return match ($source) { 'operator_explanation' => 'Operator guidance', 'artifact_truth' => 'Artifact guidance', 'blocked_reason' => 'Blocked prerequisite', 'lifecycle_attention' => 'Lifecycle guidance', default => 'General guidance', }; } /** * @return array|null */ private static function artifactTruthFact( \App\Support\Ui\EnterpriseDetail\EnterpriseDetailSectionFactory $factory, ?ArtifactTruthEnvelope $artifactTruth, ): ?array { if (! $artifactTruth instanceof ArtifactTruthEnvelope) { return null; } $badge = $artifactTruth->primaryBadgeSpec(); return $factory->keyFact( 'Artifact truth', $artifactTruth->primaryLabel, $artifactTruth->primaryExplanation, $factory->statusBadge($badge->label, $badge->color, $badge->icon, $badge->iconColor), ); } private static function decisionAttentionNote(OperationRun $record): ?string { return null; } private static function detailHintUnlessDuplicate(?string $hint, ?string $duplicateOf): ?string { $normalizedHint = static::normalizeDetailText($hint); if ($normalizedHint === null) { return null; } if ($normalizedHint === static::normalizeDetailText($duplicateOf)) { return null; } return trim($hint ?? ''); } private static function normalizeDetailText(?string $value): ?string { if (! is_string($value)) { return null; } $normalized = trim((string) preg_replace('/\s+/', ' ', $value)); if ($normalized === '') { return null; } return mb_strtolower($normalized); } /** * @return list> */ private static function summaryCountFacts( OperationRun $record, \App\Support\Ui\EnterpriseDetail\EnterpriseDetailSectionFactory $factory, ): array { $counts = \App\Support\OpsUx\SummaryCountsNormalizer::normalize(is_array($record->summary_counts) ? $record->summary_counts : []); return array_map( static fn (string $key, int $value): array => $factory->keyFact( SummaryCountsNormalizer::label($key), $value, tone: self::countTone($key, $value), ), array_keys($counts), array_values($counts), ); } private static function countTone(string $key, int $value): ?string { if (in_array($key, ['failed', 'errors_recorded', 'findings_reopened'], true)) { return $value > 0 ? 'danger' : 'success'; } if ($key === 'succeeded' && $value > 0) { return 'success'; } return null; } private static function blockedExecutionReasonCode(OperationRun $record): ?string { if ((string) $record->outcome !== OperationRunOutcome::Blocked->value) { return null; } $reasonEnvelope = app(ReasonPresenter::class)->forOperationRun($record, 'run_detail'); if ($reasonEnvelope !== null) { return $reasonEnvelope->operatorLabel; } $context = is_array($record->context) ? $record->context : []; $reasonCode = data_get($context, 'execution_legitimacy.reason_code') ?? data_get($context, 'reason_code') ?? data_get($record->failure_summary, '0.reason_code'); return is_string($reasonCode) && trim($reasonCode) !== '' ? trim($reasonCode) : null; } private static function blockedExecutionDetail(OperationRun $record): ?string { if ((string) $record->outcome !== OperationRunOutcome::Blocked->value) { return null; } $reasonEnvelope = app(ReasonPresenter::class)->forOperationRun($record, 'run_detail'); if ($reasonEnvelope !== null) { return $reasonEnvelope->shortExplanation; } $message = data_get($record->failure_summary, '0.message'); return is_string($message) && trim($message) !== '' ? trim($message) : 'Execution was refused before work began.'; } private static function blockedExecutionSource(OperationRun $record): ?string { if ((string) $record->outcome !== OperationRunOutcome::Blocked->value) { return null; } $context = is_array($record->context) ? $record->context : []; $blockedBy = $context['blocked_by'] ?? null; if (! is_string($blockedBy) || trim($blockedBy) === '') { return null; } return match (trim($blockedBy)) { 'queued_execution_legitimacy' => 'Execution legitimacy revalidation', default => ucfirst(str_replace('_', ' ', trim($blockedBy))), }; } /** * @return list> */ private static function baselineCompareFacts( OperationRun $record, \App\Support\Ui\EnterpriseDetail\EnterpriseDetailSectionFactory $factory, ): array { $context = is_array($record->context) ? $record->context : []; $gapDetails = BaselineCompareEvidenceGapDetails::fromOperationRun($record); $gapSummary = is_array($gapDetails['summary'] ?? null) ? $gapDetails['summary'] : []; $facts = []; $fidelity = data_get($context, 'baseline_compare.fidelity'); if (is_string($fidelity) && trim($fidelity) !== '') { $facts[] = $factory->keyFact('Fidelity', $fidelity); } $proof = data_get($context, 'baseline_compare.coverage.proof'); $uncoveredTypes = data_get($context, 'baseline_compare.coverage.uncovered_types'); $uncoveredTypes = is_array($uncoveredTypes) ? array_values(array_filter($uncoveredTypes, 'is_string')) : []; $facts[] = $factory->keyFact( 'Coverage', match (true) { $proof === false => 'Unproven', $uncoveredTypes !== [] => 'Warnings', $proof === true => 'Covered', default => 'Unknown', }, ); $reasonCode = data_get($context, 'baseline_compare.reason_code'); if (is_string($reasonCode) && trim($reasonCode) !== '') { $enum = BaselineCompareReasonCode::tryFrom(trim($reasonCode)); $facts[] = $factory->keyFact( 'Why no findings', $enum?->message() ?? trim($reasonCode), trim($reasonCode), ); } if ((int) ($gapSummary['count'] ?? 0) > 0) { $facts[] = $factory->keyFact( 'Evidence gap detail', match ($gapSummary['detail_state'] ?? 'no_gaps') { 'structured_details_recorded' => 'Structured subject details available', 'details_not_recorded' => 'Detailed rows were not recorded', 'legacy_broad_reason' => 'Legacy development payload should be regenerated', default => 'No evidence gaps recorded', }, ); } if ((int) ($gapSummary['structural_count'] ?? 0) > 0) { $facts[] = $factory->keyFact('Structural gaps', (string) (int) $gapSummary['structural_count']); } if ((int) ($gapSummary['operational_count'] ?? 0) > 0) { $facts[] = $factory->keyFact('Operational gaps', (string) (int) $gapSummary['operational_count']); } if ((int) ($gapSummary['transient_count'] ?? 0) > 0) { $facts[] = $factory->keyFact('Transient gaps', (string) (int) $gapSummary['transient_count']); } if ($uncoveredTypes !== []) { sort($uncoveredTypes, SORT_STRING); $facts[] = $factory->keyFact('Uncovered types', implode(', ', array_slice($uncoveredTypes, 0, 12)).(count($uncoveredTypes) > 12 ? '…' : '')); } $inventorySyncRunId = data_get($context, 'baseline_compare.inventory_sync_run_id'); if (is_numeric($inventorySyncRunId)) { $facts[] = $factory->keyFact('Inventory sync run', '#'.(int) $inventorySyncRunId); } return $facts; } /** * @return array */ private static function baselineCompareEvidencePayload(OperationRun $record): array { $context = is_array($record->context) ? $record->context : []; return array_filter([ 'subjects_total' => is_numeric(data_get($context, 'baseline_compare.subjects_total')) ? (int) data_get($context, 'baseline_compare.subjects_total') : null, 'evidence_gaps_count' => is_numeric(data_get($context, 'baseline_compare.evidence_gaps.count')) ? (int) data_get($context, 'baseline_compare.evidence_gaps.count') : null, 'resume_token' => data_get($context, 'baseline_compare.resume_token'), 'evidence_capture' => is_array(data_get($context, 'baseline_compare.evidence_capture')) ? data_get($context, 'baseline_compare.evidence_capture') : null, 'evidence_gaps' => is_array(data_get($context, 'baseline_compare.evidence_gaps')) ? data_get($context, 'baseline_compare.evidence_gaps') : null, ], static fn (mixed $value): bool => $value !== null && $value !== []); } /** * @return array */ private static function baselineCaptureEvidencePayload(OperationRun $record): array { $context = is_array($record->context) ? $record->context : []; return array_filter([ 'subjects_total' => is_numeric(data_get($context, 'baseline_capture.subjects_total')) ? (int) data_get($context, 'baseline_capture.subjects_total') : null, 'gaps_count' => is_numeric(data_get($context, 'baseline_capture.gaps.count')) ? (int) data_get($context, 'baseline_capture.gaps.count') : null, 'resume_token' => data_get($context, 'baseline_capture.resume_token'), 'evidence_capture' => is_array(data_get($context, 'baseline_capture.evidence_capture')) ? data_get($context, 'baseline_capture.evidence_capture') : null, 'gaps' => is_array(data_get($context, 'baseline_capture.gaps')) ? data_get($context, 'baseline_capture.gaps') : null, ], static fn (mixed $value): bool => $value !== null && $value !== []); } /** * @return array */ private static function verificationReportViewData(OperationRun $record): array { $report = VerificationReportViewer::report($record); $fingerprint = is_array($report) ? VerificationReportViewer::fingerprint($report) : null; $changeIndicator = VerificationReportChangeIndicator::forRun($record); $previousRunUrl = null; if ($changeIndicator !== null) { $tenant = app(OperateHubShell::class)->activeEntitledTenant(request()); $previousRunUrl = $tenant instanceof Tenant ? OperationRunLinks::view($changeIndicator['previous_report_id'], $tenant) : OperationRunLinks::tenantlessView($changeIndicator['previous_report_id']); } $acknowledgements = VerificationCheckAcknowledgement::query() ->where('tenant_id', (int) ($record->tenant_id ?? 0)) ->where('workspace_id', (int) ($record->workspace_id ?? 0)) ->where('operation_run_id', (int) $record->getKey()) ->with('acknowledgedByUser') ->get() ->mapWithKeys(static function (VerificationCheckAcknowledgement $ack): array { $user = $ack->acknowledgedByUser; return [ (string) $ack->check_key => [ 'check_key' => (string) $ack->check_key, 'ack_reason' => (string) $ack->ack_reason, 'acknowledged_at' => $ack->acknowledged_at?->toJSON(), 'expires_at' => $ack->expires_at?->toJSON(), 'acknowledged_by' => $user instanceof User ? [ 'id' => (int) $user->getKey(), 'name' => (string) $user->name, ] : null, ], ]; }) ->all(); return [ 'report' => $report, 'run' => [ 'id' => (int) $record->getKey(), 'type' => (string) $record->type, 'status' => (string) $record->status, 'outcome' => (string) $record->outcome, 'started_at' => $record->started_at?->toJSON(), 'completed_at' => $record->completed_at?->toJSON(), ], 'fingerprint' => $fingerprint, 'changeIndicator' => $changeIndicator, 'previousRunUrl' => $previousRunUrl, 'acknowledgements' => $acknowledgements, 'redactionNotes' => VerificationReportViewer::redactionNotes($report), ]; } /** * @return array */ private static function contextPayload(OperationRun $record): array { $context = is_array($record->context) ? $record->context : []; if (array_key_exists('verification_report', $context)) { $context['verification_report'] = [ 'redacted' => true, 'note' => 'Rendered in the Verification report section.', ]; } return $context; } /** * @return array{status:string,freshness_state:string} */ private static function statusBadgeState(OperationRun $record): array { return [ 'status' => (string) $record->status, 'freshness_state' => $record->freshnessState()->value, ]; } /** * @return array{outcome:string,status:string,freshness_state:string} */ private static function outcomeBadgeState(OperationRun $record): array { return [ 'outcome' => (string) $record->outcome, 'status' => (string) $record->status, 'freshness_state' => $record->freshnessState()->value, ]; } private static function freshnessLabel(OperationRun $record): ?string { return match ($record->freshnessState()->value) { 'fresh_active' => 'Fresh activity', 'likely_stale' => 'Likely stale', 'reconciled_failed' => 'Automatically reconciled', 'terminal_normal' => 'Terminal truth confirmed', default => null, }; } private static function reconciliationHeadline(OperationRun $record): ?string { if (! $record->isLifecycleReconciled()) { return null; } return 'TenantPilot force-resolved this run after normal lifecycle truth was lost.'; } private static function reconciledAtLabel(OperationRun $record): ?string { $reconciledAt = data_get($record->reconciliation(), 'reconciled_at'); return is_string($reconciledAt) && trim($reconciledAt) !== '' ? trim($reconciledAt) : null; } private static function reconciliationSourceLabel(OperationRun $record): ?string { $source = data_get($record->reconciliation(), 'source'); if (! is_string($source) || trim($source) === '') { return null; } return match (trim($source)) { 'failed_callback' => 'Direct failed() bridge', 'scheduled_reconciler' => 'Scheduled reconciler', 'adapter_reconciler' => 'Adapter reconciler', default => ucfirst(str_replace('_', ' ', trim($source))), }; } /** * @return array */ private static function reconciliationPayload(OperationRun $record): array { $reconciliation = $record->reconciliation(); return $reconciliation; } private static function formatDetailTimestamp(mixed $value): string { if (! $value instanceof \Illuminate\Support\Carbon) { return '—'; } return $value->toDayDateTimeString(); } public static function getPages(): array { return []; } private static function targetScopeDisplay(OperationRun $record): ?string { $context = is_array($record->context) ? $record->context : []; $targetScope = $context['target_scope'] ?? null; if (! is_array($targetScope)) { return null; } $entraTenantName = $targetScope['entra_tenant_name'] ?? null; $entraTenantId = $targetScope['entra_tenant_id'] ?? null; $directoryContextId = $targetScope['directory_context_id'] ?? null; $entraTenantName = is_string($entraTenantName) ? trim($entraTenantName) : null; $entraTenantId = is_string($entraTenantId) ? trim($entraTenantId) : null; $directoryContextId = match (true) { is_string($directoryContextId) => trim($directoryContextId), is_int($directoryContextId) => (string) $directoryContextId, default => null, }; $entra = null; if ($entraTenantName !== null && $entraTenantName !== '') { $entra = $entraTenantId ? "{$entraTenantName} ({$entraTenantId})" : $entraTenantName; } elseif ($entraTenantId !== null && $entraTenantId !== '') { $entra = $entraTenantId; } $parts = array_values(array_filter([ $entra, $directoryContextId ? "directory_context_id: {$directoryContextId}" : null, ], fn (?string $value): bool => $value !== null && $value !== '')); return $parts !== [] ? implode(' · ', $parts) : null; } }