finished_at || ! $current->finished_at) { throw new RuntimeException('Baseline/current run must be finished.'); } /** @var array $selection */ $selection = is_array($current->selection_payload) ? $current->selection_payload : []; $policyTypes = Arr::get($selection, 'policy_types'); if (! is_array($policyTypes)) { $policyTypes = []; } $policyTypes = array_values(array_filter(array_map('strval', $policyTypes))); $created = 0; Policy::query() ->where('tenant_id', $tenant->getKey()) ->whereIn('policy_type', $policyTypes) ->orderBy('id') ->chunk(200, function ($policies) use ($tenant, $baseline, $current, $scopeKey, &$created): void { foreach ($policies as $policy) { if (! $policy instanceof Policy) { continue; } $baselineVersion = $this->versionForRun($policy, $baseline); $currentVersion = $this->versionForRun($policy, $current); if (! $baselineVersion instanceof PolicyVersion || ! $currentVersion instanceof PolicyVersion) { continue; } $baselineAssignmentsHash = $baselineVersion->assignments_hash ?? null; $currentAssignmentsHash = $currentVersion->assignments_hash ?? null; if ($baselineAssignmentsHash === $currentAssignmentsHash) { continue; } $fingerprint = $this->hasher->fingerprint( tenantId: (int) $tenant->getKey(), scopeKey: $scopeKey, subjectType: 'assignment', subjectExternalId: (string) $policy->external_id, changeType: 'modified', baselineHash: (string) ($baselineAssignmentsHash ?? ''), currentHash: (string) ($currentAssignmentsHash ?? ''), ); $rawEvidence = [ 'change_type' => 'modified', 'summary' => 'Policy assignments changed', 'baseline' => [ 'policy_id' => $policy->external_id, 'policy_version_id' => $baselineVersion->getKey(), 'assignments_hash' => $baselineAssignmentsHash, ], 'current' => [ 'policy_id' => $policy->external_id, 'policy_version_id' => $currentVersion->getKey(), 'assignments_hash' => $currentAssignmentsHash, ], ]; Finding::query()->updateOrCreate( [ 'tenant_id' => $tenant->getKey(), 'fingerprint' => $fingerprint, ], [ 'finding_type' => Finding::FINDING_TYPE_DRIFT, 'scope_key' => $scopeKey, 'baseline_run_id' => $baseline->getKey(), 'current_run_id' => $current->getKey(), 'subject_type' => 'assignment', 'subject_external_id' => (string) $policy->external_id, 'severity' => Finding::SEVERITY_MEDIUM, 'status' => Finding::STATUS_NEW, 'acknowledged_at' => null, 'acknowledged_by_user_id' => null, 'evidence_jsonb' => $this->evidence->sanitize($rawEvidence), ], ); $created++; } }); return $created; } private function versionForRun(Policy $policy, InventorySyncRun $run): ?PolicyVersion { if (! $run->finished_at) { return null; } return PolicyVersion::query() ->where('tenant_id', $policy->tenant_id) ->where('policy_id', $policy->getKey()) ->where('captured_at', '<=', $run->finished_at) ->latest('captured_at') ->first(); } }