$severityCounts * @param list $uncoveredTypes */ private function __construct( public readonly string $state, public readonly ?string $message, public readonly ?string $profileName, public readonly ?int $profileId, public readonly ?int $snapshotId, public readonly ?int $operationRunId, public readonly ?int $findingsCount, public readonly array $severityCounts, public readonly ?string $lastComparedHuman, public readonly ?string $lastComparedIso, public readonly ?string $failureReason, public readonly ?string $coverageStatus = null, public readonly ?int $uncoveredTypesCount = null, public readonly array $uncoveredTypes = [], public readonly ?string $fidelity = null, ) {} public static function forTenant(?Tenant $tenant): self { if (! $tenant instanceof Tenant) { return self::empty('no_tenant', 'No tenant selected.'); } $assignment = BaselineTenantAssignment::query() ->where('tenant_id', $tenant->getKey()) ->first(); if (! $assignment instanceof BaselineTenantAssignment) { return self::empty( 'no_assignment', 'This tenant has no baseline assignment. A workspace manager can assign a baseline profile to this tenant.', ); } $profile = $assignment->baselineProfile; if (! $profile instanceof BaselineProfile) { return self::empty( 'no_assignment', 'The assigned baseline profile no longer exists.', ); } $profileName = (string) $profile->name; $profileId = (int) $profile->getKey(); $snapshotId = $profile->active_snapshot_id !== null ? (int) $profile->active_snapshot_id : null; if ($snapshotId === null) { return self::empty( 'no_snapshot', 'The baseline profile has no active snapshot yet. A workspace manager needs to capture a snapshot first.', profileName: $profileName, profileId: $profileId, ); } $latestRun = OperationRun::query() ->where('tenant_id', $tenant->getKey()) ->where('type', 'baseline_compare') ->latest('id') ->first(); [$coverageStatus, $uncoveredTypes, $fidelity] = self::coverageInfoForRun($latestRun); // Active run (queued/running) if ($latestRun instanceof OperationRun && in_array($latestRun->status, ['queued', 'running'], true)) { return new self( state: 'comparing', message: 'A baseline comparison is currently in progress.', profileName: $profileName, profileId: $profileId, snapshotId: $snapshotId, operationRunId: (int) $latestRun->getKey(), findingsCount: null, severityCounts: [], lastComparedHuman: null, lastComparedIso: null, failureReason: null, coverageStatus: $coverageStatus, uncoveredTypesCount: $uncoveredTypes !== [] ? count($uncoveredTypes) : 0, uncoveredTypes: $uncoveredTypes, fidelity: $fidelity, ); } // Failed run — explicit error state if ($latestRun instanceof OperationRun && $latestRun->outcome === 'failed') { $failureSummary = is_array($latestRun->failure_summary) ? $latestRun->failure_summary : []; $failureReason = $failureSummary['message'] ?? $failureSummary['reason'] ?? 'The comparison job failed. Check the run details for more information.'; return new self( state: 'failed', message: (string) $failureReason, profileName: $profileName, profileId: $profileId, snapshotId: $snapshotId, operationRunId: (int) $latestRun->getKey(), findingsCount: null, severityCounts: [], lastComparedHuman: $latestRun->finished_at?->diffForHumans(), lastComparedIso: $latestRun->finished_at?->toIso8601String(), failureReason: (string) $failureReason, coverageStatus: $coverageStatus, uncoveredTypesCount: $uncoveredTypes !== [] ? count($uncoveredTypes) : 0, uncoveredTypes: $uncoveredTypes, fidelity: $fidelity, ); } $lastComparedHuman = null; $lastComparedIso = null; if ($latestRun instanceof OperationRun && $latestRun->finished_at !== null) { $lastComparedHuman = $latestRun->finished_at->diffForHumans(); $lastComparedIso = $latestRun->finished_at->toIso8601String(); } $scopeKey = 'baseline_profile:'.$profile->getKey(); // Single grouped query instead of 4 separate COUNT queries $severityRows = Finding::query() ->where('tenant_id', $tenant->getKey()) ->where('finding_type', Finding::FINDING_TYPE_DRIFT) ->where('source', 'baseline.compare') ->where('scope_key', $scopeKey) ->whereIn('status', Finding::openStatusesForQuery()) ->selectRaw('severity, count(*) as cnt') ->groupBy('severity') ->pluck('cnt', 'severity'); $totalFindings = (int) $severityRows->sum(); $severityCounts = [ 'high' => (int) ($severityRows[Finding::SEVERITY_HIGH] ?? 0), 'medium' => (int) ($severityRows[Finding::SEVERITY_MEDIUM] ?? 0), 'low' => (int) ($severityRows[Finding::SEVERITY_LOW] ?? 0), ]; if ($totalFindings > 0) { return new self( state: 'ready', message: null, profileName: $profileName, profileId: $profileId, snapshotId: $snapshotId, operationRunId: $latestRun instanceof OperationRun ? (int) $latestRun->getKey() : null, findingsCount: $totalFindings, severityCounts: $severityCounts, lastComparedHuman: $lastComparedHuman, lastComparedIso: $lastComparedIso, failureReason: null, coverageStatus: $coverageStatus, uncoveredTypesCount: $uncoveredTypes !== [] ? count($uncoveredTypes) : 0, uncoveredTypes: $uncoveredTypes, fidelity: $fidelity, ); } if ($latestRun instanceof OperationRun && $latestRun->status === 'completed' && in_array($latestRun->outcome, ['succeeded', 'partially_succeeded'], true)) { return new self( state: 'ready', message: $latestRun->outcome === 'succeeded' ? 'No open drift findings for this baseline comparison. The tenant matches the baseline.' : 'Comparison completed with warnings. Findings may be incomplete due to missing coverage.', profileName: $profileName, profileId: $profileId, snapshotId: $snapshotId, operationRunId: (int) $latestRun->getKey(), findingsCount: 0, severityCounts: $severityCounts, lastComparedHuman: $lastComparedHuman, lastComparedIso: $lastComparedIso, failureReason: null, coverageStatus: $coverageStatus, uncoveredTypesCount: $uncoveredTypes !== [] ? count($uncoveredTypes) : 0, uncoveredTypes: $uncoveredTypes, fidelity: $fidelity, ); } return new self( state: 'idle', message: 'Baseline profile is assigned and has a snapshot. Run "Compare Now" to check for drift.', profileName: $profileName, profileId: $profileId, snapshotId: $snapshotId, operationRunId: null, findingsCount: null, severityCounts: $severityCounts, lastComparedHuman: $lastComparedHuman, lastComparedIso: $lastComparedIso, failureReason: null, coverageStatus: $coverageStatus, uncoveredTypesCount: $uncoveredTypes !== [] ? count($uncoveredTypes) : 0, uncoveredTypes: $uncoveredTypes, fidelity: $fidelity, ); } /** * Create a DTO for widget consumption (only open/new findings). */ public static function forWidget(?Tenant $tenant): self { if (! $tenant instanceof Tenant) { return self::empty('no_tenant', null); } $assignment = BaselineTenantAssignment::query() ->where('tenant_id', $tenant->getKey()) ->with('baselineProfile') ->first(); if (! $assignment instanceof BaselineTenantAssignment || $assignment->baselineProfile === null) { return self::empty('no_assignment', null); } $profile = $assignment->baselineProfile; $scopeKey = 'baseline_profile:'.$profile->getKey(); $severityRows = Finding::query() ->where('tenant_id', $tenant->getKey()) ->where('finding_type', Finding::FINDING_TYPE_DRIFT) ->where('source', 'baseline.compare') ->where('scope_key', $scopeKey) ->where('status', Finding::STATUS_NEW) ->selectRaw('severity, count(*) as cnt') ->groupBy('severity') ->pluck('cnt', 'severity'); $totalFindings = (int) $severityRows->sum(); $latestRun = OperationRun::query() ->where('tenant_id', $tenant->getKey()) ->where('type', 'baseline_compare') ->where('context->baseline_profile_id', (string) $profile->getKey()) ->whereNotNull('completed_at') ->latest('completed_at') ->first(); return new self( state: $totalFindings > 0 ? 'ready' : 'idle', message: null, profileName: (string) $profile->name, profileId: (int) $profile->getKey(), snapshotId: $profile->active_snapshot_id !== null ? (int) $profile->active_snapshot_id : null, operationRunId: $latestRun instanceof OperationRun ? (int) $latestRun->getKey() : null, findingsCount: $totalFindings, severityCounts: [ 'high' => (int) ($severityRows[Finding::SEVERITY_HIGH] ?? 0), 'medium' => (int) ($severityRows[Finding::SEVERITY_MEDIUM] ?? 0), 'low' => (int) ($severityRows[Finding::SEVERITY_LOW] ?? 0), ], lastComparedHuman: $latestRun?->finished_at?->diffForHumans(), lastComparedIso: $latestRun?->finished_at?->toIso8601String(), failureReason: null, ); } /** * @return array{0: ?string, 1: list, 2: ?string} */ private static function coverageInfoForRun(?OperationRun $run): array { if (! $run instanceof OperationRun) { return [null, [], null]; } $context = is_array($run->context) ? $run->context : []; $baselineCompare = $context['baseline_compare'] ?? null; if (! is_array($baselineCompare)) { return [null, [], null]; } $coverage = $baselineCompare['coverage'] ?? null; $coverage = is_array($coverage) ? $coverage : []; $proof = $coverage['proof'] ?? null; $proof = is_bool($proof) ? $proof : null; $uncoveredTypes = $coverage['uncovered_types'] ?? null; $uncoveredTypes = is_array($uncoveredTypes) ? array_values(array_filter($uncoveredTypes, 'is_string')) : []; $uncoveredTypes = array_values(array_unique(array_filter(array_map('trim', $uncoveredTypes), fn (string $type): bool => $type !== ''))); sort($uncoveredTypes, SORT_STRING); $coverageStatus = null; if ($proof === false) { $coverageStatus = 'unproven'; } elseif ($uncoveredTypes !== []) { $coverageStatus = 'warning'; } elseif ($proof === true) { $coverageStatus = 'ok'; } $fidelity = $baselineCompare['fidelity'] ?? null; $fidelity = is_string($fidelity) ? trim($fidelity) : null; $fidelity = $fidelity !== '' ? $fidelity : null; return [$coverageStatus, $uncoveredTypes, $fidelity]; } private static function empty( string $state, ?string $message, ?string $profileName = null, ?int $profileId = null, ): self { return new self( state: $state, message: $message, profileName: $profileName, profileId: $profileId, snapshotId: null, operationRunId: null, findingsCount: null, severityCounts: [], lastComparedHuman: null, lastComparedIso: null, failureReason: null, ); } }