environment(['local', 'testing'])) { $this->error('This cleanup command is limited to local and testing environments.'); return self::FAILURE; } $types = $this->normalizedTypes(); $workspaceIds = array_values(array_filter( array_map( static fn (mixed $workspaceId): int => is_numeric($workspaceId) ? (int) $workspaceId : 0, (array) $this->option('workspace'), ), static fn (int $workspaceId): bool => $workspaceId > 0, )); $tenantIds = $this->resolveTenantIds(array_values(array_filter((array) $this->option('tenant')))); $limit = max(1, (int) $this->option('limit')); $dryRun = ! (bool) $this->option('force'); $query = OperationRun::query() ->whereIn('type', $types) ->orderBy('id') ->limit($limit); if ($workspaceIds !== []) { $query->whereIn('workspace_id', $workspaceIds); } if ($tenantIds !== []) { $query->whereIn('tenant_id', $tenantIds); } $candidates = $query->get(); $matched = $candidates ->filter(static fn (OperationRun $run): bool => $run->hasLegacyBaselineGapPayload()) ->values(); if ($matched->isEmpty()) { $this->info('No legacy baseline gap runs matched the current filters.'); return self::SUCCESS; } $this->table( ['Run', 'Type', 'Tenant', 'Workspace', 'Legacy signal'], $matched ->map(fn (OperationRun $run): array => [ 'Run' => (string) $run->getKey(), 'Type' => (string) $run->type, 'Tenant' => $run->tenant_id !== null ? (string) $run->tenant_id : '—', 'Workspace' => $run->workspace_id !== null ? (string) $run->workspace_id : '—', 'Legacy signal' => $this->legacySignal($run), ]) ->all(), ); if ($dryRun) { $this->warn(sprintf( 'Dry run: matched %d legacy baseline run(s). Re-run with --force to delete them.', $matched->count(), )); return self::SUCCESS; } OperationRun::query() ->whereKey($matched->modelKeys()) ->delete(); $this->info(sprintf('Deleted %d legacy baseline run(s).', $matched->count())); return self::SUCCESS; } /** * @return array */ private function normalizedTypes(): array { $types = array_values(array_unique(array_filter( array_map( static fn (mixed $type): ?string => is_string($type) && trim($type) !== '' ? trim($type) : null, (array) $this->option('type'), ), ))); if ($types === []) { return ['baseline_compare', 'baseline_capture']; } return array_values(array_filter( $types, static fn (string $type): bool => in_array($type, ['baseline_compare', 'baseline_capture'], true), )); } /** * @param array $tenantIdentifiers * @return array */ private function resolveTenantIds(array $tenantIdentifiers): array { if ($tenantIdentifiers === []) { return []; } $tenantIds = []; foreach ($tenantIdentifiers as $identifier) { $tenant = Tenant::query()->forTenant($identifier)->first(); if ($tenant instanceof Tenant) { $tenantIds[] = (int) $tenant->getKey(); } } return array_values(array_unique($tenantIds)); } private function legacySignal(OperationRun $run): string { $byReason = $run->baselineGapEnvelope()['by_reason'] ?? null; $byReason = is_array($byReason) ? $byReason : []; if (array_key_exists('policy_not_found', $byReason)) { return 'legacy_reason_code'; } return 'legacy_subject_shape'; } }