explicitTrustLevel($outcome->trust); $reason = CompareResultReason::tryFrom($outcome->reasonCode) ?? $this->defaultMatchingReason($outcome); $reason = $this->reasonAllowedByTrust($reason, $explicitTrustLevel); return new CompareSubjectOutcome( reason: $reason, category: $reason->category(), actionability: $reason->actionability(), readinessImpact: $reason->readinessImpact(), identityStatus: $this->identityStatusForMatching($outcome, $reason), comparisonStatus: CompareResultComparisonStatus::NotCompared, coverageStatus: $reason->coverageStatus(), trustLevel: $this->trustLevel($outcome->trust, $reason), subject: $outcome->subject->toArray(), proof: $outcome->toArray()['proof'] ?? [], ); } /** * @param array $subject * @param array $proof */ public function fromReason( string $reasonCode, array $subject = [], array $proof = [], ?CompareState $state = null, ?string $trustLevel = null, ): CompareSubjectOutcome { $explicitTrustLevel = $this->explicitTrustLevel($trustLevel); $reason = CompareResultReason::tryFrom($reasonCode) ?? $this->defaultReasonForState($state, $explicitTrustLevel); $reason = $this->reasonAllowedByTrust($reason, $explicitTrustLevel); return new CompareSubjectOutcome( reason: $reason, category: $reason->category(), actionability: $reason->actionability(), readinessImpact: $reason->readinessImpact(), identityStatus: $this->identityStatusForReason($reason), comparisonStatus: $this->comparisonStatusForReason($reason, $state), coverageStatus: $reason->coverageStatus(), trustLevel: $this->trustLevel($trustLevel, $reason), subject: $subject, proof: $proof, ); } public function fromStrategyResult(CompareSubjectResult $result): CompareSubjectOutcome { $explicitTrustLevel = $this->explicitTrustLevel($result->trustLevel); $reason = match ($result->compareState) { CompareState::NoDrift => $this->trustedVerifiedReason( $explicitTrustLevel, CompareResultReason::VerifiedNoDrift, ), CompareState::Drift => $this->trustedVerifiedReason( $explicitTrustLevel, CompareResultReason::VerifiedDriftDetected, ), default => CompareResultReason::tryFrom((string) ($result->gapReasonCode() ?? '')) ?? $this->defaultReasonForState($result->compareState, $explicitTrustLevel), }; $reason = $this->reasonAllowedByTrust($reason, $explicitTrustLevel); return new CompareSubjectOutcome( reason: $reason, category: $reason->category(), actionability: $reason->actionability(), readinessImpact: $reason->readinessImpact(), identityStatus: $this->identityStatusForReason($reason), comparisonStatus: $this->comparisonStatusForReason($reason, $result->compareState), coverageStatus: $reason->coverageStatus(), trustLevel: $this->trustLevel($result->trustLevel, $reason), subject: [ 'domain_key' => $result->subjectIdentity->domainKey, 'subject_class' => $result->subjectIdentity->subjectClass, 'subject_type_key' => $result->subjectIdentity->subjectTypeKey, 'external_subject_id' => $result->subjectIdentity->externalSubjectId, 'subject_key' => $result->subjectIdentity->subjectKey, 'operator_label' => $result->projection->operatorLabel, ], proof: [ 'compare_state' => $result->compareState->value, 'baseline_availability' => $result->baselineAvailability, 'current_state_availability' => $result->currentStateAvailability, 'evidence_quality' => $result->evidenceQuality, 'strategy_key' => is_string($result->diagnostics['strategy_key'] ?? null) ? $result->diagnostics['strategy_key'] : null, ], ); } private function defaultMatchingReason(MatchingOutcome $outcome): CompareResultReason { return match ($outcome->status) { MatchingOutcome::Resolved => CompareResultReason::ResolvedProviderIdentity, MatchingOutcome::Ambiguous => CompareResultReason::UnresolvedDuplicateCandidates, MatchingOutcome::MissingProviderResource => CompareResultReason::MissingProviderResource, MatchingOutcome::MissingLocalEvidence => CompareResultReason::MissingLocalEvidence, MatchingOutcome::UnresolvedIdentity => CompareResultReason::IdentityRequired, MatchingOutcome::Unsupported => CompareResultReason::UnsupportedResourceClass, MatchingOutcome::Limited => CompareResultReason::AcceptedLimitation, MatchingOutcome::Excluded => CompareResultReason::ExcludedNonGoverned, default => CompareResultReason::CompareFailed, }; } private function defaultReasonForState( ?CompareState $state, ?CompareResultTrustLevel $explicitTrustLevel = null, ): CompareResultReason { if ($state === CompareState::NoDrift) { return $this->trustedVerifiedReason($explicitTrustLevel, CompareResultReason::VerifiedNoDrift); } if ($state === CompareState::Drift) { return $this->trustedVerifiedReason($explicitTrustLevel, CompareResultReason::VerifiedDriftDetected); } return match ($state) { CompareState::Unsupported => CompareResultReason::CompareNotSupported, CompareState::Ambiguous => CompareResultReason::UnresolvedAmbiguousIdentity, CompareState::Failed => CompareResultReason::CompareFailed, default => CompareResultReason::MissingLocalEvidence, }; } private function trustedVerifiedReason( ?CompareResultTrustLevel $explicitTrustLevel, CompareResultReason $verifiedReason, ): CompareResultReason { return $this->reasonAllowedByTrust($verifiedReason, $explicitTrustLevel); } private function reasonAllowedByTrust( CompareResultReason $reason, ?CompareResultTrustLevel $explicitTrustLevel, ): CompareResultReason { if (! $this->requiresTrustedComparison($reason)) { return $reason; } return $this->allowsVerifiedComparison($explicitTrustLevel) ? $reason : CompareResultReason::UnresolvedLowTrustMatch; } private function allowsVerifiedComparison(?CompareResultTrustLevel $trustLevel): bool { return in_array($trustLevel, [ CompareResultTrustLevel::High, CompareResultTrustLevel::Medium, ], true); } private function requiresTrustedComparison(CompareResultReason $reason): bool { return in_array($reason, [ CompareResultReason::VerifiedNoDrift, CompareResultReason::VerifiedDriftDetected, CompareResultReason::ResolvedActiveBinding, CompareResultReason::ResolvedCanonicalIdentity, CompareResultReason::ResolvedProviderIdentity, ], true); } private function identityStatusForMatching(MatchingOutcome $outcome, CompareResultReason $reason): CompareResultIdentityStatus { return match ($reason) { CompareResultReason::ResolvedActiveBinding => CompareResultIdentityStatus::BindingResolved, CompareResultReason::ResolvedCanonicalIdentity => CompareResultIdentityStatus::CanonicalizationResolved, CompareResultReason::ResolvedProviderIdentity, CompareResultReason::VerifiedNoDrift, CompareResultReason::VerifiedDriftDetected => CompareResultIdentityStatus::Resolved, CompareResultReason::MissingLocalEvidence, CompareResultReason::MissingProviderResource => CompareResultIdentityStatus::Missing, CompareResultReason::UnsupportedResourceClass, CompareResultReason::CompareNotSupported => CompareResultIdentityStatus::Unsupported, default => $outcome->isComparable() ? CompareResultIdentityStatus::Resolved : CompareResultIdentityStatus::Unresolved, }; } private function identityStatusForReason(CompareResultReason $reason): CompareResultIdentityStatus { return match ($reason) { CompareResultReason::ResolvedActiveBinding => CompareResultIdentityStatus::BindingResolved, CompareResultReason::ResolvedCanonicalIdentity => CompareResultIdentityStatus::CanonicalizationResolved, CompareResultReason::ResolvedProviderIdentity, CompareResultReason::VerifiedNoDrift, CompareResultReason::VerifiedDriftDetected => CompareResultIdentityStatus::Resolved, CompareResultReason::MissingLocalEvidence, CompareResultReason::MissingProviderResource => CompareResultIdentityStatus::Missing, CompareResultReason::UnsupportedResourceClass, CompareResultReason::CompareNotSupported => CompareResultIdentityStatus::Unsupported, default => CompareResultIdentityStatus::Unresolved, }; } private function comparisonStatusForReason(CompareResultReason $reason, ?CompareState $state): CompareResultComparisonStatus { return match ($reason) { CompareResultReason::VerifiedNoDrift => CompareResultComparisonStatus::NoDrift, CompareResultReason::VerifiedDriftDetected => CompareResultComparisonStatus::DriftDetected, CompareResultReason::CompareFailed => CompareResultComparisonStatus::CompareFailed, CompareResultReason::CompareNotSupported, CompareResultReason::UnsupportedResourceClass => CompareResultComparisonStatus::CompareNotSupported, default => CompareResultComparisonStatus::NotCompared, }; } private function trustLevel(?string $trustLevel, CompareResultReason $reason): CompareResultTrustLevel { return match (strtolower((string) $trustLevel)) { 'authoritative', 'trustworthy', 'high' => CompareResultTrustLevel::High, 'limited_confidence', 'limited', 'medium' => CompareResultTrustLevel::Medium, 'low' => CompareResultTrustLevel::Low, 'unusable', 'none', 'untrusted' => CompareResultTrustLevel::Untrusted, 'failed' => CompareResultTrustLevel::Failed, 'not_applicable' => CompareResultTrustLevel::NotApplicable, default => $reason->defaultTrustLevel(), }; } private function explicitTrustLevel(?string $trustLevel): ?CompareResultTrustLevel { return match (strtolower((string) $trustLevel)) { 'authoritative', 'trustworthy', 'high' => CompareResultTrustLevel::High, 'limited_confidence', 'limited', 'medium' => CompareResultTrustLevel::Medium, 'low' => CompareResultTrustLevel::Low, 'unusable', 'none', 'untrusted' => CompareResultTrustLevel::Untrusted, 'failed' => CompareResultTrustLevel::Failed, 'not_applicable' => CompareResultTrustLevel::NotApplicable, default => null, }; } }