where('tenant_id', (int) $tenant->getKey()) ->where('policy_id', (int) $policy->getKey()) ->max('version_number'); $startingVersion = is_numeric($startingVersion) ? (int) $startingVersion : 0; $baselineVersionNumber = $startingVersion + 1; $currentVersionNumber = $startingVersion + 2; PolicyVersion::factory()->for($tenant)->for($policy)->create([ 'version_number' => $baselineVersionNumber, 'policy_type' => $policy->policy_type, 'platform' => $policy->platform, 'captured_at' => $baseline->finished_at->copy()->subMinute(), 'snapshot' => $baselineSnapshot, 'assignments' => [], ]); PolicyVersion::factory()->for($tenant)->for($policy)->create([ 'version_number' => $currentVersionNumber, 'policy_type' => $policy->policy_type, 'platform' => $policy->platform, 'captured_at' => $current->finished_at->copy()->subMinute(), 'snapshot' => $currentSnapshot, 'assignments' => [], ]); } it('auto-resolves open drift findings not detected in the latest run as no_longer_detected', function (): void { [, $tenant] = createUserWithTenant(role: 'manager'); $scopeKey = hash('sha256', 'scope-drift-stale-auto-resolve'); $policy = Policy::factory()->for($tenant)->create([ 'external_id' => 'policy-stale-1', 'policy_type' => 'deviceConfiguration', 'platform' => 'windows10', ]); $baseline1 = createInventorySyncOperationRun($tenant, [ 'selection_hash' => $scopeKey, 'selection_payload' => ['policy_types' => [$policy->policy_type]], 'status' => 'success', 'finished_at' => CarbonImmutable::parse('2026-02-20T00:00:00Z'), ]); $current1 = createInventorySyncOperationRun($tenant, [ 'selection_hash' => $scopeKey, 'selection_payload' => ['policy_types' => [$policy->policy_type]], 'status' => 'success', 'finished_at' => CarbonImmutable::parse('2026-02-21T00:00:00Z'), ]); seedPolicyVersionsForStaleTest( tenant: $tenant, policy: $policy, baseline: $baseline1, current: $current1, baselineSnapshot: ['setting' => 'old'], currentSnapshot: ['setting' => 'new'], ); $generator = app(DriftFindingGenerator::class); $created1 = $generator->generate($tenant, $baseline1, $current1, $scopeKey); expect($created1)->toBe(1); $finding = Finding::query() ->where('tenant_id', $tenant->getKey()) ->where('finding_type', Finding::FINDING_TYPE_DRIFT) ->where('scope_key', $scopeKey) ->where('subject_type', 'policy') ->firstOrFail(); expect($finding->status)->toBe(Finding::STATUS_NEW); $baseline2 = createInventorySyncOperationRun($tenant, [ 'selection_hash' => $scopeKey, 'selection_payload' => ['policy_types' => [$policy->policy_type]], 'status' => 'success', 'finished_at' => CarbonImmutable::parse('2026-02-24T00:00:00Z'), ]); $current2 = createInventorySyncOperationRun($tenant, [ 'selection_hash' => $scopeKey, 'selection_payload' => ['policy_types' => [$policy->policy_type]], 'status' => 'success', 'finished_at' => CarbonImmutable::parse('2026-02-25T00:00:00Z'), ]); seedPolicyVersionsForStaleTest( tenant: $tenant, policy: $policy, baseline: $baseline2, current: $current2, baselineSnapshot: ['setting' => 'same'], currentSnapshot: ['setting' => 'same'], ); $created2 = $generator->generate($tenant, $baseline2, $current2, $scopeKey); expect($created2)->toBe(0); $finding->refresh(); expect(Finding::query() ->where('tenant_id', $tenant->getKey()) ->where('finding_type', Finding::FINDING_TYPE_DRIFT) ->where('scope_key', $scopeKey) ->count())->toBe(1); expect($finding->status)->toBe(Finding::STATUS_RESOLVED) ->and($finding->resolved_reason)->toBe('no_longer_detected') ->and($finding->resolved_at?->toIso8601String())->toBe('2026-02-25T00:00:00+00:00'); });