scopeKey($targetScope); return $this->acquireSlotInternal("bulk_ops:tenant:{$tenantId}:scope:{$scopeKey}:slot:", $max); } private function acquireSlotInternal(string $prefix, int $max): ?Lock { $ttlSeconds = (int) config('tenantpilot.bulk_operations.concurrency.lock_ttl_seconds', $this->lockTtlSeconds); $ttlSeconds = max(1, $ttlSeconds); for ($slot = 0; $slot < $max; $slot++) { $lock = Cache::lock($prefix.$slot, $ttlSeconds); if ($lock->get()) { return $lock; } } return null; } /** * @param array{entra_tenant_id?: mixed, directory_context_id?: mixed} $targetScope */ private function scopeKey(array $targetScope): string { $entraTenantId = $targetScope['entra_tenant_id'] ?? null; $directoryContextId = $targetScope['directory_context_id'] ?? null; if (is_string($entraTenantId) && trim($entraTenantId) !== '') { return 'entra:'.trim($entraTenantId); } if (is_string($directoryContextId) && trim($directoryContextId) !== '') { return 'directory_context:'.trim($directoryContextId); } if (is_int($directoryContextId)) { return 'directory_context:'.$directoryContextId; } throw new InvalidArgumentException('Target scope must include entra_tenant_id or directory_context_id.'); } }