TenantAtlas/apps/platform/app/Support/PortfolioTriage/TenantTriageReviewFingerprint.php
2026-04-10 23:34:02 +02:00

158 lines
5.0 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Support\PortfolioTriage;
use App\Support\BackupHealth\TenantBackupHealthAssessment;
use App\Support\Tenants\TenantRecoveryTriagePresentation;
use JsonException;
final class TenantTriageReviewFingerprint
{
/**
* @param array<string, mixed>|null $recoveryEvidence
* @return array{
* concern_family: string,
* concern_state: string,
* fingerprint: string,
* snapshot: array{
* concernFamily: string,
* concernState: string,
* reasonCode: ?string,
* severityKey: ?string,
* supportingKey: ?string
* }
* }|null
*/
public function forConcernFamily(
string $concernFamily,
?TenantBackupHealthAssessment $backupHealth,
?array $recoveryEvidence,
): ?array {
return match ($concernFamily) {
PortfolioArrivalContextToken::FAMILY_BACKUP_HEALTH => $this->forBackupHealth($backupHealth),
PortfolioArrivalContextToken::FAMILY_RECOVERY_EVIDENCE => $this->forRecoveryEvidence($recoveryEvidence),
default => null,
};
}
/**
* @return array{
* concern_family: string,
* concern_state: string,
* fingerprint: string,
* snapshot: array{
* concernFamily: string,
* concernState: string,
* reasonCode: ?string,
* severityKey: ?string,
* supportingKey: ?string
* }
* }|null
*/
public function forBackupHealth(?TenantBackupHealthAssessment $assessment): ?array
{
if (! $assessment instanceof TenantBackupHealthAssessment) {
return null;
}
if (! in_array($assessment->posture, [
TenantBackupHealthAssessment::POSTURE_ABSENT,
TenantBackupHealthAssessment::POSTURE_STALE,
TenantBackupHealthAssessment::POSTURE_DEGRADED,
], true)) {
return null;
}
$snapshot = [
'concernFamily' => PortfolioArrivalContextToken::FAMILY_BACKUP_HEALTH,
'concernState' => $assessment->posture,
'reasonCode' => $assessment->primaryReason,
'severityKey' => $assessment->primaryReason,
'supportingKey' => $assessment->primaryReason,
];
return [
'concern_family' => PortfolioArrivalContextToken::FAMILY_BACKUP_HEALTH,
'concern_state' => $assessment->posture,
'fingerprint' => $this->hash($snapshot),
'snapshot' => $snapshot,
];
}
/**
* @param array<string, mixed>|null $recoveryEvidence
* @return array{
* concern_family: string,
* concern_state: string,
* fingerprint: string,
* snapshot: array{
* concernFamily: string,
* concernState: string,
* reasonCode: ?string,
* severityKey: ?string,
* supportingKey: ?string
* }
* }|null
*/
public function forRecoveryEvidence(?array $recoveryEvidence): ?array
{
$state = is_array($recoveryEvidence)
? TenantRecoveryTriagePresentation::recoveryEvidenceState($recoveryEvidence)
: null;
if (! in_array($state, [
TenantRecoveryTriagePresentation::RECOVERY_EVIDENCE_WEAKENED,
TenantRecoveryTriagePresentation::RECOVERY_EVIDENCE_UNVALIDATED,
], true)) {
return null;
}
$reason = is_string($recoveryEvidence['reason'] ?? null)
? $recoveryEvidence['reason']
: null;
$supportingKey = is_string($recoveryEvidence['latest_relevant_attention_state'] ?? null)
? $recoveryEvidence['latest_relevant_attention_state']
: $reason;
$snapshot = [
'concernFamily' => PortfolioArrivalContextToken::FAMILY_RECOVERY_EVIDENCE,
'concernState' => $state,
'reasonCode' => $reason,
'severityKey' => $reason,
'supportingKey' => $supportingKey,
];
return [
'concern_family' => PortfolioArrivalContextToken::FAMILY_RECOVERY_EVIDENCE,
'concern_state' => $state,
'fingerprint' => $this->hash($snapshot),
'snapshot' => $snapshot,
];
}
/**
* @param array{
* concernFamily: string,
* concernState: string,
* reasonCode: ?string,
* severityKey: ?string,
* supportingKey: ?string
* } $snapshot
*/
private function hash(array $snapshot): string
{
try {
return hash('sha256', json_encode($snapshot, JSON_THROW_ON_ERROR));
} catch (JsonException) {
return hash('sha256', implode(':', array_map(
static fn (mixed $value): string => is_scalar($value) || $value === null
? (string) $value
: '',
$snapshot,
)));
}
}
}