|
2 | 2 |
|
3 | 3 | namespace App\Http\Controllers\Api; |
4 | 4 |
|
5 | | -use App\Models\WorkflowNamespace; |
6 | 5 | use App\Support\ControlPlaneProtocol; |
7 | | -use App\Support\NamespaceWorkflowScope; |
| 6 | +use App\Support\HistoryRetentionEnforcer; |
8 | 7 | use App\Support\ProjectionDriftMetrics; |
9 | 8 | use App\Support\WorkflowTaskFailureMetrics; |
10 | 9 | use Illuminate\Http\JsonResponse; |
11 | 10 | use Illuminate\Http\Request; |
12 | | -use Illuminate\Support\Facades\Log; |
13 | | -use Workflow\V2\Enums\RunStatus; |
14 | | -use Workflow\V2\Models\WorkflowRunSummary; |
15 | 11 | use Workflow\V2\Support\ActivityTimeoutEnforcer; |
16 | 12 | use Workflow\V2\Support\TaskRepairCandidates; |
17 | 13 | use Workflow\V2\Support\TaskRepairPolicy; |
18 | | -use Workflow\V2\Support\WorkflowRunRetentionCleanup; |
19 | 14 | use Workflow\V2\TaskWatchdog; |
20 | 15 |
|
21 | 16 | class SystemController |
@@ -208,19 +203,9 @@ public function retentionStatus(Request $request): JsonResponse |
208 | 203 | ]); |
209 | 204 | $limit = min(100, (int) ($validated['limit'] ?? 100)); |
210 | 205 |
|
211 | | - $ns = WorkflowNamespace::query()->where('name', $namespace)->first(); |
212 | | - $retentionDays = $ns?->retention_days ?? (int) config('server.history.retention_days', 30); |
| 206 | + $retentionDays = HistoryRetentionEnforcer::retentionDays($namespace); |
213 | 207 | $cutoff = now()->subDays($retentionDays); |
214 | | - |
215 | | - $expiredRunIds = NamespaceWorkflowScope::runSummaryQuery($namespace) |
216 | | - ->whereIn('workflow_run_summaries.status_bucket', ['completed', 'failed']) |
217 | | - ->whereNotNull('workflow_run_summaries.closed_at') |
218 | | - ->whereNull('workflow_run_summaries.archived_at') |
219 | | - ->where('workflow_run_summaries.closed_at', '<', $cutoff) |
220 | | - ->orderBy('workflow_run_summaries.closed_at') |
221 | | - ->limit($limit) |
222 | | - ->pluck('workflow_run_summaries.id') |
223 | | - ->all(); |
| 208 | + $expiredRunIds = HistoryRetentionEnforcer::expiredRunIds($namespace, $limit); |
224 | 209 |
|
225 | 210 | return ControlPlaneProtocol::json([ |
226 | 211 | 'namespace' => $namespace, |
@@ -252,135 +237,10 @@ public function retentionEnforcePass(Request $request): JsonResponse |
252 | 237 | $validated['run_ids'] ?? [], |
253 | 238 | )); |
254 | 239 |
|
255 | | - if ($runIds === []) { |
256 | | - $ns = WorkflowNamespace::query()->where('name', $namespace)->first(); |
257 | | - $retentionDays = $ns?->retention_days ?? (int) config('server.history.retention_days', 30); |
258 | | - $cutoff = now()->subDays($retentionDays); |
259 | | - |
260 | | - $runIds = NamespaceWorkflowScope::runSummaryQuery($namespace) |
261 | | - ->whereIn('workflow_run_summaries.status_bucket', ['completed', 'failed']) |
262 | | - ->whereNotNull('workflow_run_summaries.closed_at') |
263 | | - ->whereNull('workflow_run_summaries.archived_at') |
264 | | - ->where('workflow_run_summaries.closed_at', '<', $cutoff) |
265 | | - ->orderBy('workflow_run_summaries.closed_at') |
266 | | - ->limit($limit) |
267 | | - ->pluck('workflow_run_summaries.id') |
268 | | - ->all(); |
269 | | - } |
270 | | - |
271 | | - if ($runIds === []) { |
272 | | - return ControlPlaneProtocol::json([ |
273 | | - 'processed' => 0, |
274 | | - 'pruned' => 0, |
275 | | - 'skipped' => 0, |
276 | | - 'failed' => 0, |
277 | | - 'results' => [], |
278 | | - ]); |
279 | | - } |
280 | | - |
281 | | - $results = []; |
282 | | - $pruned = 0; |
283 | | - $skipped = 0; |
284 | | - $failed = 0; |
285 | | - |
286 | | - foreach ($runIds as $runId) { |
287 | | - try { |
288 | | - $result = $this->pruneRun($namespace, $runId); |
289 | | - |
290 | | - if ($result['pruned']) { |
291 | | - $pruned++; |
292 | | - $results[] = [ |
293 | | - 'run_id' => $runId, |
294 | | - 'outcome' => 'pruned', |
295 | | - 'history_events_deleted' => $result['history_events_deleted'], |
296 | | - 'tasks_deleted' => $result['tasks_deleted'], |
297 | | - 'deleted' => $result['deleted'], |
298 | | - ]; |
299 | | - } else { |
300 | | - $skipped++; |
301 | | - $results[] = [ |
302 | | - 'run_id' => $runId, |
303 | | - 'outcome' => 'skipped', |
304 | | - 'reason' => $result['reason'], |
305 | | - ]; |
306 | | - } |
307 | | - } catch (\Throwable $e) { |
308 | | - $failed++; |
309 | | - $results[] = [ |
310 | | - 'run_id' => $runId, |
311 | | - 'outcome' => 'error', |
312 | | - 'reason' => $e->getMessage(), |
313 | | - ]; |
314 | | - } |
315 | | - } |
316 | | - |
317 | | - $hasFailures = $failed > 0; |
318 | | - |
319 | | - return ControlPlaneProtocol::json([ |
320 | | - 'processed' => count($runIds), |
321 | | - 'pruned' => $pruned, |
322 | | - 'skipped' => $skipped, |
323 | | - 'failed' => $failed, |
324 | | - 'results' => $results, |
325 | | - ], $hasFailures ? 207 : 200); |
326 | | - } |
327 | | - |
328 | | - /** |
329 | | - * @return array{pruned: bool, reason: string|null, history_events_deleted: int, tasks_deleted: int, deleted: array<string, int>} |
330 | | - */ |
331 | | - private function pruneRun(string $namespace, string $runId): array |
332 | | - { |
333 | | - $summary = WorkflowRunSummary::query() |
334 | | - ->where('id', $runId) |
335 | | - ->where('namespace', $namespace) |
336 | | - ->first(); |
337 | | - |
338 | | - if (! $summary) { |
339 | | - return $this->skippedRetentionResult('run_not_found'); |
340 | | - } |
| 240 | + $report = HistoryRetentionEnforcer::runPass($namespace, $limit, $runIds); |
341 | 241 |
|
342 | | - $status = is_string($summary->status) ? RunStatus::tryFrom($summary->status) : null; |
| 242 | + $hasFailures = $report['failed'] > 0; |
343 | 243 |
|
344 | | - if ($status === null || ! $status->isTerminal()) { |
345 | | - return $this->skippedRetentionResult('run_not_terminal'); |
346 | | - } |
347 | | - |
348 | | - if ($summary->archived_at !== null) { |
349 | | - return $this->skippedRetentionResult('run_archived'); |
350 | | - } |
351 | | - |
352 | | - // Audit log before deletion |
353 | | - Log::info('retention_prune_run', [ |
354 | | - 'namespace' => $namespace, |
355 | | - 'run_id' => $runId, |
356 | | - 'workflow_instance_id' => $summary->workflow_instance_id, |
357 | | - 'workflow_type' => $summary->workflow_type, |
358 | | - 'status' => $summary->status, |
359 | | - 'closed_at' => $summary->closed_at?->toIso8601String(), |
360 | | - ]); |
361 | | - |
362 | | - $report = WorkflowRunRetentionCleanup::pruneRun($runId); |
363 | | - |
364 | | - return [ |
365 | | - 'pruned' => true, |
366 | | - 'reason' => null, |
367 | | - 'history_events_deleted' => $report['history_events_deleted'] ?? 0, |
368 | | - 'tasks_deleted' => $report['tasks_deleted'] ?? 0, |
369 | | - 'deleted' => $report, |
370 | | - ]; |
371 | | - } |
372 | | - |
373 | | - /** |
374 | | - * @return array{pruned: false, reason: string, history_events_deleted: 0, tasks_deleted: 0, deleted: array<string, int>} |
375 | | - */ |
376 | | - private function skippedRetentionResult(string $reason): array |
377 | | - { |
378 | | - return [ |
379 | | - 'pruned' => false, |
380 | | - 'reason' => $reason, |
381 | | - 'history_events_deleted' => 0, |
382 | | - 'tasks_deleted' => 0, |
383 | | - 'deleted' => [], |
384 | | - ]; |
| 244 | + return ControlPlaneProtocol::json($report, $hasFailures ? 207 : 200); |
385 | 245 | } |
386 | 246 | } |
0 commit comments