noValid( state: EvidenceAnchorResult::STATE_UNKNOWN, primaryReason: 'Select an environment to open current evidence.', blockingReasons: ['Workspace-wide evidence cannot be represented by one arbitrary environment snapshot.'], displayLabel: 'Current evidence', ); } if ((int) $environment->workspace_id !== (int) $workspace->getKey()) { return $this->noValid( state: EvidenceAnchorResult::STATE_BLOCKED, primaryReason: 'The environment does not belong to the selected workspace.', blockingReasons: ['Evidence scope mismatch.'], displayLabel: 'Current evidence', ); } if ($actor instanceof User && ! $actor->canAccessTenant($environment)) { return $this->noValid( state: EvidenceAnchorResult::STATE_BLOCKED, primaryReason: 'You do not have access to this environment.', blockingReasons: ['Environment access is required before evidence can be linked.'], displayLabel: 'Current evidence', ); } $snapshot = $this->currentSnapshotForEnvironment($environment); if (! $snapshot instanceof EvidenceSnapshot) { $hasAnySnapshot = EvidenceSnapshot::query() ->where('workspace_id', (int) $workspace->getKey()) ->where('managed_environment_id', (int) $environment->getKey()) ->exists(); return $this->noValid( state: $hasAnySnapshot ? EvidenceAnchorResult::STATE_NEEDS_ATTENTION : EvidenceAnchorResult::STATE_NOT_CONFIGURED, primaryReason: $hasAnySnapshot ? 'No complete active evidence snapshot is available for this environment.' : 'No evidence snapshot is configured for this environment.', blockingReasons: $hasAnySnapshot ? ['Only active, complete, non-expired evidence can be used as current evidence.'] : ['Generate evidence before opening a current evidence detail.'], displayLabel: 'Current evidence', ); } return $this->fromSnapshot( snapshot: $snapshot, anchorType: EvidenceAnchorResult::TYPE_CURRENT_SCOPE_EVIDENCE, actor: $actor, displayLabel: 'Current evidence', primaryReason: 'Current complete evidence is available for this environment.', isCurrent: true, ); } public function currentSnapshotForEnvironment(ManagedEnvironment $environment): ?EvidenceSnapshot { $workspace = $environment->workspace; if (! $workspace instanceof Workspace) { return null; } return $this->currentSnapshotQuery($workspace, $environment) ->with([ 'tenant', 'operationRun', 'reviewPacks.operationRun', 'reviewPacks.environmentReview.currentExportReviewPack', 'items', ]) ->first(); } public function currentSnapshotQuery(Workspace $workspace, ManagedEnvironment $environment): Builder { return EvidenceSnapshot::query() ->where('workspace_id', (int) $workspace->getKey()) ->where('managed_environment_id', (int) $environment->getKey()) ->where('status', EvidenceSnapshotStatus::Active->value) ->where('completeness_state', EvidenceCompletenessState::Complete->value) ->where(function (Builder $query): void { $query ->whereNull('expires_at') ->orWhere('expires_at', '>', now()); }) ->orderByRaw('generated_at IS NULL') ->orderByDesc('generated_at') ->orderByDesc('id'); } public function forReviewPackDraft(ReviewPack $reviewPack, ?User $actor = null): EvidenceAnchorResult { $reviewPack->loadMissing(['tenant', 'evidenceSnapshot']); if (! $reviewPack->evidenceSnapshot instanceof EvidenceSnapshot) { return $this->noValid( state: EvidenceAnchorResult::STATE_NOT_CONFIGURED, primaryReason: 'This draft review pack has no bound evidence snapshot.', blockingReasons: ['Draft review evidence is missing.'], displayLabel: 'Draft review evidence', ); } return $this->fromSnapshot( snapshot: $reviewPack->evidenceSnapshot, anchorType: EvidenceAnchorResult::TYPE_REVIEW_DRAFT_EVIDENCE, actor: $actor, displayLabel: 'Draft review evidence', primaryReason: 'Draft review evidence is bound to this pack.', isTechnicalOnly: true, ); } public function forReviewPackRelease(ReviewPack $reviewPack, ?User $actor = null): EvidenceAnchorResult { $reviewPack->loadMissing(['tenant', 'evidenceSnapshot', 'environmentReview.evidenceSnapshot']); $snapshot = $reviewPack->evidenceSnapshot ?? $reviewPack->environmentReview?->evidenceSnapshot; if (! $snapshot instanceof EvidenceSnapshot) { return $this->noValid( state: EvidenceAnchorResult::STATE_NOT_CONFIGURED, primaryReason: 'This review pack has no release-bound evidence snapshot.', blockingReasons: ['Release-bound evidence is missing.'], displayLabel: 'Review evidence', ); } if (! $this->snapshotMatchesArtifactScope($snapshot, $reviewPack)) { return $this->noValid( state: EvidenceAnchorResult::STATE_BLOCKED, primaryReason: 'The bound evidence snapshot does not match the review pack scope.', blockingReasons: ['Release-bound evidence scope mismatch.'], displayLabel: 'Review evidence', ); } return $this->fromSnapshot( snapshot: $snapshot, anchorType: EvidenceAnchorResult::TYPE_REVIEW_RELEASED_EVIDENCE, actor: $actor, displayLabel: 'Review evidence', primaryReason: 'Evidence captured for this released review is available.', isReleaseBound: true, ); } public function forEnvironmentReviewRelease(EnvironmentReview $review, ?User $actor = null): EvidenceAnchorResult { $review->loadMissing(['tenant', 'evidenceSnapshot']); if (! $review->evidenceSnapshot instanceof EvidenceSnapshot) { return $this->noValid( state: EvidenceAnchorResult::STATE_NOT_CONFIGURED, primaryReason: 'This review has no bound evidence snapshot.', blockingReasons: ['Review evidence is missing.'], displayLabel: 'Review evidence', ); } if (! $this->snapshotMatchesArtifactScope($review->evidenceSnapshot, $review)) { return $this->noValid( state: EvidenceAnchorResult::STATE_BLOCKED, primaryReason: 'The bound evidence snapshot does not match the review scope.', blockingReasons: ['Review evidence scope mismatch.'], displayLabel: 'Review evidence', ); } return $this->fromSnapshot( snapshot: $review->evidenceSnapshot, anchorType: EvidenceAnchorResult::TYPE_REVIEW_RELEASED_EVIDENCE, actor: $actor, displayLabel: 'Review evidence', primaryReason: 'Evidence captured for this review is available.', isReleaseBound: true, ); } public function forCustomerWorkspace(EnvironmentReview|ReviewPack $reviewPackOrReview, ?User $actor = null): EvidenceAnchorResult { $snapshot = match (true) { $reviewPackOrReview instanceof EnvironmentReview => tap($reviewPackOrReview)->loadMissing('evidenceSnapshot')->evidenceSnapshot, $reviewPackOrReview instanceof ReviewPack => tap($reviewPackOrReview)->loadMissing(['evidenceSnapshot', 'environmentReview.evidenceSnapshot'])->evidenceSnapshot ?? $reviewPackOrReview->environmentReview?->evidenceSnapshot, }; if (! $snapshot instanceof EvidenceSnapshot) { return $this->noValid( anchorType: EvidenceAnchorResult::TYPE_CUSTOMER_SAFE_EVIDENCE_SUMMARY, state: EvidenceAnchorResult::STATE_NOT_CONFIGURED, primaryReason: 'No review evidence summary is available for this customer workspace item.', blockingReasons: ['Released review evidence is missing.'], displayLabel: 'Review evidence', isCustomerSafe: true, ); } if (! $this->snapshotMatchesArtifactScope($snapshot, $reviewPackOrReview)) { return $this->noValid( state: EvidenceAnchorResult::STATE_BLOCKED, primaryReason: 'Review evidence cannot be summarized because its scope does not match this customer workspace item.', blockingReasons: ['Review evidence scope mismatch.'], displayLabel: 'Review evidence', ); } $state = $this->stateForSnapshot($snapshot, releaseBound: true); $isExpired = $this->isExpired($snapshot); $isPartial = $this->isPartial($snapshot); return new EvidenceAnchorResult( anchorType: EvidenceAnchorResult::TYPE_CUSTOMER_SAFE_EVIDENCE_SUMMARY, state: $state, evidenceSnapshotId: null, targetRoute: null, isCurrent: false, isReleaseBound: true, isCustomerSafe: true, isTechnicalOnly: false, isPartial: $isPartial, isSuperseded: (string) $snapshot->status === EvidenceSnapshotStatus::Superseded->value, isExpired: $isExpired, canLink: false, canViewTechnicalDetail: false, primaryReason: $isExpired ? 'The evidence captured for this review is expired.' : 'Evidence captured for this review is summarized for customer review.', blockingReasons: $state === EvidenceAnchorResult::STATE_READY ? [] : [$this->blockingReasonForSnapshot($snapshot, true)], displayLabel: 'Evidence captured for this review', ); } public function technicalDetail(EvidenceSnapshot $snapshot, ?User $actor = null): EvidenceAnchorResult { $snapshot->loadMissing('tenant'); $targetRoute = $this->targetRouteFor($snapshot, $actor); return $this->fromSnapshot( snapshot: $snapshot, anchorType: EvidenceAnchorResult::TYPE_TECHNICAL_EVIDENCE_DETAIL, actor: $actor, displayLabel: 'View internal evidence details', primaryReason: $targetRoute !== null ? 'Internal evidence details are available for authorized operators.' : 'Internal evidence details require evidence view permission.', isTechnicalOnly: true, targetRoute: $targetRoute, ); } private function fromSnapshot( EvidenceSnapshot $snapshot, string $anchorType, ?User $actor, string $displayLabel, string $primaryReason, bool $isCurrent = false, bool $isReleaseBound = false, bool $isCustomerSafe = false, bool $isTechnicalOnly = false, ?string $targetRoute = null, ): EvidenceAnchorResult { $snapshot->loadMissing('tenant'); $targetRoute ??= $this->targetRouteFor($snapshot, $actor); $state = $this->stateForSnapshot($snapshot, $isReleaseBound); $canViewTechnicalDetail = $this->targetRouteFor($snapshot, $actor) !== null; return new EvidenceAnchorResult( anchorType: $anchorType, state: $state, evidenceSnapshotId: (int) $snapshot->getKey(), targetRoute: $targetRoute, isCurrent: $isCurrent, isReleaseBound: $isReleaseBound, isCustomerSafe: $isCustomerSafe, isTechnicalOnly: $isTechnicalOnly, isPartial: $this->isPartial($snapshot), isSuperseded: (string) $snapshot->status === EvidenceSnapshotStatus::Superseded->value, isExpired: $this->isExpired($snapshot), canLink: $targetRoute !== null, canViewTechnicalDetail: $canViewTechnicalDetail, primaryReason: $primaryReason, blockingReasons: $state === EvidenceAnchorResult::STATE_READY ? [] : [$this->blockingReasonForSnapshot($snapshot, $isReleaseBound)], displayLabel: $displayLabel, ); } /** * @param list $blockingReasons */ private function noValid( string $state, string $primaryReason, array $blockingReasons, string $displayLabel, string $anchorType = EvidenceAnchorResult::TYPE_NO_VALID_EVIDENCE, bool $isCustomerSafe = false, ): EvidenceAnchorResult { return new EvidenceAnchorResult( anchorType: $anchorType, state: $state, evidenceSnapshotId: null, targetRoute: null, isCurrent: false, isReleaseBound: false, isCustomerSafe: $isCustomerSafe, isTechnicalOnly: false, isPartial: false, isSuperseded: false, isExpired: false, canLink: false, canViewTechnicalDetail: false, primaryReason: $primaryReason, blockingReasons: $blockingReasons, displayLabel: $displayLabel, ); } private function targetRouteFor(EvidenceSnapshot $snapshot, ?User $actor): ?string { $tenant = $snapshot->tenant; if (! $actor instanceof User || ! $tenant instanceof ManagedEnvironment) { return null; } if (! $actor->canAccessTenant($tenant) || ! $actor->can(Capabilities::EVIDENCE_VIEW, $tenant)) { return null; } return EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $tenant, panel: 'admin'); } private function stateForSnapshot(EvidenceSnapshot $snapshot, bool $releaseBound): string { if ($this->isExpired($snapshot)) { return EvidenceAnchorResult::STATE_EXPIRED; } if ((string) $snapshot->status === EvidenceSnapshotStatus::Failed->value) { return EvidenceAnchorResult::STATE_BLOCKED; } if ($this->isPartial($snapshot)) { return EvidenceAnchorResult::STATE_NEEDS_ATTENTION; } if (! $releaseBound && (string) $snapshot->status !== EvidenceSnapshotStatus::Active->value) { return EvidenceAnchorResult::STATE_BLOCKED; } return EvidenceAnchorResult::STATE_READY; } private function blockingReasonForSnapshot(EvidenceSnapshot $snapshot, bool $releaseBound): string { if ($this->isExpired($snapshot)) { return 'Evidence has expired.'; } if ((string) $snapshot->status === EvidenceSnapshotStatus::Failed->value) { return 'Evidence generation failed.'; } if ($this->isPartial($snapshot)) { return 'Evidence completeness requires attention.'; } if (! $releaseBound && (string) $snapshot->status !== EvidenceSnapshotStatus::Active->value) { return 'Evidence is not an active current snapshot.'; } return 'Evidence cannot be linked from this surface.'; } private function isPartial(EvidenceSnapshot $snapshot): bool { return (string) $snapshot->completeness_state !== EvidenceCompletenessState::Complete->value; } private function isExpired(EvidenceSnapshot $snapshot): bool { return (string) $snapshot->status === EvidenceSnapshotStatus::Expired->value || ($snapshot->expires_at !== null && $snapshot->expires_at->isPast()); } private function snapshotMatchesArtifactScope(EvidenceSnapshot $snapshot, EnvironmentReview|ReviewPack $artifact): bool { return (int) $snapshot->workspace_id === (int) $artifact->workspace_id && (int) $snapshot->managed_environment_id === (int) $artifact->managed_environment_id; } }