pendingKey(), $archiveUid); } public function pop(int $timeout = 5): ?string { $result = Redis::brPop([$this->pendingKey()], $timeout); if (!is_array($result) || count($result) < 2) { return null; } return (string) $result[1]; } public function retryLater(string $archiveUid, string $error): void { $retryKey = $this->retryKey($archiveUid); $retryCount = (int) Redis::incr($retryKey); Redis::setEx($this->errorKey($archiveUid), 86400, $error); if ($retryCount > $this->maxRetries()) { Redis::lPush($this->failedKey(), $archiveUid); return; } $delay = $this->baseDelaySeconds() * (2 ** max(0, $retryCount - 1)); Redis::zAdd($this->delayedKey(), time() + $delay, $archiveUid); } public function releaseDueDelayed(): int { $now = time(); $items = Redis::zRangeByScore($this->delayedKey(), '-inf', (string) $now, ['limit' => [0, 100]]); if (!is_array($items) || $items === []) { return 0; } foreach ($items as $archiveUid) { Redis::zRem($this->delayedKey(), $archiveUid); Redis::lPush($this->pendingKey(), $archiveUid); } return count($items); } public function clearRetry(string $archiveUid): void { Redis::del($this->retryKey($archiveUid), $this->errorKey($archiveUid)); } public function blockTimeout(): int { return (int) config('queue.ai_metadata.block_timeout', 5); } private function pendingKey(): string { return config('queue.ai_metadata.pending', 'proofdb:ai:metadata:pending'); } private function delayedKey(): string { return config('queue.ai_metadata.delayed', 'proofdb:ai:metadata:delayed'); } private function failedKey(): string { return config('queue.ai_metadata.failed', 'proofdb:ai:metadata:failed'); } private function retryKey(string $archiveUid): string { return config('queue.ai_metadata.retry_prefix', 'proofdb:ai:metadata:retry:') . $archiveUid; } private function errorKey(string $archiveUid): string { return config('queue.ai_metadata.error_prefix', 'proofdb:ai:metadata:error:') . $archiveUid; } private function maxRetries(): int { return (int) config('queue.ai_metadata.max_retries', 5); } private function baseDelaySeconds(): int { return (int) config('queue.ai_metadata.base_delay_seconds', 60); } }