Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m12s
Replaced legacy tenant and environment bindings in the BaselineDriftEngine with the new ProviderResourceIdentity framework as defined in Spec 382.
196 lines
8.1 KiB
PHP
196 lines
8.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Support\Baselines;
|
|
|
|
use App\Support\Inventory\InventoryPolicyTypeMeta;
|
|
|
|
final class SubjectResolver
|
|
{
|
|
public function capability(string $policyType): SupportCapabilityRecord
|
|
{
|
|
$contract = InventoryPolicyTypeMeta::baselineSupportContract($policyType);
|
|
|
|
return new SupportCapabilityRecord(
|
|
policyType: $policyType,
|
|
subjectClass: SubjectClass::from($contract['subject_class']),
|
|
compareCapability: $contract['compare_capability'],
|
|
captureCapability: $contract['capture_capability'],
|
|
resolutionPath: ResolutionPath::from($contract['resolution_path']),
|
|
configSupported: (bool) $contract['config_supported'],
|
|
runtimeValid: (bool) $contract['runtime_valid'],
|
|
sourceModelExpected: $contract['source_model_expected'],
|
|
);
|
|
}
|
|
|
|
public function describeForCompare(string $policyType, ?string $subjectExternalId = null, ?string $subjectKey = null): SubjectDescriptor
|
|
{
|
|
return $this->describe(operation: 'compare', policyType: $policyType, subjectExternalId: $subjectExternalId, subjectKey: $subjectKey);
|
|
}
|
|
|
|
public function describeForCapture(string $policyType, ?string $subjectExternalId = null, ?string $subjectKey = null): SubjectDescriptor
|
|
{
|
|
return $this->describe(operation: 'capture', policyType: $policyType, subjectExternalId: $subjectExternalId, subjectKey: $subjectKey);
|
|
}
|
|
|
|
public function resolved(SubjectDescriptor $descriptor, ?string $sourceModelFound = null): ResolutionOutcomeRecord
|
|
{
|
|
$outcome = $descriptor->expectsPolicy()
|
|
? ResolutionOutcome::ResolvedPolicy
|
|
: ResolutionOutcome::ResolvedInventory;
|
|
|
|
return new ResolutionOutcomeRecord(
|
|
resolutionOutcome: $outcome,
|
|
reasonCode: $outcome->value,
|
|
operatorActionCategory: OperatorActionCategory::None,
|
|
structural: false,
|
|
retryable: false,
|
|
sourceModelExpected: $descriptor->sourceModelExpected,
|
|
sourceModelFound: $sourceModelFound ?? $descriptor->sourceModelExpected,
|
|
);
|
|
}
|
|
|
|
public function missingExpectedRecord(SubjectDescriptor $descriptor): ResolutionOutcomeRecord
|
|
{
|
|
$expectsPolicy = $descriptor->expectsPolicy();
|
|
|
|
return new ResolutionOutcomeRecord(
|
|
resolutionOutcome: $expectsPolicy ? ResolutionOutcome::PolicyRecordMissing : ResolutionOutcome::InventoryRecordMissing,
|
|
reasonCode: $expectsPolicy ? 'policy_record_missing' : 'inventory_record_missing',
|
|
operatorActionCategory: $expectsPolicy ? OperatorActionCategory::RunPolicySyncOrBackup : OperatorActionCategory::RunInventorySync,
|
|
structural: false,
|
|
retryable: false,
|
|
sourceModelExpected: $descriptor->sourceModelExpected,
|
|
);
|
|
}
|
|
|
|
public function structuralInventoryOnly(SubjectDescriptor $descriptor): ResolutionOutcomeRecord
|
|
{
|
|
return new ResolutionOutcomeRecord(
|
|
resolutionOutcome: ResolutionOutcome::FoundationInventoryOnly,
|
|
reasonCode: 'foundation_not_policy_backed',
|
|
operatorActionCategory: OperatorActionCategory::ProductFollowUp,
|
|
structural: true,
|
|
retryable: false,
|
|
sourceModelExpected: $descriptor->sourceModelExpected,
|
|
sourceModelFound: 'inventory',
|
|
);
|
|
}
|
|
|
|
public function invalidSubject(SubjectDescriptor $descriptor): ResolutionOutcomeRecord
|
|
{
|
|
return new ResolutionOutcomeRecord(
|
|
resolutionOutcome: ResolutionOutcome::InvalidSubject,
|
|
reasonCode: 'invalid_subject',
|
|
operatorActionCategory: OperatorActionCategory::InspectSubjectMapping,
|
|
structural: false,
|
|
retryable: false,
|
|
sourceModelExpected: $descriptor->sourceModelExpected,
|
|
);
|
|
}
|
|
|
|
public function duplicateSubject(SubjectDescriptor $descriptor): ResolutionOutcomeRecord
|
|
{
|
|
return new ResolutionOutcomeRecord(
|
|
resolutionOutcome: ResolutionOutcome::DuplicateSubject,
|
|
reasonCode: 'duplicate_subject',
|
|
operatorActionCategory: OperatorActionCategory::InspectSubjectMapping,
|
|
structural: false,
|
|
retryable: false,
|
|
sourceModelExpected: $descriptor->sourceModelExpected,
|
|
);
|
|
}
|
|
|
|
public function ambiguousMatch(SubjectDescriptor $descriptor): ResolutionOutcomeRecord
|
|
{
|
|
return new ResolutionOutcomeRecord(
|
|
resolutionOutcome: ResolutionOutcome::AmbiguousMatch,
|
|
reasonCode: 'ambiguous_match',
|
|
operatorActionCategory: OperatorActionCategory::InspectSubjectMapping,
|
|
structural: false,
|
|
retryable: false,
|
|
sourceModelExpected: $descriptor->sourceModelExpected,
|
|
);
|
|
}
|
|
|
|
public function invalidSupportConfiguration(SupportCapabilityRecord $capability): ResolutionOutcomeRecord
|
|
{
|
|
return new ResolutionOutcomeRecord(
|
|
resolutionOutcome: ResolutionOutcome::InvalidSupportConfig,
|
|
reasonCode: 'invalid_support_config',
|
|
operatorActionCategory: OperatorActionCategory::ProductFollowUp,
|
|
structural: true,
|
|
retryable: false,
|
|
sourceModelExpected: $capability->sourceModelExpected,
|
|
);
|
|
}
|
|
|
|
public function throttled(SubjectDescriptor $descriptor): ResolutionOutcomeRecord
|
|
{
|
|
return new ResolutionOutcomeRecord(
|
|
resolutionOutcome: ResolutionOutcome::Throttled,
|
|
reasonCode: 'throttled',
|
|
operatorActionCategory: OperatorActionCategory::Retry,
|
|
structural: false,
|
|
retryable: true,
|
|
sourceModelExpected: $descriptor->sourceModelExpected,
|
|
);
|
|
}
|
|
|
|
public function captureFailed(SubjectDescriptor $descriptor, bool $retryable = false): ResolutionOutcomeRecord
|
|
{
|
|
return new ResolutionOutcomeRecord(
|
|
resolutionOutcome: $retryable ? ResolutionOutcome::RetryableCaptureFailure : ResolutionOutcome::CaptureFailed,
|
|
reasonCode: $retryable ? 'retryable_capture_failure' : 'capture_failed',
|
|
operatorActionCategory: OperatorActionCategory::Retry,
|
|
structural: false,
|
|
retryable: $retryable,
|
|
sourceModelExpected: $descriptor->sourceModelExpected,
|
|
);
|
|
}
|
|
|
|
public function budgetExhausted(SubjectDescriptor $descriptor): ResolutionOutcomeRecord
|
|
{
|
|
return new ResolutionOutcomeRecord(
|
|
resolutionOutcome: ResolutionOutcome::BudgetExhausted,
|
|
reasonCode: 'budget_exhausted',
|
|
operatorActionCategory: OperatorActionCategory::Retry,
|
|
structural: false,
|
|
retryable: true,
|
|
sourceModelExpected: $descriptor->sourceModelExpected,
|
|
);
|
|
}
|
|
|
|
private function describe(string $operation, string $policyType, ?string $subjectExternalId = null, ?string $subjectKey = null): SubjectDescriptor
|
|
{
|
|
$capability = $this->capability($policyType);
|
|
$resolvedSubjectKey = $this->normalizeSubjectKey($policyType, $subjectExternalId, $subjectKey);
|
|
|
|
return new SubjectDescriptor(
|
|
policyType: $policyType,
|
|
subjectExternalId: $subjectExternalId !== null && trim($subjectExternalId) !== '' ? trim($subjectExternalId) : null,
|
|
subjectKey: $resolvedSubjectKey,
|
|
subjectClass: $capability->subjectClass,
|
|
resolutionPath: $capability->resolutionPath,
|
|
supportMode: $capability->supportModeFor($operation),
|
|
sourceModelExpected: $capability->sourceModelExpected,
|
|
);
|
|
}
|
|
|
|
private function normalizeSubjectKey(string $policyType, ?string $subjectExternalId, ?string $subjectKey): string
|
|
{
|
|
$trimmedSubjectKey = is_string($subjectKey) ? trim($subjectKey) : '';
|
|
|
|
if ($trimmedSubjectKey !== '') {
|
|
return $trimmedSubjectKey;
|
|
}
|
|
|
|
$fallbackExternalId = is_string($subjectExternalId) && trim($subjectExternalId) !== ''
|
|
? trim($subjectExternalId)
|
|
: 'unknown';
|
|
|
|
return 'identity-required:'.trim($policyType).':'.$fallbackExternalId;
|
|
}
|
|
}
|