$this->forBaselineSnapshot($record), $record instanceof EvidenceSnapshot => $this->forEvidenceSnapshot($record), $record instanceof TenantReview => $this->forTenantReview($record), $record instanceof ReviewPack => $this->forReviewPack($record), $record instanceof OperationRun => $this->forOperationRun($record), default => null, }; } public function forBaselineSnapshot(BaselineSnapshot $snapshot): ArtifactTruthEnvelope { $snapshot->loadMissing('baselineProfile'); $summary = is_array($snapshot->summary_jsonb) ? $snapshot->summary_jsonb : []; $hasItems = (int) ($summary['total_items'] ?? 0) > 0; $fidelity = FidelityState::fromSummary($summary, $hasItems); $effectiveSnapshot = $snapshot->baselineProfile !== null ? $this->snapshotTruthResolver->resolveEffectiveSnapshot($snapshot->baselineProfile) : null; $isHistorical = $this->snapshotTruthResolver->isHistoricallySuperseded($snapshot, $effectiveSnapshot); $gapReasons = is_array(Arr::get($summary, 'gaps.by_reason')) ? Arr::get($summary, 'gaps.by_reason') : []; $severeGapReasons = array_filter( $gapReasons, static fn (mixed $count, string $reason): bool => is_numeric($count) && (int) $count > 0 && $reason !== 'meta_fallback', ARRAY_FILTER_USE_BOTH, ); $reasonCode = $this->snapshotTruthResolver->artifactReasonCode($snapshot, $effectiveSnapshot) ?? $this->firstReasonCode($severeGapReasons); $reason = $this->reasonPresenter->forArtifactTruth($reasonCode, 'artifact_truth'); $artifactExistence = match (true) { $isHistorical => 'historical_only', $snapshot->isBuilding(), $snapshot->isIncomplete() => 'created_but_not_usable', ! $snapshot->isConsumable() => 'created_but_not_usable', default => 'created', }; $contentState = match ($fidelity) { FidelityState::Full => $snapshot->isIncomplete() ? ($hasItems ? 'partial' : 'missing_input') : ($severeGapReasons === [] ? 'trusted' : 'partial'), FidelityState::Partial => $snapshot->isBuilding() ? 'missing_input' : 'partial', FidelityState::ReferenceOnly => $snapshot->isBuilding() ? 'missing_input' : 'reference_only', FidelityState::Unsupported => $snapshot->isBuilding() ? 'missing_input' : ($hasItems ? 'unsupported' : 'trusted'), }; if (($snapshot->isBuilding() || $snapshot->isIncomplete()) && $reasonCode !== null) { $contentState = 'missing_input'; } $freshnessState = match (true) { $snapshot->isBuilding() => 'unknown', $isHistorical => 'stale', default => 'current', }; $supportState = in_array($contentState, ['reference_only', 'unsupported'], true) ? 'limited_support' : 'normal'; $actionability = match (true) { $artifactExistence === 'historical_only' => 'none', $snapshot->isBuilding() => 'optional', $contentState === 'trusted' && $freshnessState === 'current' => 'none', $freshnessState === 'stale' => 'optional', in_array($contentState, ['reference_only', 'unsupported'], true) => 'optional', default => 'required', }; [$primaryDomain, $primaryState, $primaryExplanation, $diagnosticLabel] = match (true) { $artifactExistence === 'historical_only' => [ BadgeDomain::GovernanceArtifactExistence, 'historical_only', $reason?->shortExplanation ?? 'This snapshot remains readable for history, but a newer complete snapshot is the current baseline truth.', BadgeCatalog::spec(BadgeDomain::BaselineSnapshotLifecycle, 'superseded')->label, ], $artifactExistence === 'created_but_not_usable' => [ BadgeDomain::GovernanceArtifactExistence, 'created_but_not_usable', $reason?->shortExplanation ?? 'A snapshot row exists, but it does not contain a trustworthy baseline artifact yet.', BadgeCatalog::spec(BadgeDomain::BaselineSnapshotLifecycle, $snapshot->lifecycleState()->value)->label, ], $contentState !== 'trusted' => [ BadgeDomain::GovernanceArtifactContent, $contentState, $reason?->shortExplanation ?? $this->contentExplanation($contentState), $supportState === 'limited_support' ? 'Support limited' : BadgeCatalog::spec(BadgeDomain::BaselineSnapshotLifecycle, $snapshot->lifecycleState()->value)->label, ], default => [ BadgeDomain::GovernanceArtifactContent, 'trusted', $hasItems ? 'Structured capture content is available for this baseline snapshot.' : 'This empty baseline snapshot completed successfully and can still be used for compare.', BadgeCatalog::spec(BadgeDomain::BaselineSnapshotLifecycle, $snapshot->lifecycleState()->value)->label, ], }; return $this->makeEnvelope( artifactFamily: 'baseline_snapshot', artifactKey: 'baseline_snapshot:'.$snapshot->getKey(), workspaceId: (int) $snapshot->workspace_id, tenantId: null, executionOutcome: null, artifactExistence: $artifactExistence, contentState: $contentState, freshnessState: $freshnessState, publicationReadiness: null, supportState: $supportState, actionability: $actionability, primaryDomain: $primaryDomain, primaryState: $primaryState, primaryExplanation: $primaryExplanation, diagnosticLabel: $diagnosticLabel, reason: ArtifactTruthCause::fromReasonResolutionEnvelope($reason, ReasonPresenter::GOVERNANCE_ARTIFACT_TRUTH_ARTIFACT), nextActionLabel: $this->nextActionLabel( $actionability, $reason, match ($actionability) { 'required' => 'Inspect the related capture diagnostics before using this snapshot', 'optional' => 'Review the capture diagnostics before comparing this snapshot', default => null, }, ), nextActionUrl: null, relatedRunId: null, relatedArtifactUrl: BaselineSnapshotResource::getUrl('view', ['record' => $snapshot], panel: 'admin'), includePublicationDimension: false, ); } public function forEvidenceSnapshot(EvidenceSnapshot $snapshot): ArtifactTruthEnvelope { $snapshot->loadMissing('tenant'); $summary = is_array($snapshot->summary) ? $snapshot->summary : []; $missingDimensions = (int) ($summary['missing_dimensions'] ?? 0); $staleDimensions = (int) ($summary['stale_dimensions'] ?? 0); $status = (string) $snapshot->status; $artifactExistence = match ($status) { 'queued', 'generating' => 'not_created', 'expired', 'superseded' => 'historical_only', 'failed' => 'created_but_not_usable', default => 'created', }; $contentState = match (true) { $artifactExistence === 'not_created' => 'missing_input', $artifactExistence === 'historical_only' && $snapshot->completeness_state === 'missing' => 'empty', $status === 'failed' => 'missing_input', $snapshot->completeness_state === 'missing' => 'missing_input', $snapshot->completeness_state === 'partial' => 'partial', default => 'trusted', }; if ((int) ($summary['dimension_count'] ?? 0) === 0 && $artifactExistence !== 'not_created') { $contentState = 'empty'; } $freshnessState = match (true) { $artifactExistence === 'historical_only' => 'stale', $snapshot->completeness_state === 'stale' || $staleDimensions > 0 => 'stale', in_array($status, ['queued', 'generating'], true) => 'unknown', default => 'current', }; $actionability = match (true) { $artifactExistence === 'historical_only' => 'none', in_array($status, ['queued', 'generating'], true) => 'optional', $contentState === 'trusted' && $freshnessState === 'current' => 'none', $freshnessState === 'stale' => 'optional', default => 'required', }; $reasonCode = match (true) { $status === 'failed' => 'evidence_generation_failed', $missingDimensions > 0 => 'evidence_missing_dimensions', $staleDimensions > 0 => 'evidence_stale_dimensions', default => null, }; $reason = $this->reasonPresenter->forArtifactTruth($reasonCode, 'artifact_truth'); [$primaryDomain, $primaryState, $primaryExplanation] = match (true) { $artifactExistence === 'not_created' => [ BadgeDomain::GovernanceArtifactExistence, 'not_created', 'The evidence generation request exists, but no tenant snapshot is available yet.', ], $artifactExistence === 'historical_only' => [ BadgeDomain::GovernanceArtifactExistence, 'historical_only', 'This evidence snapshot remains available for history, but it is not the current working evidence artifact.', ], $contentState !== 'trusted' => [ BadgeDomain::GovernanceArtifactContent, $contentState, $reason?->shortExplanation ?? $this->contentExplanation($contentState), ], $freshnessState === 'stale' => [ BadgeDomain::GovernanceArtifactFreshness, 'stale', $reason?->shortExplanation ?? 'The snapshot exists, but one or more evidence dimensions should be refreshed before relying on it.', ], default => [ BadgeDomain::GovernanceArtifactContent, 'trusted', 'A current evidence snapshot is available for review work.', ], }; $nextActionUrl = $snapshot->operation_run_id ? OperationRunLinks::tenantlessView((int) $snapshot->operation_run_id) : null; return $this->makeEnvelope( artifactFamily: 'evidence_snapshot', artifactKey: 'evidence_snapshot:'.$snapshot->getKey(), workspaceId: (int) $snapshot->workspace_id, tenantId: $snapshot->tenant_id !== null ? (int) $snapshot->tenant_id : null, executionOutcome: null, artifactExistence: $artifactExistence, contentState: $contentState, freshnessState: $freshnessState, publicationReadiness: null, supportState: 'normal', actionability: $actionability, primaryDomain: $primaryDomain, primaryState: $primaryState, primaryExplanation: $primaryExplanation, diagnosticLabel: $missingDimensions > 0 && $staleDimensions > 0 ? sprintf('%d missing, %d stale dimensions', $missingDimensions, $staleDimensions) : null, reason: ArtifactTruthCause::fromReasonResolutionEnvelope($reason, ReasonPresenter::GOVERNANCE_ARTIFACT_TRUTH_ARTIFACT), nextActionLabel: $this->nextActionLabel( $actionability, $reason, match ($actionability) { 'required' => 'Refresh evidence before using this snapshot', 'optional' => in_array($status, ['queued', 'generating'], true) ? 'Wait for evidence generation to finish' : 'Review the evidence freshness before relying on this snapshot', default => null, }, ), nextActionUrl: $nextActionUrl, relatedRunId: $snapshot->operation_run_id !== null ? (int) $snapshot->operation_run_id : null, relatedArtifactUrl: $snapshot->tenant !== null ? EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $snapshot->tenant) : null, includePublicationDimension: false, ); } public function forTenantReview(TenantReview $review): ArtifactTruthEnvelope { $review->loadMissing(['tenant', 'currentExportReviewPack']); $summary = is_array($review->summary) ? $review->summary : []; $publishBlockers = $review->publishBlockers(); $status = $review->statusEnum(); $completeness = $review->completenessEnum()->value; $sectionCounts = is_array($summary['section_state_counts'] ?? null) ? $summary['section_state_counts'] : []; $staleSections = (int) ($sectionCounts['stale'] ?? 0); $artifactExistence = match ($status) { TenantReviewStatus::Archived, TenantReviewStatus::Superseded => 'historical_only', TenantReviewStatus::Failed => 'created_but_not_usable', default => 'created', }; $contentState = match ($completeness) { TenantReviewCompletenessState::Complete->value => 'trusted', TenantReviewCompletenessState::Partial->value => 'partial', TenantReviewCompletenessState::Missing->value => 'missing_input', TenantReviewCompletenessState::Stale->value => 'trusted', default => 'partial', }; $freshnessState = match (true) { $artifactExistence === 'historical_only' => 'stale', $completeness === TenantReviewCompletenessState::Stale->value || $staleSections > 0 => 'stale', default => 'current', }; $publicationReadiness = match (true) { $artifactExistence === 'historical_only' => 'internal_only', $status === TenantReviewStatus::Published => 'publishable', $publishBlockers !== [] => 'blocked', $status === TenantReviewStatus::Ready => 'publishable', default => 'internal_only', }; $actionability = match (true) { $artifactExistence === 'historical_only' => 'none', $publicationReadiness === 'publishable' && $freshnessState === 'current' => 'none', $publicationReadiness === 'internal_only' && $contentState === 'trusted' => 'optional', $freshnessState === 'stale' && $publishBlockers === [] => 'optional', default => 'required', }; $reasonCode = match (true) { $publishBlockers !== [] => 'review_publish_blocked', $status === TenantReviewStatus::Failed => 'review_generation_failed', $completeness === TenantReviewCompletenessState::Missing->value => 'review_missing_sections', $completeness === TenantReviewCompletenessState::Stale->value => 'review_stale_sections', default => null, }; $reason = $this->reasonPresenter->forArtifactTruth($reasonCode, 'artifact_truth'); [$primaryDomain, $primaryState, $primaryExplanation] = match (true) { $artifactExistence === 'historical_only' => [ BadgeDomain::GovernanceArtifactExistence, 'historical_only', 'This review remains available as historical evidence, but it is no longer the current review artifact.', ], $publicationReadiness === 'blocked' => [ BadgeDomain::GovernanceArtifactPublicationReadiness, 'blocked', $publishBlockers[0] ?? $reason?->shortExplanation ?? 'This review exists, but it is blocked from publication or export.', ], $publicationReadiness === 'internal_only' => [ BadgeDomain::GovernanceArtifactPublicationReadiness, 'internal_only', 'This review exists and is useful internally, but it is not yet ready for stakeholder publication.', ], $freshnessState === 'stale' => [ BadgeDomain::GovernanceArtifactFreshness, 'stale', $reason?->shortExplanation ?? 'The review exists, but one or more required sections should be refreshed before publication.', ], default => [ BadgeDomain::GovernanceArtifactPublicationReadiness, 'publishable', 'This review is ready for publication and executive-pack export.', ], }; $nextActionUrl = $review->operation_run_id ? OperationRunLinks::tenantlessView((int) $review->operation_run_id) : null; if ($publishBlockers !== [] && $review->tenant !== null) { $nextActionUrl = TenantReviewResource::tenantScopedUrl('view', ['record' => $review], $review->tenant); } return $this->makeEnvelope( artifactFamily: 'tenant_review', artifactKey: 'tenant_review:'.$review->getKey(), workspaceId: (int) $review->workspace_id, tenantId: $review->tenant_id !== null ? (int) $review->tenant_id : null, executionOutcome: null, artifactExistence: $artifactExistence, contentState: $contentState, freshnessState: $freshnessState, publicationReadiness: $publicationReadiness, supportState: 'normal', actionability: $actionability, primaryDomain: $primaryDomain, primaryState: $primaryState, primaryExplanation: $primaryExplanation, diagnosticLabel: $contentState !== 'trusted' ? BadgeCatalog::spec(BadgeDomain::GovernanceArtifactContent, $contentState)->label : null, reason: ArtifactTruthCause::fromReasonResolutionEnvelope($reason, ReasonPresenter::GOVERNANCE_ARTIFACT_TRUTH_ARTIFACT), nextActionLabel: $this->nextActionLabel( $actionability, $reason, match ($actionability) { 'required' => 'Resolve the review blockers before publication', 'optional' => 'Complete the remaining review work before publication', default => null, }, ), nextActionUrl: $nextActionUrl, relatedRunId: $review->operation_run_id !== null ? (int) $review->operation_run_id : null, relatedArtifactUrl: $review->tenant !== null ? TenantReviewResource::tenantScopedUrl('view', ['record' => $review], $review->tenant) : null, includePublicationDimension: true, ); } public function forReviewPack(ReviewPack $pack): ArtifactTruthEnvelope { $pack->loadMissing(['tenant', 'tenantReview']); $summary = is_array($pack->summary) ? $pack->summary : []; $status = (string) $pack->status; $evidenceResolution = is_array($summary['evidence_resolution'] ?? null) ? $summary['evidence_resolution'] : []; $sourceReview = $pack->tenantReview; $sourceBlockers = $sourceReview instanceof TenantReview ? $sourceReview->publishBlockers() : []; $sourceReviewStatus = $sourceReview instanceof TenantReview ? $sourceReview->statusEnum() : null; $artifactExistence = match ($status) { ReviewPackStatus::Queued->value, ReviewPackStatus::Generating->value => 'not_created', ReviewPackStatus::Expired->value => 'historical_only', ReviewPackStatus::Failed->value => 'created_but_not_usable', default => 'created', }; $contentState = match (true) { $artifactExistence === 'not_created' => 'missing_input', $status === ReviewPackStatus::Failed->value => 'missing_input', ($evidenceResolution['outcome'] ?? null) !== 'resolved' => 'missing_input', $sourceReview instanceof TenantReview && $sourceBlockers !== [] => 'partial', default => 'trusted', }; $freshnessState = $artifactExistence === 'historical_only' ? 'stale' : 'current'; $publicationReadiness = match (true) { $artifactExistence === 'historical_only' => 'internal_only', $artifactExistence === 'not_created' => 'blocked', $status === ReviewPackStatus::Failed->value => 'blocked', $sourceReview instanceof TenantReview && $sourceBlockers !== [] => 'blocked', $sourceReviewStatus === TenantReviewStatus::Draft || $sourceReviewStatus === TenantReviewStatus::Failed => 'internal_only', default => $status === ReviewPackStatus::Ready->value ? 'publishable' : 'blocked', }; $actionability = match (true) { $artifactExistence === 'historical_only' => 'none', $publicationReadiness === 'publishable' => 'none', $publicationReadiness === 'internal_only' => 'optional', default => 'required', }; $reasonCode = match (true) { $status === ReviewPackStatus::Failed->value => 'review_pack_generation_failed', ($evidenceResolution['outcome'] ?? null) !== 'resolved' => 'review_pack_missing_snapshot', $sourceReview instanceof TenantReview && $sourceBlockers !== [] => 'review_pack_source_not_publishable', $artifactExistence === 'historical_only' => 'review_pack_expired', default => null, }; $reason = $this->reasonPresenter->forArtifactTruth($reasonCode, 'artifact_truth'); [$primaryDomain, $primaryState, $primaryExplanation] = match (true) { $artifactExistence === 'historical_only' => [ BadgeDomain::GovernanceArtifactExistence, 'historical_only', 'This pack remains available as a historical export, but it is no longer the current stakeholder artifact.', ], $publicationReadiness === 'blocked' => [ BadgeDomain::GovernanceArtifactPublicationReadiness, 'blocked', $sourceBlockers[0] ?? $reason?->shortExplanation ?? 'A pack file is not yet available for trustworthy stakeholder delivery.', ], $publicationReadiness === 'internal_only' => [ BadgeDomain::GovernanceArtifactPublicationReadiness, 'internal_only', 'This pack can be reviewed internally, but the source review is not currently publishable.', ], default => [ BadgeDomain::GovernanceArtifactPublicationReadiness, 'publishable', 'This executive pack is ready for stakeholder delivery.', ], }; $nextActionUrl = null; if ($sourceReview instanceof TenantReview && $pack->tenant !== null) { $nextActionUrl = TenantReviewResource::tenantScopedUrl('view', ['record' => $sourceReview], $pack->tenant); } elseif ($pack->operation_run_id !== null) { $nextActionUrl = OperationRunLinks::tenantlessView((int) $pack->operation_run_id); } return $this->makeEnvelope( artifactFamily: 'review_pack', artifactKey: 'review_pack:'.$pack->getKey(), workspaceId: (int) $pack->workspace_id, tenantId: $pack->tenant_id !== null ? (int) $pack->tenant_id : null, executionOutcome: null, artifactExistence: $artifactExistence, contentState: $contentState, freshnessState: $freshnessState, publicationReadiness: $publicationReadiness, supportState: 'normal', actionability: $actionability, primaryDomain: $primaryDomain, primaryState: $primaryState, primaryExplanation: $primaryExplanation, diagnosticLabel: $contentState !== 'trusted' ? BadgeCatalog::spec(BadgeDomain::GovernanceArtifactContent, $contentState)->label : null, reason: ArtifactTruthCause::fromReasonResolutionEnvelope($reason, ReasonPresenter::GOVERNANCE_ARTIFACT_TRUTH_ARTIFACT), nextActionLabel: $this->nextActionLabel( $actionability, $reason, match ($actionability) { 'required' => 'Open the source review before sharing this pack', 'optional' => 'Review the source review before sharing this pack', default => null, }, ), nextActionUrl: $nextActionUrl, relatedRunId: $pack->operation_run_id !== null ? (int) $pack->operation_run_id : null, relatedArtifactUrl: $pack->tenant !== null ? ReviewPackResource::getUrl('view', ['record' => $pack], tenant: $pack->tenant) : null, includePublicationDimension: true, ); } public function forOperationRun(OperationRun $run): ArtifactTruthEnvelope { $artifact = $this->resolveArtifactForRun($run); $reason = $this->reasonPresenter->forOperationRun($run, 'run_detail'); if ($artifact !== null) { $artifactEnvelope = $this->for($artifact); if ($artifactEnvelope instanceof ArtifactTruthEnvelope) { $diagnosticParts = array_values(array_filter([ BadgeCatalog::spec(BadgeDomain::OperationRunOutcome, $run->outcome)->label !== 'Unknown' ? BadgeCatalog::spec(BadgeDomain::OperationRunOutcome, $run->outcome)->label : null, $artifactEnvelope->diagnosticLabel, ])); return $this->makeEnvelope( artifactFamily: 'artifact_run', artifactKey: 'artifact_run:'.$run->getKey(), workspaceId: (int) $run->workspace_id, tenantId: $run->tenant_id !== null ? (int) $run->tenant_id : null, executionOutcome: $run->outcome !== null ? (string) $run->outcome : null, artifactExistence: $artifactEnvelope->artifactExistence, contentState: $artifactEnvelope->contentState, freshnessState: $artifactEnvelope->freshnessState, publicationReadiness: $artifactEnvelope->publicationReadiness, supportState: $artifactEnvelope->supportState, actionability: $artifactEnvelope->actionability, primaryDomain: $artifactEnvelope->primaryDimension()?->badgeDomain ?? BadgeDomain::GovernanceArtifactExistence, primaryState: $artifactEnvelope->primaryDimension()?->badgeState ?? $artifactEnvelope->artifactExistence, primaryExplanation: $artifactEnvelope->primaryExplanation ?? $reason?->shortExplanation ?? 'The run finished, but the related artifact needs review.', diagnosticLabel: $diagnosticParts === [] ? null : implode(' ยท ', $diagnosticParts), reason: $artifactEnvelope->reason, nextActionLabel: $artifactEnvelope->nextActionLabel, nextActionUrl: $artifactEnvelope->relatedArtifactUrl ?? $artifactEnvelope->nextActionUrl, relatedRunId: (int) $run->getKey(), relatedArtifactUrl: $artifactEnvelope->relatedArtifactUrl, includePublicationDimension: $artifactEnvelope->publicationReadiness !== null, ); } } $artifactExistence = match ((string) $run->status) { OperationRunStatus::Queued->value, OperationRunStatus::Running->value => 'not_created', default => 'not_created', }; $contentState = in_array((string) $run->outcome, [OperationRunOutcome::Blocked->value, OperationRunOutcome::Failed->value], true) ? 'missing_input' : 'empty'; $actionability = in_array((string) $run->outcome, [OperationRunOutcome::Blocked->value, OperationRunOutcome::Failed->value], true) ? 'required' : 'optional'; $primaryState = in_array((string) $run->outcome, [OperationRunOutcome::Blocked->value, OperationRunOutcome::Failed->value], true) ? 'created_but_not_usable' : 'not_created'; return $this->makeEnvelope( artifactFamily: 'artifact_run', artifactKey: 'artifact_run:'.$run->getKey(), workspaceId: (int) $run->workspace_id, tenantId: $run->tenant_id !== null ? (int) $run->tenant_id : null, executionOutcome: $run->outcome !== null ? (string) $run->outcome : null, artifactExistence: $artifactExistence, contentState: $contentState, freshnessState: 'unknown', publicationReadiness: OperationCatalog::governanceArtifactFamily((string) $run->type) === 'review_pack' || OperationCatalog::governanceArtifactFamily((string) $run->type) === 'tenant_review' ? 'blocked' : null, supportState: 'normal', actionability: $actionability, primaryDomain: BadgeDomain::GovernanceArtifactExistence, primaryState: $primaryState, primaryExplanation: $reason?->shortExplanation ?? match ((string) $run->status) { OperationRunStatus::Queued->value, OperationRunStatus::Running->value => 'The artifact-producing run is still in progress, so no artifact is available yet.', default => 'The run finished without a usable artifact result.', }, diagnosticLabel: BadgeCatalog::spec(BadgeDomain::OperationRunOutcome, $run->outcome)->label, reason: ArtifactTruthCause::fromReasonResolutionEnvelope($reason, ReasonPresenter::GOVERNANCE_ARTIFACT_TRUTH_ARTIFACT), nextActionLabel: $this->nextActionLabel( $actionability, $reason, $actionability === 'required' ? 'Inspect the blocked run details before retrying' : 'Wait for the artifact-producing run to finish', ), nextActionUrl: null, relatedRunId: (int) $run->getKey(), relatedArtifactUrl: null, includePublicationDimension: OperationCatalog::governanceArtifactFamily((string) $run->type) === 'review_pack' || OperationCatalog::governanceArtifactFamily((string) $run->type) === 'tenant_review', ); } private function resolveArtifactForRun(OperationRun $run): BaselineSnapshot|EvidenceSnapshot|TenantReview|ReviewPack|null { return match (OperationCatalog::governanceArtifactFamily((string) $run->type)) { 'baseline_snapshot' => $run->relatedArtifactId() !== null ? BaselineSnapshot::query()->with('baselineProfile')->find($run->relatedArtifactId()) : null, 'evidence_snapshot' => EvidenceSnapshot::query()->with('tenant')->where('operation_run_id', (int) $run->getKey())->latest('id')->first(), 'tenant_review' => TenantReview::query()->with(['tenant', 'currentExportReviewPack'])->where('operation_run_id', (int) $run->getKey())->latest('id')->first(), 'review_pack' => ReviewPack::query()->with(['tenant', 'tenantReview'])->where('operation_run_id', (int) $run->getKey())->latest('id')->first(), default => null, }; } private function contentExplanation(string $contentState): string { return match ($contentState) { 'partial' => 'The artifact exists, but the captured content is incomplete for the primary operator task.', 'missing_input' => 'The artifact is blocked by missing upstream inputs or failed capture prerequisites.', 'metadata_only' => 'Only metadata was captured for this artifact. Use diagnostics for context, not as the primary truth signal.', 'reference_only' => 'Only reference-level placeholders were captured for this artifact.', 'empty' => 'The artifact row exists, but it does not contain usable captured content.', 'unsupported' => 'Structured support is limited for this artifact family, so the current rendering should be treated as diagnostic only.', default => 'The artifact content is available for the intended workflow.', }; } /** * @param array $reasons */ private function firstReasonCode(array $reasons): ?string { foreach ($reasons as $reason => $count) { if ((int) $count > 0 && is_string($reason) && trim($reason) !== '') { return trim($reason); } } return null; } private function nextActionLabel( string $actionability, ?ReasonResolutionEnvelope $reason, ?string $fallback = null, ): ?string { if ($actionability === 'none') { return 'No action needed'; } if (is_string($fallback) && trim($fallback) !== '') { return $fallback; } if ($reason instanceof ReasonResolutionEnvelope && $reason->firstNextStep() !== null) { return $reason->firstNextStep()?->label; } return null; } private function makeEnvelope( string $artifactFamily, string $artifactKey, int $workspaceId, ?int $tenantId, ?string $executionOutcome, string $artifactExistence, string $contentState, string $freshnessState, ?string $publicationReadiness, string $supportState, string $actionability, BadgeDomain $primaryDomain, string $primaryState, ?string $primaryExplanation, ?string $diagnosticLabel, ?ArtifactTruthCause $reason, ?string $nextActionLabel, ?string $nextActionUrl, ?int $relatedRunId, ?string $relatedArtifactUrl, bool $includePublicationDimension, ): ArtifactTruthEnvelope { $primarySpec = BadgeCatalog::spec($primaryDomain, $primaryState); $dimensions = [ $this->dimension(BadgeDomain::GovernanceArtifactExistence, $artifactExistence, 'artifact_existence', $primaryDomain === BadgeDomain::GovernanceArtifactExistence ? 'primary' : 'diagnostic'), $this->dimension(BadgeDomain::GovernanceArtifactContent, $contentState, 'content_fidelity', $primaryDomain === BadgeDomain::GovernanceArtifactContent ? 'primary' : 'diagnostic'), $this->dimension(BadgeDomain::GovernanceArtifactFreshness, $freshnessState, 'data_freshness', $primaryDomain === BadgeDomain::GovernanceArtifactFreshness ? 'primary' : 'diagnostic'), $this->dimension(BadgeDomain::GovernanceArtifactActionability, $actionability, 'operator_actionability', 'diagnostic'), ]; if ($includePublicationDimension && $publicationReadiness !== null) { $dimensions[] = $this->dimension( BadgeDomain::GovernanceArtifactPublicationReadiness, $publicationReadiness, 'publication_readiness', $primaryDomain === BadgeDomain::GovernanceArtifactPublicationReadiness ? 'primary' : 'diagnostic', ); } if ($executionOutcome !== null && trim($executionOutcome) !== '') { $dimensions[] = $this->dimension(BadgeDomain::OperationRunOutcome, $executionOutcome, 'execution_outcome', 'diagnostic'); } if ($supportState === 'limited_support') { $dimensions[] = new ArtifactTruthDimension( axis: 'support_maturity', state: 'limited_support', label: 'Support limited', classification: 'diagnostic', badgeDomain: BadgeDomain::GovernanceArtifactContent, badgeState: 'unsupported', ); } return new ArtifactTruthEnvelope( artifactFamily: $artifactFamily, artifactKey: $artifactKey, workspaceId: $workspaceId, tenantId: $tenantId, executionOutcome: $executionOutcome, artifactExistence: $artifactExistence, contentState: $contentState, freshnessState: $freshnessState, publicationReadiness: $publicationReadiness, supportState: $supportState, actionability: $actionability, primaryLabel: $primarySpec->label, primaryExplanation: $primaryExplanation, diagnosticLabel: $diagnosticLabel, nextActionLabel: $nextActionLabel, nextActionUrl: $nextActionUrl, relatedRunId: $relatedRunId, relatedArtifactUrl: $relatedArtifactUrl, dimensions: array_values($dimensions), reason: $reason, ); } private function dimension( BadgeDomain $domain, string $state, string $axis, string $classification, ): ArtifactTruthDimension { return new ArtifactTruthDimension( axis: $axis, state: $state, label: BadgeCatalog::spec($domain, $state)->label, classification: $classification, badgeDomain: $domain, badgeState: $state, ); } }