$diagnostics */ public function __construct( public readonly CompareSubjectIdentity $subjectIdentity, public readonly CompareSubjectProjection $projection, public readonly string $baselineAvailability, public readonly string $currentStateAvailability, public readonly CompareState $compareState, public readonly string $trustLevel, public readonly string $evidenceQuality, public readonly ?string $severityRecommendation = null, public readonly ?CompareFindingCandidate $findingCandidate = null, public readonly array $diagnostics = [], ) { if (trim($this->baselineAvailability) === '' || trim($this->currentStateAvailability) === '' || trim($this->trustLevel) === '' || trim($this->evidenceQuality) === '') { throw new InvalidArgumentException('Compare subject results require non-empty availability, trust level, and evidence quality values.'); } if ($this->compareState === CompareState::Drift && ! $this->findingCandidate instanceof CompareFindingCandidate) { throw new InvalidArgumentException('Drift compare subject results require a finding candidate.'); } if ($this->compareState !== CompareState::Drift && $this->findingCandidate instanceof CompareFindingCandidate) { throw new InvalidArgumentException('Only drift compare subject results may carry a finding candidate.'); } } public function hasFindingCandidate(): bool { return $this->findingCandidate instanceof CompareFindingCandidate; } public function isGapState(): bool { return in_array($this->compareState, [ CompareState::Unsupported, CompareState::Incomplete, CompareState::Ambiguous, CompareState::Failed, ], true); } public function gapReasonCode(): ?string { $reasonCode = $this->diagnostics['reason_code'] ?? null; return is_string($reasonCode) && trim($reasonCode) !== '' ? trim($reasonCode) : null; } /** * @return array|null */ public function gapRecord(): ?array { $gapRecord = $this->diagnostics['gap_record'] ?? null; return is_array($gapRecord) ? $gapRecord : null; } /** * @return array{ * subject_identity: array{ * domain_key: string, * subject_class: string, * subject_type_key: string, * external_subject_id: ?string, * subject_key: string * }, * projection: array{ * platform_subject_class: string, * domain_key: string, * subject_type_key: string, * operator_label: string, * summary_kind: ?string, * additional_labels: array * }, * baseline_availability: string, * current_state_availability: string, * compare_state: string, * trust_level: string, * evidence_quality: string, * severity_recommendation: ?string, * finding_candidate: ?array{ * change_type: string, * severity: string, * fingerprint_basis: array, * evidence_payload: array, * auto_close_eligible: bool * }, * diagnostics: array * } */ public function toArray(): array { return [ 'subject_identity' => $this->subjectIdentity->toArray(), 'projection' => $this->projection->toArray(), 'baseline_availability' => $this->baselineAvailability, 'current_state_availability' => $this->currentStateAvailability, 'compare_state' => $this->compareState->value, 'trust_level' => $this->trustLevel, 'evidence_quality' => $this->evidenceQuality, 'severity_recommendation' => $this->severityRecommendation, 'finding_candidate' => $this->findingCandidate?->toArray(), 'diagnostics' => $this->diagnostics, ]; } }