{ "run_id": "20260831T120914Z", "created_at": "2026-08-31T12:09:14.858871+00:00", "runtime": { "name": "ollama", "kind": "ollama", "base_url": "http://127.0.0.1:11434", "model": "qwen3.8:27b-mlx" }, "host": { "platform": "macOS-15.6-arm64-arm-64bit", "machine": "arm64", "python": "3.12.8" }, "package_versions": { "vllm": "0.28.0+cpu", "vllm-metal": "0.4.0.dev20260831112704", "mlx": "0.32.0", "mlx-lm": "0.31.3", "mlx-vlm": "0.6.4" }, "settings": { "temperature": 0, "seed": 42, "max_tokens": 3072, "context_tokens": 8192, "raw_completion": false, "chat_template": true, "thinking": false, "ollama_speculative_drafting": false }, "warmup": { "task": "warmup", "runtime": "ollama", "model": "qwen3.8:27b-mlx", "started_at": "2026-08-31T12:05:22.804254+00:00", "prompt_chars": 27, "max_tokens": 8, "ttft_s": 0.38745154201751575, "total_s": 0.3874947500298731, "prompt_tokens": 41, "output_tokens": 1, "decode_tps": 14.664458922599563, "finish_reason": "stop", "output_chars": 5, "output_sha256": "c2e3ac47f4a325469c1a2d5f117e463ec943c721986d5d9f09ac4540b7d80526", "output": "READY", "error": null }, "tasks": { "python_async_cache": "Act as a senior Python engineer. Implement a production-ready, single-file Python 3.12 module\ncontaining a generic AsyncTTLCache[K, V]. Requirements: weighted LRU capacity; per-entry TTL based\non time.monotonic; single-flight get_or_create so concurrent misses for one key invoke the async\nfactory exactly once; cancellation of one waiter must not cancel the shared factory; exceptions are\nnever cached; invalidating a key while its factory is running must prevent stale publication; never\nhold the cache lock while awaiting user code; clear(), invalidate(), and immutable hit/miss/eviction\nstatistics. Include concise pytest-asyncio tests covering races, cancellation, expiry, weighted\ncapacity, factory failure, and invalidation during creation. Use only the standard library plus\npytest. Output only the complete module, without Markdown fences or prose.", "php_idempotent_transfer": "Act as a senior Laravel 12 engineer. Produce one self-contained PHP file that demonstrates an\nidempotent money-transfer service and its PHPUnit tests. Store money as integer cents. Requirements:\nvalidate positive amount and distinct accounts; use a database transaction; lock both account rows\nin deterministic ID order; reject insufficient funds; use a unique client idempotency key so retries\nreturn the original transfer; retry deadlocks without duplicating effects; persist an outbox event\nin the same transaction; and prove with tests that duplicate and competing requests preserve\nbalances, create one transfer, and create one outbox event. Include minimal model/table assumptions\nas comments.\nOutput only valid PHP, without Markdown fences or prose.", "typescript_dag_scheduler": "Act as a senior TypeScript engineer. Implement a strict-mode, dependency-free DAG task scheduler in\none TypeScript file. Each task has an ID, dependency IDs, and an async run(signal) function.\nRequirements: validate unknown dependencies and duplicate IDs; report an explicit cycle path; stable\ninput-order scheduling; configurable maximum concurrency; AbortSignal support; fail fast after the\nfirst error while awaiting already-running tasks; never start dependents of failed tasks; return a\nMap of typed results on success; and avoid unhandled promise rejections. Include a compact test\nharness covering a diamond graph, concurrency enforcement, stable order, cycle detection, external\ncancellation, and rejection. Output only TypeScript, without Markdown fences or prose." }, "results": [ { "task": "python_async_cache", "runtime": "ollama", "model": "qwen3.8:27b-mlx", "started_at": "2026-08-31T12:05:23.192111+00:00", "prompt_chars": 857, "max_tokens": 3072, "ttft_s": 1.021859125001356, "total_s": 56.40731879102532, "prompt_tokens": 211, "output_tokens": 3072, "decode_tps": 55.37840341105521, "finish_reason": "length", "output_chars": 12165, "output_sha256": "a3355a481519820f9021b6846e3f784fba8aa922d3317c5a84d6762447d9dd97", "output": "import asyncio\nimport time\nfrom collections import OrderedDict\nfrom dataclasses import dataclass, field\nfrom typing import (\n Any,\n Awaitable,\n Callable,\n Generic,\n Optional,\n TypeVar,\n)\n\nK = TypeVar(\"K\")\nV = TypeVar(\"V\")\n\n\n@dataclass(frozen=True)\nclass CacheStats:\n hits: int\n misses: int\n evictions: int\n factory_calls: int\n factory_errors: int\n invalidations: int\n\n\nclass _Entry(Generic[V]):\n __slots__ = (\"value\", \"expires_at\", \"weight\")\n\n def __init__(self, value: V, expires_at: float, weight: int) -> None:\n self.value = value\n self.expires_at = expires_at\n self.weight = weight\n\n\nclass _Inflight(Generic[V]):\n __slots__ = (\"event\", \"value\", \"error\", \"cancelled\")\n\n def __init__(self) -> None:\n self.event = asyncio.Event()\n self.value: Optional[V] = None\n self.error: Optional[BaseException] = None\n self.cancelled = False\n\n\nclass AsyncTTLCache(Generic[K, V]):\n def __init__(\n self,\n max_weight: int,\n default_ttl: float,\n *,\n weight_fn: Optional[Callable[[V], int]] = None,\n ) -> None:\n if max_weight <= 0:\n raise ValueError(\"max_weight must be positive\")\n if default_ttl <= 0:\n raise ValueError(\"default_ttl must be positive\")\n self._max_weight = max_weight\n self._default_ttl = default_ttl\n self._weight_fn = weight_fn or (lambda _v: 1)\n self._lock = asyncio.Lock()\n self._store: \"OrderedDict[K, _Entry[V]]\" = OrderedDict()\n self._inflight: \"dict[K, _Inflight[V]]\" = {}\n self._current_weight = 0\n self._stats = CacheStats(0, 0, 0, 0, 0, 0)\n\n @property\n def stats(self) -> CacheStats:\n return self._stats\n\n async def get_or_create(\n self,\n key: K,\n factory: Callable[[], Awaitable[V]],\n *,\n ttl: Optional[float] = None,\n weight: Optional[int] = None,\n ) -> V:\n async with self._lock:\n entry = self._store.get(key)\n if entry is not None:\n if time.monotonic() < entry.expires_at:\n self._store.move_to_end(key)\n self._stats = CacheStats(\n self._stats.hits + 1,\n self._stats.misses,\n self._stats.evictions,\n self._stats.factory_calls,\n self._stats.factory_errors,\n self._stats.invalidations,\n )\n return entry.value\n self._remove_entry(key)\n\n self._stats = CacheStats(\n self._stats.hits,\n self._stats.misses + 1,\n self._stats.evictions,\n self._stats.factory_calls,\n self._stats.factory_errors,\n self._stats.invalidations,\n )\n\n inflight = self._inflight.get(key)\n if inflight is not None:\n waiter = inflight\n else:\n waiter = _Inflight()\n self._inflight[key] = waiter\n self._stats = CacheStats(\n self._stats.hits,\n self._stats.misses,\n self._stats.evictions,\n self._stats.factory_calls + 1,\n self._stats.factory_errors,\n self._stats.invalidations,\n )\n self._run_factory(key, factory, ttl, weight, waiter)\n\n await waiter.event.wait()\n\n if waiter.cancelled:\n raise asyncio.CancelledError()\n if waiter.error is not None:\n raise waiter.error\n return waiter.value\n\n def _run_factory(\n self,\n key: K,\n factory: Callable[[], Awaitable[V]],\n ttl: Optional[float],\n weight: Optional[int],\n waiter: _Inflight[V],\n ) -> None:\n async def runner() -> None:\n try:\n value = await factory()\n except asyncio.CancelledError:\n waiter.cancelled = True\n waiter.event.set()\n return\n except BaseException as exc:\n waiter.error = exc\n waiter.event.set()\n async with self._lock:\n self._inflight.pop(key, None)\n self._stats = CacheStats(\n self._stats.hits,\n self._stats.misses,\n self._stats.evictions,\n self._stats.factory_calls,\n self._stats.factory_errors + 1,\n self._stats.invalidations,\n )\n return\n\n async with self._lock:\n current = self._inflight.get(key)\n if current is not waiter:\n return\n self._inflight.pop(key, None)\n self._put_entry(key, value, ttl, weight)\n waiter.value = value\n waiter.event.set()\n\n asyncio.create_task(runner())\n\n def _put_entry(\n self,\n key: K,\n value: V,\n ttl: Optional[float],\n weight: Optional[int],\n ) -> None:\n if weight is None:\n weight = self._weight_fn(value)\n if weight <= 0:\n raise ValueError(\"weight must be positive\")\n if ttl is None:\n ttl = self._default_ttl\n if ttl <= 0:\n raise ValueError(\"ttl must be positive\")\n\n expires_at = time.monotonic() + ttl\n entry = _Entry(value, expires_at, weight)\n\n if self._current_weight + weight > self._max_weight:\n while self._current_weight > 0 and self._current_weight + weight > self._max_weight:\n evict_key, evict_entry = next(iter(self._store.items()))\n self._remove_entry(evict_key)\n self._stats = CacheStats(\n self._stats.hits,\n self._stats.misses,\n self._stats.evictions + 1,\n self._stats.factory_calls,\n self._stats.factory_errors,\n self._stats.invalidations,\n )\n\n self._store[key] = entry\n self._store.move_to_end(key)\n self._current_weight += weight\n\n def _remove_entry(self, key: K) -> None:\n entry = self._store.pop(key, None)\n if entry is not None:\n self._current_weight -= entry.weight\n\n async def invalidate(self, key: K) -> bool:\n async with self._lock:\n removed = self._remove_entry(key)\n inflight = self._inflight.pop(key, None)\n if inflight is not None:\n inflight.cancelled = True\n inflight.event.set()\n if removed or inflight is not None:\n self._stats = CacheStats(\n self._stats.hits,\n self._stats.misses,\n self._stats.evictions,\n self._stats.factory_calls,\n self._stats.factory_errors,\n self._stats.invalidations + 1,\n )\n return True\n return False\n\n async def clear(self) -> None:\n async with self._lock:\n self._store.clear()\n self._current_weight = 0\n for inflight in self._inflight.values():\n inflight.cancelled = True\n inflight.event.set()\n self._inflight.clear()\n\n async def __aenter__(self) -> \"AsyncTTLCache[K, V]\":\n return self\n\n async def __aexit__(self, *args: Any) -> None:\n await self.clear()\n\n\nimport pytest\n\n\n@pytest.mark.asyncio\nasync def test_basic_hit_and_miss() -> None:\n cache = AsyncTTLCache[int, str](max_weight=10, default_ttl=1.0)\n calls = 0\n\n async def factory() -> str:\n nonlocal calls\n calls += 1\n await asyncio.sleep(0.01)\n return \"value\"\n\n result = await cache.get_or_create(1, factory)\n assert result == \"value\"\n assert calls == 1\n assert cache.stats.misses == 1\n assert cache.stats.hits == 0\n\n result = await cache.get_or_create(1, factory)\n assert result == \"value\"\n assert calls == 1\n assert cache.stats.hits == 1\n\n\n@pytest.mark.asyncio\nasync def test_single_flight() -> None:\n cache = AsyncTTLCache[int, int](max_weight=10, default_ttl=1.0)\n calls = 0\n\n async def factory() -> int:\n nonlocal calls\n calls += 1\n await asyncio.sleep(0.05)\n return 42\n\n results = await asyncio.gather(\n cache.get_or_create(1, factory),\n cache.get_or_create(1, factory),\n cache.get_or_create(1, factory),\n )\n assert all(r == 42 for r in results)\n assert calls == 1\n assert cache.stats.factory_calls == 1\n\n\n@pytest.mark.asyncio\nasync def test_cancellation_does_not_cancel_shared_factory() -> None:\n cache = AsyncTTLCache[int, int](max_weight=10, default_ttl=1.0)\n calls = 0\n\n async def factory() -> int:\n nonlocal calls\n calls += 1\n await asyncio.sleep(0.05)\n return 99\n\n task1 = asyncio.create_task(cache.get_or_create(1, factory))\n task2 = asyncio.create_task(cache.get_or_create(1, factory))\n await asyncio.sleep(0.01)\n task1.cancel()\n with pytest.raises(asyncio.CancelledError):\n await task1\n result = await task2\n assert result == 99\n assert calls == 1\n\n\n@pytest.mark.asyncio\nasync def test_expiry() -> None:\n cache = AsyncTTLCache[int, int](max_weight=10, default_ttl=0.05)\n calls = 0\n\n async def factory() -> int:\n nonlocal calls\n calls += 1\n return calls\n\n r1 = await cache.get_or_create(1, factory)\n assert r1 == 1\n await asyncio.sleep(0.06)\n r2 = await cache.get_or_create(1, factory)\n assert r2 == 2\n assert cache.stats.misses == 2\n\n\n@pytest.mark.asyncio\nasync def test_weighted_capacity_eviction() -> None:\n cache = AsyncTTLCache[int, int](max_weight=10, default_ttl=1.0)\n calls = 0\n\n async def factory() -> int:\n nonlocal calls\n calls += 1\n return calls\n\n await cache.get_or_create(1, factory, weight=6)\n await cache.get_or_create(2, factory, weight=5)\n assert cache.stats.evictions == 1\n assert 1 not in cache._store\n assert 2 in cache._store\n\n\n@pytest.mark.asyncio\nasync def test_factory_failure_not_cached() -> None:\n cache = AsyncTTLCache[int, int](max_weight=10, default_ttl=1.0)\n calls = 0\n\n async def factory() -> int:\n nonlocal calls\n calls += 1\n raise ValueError(\"fail\")\n\n with pytest.raises(ValueError):\n await cache.get_or_create(1, factory)\n assert cache.stats.factory_errors == 1\n assert 1 not in cache._store\n\n async def good_factory() -> int:\n return 42\n\n result = await cache.get_or_create(1, good_factory)\n assert result == 42\n assert cache.stats.factory_calls == 2\n\n\n@pytest.mark.asyncio\nasync def test_invalidation_during_creation_prevents_stale_publication() -> None:\n cache = AsyncTTLCache[int, int](max_weight=10, default_ttl=1.0)\n calls = 0\n\n async def factory() -> int:\n nonlocal calls\n calls += 1\n await asyncio.sleep(0.05)\n return 100\n\n task = asyncio.create_task(cache.get_or_create(1, factory))\n await asyncio.sleep(0.01)\n invalidated = await cache.invalidate(1)\n assert invalidated is True\n with pytest.raises(asyncio.CancelledError):\n await task\n assert 1 not in cache._store\n assert calls == 1\n\n async def new_factory() -> int:\n return 200\n\n result = await cache.get_or_create(1, new_factory)\n assert result == 200\n assert cache.stats.factory_calls == 2\n\n\n@pytest.mark.asyncio\nasync def test_clear_cancels_inflight() -> None:\n cache = AsyncTTLCache[int, int](max_weight=10, default_ttl=1.0)\n\n async def factory() -> int:\n await asyncio.sleep(0.05)\n return 1\n\n task = asyncio.create_task(cache.get_or_create(1, factory))\n await asyncio.sleep(0.01)\n await cache.clear()\n with pytest.raises(asyncio.CancelledError):\n await task", "error": null }, { "task": "php_idempotent_transfer", "runtime": "ollama", "model": "qwen3.8:27b-mlx", "started_at": "2026-08-31T12:06:19.598953+00:00", "prompt_chars": 756, "max_tokens": 3072, "ttft_s": 1.2263128339545801, "total_s": 78.72023624996655, "prompt_tokens": 182, "output_tokens": 3072, "decode_tps": 39.58218973701569, "finish_reason": "length", "output_chars": 12654, "output_sha256": "070acfea480b76f0913ce21686f2031f7a32421f5645f28eebe0c2ef9d2728ec", "output": "```php\nexecuteTransfer($fromAccountId, $toAccountId, $amountCents, $idempotencyKey);\n } catch (\\Throwable $e) {\n if ($this->isDeadlock($e)) {\n $lastException = $e;\n // Exponential backoff with jitter\n $sleepMs = (int) (10 * pow(2, $attempt) + random_int(0, 10));\n usleep($sleepMs * 1000);\n continue;\n }\n throw $e;\n }\n }\n\n // All retries exhausted\n throw new \\RuntimeException(\n 'Transfer failed after ' . $maxRetries . ' deadlock retries.',\n 0,\n $lastException\n );\n }\n\n /**\n * Execute the transfer inside a database transaction.\n */\n private function executeTransfer(\n int $fromAccountId,\n int $toAccountId,\n int $amountCents,\n string $idempotencyKey\n ): array {\n return DB::transaction(function () use ($fromAccountId, $toAccountId, $amountCents, $idempotencyKey) {\n // 1. Check idempotency: if a transfer with this key already exists, return it.\n $existing = DB::table('transfers')\n ->where('idempotency_key', $idempotencyKey)\n ->lockForUpdate()\n ->first();\n\n if ($existing !== null) {\n return [\n 'transfer_id' => (int) $existing->id,\n 'idempotency_key' => $existing->idempotency_key,\n 'from_account_id' => (int) $existing->from_account_id,\n 'to_account_id' => (int) $existing->to_account_id,\n 'amount_cents' => (int) $existing->amount_cents,\n 'status' => $existing->status,\n 'was_duplicate' => true,\n ];\n }\n\n // 2. Lock both account rows in deterministic ID order to avoid deadlocks.\n $lockOrder = [$fromAccountId, $toAccountId];\n sort($lockOrder);\n\n $lockedAccounts = [];\n foreach ($lockOrder as $accountId) {\n $row = DB::table('accounts')\n ->where('id', $accountId)\n ->lockForUpdate()\n ->first();\n\n if ($row === null) {\n throw new InvalidTransferException(\"Account {$accountId} does not exist.\");\n }\n $lockedAccounts[$accountId] = $row;\n }\n\n // 3. Check sufficient funds on the source account.\n $fromAccount = $lockedAccounts[$fromAccountId];\n if ($fromAccount->balance_cents < $amountCents) {\n throw new InsufficientFundsException(\n (int) $fromAccount->balance_cents,\n $amountCents\n );\n }\n\n // 4. Perform the transfer: debit source, credit destination.\n DB::table('accounts')\n ->where('id', $fromAccountId)\n ->update([\n 'balance_cents' => DB::raw('balance_cents - ' . $amountCents),\n 'version' => DB::raw('version + 1'),\n ]);\n\n DB::table('accounts')\n ->where('id', $toAccountId)\n ->update([\n 'balance_cents' => DB::raw('balance_cents + ' . $amountCents),\n 'version' => DB::raw('version + 1'),\n ]);\n\n // 5. Persist the transfer record.\n $transferId = DB::table('transfers')->insertGetId([\n 'idempotency_key' => $idempotencyKey,\n 'from_account_id' => $fromAccountId,\n 'to_account_id' => $toAccountId,\n 'amount_cents' => $amountCents,\n 'status' => 'completed',\n 'created_at' => now(),\n 'updated_at' => now(),\n ]);\n\n // 6. Persist the outbox event in the same transaction.\n DB::table('outbox_events')->insert([\n 'transfer_id' => $transferId,\n 'event_type' => 'transfer.completed',\n 'payload' => json_encode([\n 'transfer_id' => $transferId,\n 'idempotency_key' => $idempotencyKey,\n 'from_account_id' => $fromAccountId,\n 'to_account_id' => $toAccountId,\n 'amount_cents' => $amountCents,\n ]),\n 'created_at' => now(),\n ]);\n\n return [\n 'transfer_id' => (int) $transferId,\n 'idempotency_key' => $idempotencyKey,\n 'from_account_id' => $fromAccountId,\n 'to_account_id' => $toAccountId,\n 'amount_cents' => $amountCents,\n 'status' => 'completed',\n 'was_duplicate' => false,\n ];\n });\n }\n\n /**\n * Detect whether a Throwable represents a database deadlock.\n */\n private function isDeadlock(\\Throwable $e): bool\n {\n // MySQL/InnoDB deadlock error code is 1213\n // PostgreSQL deadlock error code is 40P01\n $message = $e->getMessage();\n $code = $e->getCode();\n\n if ($code === 1213 || $code === 40P01) {\n return true;\n }\n\n // Check SQLSTATE or message for deadlock indicators\n if (str_contains($message, 'Deadlock') || str_contains($message, 'deadlock')) {\n return true;\n }\n\n // Check for SQLSTATE 40001 (serialization failure)\n if (str_contains($message, '40001')) {\n return true;\n }\n\n return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// PHPUnit Tests\n// ---------------------------------------------------------------------------\n\nclass MoneyTransferServiceTest extends TestCase\n{\n use RefreshDatabase;\n\n private MoneyTransferService $service;\n\n protected function setUp(): void\n {\n parent::setUp();\n\n // Create the schema for testing\n Schema::create('accounts', function (Blueprint $table) {\n $table->id();\n $table->bigInteger('balance_cents')->default(0);\n $table->unsignedInteger('version')->default(0);\n $table->timestamps();\n });\n\n Schema::create('transfers', function (Blueprint $table) {\n $table->id();\n $table->string('idempotency_key')->unique();\n $table->unsignedBigInteger('from_account_id');\n $table->unsignedBigInteger('to_account_id');\n $table->bigInteger('amount_cents');\n $table->string('status')->default('completed');\n $table->timestamps();\n });\n\n Schema::create('outbox_events', function (Blueprint $table) {\n $table->id();\n $table->unsignedBigInteger('transfer_id');\n $table->string('event_type');\n $table->text('payload');\n $table->timestamps();\n });\n\n $this->service = new MoneyTransferService();\n }\n\n /**\n * Test that a valid transfer moves money correctly.\n */\n public function test_valid_transfer_moves_money(): void\n {\n $fromId = $this->createAccount(10000); // 100.00\n $toId = $this->createAccount(0); // 0.00\n\n $result = $this->service->transfer($fromId, $toId, 5000, 'key-001');\n\n $this->assertFalse($result['was_duplicate']);\n $this->assertEquals('completed', $result['status']);\n $this->assertEquals(5000, $result['amount_cents']);\n\n $fromBalance = DB::table('accounts')->where('id', $fromId)->value('balance_cents');\n $toBalance = DB::table('accounts')->where('id', $toId)->value('balance_cents');\n\n $this->assertEquals(5000, (int) $fromBalance);\n $this->assertEquals(5000, (int) $toBalance);\n }\n\n /**\n * Test that a duplicate idempotency key returns the original transfer\n * without moving money again.\n */\n public function test_duplicate_idempotency_key_returns_original_transfer(): void\n {\n $fromId = $this->createAccount(10000);\n $toId = $this->createAccount(0);\n\n $key = 'key-dup-001';\n\n $result1 = $this->service->transfer($fromId, $toId, 3000, $key);\n $this->assertFalse($result1['was_duplicate']);\n\n $result2 = $this->service->transfer($fromId, $toId, 3000, $key);\n $this->assertTrue($result2['was_duplicate']);\n\n // Balances should reflect only one transfer\n $fromBalance = DB::table('accounts')->where('id', $fromId)->value('balance_cents');\n $toBalance =", "error": null }, { "task": "typescript_dag_scheduler", "runtime": "ollama", "model": "qwen3.8:27b-mlx", "started_at": "2026-08-31T12:07:38.318694+00:00", "prompt_chars": 773, "max_tokens": 3072, "ttft_s": 1.5556320829782635, "total_s": 96.54028320801444, "prompt_tokens": 183, "output_tokens": 3072, "decode_tps": 32.29928081838954, "finish_reason": "length", "output_chars": 11211, "output_sha256": "f97ad0b443b4fe7ae9a5b2a3f0cc3c8ad2b71bc2ba4d5849bd679b498488c654", "output": "```typescript\n// strict-mode, dependency-free DAG task scheduler\n\nexport interface Task {\n id: string;\n deps: string[];\n run: (signal: AbortSignal) => Promise;\n}\n\nexport interface ScheduleOptions {\n maxConcurrency?: number;\n signal?: AbortSignal;\n}\n\nexport type TaskResults = Map;\n\nexport class CycleError extends Error {\n constructor(public readonly path: string[]) {\n super(`Cycle detected: ${path.join(\" -> \")}`);\n this.name = \"CycleError\";\n }\n}\n\nexport class ValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ValidationError\";\n }\n}\n\nexport class CancellationError extends Error {\n constructor() {\n super(\"Schedule cancelled\");\n this.name = \"CancellationError\";\n }\n}\n\nexport class TaskError extends Error {\n constructor(\n public readonly taskId: string,\n public readonly cause: unknown\n ) {\n super(`Task \"${taskId}\" failed: ${cause instanceof Error ? cause.message : String(cause)}`);\n this.name = \"TaskError\";\n }\n}\n\n// Internal node representation\ninterface Node {\n id: string;\n deps: string[];\n run: (signal: AbortSignal) => Promise;\n inDegree: number;\n dependents: string[];\n result?: unknown;\n error?: unknown;\n status: \"pending\" | \"running\" | \"done\" | \"failed\" | \"skipped\";\n promise?: Promise;\n}\n\n// Topological sort with cycle detection using DFS\nfunction topologicalSort(nodes: Map, order: string[]): string[] {\n const visited = new Set();\n const inStack = new Set();\n const sorted: string[] = [];\n\n function dfs(id: string, stack: string[]): void {\n if (inStack.has(id)) {\n // Found a cycle; extract the cycle path\n const cycleStart = stack.indexOf(id);\n const cyclePath = stack.slice(cycleStart).concat(id);\n throw new CycleError(cyclePath);\n }\n if (visited.has(id)) return;\n\n inStack.add(id);\n stack.push(id);\n\n const node = nodes.get(id)!;\n // Process dependencies in the order they appear in the input\n for (const dep of node.deps) {\n dfs(dep, stack);\n }\n\n stack.pop();\n inStack.delete(id);\n visited.add(id);\n sorted.push(id);\n }\n\n // Process nodes in input order for stable scheduling\n for (const id of order) {\n if (!visited.has(id)) {\n dfs(id, []);\n }\n }\n\n return sorted;\n}\n\n// Kahn's algorithm variant that preserves input order for stable scheduling\nfunction kahnSort(nodes: Map, inputOrder: string[]): string[] {\n // Build in-degree map\n const inDegree = new Map();\n const dependents = new Map();\n\n for (const [id, node] of nodes) {\n inDegree.set(id, node.deps.length);\n dependents.set(id, []);\n }\n\n for (const [id, node] of nodes) {\n for (const dep of node.deps) {\n const deps = dependents.get(dep);\n if (deps) {\n deps.push(id);\n }\n }\n }\n\n // Use a queue that preserves input order\n // Initialize with nodes that have in-degree 0, in input order\n const queue: string[] = [];\n for (const id of inputOrder) {\n if (inDegree.get(id) === 0) {\n queue.push(id);\n }\n }\n\n const sorted: string[] = [];\n let head = 0;\n\n while (head < queue.length) {\n const id = queue[head++];\n sorted.push(id);\n\n const deps = dependents.get(id) || [];\n for (const dep of deps) {\n const deg = inDegree.get(dep)!;\n inDegree.set(dep, deg - 1);\n if (inDegree.get(dep) === 0) {\n queue.push(dep);\n }\n }\n }\n\n if (sorted.length !== nodes.size) {\n // Cycle exists; use DFS to find the cycle path\n const visited = new Set();\n const inStack = new Set();\n\n function dfs(id: string, stack: string[]): void {\n if (inStack.has(id)) {\n const cycleStart = stack.indexOf(id);\n const cyclePath = stack.slice(cycleStart).concat(id);\n throw new CycleError(cyclePath);\n }\n if (visited.has(id)) return;\n\n inStack.add(id);\n stack.push(id);\n\n const node = nodes.get(id)!;\n for (const dep of node.deps) {\n dfs(dep, stack);\n }\n\n stack.pop();\n inStack.delete(id);\n visited.add(id);\n }\n\n for (const id of inputOrder) {\n if (!visited.has(id)) {\n dfs(id, []);\n }\n }\n // Should not reach here\n throw new CycleError([\"unknown\"]);\n }\n\n return sorted;\n}\n\nexport async function schedule(\n tasks: Task[],\n options: ScheduleOptions = {}\n): Promise {\n const maxConcurrency = options.maxConcurrency ?? Infinity;\n const externalSignal = options.signal;\n\n // Validate: check for duplicate IDs\n const idSet = new Set();\n for (const task of tasks) {\n if (idSet.has(task.id)) {\n throw new ValidationError(`Duplicate task ID: \"${task.id}\"`);\n }\n idSet.add(task.id);\n }\n\n // Validate: check for unknown dependencies\n for (const task of tasks) {\n for (const dep of task.deps) {\n if (!idSet.has(dep)) {\n throw new ValidationError(\n `Task \"${task.id}\" depends on unknown task \"${dep}\"`\n );\n }\n }\n }\n\n // Build node map\n const nodes = new Map();\n const inputOrder: string[] = [];\n\n for (const task of tasks) {\n nodes.set(task.id, {\n id: task.id,\n deps: task.deps,\n run: task.run,\n inDegree: task.deps.length,\n dependents: [],\n status: \"pending\",\n });\n inputOrder.push(task.id);\n }\n\n // Build dependents map\n for (const [id, node] of nodes) {\n for (const dep of node.deps) {\n const depNode = nodes.get(dep);\n if (depNode) {\n depNode.dependents.push(id);\n }\n }\n }\n\n // Detect cycles and get topological order\n const topoOrder = kahnSort(nodes, inputOrder);\n\n // Create an internal AbortController that we can abort on failure or external signal\n const controller = new AbortController();\n const internalSignal = controller.signal;\n\n // Link external signal to internal controller\n if (externalSignal) {\n if (externalSignal.aborted) {\n controller.abort();\n } else {\n externalSignal.addEventListener(\"abort\", () => {\n controller.abort();\n }, { once: true });\n }\n }\n\n // Track running tasks and their promises\n const runningPromises = new Set>();\n const results = new Map();\n let failed = false;\n let failureError: unknown = null;\n\n // Track which tasks have been started (to avoid starting dependents of failed tasks)\n const started = new Set();\n const skipped = new Set();\n\n // Helper: mark all transitive dependents as skipped\n function skipDependents(id: string): void {\n const node = nodes.get(id)!;\n for (const dep of node.dependents) {\n if (!skipped.has(dep) && !started.has(dep)) {\n skipped.add(dep);\n skipDependents(dep);\n }\n }\n }\n\n // Helper: check if all dependencies of a task are done (not failed/skipped)\n function depsReady(id: string): boolean {\n const node = nodes.get(id)!;\n for (const dep of node.deps) {\n const depNode = nodes.get(dep)!;\n if (depNode.status === \"failed\" || depNode.status === \"skipped\") {\n return false;\n }\n if (depNode.status !== \"done\") {\n return false;\n }\n }\n return true;\n }\n\n // Helper: check if a task should be skipped (any dep failed or skipped)\n function shouldSkip(id: string): boolean {\n const node = nodes.get(id)!;\n for (const dep of node.deps) {\n const depNode = nodes.get(dep)!;\n if (depNode.status === \"failed\" || depNode.status === \"skipped\") {\n return true;\n }\n }\n return false;\n }\n\n // Main scheduling loop\n // We process tasks in topological order, respecting concurrency limits\n // Use a pointer to the next task to consider\n let nextIndex = 0;\n const readyQueue: string[] = [];\n\n // Initialize ready queue with tasks that have no dependencies\n for (const id of topoOrder) {\n const node = nodes.get(id)!;\n if (node.deps.length === 0) {\n readyQueue.push(id);\n }\n }\n\n // We need to process tasks as they become ready\n // Use a more dynamic approach: after each task completes, check if new tasks are ready\n\n // Process the schedule\n async function runTask(id: string): Promise {\n const node = nodes.get(id)!;\n if (node.status !== \"pending\") return;\n\n // Check if we should skip this task\n if (shouldSkip(id)) {\n node.status = \"skipped\";\n skipDependents(id);\n return;\n }\n\n if (internalSignal.aborted) {\n node.status = \"skipped\";\n skipDependents(id);\n return;\n }\n\n started.add(id);\n node.status = \"running\";\n\n const taskPromise = node.run(internalSignal);\n node.promise = taskPromise;\n runningPromises.add(taskPromise);\n\n try {\n const result = await taskPromise;\n node.result = result;\n node.status = \"done\";\n results.set(id, result);\n } catch (err) {\n node.error = err;\n node.status = \"failed\";\n failed = true;\n failureError = new TaskError(id, err);\n // Abort all running tasks\n controller.abort();\n // Skip all dependents\n skipDependents(id);\n } finally {\n runningPromises.delete(taskPromise);\n }\n }\n\n // Process tasks in waves, respecting concurrency\n // We use a simple approach: maintain a set of running promises and a queue of ready tasks\n const running = new Set>();\n\n // Add initial ready tasks\n for (const id of readyQueue) {\n if (running.size >= maxConcurrency) break;\n if (skipped.has(id)) continue;\n const p = runTask(id);\n running.add(p);\n }\n\n // Process remaining tasks as they become ready\n // We need to wait for tasks to complete and then check for new ready tasks\n let processed = 0;\n const totalTasks = nodes.size;\n\n while (processed < totalTasks) {\n // Wait for at least one running task to complete\n if (running.size === 0) {\n // No more running tasks; check if there are more ready tasks\n // Find new ready tasks\n let foundNew = false;\n for (const id of topoOrder) {\n const node = nodes.get(id)!;\n if (node.status === \"pending\" && !skipped.has(id)) {\n if (depsReady(id)) {\n if (running.size >= maxConcurrency) break;\n const p = runTask(id);\n running.add(p);\n foundNew = true;\n }\n }\n }\n if (!foundNew) {\n // No more tasks to run\n break;\n }\n }\n\n // Wait for one task to complete\n if (running.size > 0) {\n // Use Promise.race but handle rejections\n const promises = Array.from(running);\n // We need to wait for one to settle without unhandled rejections\n // Create a race that catches rejections\n const settled = await Promise.race(\n promises.map((p) =>\n p.then(\n () => ({ type: \"fulfilled\" as const, promise: p }),\n () => ({ type: \"rejected\" as const, promise: p })\n )\n )\n );\n running.delete(settled.promise);\n processed++;\n }\n\n // After a task completes, check for new ready tasks\n if (!failed && !internalSignal.aborted) {\n for (const id of topo", "error": null } ] }