|null $normalizedPayload * @param array $context * @param array|null $compareResult * @return array */ public function evaluate( string $canonicalType, ?array $normalizedPayload, array $context = [], ?array $compareResult = null, ): array { if ($normalizedPayload === null) { return $this->result( state: 'readiness_not_assessed', label: 'Not assessed', reason: 'No structured Security and Compliance evidence is available for operator review.', blockers: ['not_assessed'], ); } if (($normalizedPayload['supported'] ?? false) !== true) { return $this->result( state: 'readiness_blocked_unsupported', label: 'Blocked', reason: 'This Security and Compliance resource type is not supported by this readiness pack.', blockers: ['unsupported_type'], ); } if ($this->evidenceBlocked($context)) { return $this->result( state: $this->permissionBlocked($context) ? 'readiness_blocked_permission' : 'readiness_blocked_evidence', label: 'Blocked', reason: $this->permissionBlocked($context) ? 'Provider permission or source availability blocks reliable operator review.' : 'Content-backed captured evidence is required before readiness can be assessed.', blockers: [$this->permissionBlocked($context) ? 'permission_blocked' : 'evidence_blocked'], ); } if ($this->identityBlocked($context)) { return $this->result( state: 'readiness_blocked_identity', label: 'Blocked', reason: 'Identity is unsafe for Security and Compliance readiness assessment.', blockers: ['identity_blocked'], ); } $unsupportedFields = $this->actionableUnsupportedFields($normalizedPayload); $manualReviewFields = $this->diagnosticFields($normalizedPayload, 'manual_review_fields'); if ($manualReviewFields !== []) { return $this->result( state: 'readiness_requires_manual_review', label: 'Manual review required', reason: 'Unsupported Security and Compliance fields require internal operator review.', manualReviewRequired: true, blockers: array_values(array_unique([ ...array_map(static fn (string $field): string => 'manual_review:'.$field, $manualReviewFields), ])), ); } if ($unsupportedFields !== []) { return $this->result( state: 'readiness_blocked_unsupported', label: 'Blocked', reason: 'Unsupported fields require review before this evidence can be trusted.', blockers: array_values(array_unique([ ...array_map(static fn (string $field): string => 'unsupported:'.$field, $unsupportedFields), ])), ); } if ($this->requiresManualReview($compareResult)) { return $this->result( state: 'readiness_requires_manual_review', label: 'Manual review required', reason: 'Critical Security and Compliance changes require internal operator review.', manualReviewRequired: true, blockers: ['critical_material_change'], ); } return $this->result( state: 'readiness_ready_for_operator_review', label: 'Ready for operator review', reason: 'Structured evidence is available for internal operator review.', ); } /** * @return array */ private function result( string $state, string $label, string $reason, bool $manualReviewRequired = false, array $blockers = [], ): array { return [ 'state' => $state, 'label' => $label, 'reason' => $reason, 'manual_review_required' => $manualReviewRequired, 'blockers' => array_values($blockers), ]; } /** * @param array $context */ private function evidenceBlocked(array $context): bool { return $this->permissionBlocked($context) || ($context['evidence_state'] ?? null) !== 'content_backed' || ($context['capture_outcome'] ?? null) !== 'captured' || ! in_array($context['coverage_level'] ?? null, ['renderable', 'comparable'], true); } /** * @param array $context */ private function permissionBlocked(array $context): bool { return (bool) ($context['permission_blocked'] ?? false); } /** * @param array $context */ private function identityBlocked(array $context): bool { return in_array($context['identity_state'] ?? null, [ 'identity_conflict', 'missing_external_id', 'unsupported_identity', ], true); } /** * @param array|null $compareResult */ private function requiresManualReview(?array $compareResult): bool { if ($compareResult === null) { return false; } if (($compareResult['classification'] ?? null) === 'manual_review_required') { return true; } $changes = $compareResult['changes'] ?? []; if (! is_array($changes)) { return false; } foreach ($changes as $change) { if (! is_array($change)) { continue; } if (($change['classification'] ?? null) === 'manual_review_required' || ($change['importance'] ?? null) === 'critical' || ($change['importance'] ?? null) === 'manual_review_required' ) { return true; } } return false; } /** * @param array $payload * @return list */ private function diagnosticFields(array $payload, string $key): array { $fields = data_get($payload, 'diagnostics.'.$key, []); if (! is_array($fields)) { return []; } return array_values(array_filter( array_map(static fn (mixed $field): string => is_string($field) ? trim($field) : '', $fields), static fn (string $field): bool => $field !== '', )); } /** * @param array $payload * @return list */ private function actionableUnsupportedFields(array $payload): array { $unsupportedFields = $this->diagnosticFields($payload, 'unsupported_fields'); $redactedFields = $this->diagnosticFields($payload, 'redacted_fields'); return array_values(array_filter( $unsupportedFields, static fn (string $field): bool => ! in_array($field, $redactedFields, true), )); } }