## Summary - add shared governance artifact truth presentation and badge taxonomy - integrate artifact-truth messaging across baseline, evidence, tenant review, review pack, and operation run surfaces - add focused regression coverage and spec artifacts for artifact truth semantics ## Testing - not run in this step Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #188
91 lines
2.8 KiB
PHP
91 lines
2.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\Baselines;
|
|
|
|
final class BaselineSnapshotItemNormalizer
|
|
{
|
|
/**
|
|
* @param list<array{subject_type: string, subject_external_id: string, subject_key: string, policy_type: string, baseline_hash: string, meta_jsonb: array<string, mixed>}> $items
|
|
* @return array{items: list<array{subject_type: string, subject_external_id: string, subject_key: string, policy_type: string, baseline_hash: string, meta_jsonb: array<string, mixed>}>, duplicates: int}
|
|
*/
|
|
public function deduplicate(array $items): array
|
|
{
|
|
$uniqueItems = [];
|
|
$duplicates = 0;
|
|
|
|
foreach ($items as $item) {
|
|
$key = trim((string) ($item['subject_type'] ?? '')).'|'.trim((string) ($item['subject_external_id'] ?? ''));
|
|
|
|
if ($key === '|') {
|
|
continue;
|
|
}
|
|
|
|
if (! array_key_exists($key, $uniqueItems)) {
|
|
$uniqueItems[$key] = $item;
|
|
|
|
continue;
|
|
}
|
|
|
|
$duplicates++;
|
|
|
|
if ($this->shouldReplace($uniqueItems[$key], $item)) {
|
|
$uniqueItems[$key] = $item;
|
|
}
|
|
}
|
|
|
|
return [
|
|
'items' => array_values($uniqueItems),
|
|
'duplicates' => $duplicates,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param array{meta_jsonb?: array<string, mixed>, baseline_hash?: string} $current
|
|
* @param array{meta_jsonb?: array<string, mixed>, baseline_hash?: string} $candidate
|
|
*/
|
|
private function shouldReplace(array $current, array $candidate): bool
|
|
{
|
|
$currentFidelity = $this->fidelityRank($current);
|
|
$candidateFidelity = $this->fidelityRank($candidate);
|
|
|
|
if ($candidateFidelity !== $currentFidelity) {
|
|
return $candidateFidelity > $currentFidelity;
|
|
}
|
|
|
|
$currentObservedAt = $this->observedAt($current);
|
|
$candidateObservedAt = $this->observedAt($candidate);
|
|
|
|
if ($candidateObservedAt !== $currentObservedAt) {
|
|
return $candidateObservedAt > $currentObservedAt;
|
|
}
|
|
|
|
return strcmp((string) ($candidate['baseline_hash'] ?? ''), (string) ($current['baseline_hash'] ?? '')) > 0;
|
|
}
|
|
|
|
/**
|
|
* @param array{meta_jsonb?: array<string, mixed>} $item
|
|
*/
|
|
private function fidelityRank(array $item): int
|
|
{
|
|
$fidelity = data_get($item, 'meta_jsonb.evidence.fidelity');
|
|
|
|
return match ($fidelity) {
|
|
'content' => 2,
|
|
'meta' => 1,
|
|
default => 0,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param array{meta_jsonb?: array<string, mixed>} $item
|
|
*/
|
|
private function observedAt(array $item): string
|
|
{
|
|
$observedAt = data_get($item, 'meta_jsonb.evidence.observed_at');
|
|
|
|
return is_string($observedAt) ? $observedAt : '';
|
|
}
|
|
}
|