Implements Spec 117 (Golden Master Baseline Drift Engine): - Adds provider-chain resolver for current state hashes (content evidence via PolicyVersion, meta evidence via inventory) - Updates baseline capture + compare jobs to use resolver and persist provenance + fidelity - Adds evidence_fidelity column/index + Filament UI badge/filter/provenance display for findings - Adds performance guard test + integration tests for drift, fidelity semantics, provenance, filter behavior - UX fix: Policies list shows "Sync from Intune" header action only when records exist; empty-state CTA remains and is functional Tests: - `vendor/bin/sail artisan test --compact tests/Feature/Filament/PolicySyncCtaPlacementTest.php` - `vendor/bin/sail artisan test --compact --filter=Baseline` Checklist: - specs/117-baseline-drift-engine/checklists/requirements.md ✓ Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #142
128 lines
4.1 KiB
PHP
128 lines
4.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\Baselines\Evidence;
|
|
|
|
use App\Models\InventoryItem;
|
|
use App\Models\Tenant;
|
|
use App\Services\Baselines\BaselineSnapshotIdentity;
|
|
use App\Services\Baselines\CurrentStateEvidenceProvider;
|
|
use Carbon\CarbonImmutable;
|
|
use Illuminate\Support\Collection;
|
|
|
|
final class MetaEvidenceProvider implements CurrentStateEvidenceProvider
|
|
{
|
|
public function __construct(
|
|
private readonly BaselineSnapshotIdentity $snapshotIdentity,
|
|
) {}
|
|
|
|
public function name(): string
|
|
{
|
|
return 'inventory';
|
|
}
|
|
|
|
public function resolve(Tenant $tenant, array $subjects, ?CarbonImmutable $since = null, ?int $latestInventorySyncRunId = null): array
|
|
{
|
|
if ($subjects === []) {
|
|
return [];
|
|
}
|
|
|
|
$requestedKeys = $this->requestedKeys($subjects);
|
|
|
|
if ($requestedKeys === []) {
|
|
return [];
|
|
}
|
|
|
|
$policyTypes = array_values(array_unique(array_map(
|
|
static fn (array $subject): string => trim((string) ($subject['policy_type'] ?? '')),
|
|
$subjects,
|
|
)));
|
|
$policyTypes = array_values(array_filter($policyTypes, static fn (string $value): bool => $value !== ''));
|
|
|
|
$externalIds = array_values(array_unique(array_map(
|
|
static fn (array $subject): string => trim((string) ($subject['subject_external_id'] ?? '')),
|
|
$subjects,
|
|
)));
|
|
$externalIds = array_values(array_filter($externalIds, static fn (string $value): bool => $value !== ''));
|
|
|
|
if ($policyTypes === [] || $externalIds === []) {
|
|
return [];
|
|
}
|
|
|
|
$query = InventoryItem::query()
|
|
->where('tenant_id', (int) $tenant->getKey())
|
|
->whereIn('policy_type', $policyTypes)
|
|
->whereIn('external_id', $externalIds);
|
|
|
|
if (is_int($latestInventorySyncRunId) && $latestInventorySyncRunId > 0) {
|
|
$query->where('last_seen_operation_run_id', $latestInventorySyncRunId);
|
|
}
|
|
|
|
/** @var Collection<int, InventoryItem> $inventoryItems */
|
|
$inventoryItems = $query->get();
|
|
|
|
$resolved = [];
|
|
|
|
foreach ($inventoryItems as $item) {
|
|
if (! $item instanceof InventoryItem) {
|
|
continue;
|
|
}
|
|
|
|
$key = (string) $item->policy_type.'|'.(string) $item->external_id;
|
|
|
|
if (! array_key_exists($key, $requestedKeys)) {
|
|
continue;
|
|
}
|
|
|
|
$metaJsonb = is_array($item->meta_jsonb) ? $item->meta_jsonb : [];
|
|
|
|
$hash = $this->snapshotIdentity->hashItemContent(
|
|
policyType: (string) $item->policy_type,
|
|
subjectExternalId: (string) $item->external_id,
|
|
metaJsonb: $metaJsonb,
|
|
);
|
|
|
|
$observedAt = $item->last_seen_at ? CarbonImmutable::instance($item->last_seen_at) : null;
|
|
$observedOperationRunId = is_numeric($item->last_seen_operation_run_id)
|
|
? (int) $item->last_seen_operation_run_id
|
|
: null;
|
|
|
|
$resolved[$key] = new ResolvedEvidence(
|
|
policyType: (string) $item->policy_type,
|
|
subjectExternalId: (string) $item->external_id,
|
|
hash: $hash,
|
|
fidelity: EvidenceProvenance::FidelityMeta,
|
|
source: EvidenceProvenance::SourceInventory,
|
|
observedAt: $observedAt,
|
|
observedOperationRunId: $observedOperationRunId,
|
|
meta: [],
|
|
);
|
|
}
|
|
|
|
return $resolved;
|
|
}
|
|
|
|
/**
|
|
* @param list<array{policy_type: string, subject_external_id: string}> $subjects
|
|
* @return array<string, true>
|
|
*/
|
|
private function requestedKeys(array $subjects): array
|
|
{
|
|
$keys = [];
|
|
|
|
foreach ($subjects as $subject) {
|
|
$policyType = trim((string) ($subject['policy_type'] ?? ''));
|
|
$externalId = trim((string) ($subject['subject_external_id'] ?? ''));
|
|
|
|
if ($policyType === '' || $externalId === '') {
|
|
continue;
|
|
}
|
|
|
|
$keys[$policyType.'|'.$externalId] = true;
|
|
}
|
|
|
|
return $keys;
|
|
}
|
|
}
|