Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 4m7s
Implemented deterministic Baseline Result Semantics (Spec 383), introducing CompareSubjectResult and CompareEvidenceResult. Replaced generic arrays with strict Data Transfer Objects for Baseline engine output.
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,
|
|
};
|
|
}
|
|
}
|