*/ public function coveredTypes(): array { $coveredTypes = config('tenantpilot.operations.lifecycle.covered_types', []); return is_array($coveredTypes) ? $coveredTypes : []; } /** * @return array{ * job_class?: class-string, * queued_stale_after_seconds:int, * running_stale_after_seconds:int, * expected_max_runtime_seconds:?int, * direct_failed_bridge:bool, * scheduled_reconciliation:bool * }|null */ public function definition(string $operationType): ?array { $operationType = trim($operationType); if ($operationType === '') { return null; } $definition = $this->coveredTypes()[$operationType] ?? null; if (! is_array($definition)) { return null; } return [ 'job_class' => is_string($definition['job_class'] ?? null) ? $definition['job_class'] : null, 'queued_stale_after_seconds' => max(1, (int) ($definition['queued_stale_after_seconds'] ?? 300)), 'running_stale_after_seconds' => max(1, (int) ($definition['running_stale_after_seconds'] ?? 900)), 'expected_max_runtime_seconds' => is_numeric($definition['expected_max_runtime_seconds'] ?? null) ? max(1, (int) $definition['expected_max_runtime_seconds']) : null, 'direct_failed_bridge' => (bool) ($definition['direct_failed_bridge'] ?? false), 'scheduled_reconciliation' => (bool) ($definition['scheduled_reconciliation'] ?? true), ]; } public function supports(string $operationType): bool { return $this->definition($operationType) !== null; } /** * @return list */ public function coveredTypeNames(): array { return array_values(array_keys($this->coveredTypes())); } public function queuedStaleAfterSeconds(string $operationType): int { return (int) ($this->definition($operationType)['queued_stale_after_seconds'] ?? 300); } public function runningStaleAfterSeconds(string $operationType): int { return (int) ($this->definition($operationType)['running_stale_after_seconds'] ?? 900); } public function expectedMaxRuntimeSeconds(string $operationType): ?int { $expectedMaxRuntimeSeconds = $this->definition($operationType)['expected_max_runtime_seconds'] ?? null; return is_int($expectedMaxRuntimeSeconds) ? $expectedMaxRuntimeSeconds : null; } public function requiresDirectFailedBridge(string $operationType): bool { return (bool) ($this->definition($operationType)['direct_failed_bridge'] ?? false); } public function supportsScheduledReconciliation(string $operationType): bool { return (bool) ($this->definition($operationType)['scheduled_reconciliation'] ?? false); } public function reconciliationBatchLimit(): int { return max(1, (int) config('tenantpilot.operations.lifecycle.reconciliation.batch_limit', 100)); } public function reconciliationScheduleMinutes(): int { return max(1, (int) config('tenantpilot.operations.lifecycle.reconciliation.schedule_minutes', 5)); } public function retryAfterSafetyMarginSeconds(): int { return max(1, (int) config('queue.lifecycle_invariants.retry_after_safety_margin', 30)); } public function queueConnection(string $operationType): ?string { $jobClass = $this->jobClass($operationType); if ($jobClass === null || ! class_exists($jobClass)) { return null; } $connection = Arr::get(get_class_vars($jobClass), 'connection'); return is_string($connection) && trim($connection) !== '' ? trim($connection) : config('queue.default'); } public function queueRetryAfterSeconds(?string $connection = null): ?int { $connection = is_string($connection) && trim($connection) !== '' ? trim($connection) : (string) config('queue.default', 'database'); $retryAfter = config("queue.connections.{$connection}.retry_after"); if (is_numeric($retryAfter)) { return max(1, (int) $retryAfter); } $databaseRetryAfter = config('queue.connections.database.retry_after'); return is_numeric($databaseRetryAfter) ? max(1, (int) $databaseRetryAfter) : null; } public function jobClass(string $operationType): ?string { $jobClass = $this->definition($operationType)['job_class'] ?? null; return is_string($jobClass) && $jobClass !== '' ? $jobClass : null; } }