TenantAtlas/app/Services/Baselines/BaselineAutoCloseService.php
2026-03-01 03:23:39 +01:00

122 lines
3.5 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services\Baselines;
use App\Models\Finding;
use App\Models\OperationRun;
use App\Models\Tenant;
use App\Models\Workspace;
use App\Services\Settings\SettingsResolver;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
final class BaselineAutoCloseService
{
public function __construct(
private readonly SettingsResolver $settingsResolver,
) {}
public function shouldAutoClose(Tenant $tenant, OperationRun $run): bool
{
if ($run->status !== OperationRunStatus::Completed->value) {
return false;
}
if ($run->outcome !== OperationRunOutcome::Succeeded->value) {
return false;
}
$summaryCounts = is_array($run->summary_counts) ? $run->summary_counts : [];
if (
! array_key_exists('total', $summaryCounts)
|| ! array_key_exists('processed', $summaryCounts)
|| ! array_key_exists('failed', $summaryCounts)
) {
return false;
}
$total = (int) $summaryCounts['total'];
$processed = (int) $summaryCounts['processed'];
$failed = (int) $summaryCounts['failed'];
if ($processed !== $total || $failed !== 0) {
return false;
}
$workspace = $this->resolveWorkspace($tenant);
if (! $workspace instanceof Workspace) {
return false;
}
try {
return (bool) $this->settingsResolver->resolveValue(
workspace: $workspace,
domain: 'baseline',
key: 'auto_close_enabled',
);
} catch (\InvalidArgumentException) {
return false;
}
}
/**
* @param array<int, string> $seenFingerprints
*/
public function resolveStaleFindings(
Tenant $tenant,
int $baselineProfileId,
array $seenFingerprints,
int $currentOperationRunId,
): int {
$scopeKey = 'baseline_profile:'.$baselineProfileId;
$resolvedAt = now();
$resolvedCount = 0;
$query = Finding::query()
->where('tenant_id', (int) $tenant->getKey())
->where('finding_type', Finding::FINDING_TYPE_DRIFT)
->where('source', 'baseline.compare')
->where('scope_key', $scopeKey)
->whereIn('status', Finding::openStatusesForQuery())
->orderBy('id');
if ($seenFingerprints !== []) {
$query->whereNotIn('fingerprint', array_values(array_unique($seenFingerprints)));
}
$query->chunkById(100, function ($findings) use (&$resolvedCount, $resolvedAt, $currentOperationRunId): void {
foreach ($findings as $finding) {
if (! $finding instanceof Finding) {
continue;
}
$finding->forceFill([
'status' => Finding::STATUS_RESOLVED,
'resolved_at' => $resolvedAt,
'resolved_reason' => 'no_longer_drifting',
'current_operation_run_id' => $currentOperationRunId,
])->save();
$resolvedCount++;
}
});
return $resolvedCount;
}
private function resolveWorkspace(Tenant $tenant): ?Workspace
{
$workspaceId = (int) ($tenant->workspace_id ?? 0);
if ($workspaceId <= 0) {
return null;
}
return Workspace::query()->whereKey($workspaceId)->first();
}
}