Implemented deterministic Baseline Result Semantics (Spec 383), introducing CompareSubjectResult and CompareEvidenceResult. Replaced generic arrays with strict Data Transfer Objects for Baseline engine output. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #454
267 lines
12 KiB
PHP
267 lines
12 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Support\Baselines\CompareSemantics;
|
|
|
|
use App\Support\Baselines\Compare\CompareState;
|
|
use App\Support\Baselines\Compare\CompareSubjectResult;
|
|
use App\Support\Baselines\Matching\MatchingOutcome;
|
|
|
|
final class BaselineCompareOutcomeClassifier
|
|
{
|
|
public function fromMatchingOutcome(MatchingOutcome $outcome): CompareSubjectOutcome
|
|
{
|
|
$explicitTrustLevel = $this->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<string, mixed> $subject
|
|
* @param array<string, mixed> $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,
|
|
};
|
|
}
|
|
}
|