active()->create([ 'workspace_id' => $tenant->workspace_id, 'scope_jsonb' => ['policy_types' => ['deviceConfiguration']], ]); $snapshot = BaselineSnapshot::factory()->create([ 'workspace_id' => $tenant->workspace_id, 'baseline_profile_id' => $profile->getKey(), ]); $profile->update(['active_snapshot_id' => $snapshot->getKey()]); return [$user, $tenant, $profile, $snapshot]; } function runBaselineCompareForSnapshot( User $user, Tenant $tenant, BaselineProfile $profile, BaselineSnapshot $snapshot, ): OperationRun { $operationRuns = app(OperationRunService::class); $run = $operationRuns->ensureRunWithIdentity( tenant: $tenant, type: OperationRunType::BaselineCompare->value, identityInputs: ['baseline_profile_id' => (int) $profile->getKey()], context: [ 'baseline_profile_id' => (int) $profile->getKey(), 'baseline_snapshot_id' => (int) $snapshot->getKey(), 'effective_scope' => ['policy_types' => ['deviceConfiguration']], ], initiator: $user, ); $job = new CompareBaselineToTenantJob($run); $job->handle( app(DriftHasher::class), app(BaselineSnapshotIdentity::class), app(AuditLogger::class), $operationRuns, ); return $run->fresh(); } it('resolves stale baseline findings after a fully successful compare', function (): void { [$user, $tenant, $profile, $firstSnapshot] = createBaselineOperabilityFixture(); BaselineSnapshotItem::factory()->create([ 'baseline_snapshot_id' => $firstSnapshot->getKey(), 'subject_type' => 'policy', 'subject_external_id' => 'stale-policy', 'policy_type' => 'deviceConfiguration', 'baseline_hash' => hash('sha256', 'baseline-content'), 'meta_jsonb' => ['display_name' => 'Stale Policy'], ]); $firstRun = runBaselineCompareForSnapshot($user, $tenant, $profile, $firstSnapshot); $finding = Finding::query() ->where('tenant_id', (int) $tenant->getKey()) ->where('source', 'baseline.compare') ->first(); expect($finding)->not->toBeNull(); expect($finding?->status)->toBe(Finding::STATUS_NEW); $secondSnapshot = BaselineSnapshot::factory()->create([ 'workspace_id' => $tenant->workspace_id, 'baseline_profile_id' => $profile->getKey(), ]); $profile->update(['active_snapshot_id' => $secondSnapshot->getKey()]); $firstRun->update(['completed_at' => now()->subMinute()]); $secondRun = runBaselineCompareForSnapshot($user, $tenant, $profile, $secondSnapshot); $finding->refresh(); expect($finding->status)->toBe(Finding::STATUS_RESOLVED); expect($finding->resolved_reason)->toBe('no_longer_drifting'); expect($finding->resolved_at)->not->toBeNull(); expect($finding->current_operation_run_id)->toBe((int) $secondRun->getKey()); }); dataset('baseline auto close safety gates', [ 'safe and enabled' => [ OperationRunOutcome::Succeeded->value, ['total' => 2, 'processed' => 2, 'failed' => 0], null, true, ], 'disabled by workspace setting' => [ OperationRunOutcome::Succeeded->value, ['total' => 2, 'processed' => 2, 'failed' => 0], false, false, ], 'partially succeeded outcome' => [ OperationRunOutcome::PartiallySucceeded->value, ['total' => 2, 'processed' => 2, 'failed' => 0], null, false, ], 'failed outcome' => [ OperationRunOutcome::Failed->value, ['total' => 2, 'processed' => 2, 'failed' => 0], null, false, ], 'incomplete processed count' => [ OperationRunOutcome::Succeeded->value, ['total' => 2, 'processed' => 1, 'failed' => 0], null, false, ], 'failed work recorded' => [ OperationRunOutcome::Succeeded->value, ['total' => 2, 'processed' => 2, 'failed' => 1], null, false, ], 'missing counters' => [ OperationRunOutcome::Succeeded->value, ['processed' => 2, 'failed' => 0], null, false, ], ]); it('gates auto close on outcome completion counts and workspace setting', function ( string $outcome, array $summaryCounts, ?bool $settingValue, bool $expected, ): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); if ($settingValue !== null) { WorkspaceSetting::query()->create([ 'workspace_id' => (int) $tenant->workspace_id, 'domain' => 'baseline', 'key' => 'auto_close_enabled', 'value' => $settingValue, 'updated_by_user_id' => (int) $user->getKey(), ]); } $run = OperationRun::factory()->create([ 'workspace_id' => (int) $tenant->workspace_id, 'tenant_id' => (int) $tenant->getKey(), 'user_id' => (int) $user->getKey(), 'type' => OperationRunType::BaselineCompare->value, 'status' => OperationRunStatus::Completed->value, 'outcome' => $outcome, 'summary_counts' => $summaryCounts, 'completed_at' => now(), ]); expect(app(BaselineAutoCloseService::class)->shouldAutoClose($tenant, $run))->toBe($expected); })->with('baseline auto close safety gates');