@ewhauser/world-celld 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/README.md +43 -15
- package/celld-queue-worker/README.md +2 -2
- package/celld-queue-worker/wrangler.jsonc +3 -1
- package/celld-worker/README.md +3 -3
- package/dist/index.d.ts +44 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +113 -99
- package/dist/index.js.map +1 -1
- package/dist/queue-consumer.js +1 -1
- package/dist/{queue-protocol-Ds44uuND.js → queue-protocol-CtH7UUCO.js} +2 -2
- package/dist/{queue-protocol-Ds44uuND.js.map → queue-protocol-CtH7UUCO.js.map} +1 -1
- package/dist/{queue-protocol-CmJNMH_5.js → run-list-B__jKuE7.js} +124 -18
- package/dist/run-list-B__jKuE7.js.map +1 -0
- package/dist/testing.d.ts +2 -2
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +211 -25
- package/dist/testing.js.map +1 -1
- package/dist/worker.d.ts +19 -0
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +313 -33
- package/dist/worker.js.map +1 -1
- package/docs/celld-v0.6-upgrade.md +121 -0
- package/package.json +7 -5
- package/dist/queue-protocol-CmJNMH_5.js.map +0 -1
- package/docs/celld-v0.5-upgrade.md +0 -130
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# celld v0.6.0 upgrade and feature review
|
|
2
|
+
|
|
3
|
+
Reviewed September 26, 2026. The previous runtime baseline was celld v0.5.0;
|
|
4
|
+
v0.6.0 is the latest published runtime release, and world-celld now requires
|
|
5
|
+
it. The World data model, binding names, worker configuration, and node
|
|
6
|
+
environment variables are unchanged. The npm and runtime versions are
|
|
7
|
+
independent.
|
|
8
|
+
|
|
9
|
+
Sources: [v0.5.1 release](https://github.com/denoland/celld/releases/tag/v0.5.1),
|
|
10
|
+
[v0.6.0 release](https://github.com/denoland/celld/releases/tag/v0.6.0), and
|
|
11
|
+
[versioned runtime documentation](https://github.com/denoland/celld/blob/v0.6.0/docs/README.md).
|
|
12
|
+
|
|
13
|
+
Fleets still on celld v0.4.x must first apply the configuration changes in the
|
|
14
|
+
[v0.5.0 upgrade guide](https://github.com/ewhauser/world-celld/blob/main/docs/celld-v0.5-upgrade.md)
|
|
15
|
+
(`CELLD_VAR_*` removal, telemetry variables, removed tuning variables).
|
|
16
|
+
|
|
17
|
+
## Required deployment changes
|
|
18
|
+
|
|
19
|
+
1. celld defaults to `fleet` durability, which a fleet of two or more nodes
|
|
20
|
+
uses unless `CELLD_DURABILITY=bucket` is set. For such a fleet, schedule
|
|
21
|
+
downtime and stop **every old celld node** before starting v0.6.0. A v0.6.0
|
|
22
|
+
node refuses to start when a v0.5.x follower returns the old log-tail
|
|
23
|
+
format, so a fleet-durability fleet cannot mix the versions.
|
|
24
|
+
2. A single node, or a fleet with `CELLD_DURABILITY=bucket`, has no followers.
|
|
25
|
+
celld documents a rolling update from v0.5.1 for that case. From v0.5.0,
|
|
26
|
+
either roll to v0.5.1 first or use the full stop above.
|
|
27
|
+
3. Do not start a v0.5.x binary against the bucket after v0.6.0 has run.
|
|
28
|
+
celld does not document a downgrade path.
|
|
29
|
+
4. Using the v0.6.0 CLI, deploy the configured Queue consumer first, then the
|
|
30
|
+
primary worker. Start v0.6.0 nodes with the existing bucket, storage
|
|
31
|
+
credentials, listener configuration, and node environment. Check an
|
|
32
|
+
authenticated World call and a queued callback before reopening traffic.
|
|
33
|
+
|
|
34
|
+
There is no World data-model or binding-name migration in this change. Existing
|
|
35
|
+
native Queue attachments, Queue payload keys, and Durable Object storage retain
|
|
36
|
+
their identities.
|
|
37
|
+
|
|
38
|
+
## Adopt telemetry
|
|
39
|
+
|
|
40
|
+
Enable bucket telemetry on every node to record runtime traces and logs without
|
|
41
|
+
adding a collector. Add these variables to the existing node service definition:
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
CELLD_OTEL=1
|
|
45
|
+
CELLD_OTEL_RETENTION=30d
|
|
46
|
+
OTEL_SERVICE_NAME=world-celld
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
celld writes Parquet files under `telemetry/` in the fleet bucket and expires
|
|
50
|
+
them after 30 days. The default flush interval is five minutes. Telemetry
|
|
51
|
+
retention is independent of World run retention. This is an opt-in operational
|
|
52
|
+
setting; installing the package does not change node environments.
|
|
53
|
+
|
|
54
|
+
For an existing OTLP collector, use these settings instead:
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
CELLD_OTEL=https://collector.example.com:4318
|
|
58
|
+
OTEL_SERVICE_NAME=world-celld
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
celld appends `/v1/traces` and `/v1/logs` to the collector base URL. Supply any
|
|
62
|
+
collector credentials through `OTEL_EXPORTER_OTLP_HEADERS` from your secret
|
|
63
|
+
manager. Details are in the
|
|
64
|
+
[v0.6.0 telemetry documentation](https://github.com/denoland/celld/blob/v0.6.0/docs/telemetry.md).
|
|
65
|
+
|
|
66
|
+
## Development variables
|
|
67
|
+
|
|
68
|
+
For `celld dev`, put local values in `.dev.vars` beside the Wrangler config:
|
|
69
|
+
|
|
70
|
+
```dotenv
|
|
71
|
+
WORLD_SECRET=local-development-secret
|
|
72
|
+
WORKFLOW_RETENTION_MS=0
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Use the same local secret in the consumer and primary worker configurations.
|
|
76
|
+
Changes reload automatically. Both `.dev.vars` and `.celld/` are ignored by
|
|
77
|
+
this repository; add those ignore rules when copying the workers elsewhere.
|
|
78
|
+
These variables do not reach a production deployment. Keep using the native
|
|
79
|
+
smoke for verification of the complete two-script topology.
|
|
80
|
+
|
|
81
|
+
## Feature decisions
|
|
82
|
+
|
|
83
|
+
| Change | Relevance to world-celld | Decision |
|
|
84
|
+
| ------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
85
|
+
| v0.6.0 ranged log-tail recovery for fleet durability | Run, catalog, hook, stream, and Queue cells recover through this path after a node loss. | Adopt through the upgrade. It forces the full-stop upgrade above. The two-node failover harness covers owner loss on v0.6.0. |
|
|
86
|
+
| v0.6.0 per-cell memory: bounded SQL statement cache, one object-store client per cell | Every World cell uses SQL-backed storage. | Adopt through the upgrade. Keep bounded cleanup and paging; these still limit application memory and work. |
|
|
87
|
+
| v0.6.0 #228 re-armed alarms fire on time despite pending handler timers | Retention progress and native Queue delivery depend on durable alarms. | Adopt through the upgrade. Keep the existing bounded alarm state machines; no workaround is removed in this change. |
|
|
88
|
+
| v0.6.0 independent facet databases and facet migration on first open | world-celld does not use facets. | No action. Facet adoption remains deferred for the reasons in the v0.5.0 review. |
|
|
89
|
+
| v0.6.0 R2 empty key segments and #232 non-ASCII `list()` keys | Queue payload keys are `workflow-queue/<runId>/<messageId>` with URI-encoded, non-empty segments. The worker never lists the payload bucket. | No action. Existing payload objects keep their keys. |
|
|
90
|
+
| v0.6.0 stricter workerd parity (`WorkerCode`, wasm modules, string exports, `transactionSync()`) | The worker bundles export only classes and functions, use no Dynamic Workers or wasm, and use async `transaction()`. | No action. `pnpm check` bundles both workers under workerd conditions. |
|
|
91
|
+
| v0.6.0 `ctx.exports` entrypoints, Ed25519/X25519, UTF-8 `Headers`, WebSocket `wasClean` | No current dependency. | Accept runtime fixes; no new API dependency is needed in the current worker bundle. |
|
|
92
|
+
| v0.5.1 Dynamic Worker limits and Tail reports, `celld r2` CLI, Wrangler `define`/`rules` | `celld r2` can inspect Queue payload objects under the `WORKFLOW_QUEUE_PAYLOADS` binding without a running node. | Optional operator tool. The remaining additions support a future worker-hosted execution service and are deferred until there is such a requirement. |
|
|
93
|
+
| v0.5.1 LTX page-cache caps, compaction of retained bundles, `/evict` and `/state` reporting | Lower node memory and better operator diagnostics. | Adopt defaults. |
|
|
94
|
+
|
|
95
|
+
The native Queue limits remain 128,000 bytes per message, 86,400 seconds of
|
|
96
|
+
producer delay, and four days of broker retention in
|
|
97
|
+
[the v0.6.0 queue policy](https://github.com/denoland/celld/blob/v0.6.0/crates/logic/queue.rs).
|
|
98
|
+
Keep external payload storage and long-delay republishing.
|
|
99
|
+
|
|
100
|
+
## Repository validation
|
|
101
|
+
|
|
102
|
+
The CI/native smoke and Docker performance harness pin v0.6.0. The native
|
|
103
|
+
runner verifies the release asset digests on Linux x86-64 and macOS arm64
|
|
104
|
+
against the digests GitHub publishes for the release.
|
|
105
|
+
|
|
106
|
+
These are fresh v0.6.0 deployments; they do not establish recovery of a
|
|
107
|
+
production v0.5.x bucket.
|
|
108
|
+
|
|
109
|
+
Validation on macOS arm64:
|
|
110
|
+
|
|
111
|
+
- `pnpm check` passed (25 test files, 470 tests, worker bundle checks, and
|
|
112
|
+
package checks).
|
|
113
|
+
- `pnpm test:integration:celld-smoke` passed all 10 real v0.6.0 tests.
|
|
114
|
+
- `pnpm test:perf:minio` passed the queue, mixed workflow lifecycle, and
|
|
115
|
+
retention workloads.
|
|
116
|
+
- `pnpm test:perf:minio:failover` passed: 96 of 96 accepted messages delivered
|
|
117
|
+
across a primary SIGKILL, with no duplicate callbacks, no mismatched message
|
|
118
|
+
IDs, and about two seconds to peer ownership.
|
|
119
|
+
|
|
120
|
+
No v0.5.0 performance baseline was preserved for this comparison, so no
|
|
121
|
+
performance change is claimed.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ewhauser/world-celld",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "celld (self-hosted Durable Objects) World implementation for the Vercel Workflow DevKit",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"celld",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"celld-worker",
|
|
26
26
|
"celld-queue-worker",
|
|
27
27
|
"NOTICE",
|
|
28
|
-
"docs/celld-v0.
|
|
28
|
+
"docs/celld-v0.6-upgrade.md"
|
|
29
29
|
],
|
|
30
30
|
"type": "module",
|
|
31
31
|
"sideEffects": false,
|
|
@@ -70,6 +70,8 @@
|
|
|
70
70
|
"test:perf:index": "vitest run --config vitest.index-perf.config.ts",
|
|
71
71
|
"test:perf:index:baseline": "node scripts/measure-index-main-baseline.mjs",
|
|
72
72
|
"test:perf:minio": "bash test/perf/minio/run.sh",
|
|
73
|
+
"test:perf:minio:matrix": "node scripts/run-perf-matrix.mjs",
|
|
74
|
+
"test:perf:minio:failover": "bash test/perf/minio/run-multi.sh",
|
|
73
75
|
"typecheck": "tsc --noEmit",
|
|
74
76
|
"check:worker-bundle": "node scripts/check-worker-bundle.mjs",
|
|
75
77
|
"pack:check": "node scripts/check-package.mjs",
|
|
@@ -77,15 +79,15 @@
|
|
|
77
79
|
"prepack": "pnpm build"
|
|
78
80
|
},
|
|
79
81
|
"dependencies": {
|
|
80
|
-
"@workflow/errors": "5.0.0-beta.
|
|
81
|
-
"@workflow/world": "5.0.0-beta.
|
|
82
|
+
"@workflow/errors": "5.0.0-beta.21",
|
|
83
|
+
"@workflow/world": "5.0.0-beta.36",
|
|
82
84
|
"ulid": "^3.0.2"
|
|
83
85
|
},
|
|
84
86
|
"devDependencies": {
|
|
85
87
|
"@cloudflare/workers-types": "^5.20260708.1",
|
|
86
88
|
"@ewhauser/world-celld": "link:.",
|
|
87
89
|
"@types/node": "^26.1.2",
|
|
88
|
-
"@workflow/world-testing": "5.0.0-beta.
|
|
90
|
+
"@workflow/world-testing": "5.0.0-beta.53",
|
|
89
91
|
"esbuild": "^0.28.2",
|
|
90
92
|
"oxfmt": "^0.65.0",
|
|
91
93
|
"oxlint": "^1.78.0",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"queue-protocol-CmJNMH_5.js","names":[],"sources":["../src/stream-protocol.ts","../src/validation.ts","../src/lifecycle.ts","../src/indexes.ts","../src/vendor/shared/serialization.ts","../src/codec.ts","../src/vendor/shared/util.ts","../src/queue-protocol.ts"],"sourcesContent":["/**\n * Hard-cutover stream protocol shared by the client, Worker router, and\n * StreamDO. Chunk payloads use a compact binary envelope on the public HTTP\n * boundary and Uint8Array values on the DO/storage boundary.\n */\n\nexport const STREAM_BATCH_CONTENT_TYPE = 'application/vnd.world-celld.stream-batch.v1';\n\n/** Metadata plus payload and size-index entries remain below the 128-key platform limit. */\nexport const MAX_STREAM_WRITE_CHUNKS = 32;\n/** A chunk remains below the 2 MiB SQLite-backed DO key/value limit. */\nexport const MAX_STREAM_CHUNK_BYTES = 1024 * 1024;\n/** Keeps serialized RPC bodies well below the 32 MiB Workers RPC limit. */\nexport const MAX_STREAM_BATCH_BYTES = 8 * 1024 * 1024;\nexport const MAX_STREAM_READ_CHUNKS = 32;\nexport const MAX_STREAM_READ_BYTES = 8 * 1024 * 1024;\nexport const MAX_STREAM_ERROR_BYTES = 16 * 1024;\n/** Must remain below the default 30 second fleet request deadline. */\nexport const MAX_STREAM_LONG_POLL_MS = 20_000;\n\nexport type StreamTerminalState = 'open' | 'closed' | 'errored' | 'expired';\n\nexport interface StreamErrorData {\n name: string;\n message: string;\n}\n\nexport interface StreamWriteResult {\n startIndex: number;\n count: number;\n tailIndex: number;\n}\n\nexport interface StreamReadRequest {\n runId: string;\n startIndex: number;\n maxChunks: number;\n maxBytes: number;\n waitMs: number;\n}\n\nexport interface StreamReadResult {\n startIndex: number;\n tailIndex: number;\n chunks: Uint8Array[];\n state: StreamTerminalState;\n timedOut: boolean;\n error?: StreamErrorData;\n}\n\nconst WRITE_MAGIC = 0x57435357; // WCSW\nconst WRITE_ACK_MAGIC = 0x57435341; // WCSA\nconst READ_MAGIC = 0x57435352; // WCSR\nconst PROTOCOL_VERSION = 1;\nconst WRITE_HEADER_BYTES = 8;\nconst WRITE_ACK_BYTES = 16;\nconst READ_HEADER_BYTES = 28;\n\nconst STATE_TO_WIRE: Record<StreamTerminalState, number> = {\n open: 0,\n closed: 1,\n errored: 2,\n expired: 3,\n};\n\nconst WIRE_TO_STATE: readonly StreamTerminalState[] = ['open', 'closed', 'errored', 'expired'];\n\nfunction protocolError(message: string): Error {\n const error = new Error(`world-celld: invalid stream protocol: ${message}`);\n error.name = 'StreamProtocolError';\n return error;\n}\n\nfunction requireSafeIndex(value: number, field: string): void {\n if (!Number.isSafeInteger(value) || value < -1 || value > 0x7fffffff) {\n throw protocolError(`${field} is out of range`);\n }\n}\n\nexport function validateStreamWriteChunks(chunks: readonly Uint8Array[]): number {\n if (chunks.length === 0) throw protocolError('batch must contain at least one chunk');\n if (chunks.length > MAX_STREAM_WRITE_CHUNKS) {\n throw protocolError(`batch exceeds ${MAX_STREAM_WRITE_CHUNKS} chunks`);\n }\n\n let totalBytes = 0;\n for (const chunk of chunks) {\n if (!(chunk instanceof Uint8Array)) throw protocolError('chunk is not binary data');\n if (chunk.byteLength > MAX_STREAM_CHUNK_BYTES) {\n throw protocolError(`chunk exceeds ${MAX_STREAM_CHUNK_BYTES} bytes`);\n }\n totalBytes += chunk.byteLength;\n if (totalBytes > MAX_STREAM_BATCH_BYTES) {\n throw protocolError(`batch exceeds ${MAX_STREAM_BATCH_BYTES} bytes`);\n }\n }\n return totalBytes;\n}\n\nexport function validateStreamReadRequest(request: StreamReadRequest): void {\n if (!request.runId) throw protocolError('runId is required');\n if (\n !Number.isSafeInteger(request.startIndex) ||\n request.startIndex < 0 ||\n request.startIndex > 0x7fffffff\n ) {\n throw protocolError('startIndex must be between 0 and 2147483647');\n }\n if (\n !Number.isSafeInteger(request.maxChunks) ||\n request.maxChunks < 0 ||\n request.maxChunks > MAX_STREAM_READ_CHUNKS\n ) {\n throw protocolError(`maxChunks must be between 0 and ${MAX_STREAM_READ_CHUNKS}`);\n }\n if (\n !Number.isSafeInteger(request.maxBytes) ||\n request.maxBytes < MAX_STREAM_CHUNK_BYTES ||\n request.maxBytes > MAX_STREAM_READ_BYTES\n ) {\n throw protocolError(\n `maxBytes must be between ${MAX_STREAM_CHUNK_BYTES} and ${MAX_STREAM_READ_BYTES}`,\n );\n }\n if (\n !Number.isSafeInteger(request.waitMs) ||\n request.waitMs < 0 ||\n request.waitMs > MAX_STREAM_LONG_POLL_MS\n ) {\n throw protocolError(`waitMs must be between 0 and ${MAX_STREAM_LONG_POLL_MS}`);\n }\n}\n\nexport function encodeStreamWriteBatch(chunks: readonly Uint8Array[]): Uint8Array {\n const totalBytes = validateStreamWriteChunks(chunks);\n const encoded = new Uint8Array(WRITE_HEADER_BYTES + chunks.length * 4 + totalBytes);\n const view = new DataView(encoded.buffer);\n view.setUint32(0, WRITE_MAGIC);\n view.setUint16(4, PROTOCOL_VERSION);\n view.setUint16(6, chunks.length);\n\n let offset = WRITE_HEADER_BYTES;\n for (const chunk of chunks) {\n view.setUint32(offset, chunk.byteLength);\n offset += 4;\n encoded.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return encoded;\n}\n\nexport function decodeStreamWriteBatch(encoded: Uint8Array): Uint8Array[] {\n if (encoded.byteLength < WRITE_HEADER_BYTES) throw protocolError('truncated write header');\n const view = new DataView(encoded.buffer, encoded.byteOffset, encoded.byteLength);\n if (view.getUint32(0) !== WRITE_MAGIC) throw protocolError('bad write magic');\n if (view.getUint16(4) !== PROTOCOL_VERSION) throw protocolError('unsupported write version');\n const count = view.getUint16(6);\n const chunks: Uint8Array[] = [];\n let offset = WRITE_HEADER_BYTES;\n for (let index = 0; index < count; index++) {\n if (offset + 4 > encoded.byteLength) throw protocolError('truncated chunk length');\n const length = view.getUint32(offset);\n offset += 4;\n if (offset + length > encoded.byteLength) throw protocolError('truncated chunk data');\n // Persisted DO values are structured-cloned. A view would retain the\n // complete batch backing buffer and could exceed the per-value storage\n // limit even when this individual chunk is valid.\n chunks.push(new Uint8Array(encoded.subarray(offset, offset + length)));\n offset += length;\n }\n if (offset !== encoded.byteLength) throw protocolError('trailing write data');\n validateStreamWriteChunks(chunks);\n return chunks;\n}\n\nexport function encodeStreamWriteResult(result: StreamWriteResult): Uint8Array {\n requireSafeIndex(result.startIndex, 'startIndex');\n requireSafeIndex(result.tailIndex, 'tailIndex');\n if (\n !Number.isSafeInteger(result.count) ||\n result.count < 1 ||\n result.count > MAX_STREAM_WRITE_CHUNKS\n ) {\n throw protocolError('write count is out of range');\n }\n const encoded = new Uint8Array(WRITE_ACK_BYTES);\n const view = new DataView(encoded.buffer);\n view.setUint32(0, WRITE_ACK_MAGIC);\n view.setUint16(4, PROTOCOL_VERSION);\n view.setUint16(6, result.count);\n view.setInt32(8, result.startIndex);\n view.setInt32(12, result.tailIndex);\n return encoded;\n}\n\nexport function decodeStreamWriteResult(encoded: Uint8Array): StreamWriteResult {\n if (encoded.byteLength !== WRITE_ACK_BYTES) throw protocolError('invalid write response size');\n const view = new DataView(encoded.buffer, encoded.byteOffset, encoded.byteLength);\n if (view.getUint32(0) !== WRITE_ACK_MAGIC) throw protocolError('bad write response magic');\n if (view.getUint16(4) !== PROTOCOL_VERSION) {\n throw protocolError('unsupported write response version');\n }\n const count = view.getUint16(6);\n if (count < 1 || count > MAX_STREAM_WRITE_CHUNKS) {\n throw protocolError('write response count is out of range');\n }\n const result = {\n count,\n startIndex: view.getInt32(8),\n tailIndex: view.getInt32(12),\n };\n requireSafeIndex(result.startIndex, 'startIndex');\n requireSafeIndex(result.tailIndex, 'tailIndex');\n if (result.tailIndex !== result.startIndex + result.count - 1) {\n throw protocolError('write response offsets are inconsistent');\n }\n return result;\n}\n\nexport function encodeStreamReadResult(result: StreamReadResult): Uint8Array {\n requireSafeIndex(result.startIndex, 'startIndex');\n requireSafeIndex(result.tailIndex, 'tailIndex');\n let chunkBytes = 0;\n for (const chunk of result.chunks) chunkBytes += 4 + chunk.byteLength;\n if (result.chunks.length > MAX_STREAM_READ_CHUNKS || chunkBytes > MAX_STREAM_READ_BYTES + 128) {\n throw protocolError('read result exceeds configured bounds');\n }\n\n const encoder = new TextEncoder();\n const errorName = result.error ? encoder.encode(result.error.name) : new Uint8Array();\n const errorMessage = result.error ? encoder.encode(result.error.message) : new Uint8Array();\n if (errorName.byteLength + errorMessage.byteLength > MAX_STREAM_ERROR_BYTES) {\n throw protocolError(`stream error exceeds ${MAX_STREAM_ERROR_BYTES} bytes`);\n }\n const encoded = new Uint8Array(\n READ_HEADER_BYTES + chunkBytes + errorName.byteLength + errorMessage.byteLength,\n );\n const view = new DataView(encoded.buffer);\n view.setUint32(0, READ_MAGIC);\n view.setUint8(4, PROTOCOL_VERSION);\n const state = STATE_TO_WIRE[result.state];\n if (state === undefined) throw protocolError('unknown stream state');\n view.setUint8(5, state);\n view.setUint8(6, result.timedOut ? 1 : 0);\n view.setUint8(7, 0);\n view.setInt32(8, result.startIndex);\n view.setInt32(12, result.tailIndex);\n view.setUint32(16, result.chunks.length);\n view.setUint32(20, errorName.byteLength);\n view.setUint32(24, errorMessage.byteLength);\n\n let offset = READ_HEADER_BYTES;\n for (const chunk of result.chunks) {\n view.setUint32(offset, chunk.byteLength);\n offset += 4;\n encoded.set(chunk, offset);\n offset += chunk.byteLength;\n }\n encoded.set(errorName, offset);\n offset += errorName.byteLength;\n encoded.set(errorMessage, offset);\n return encoded;\n}\n\nexport function decodeStreamReadResult(encoded: Uint8Array): StreamReadResult {\n if (encoded.byteLength < READ_HEADER_BYTES) throw protocolError('truncated read header');\n const view = new DataView(encoded.buffer, encoded.byteOffset, encoded.byteLength);\n if (view.getUint32(0) !== READ_MAGIC) throw protocolError('bad read magic');\n if (view.getUint8(4) !== PROTOCOL_VERSION) throw protocolError('unsupported read version');\n const state = WIRE_TO_STATE[view.getUint8(5)];\n if (!state) throw protocolError('unknown stream state');\n const startIndex = view.getInt32(8);\n const tailIndex = view.getInt32(12);\n requireSafeIndex(startIndex, 'startIndex');\n requireSafeIndex(tailIndex, 'tailIndex');\n const count = view.getUint32(16);\n const errorNameLength = view.getUint32(20);\n const errorMessageLength = view.getUint32(24);\n if (count > MAX_STREAM_READ_CHUNKS) throw protocolError('read chunk count exceeds limit');\n if (errorNameLength + errorMessageLength > MAX_STREAM_ERROR_BYTES) {\n throw protocolError('read error exceeds limit');\n }\n\n const chunks: Uint8Array[] = [];\n let offset = READ_HEADER_BYTES;\n let totalBytes = 0;\n for (let index = 0; index < count; index++) {\n if (offset + 4 > encoded.byteLength) throw protocolError('truncated read chunk length');\n const length = view.getUint32(offset);\n offset += 4;\n if (offset + length > encoded.byteLength) throw protocolError('truncated read chunk data');\n totalBytes += length;\n if (totalBytes > MAX_STREAM_READ_BYTES) throw protocolError('read bytes exceed limit');\n chunks.push(encoded.subarray(offset, offset + length));\n offset += length;\n }\n\n if (offset + errorNameLength + errorMessageLength !== encoded.byteLength) {\n throw protocolError('invalid read error payload');\n }\n const decoder = new TextDecoder();\n const error =\n errorNameLength > 0 || errorMessageLength > 0\n ? {\n name: decoder.decode(encoded.subarray(offset, offset + errorNameLength)),\n message: decoder.decode(encoded.subarray(offset + errorNameLength)),\n }\n : undefined;\n\n return {\n startIndex,\n tailIndex,\n chunks,\n state,\n timedOut: (view.getUint8(6) & 1) !== 0,\n error,\n };\n}\n\nexport function normalizeStreamError(error: unknown): StreamErrorData {\n let normalized: StreamErrorData;\n if (error instanceof Error) {\n normalized = { name: error.name || 'Error', message: error.message };\n } else if (typeof error === 'string') {\n normalized = { name: 'Error', message: error };\n } else if (\n error &&\n typeof error === 'object' &&\n typeof (error as { name?: unknown }).name === 'string' &&\n typeof (error as { message?: unknown }).message === 'string'\n ) {\n normalized = {\n name: (error as StreamErrorData).name,\n message: (error as StreamErrorData).message,\n };\n } else {\n normalized = { name: 'Error', message: String(error) };\n }\n\n // Bound persisted terminal metadata as well as its binary response frame.\n normalized.name = normalized.name.slice(0, 1024);\n normalized.message = normalized.message.slice(0, 4096);\n while (\n new TextEncoder().encode(normalized.name).byteLength +\n new TextEncoder().encode(normalized.message).byteLength >\n MAX_STREAM_ERROR_BYTES\n ) {\n normalized.message = normalized.message.slice(0, Math.max(0, normalized.message.length - 256));\n if (normalized.message.length === 0) {\n normalized.name = normalized.name.slice(0, Math.max(0, normalized.name.length - 256));\n }\n }\n return normalized;\n}\n","/** Largest epoch-ms deadline that fits the queue's fixed-width 13-digit keys. */\nexport const MAX_QUEUE_TIMESTAMP_MS = 9_999_999_999_999;\nexport const MAX_QUEUE_DELAY_SECONDS = Math.floor(MAX_QUEUE_TIMESTAMP_MS / 1000);\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nexport function isNonNegativeSafeInteger(value: unknown): value is number {\n return Number.isSafeInteger(value) && (value as number) >= 0;\n}\n\nexport function isPositiveSafeInteger(value: unknown): value is number {\n return Number.isSafeInteger(value) && (value as number) > 0;\n}\n\nexport function boundedIntegerOption(\n name: string,\n value: unknown,\n fallback: number,\n minimum: number,\n maximum = Number.MAX_SAFE_INTEGER,\n): number {\n if (value === undefined) return fallback;\n if (!Number.isSafeInteger(value) || (value as number) < minimum || (value as number) > maximum) {\n throw new Error(`${name} must be an integer between ${minimum} and ${maximum}`);\n }\n return value as number;\n}\n\n/**\n * Parse a deployment/runtime integer setting without accepting whitespace,\n * exponents, fractions, or numeric prefixes such as `5junk`.\n */\nexport function strictIntegerSetting(\n name: string,\n raw: string | number | undefined,\n fallback: number,\n minimum: number,\n maximum = Number.MAX_SAFE_INTEGER,\n): number {\n if (raw === undefined) return fallback;\n const value = typeof raw === 'number' ? raw : /^\\d+$/.test(raw) ? Number(raw) : Number.NaN;\n if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {\n throw new Error(`${name} must be an integer between ${minimum} and ${maximum}`);\n }\n return value;\n}\n\nexport function isValidQueueDelaySeconds(value: unknown, minimum: 0 | 1 = 0): value is number {\n return (\n Number.isSafeInteger(value) &&\n (value as number) >= minimum &&\n (value as number) <= MAX_QUEUE_DELAY_SECONDS\n );\n}\n\nexport function queueDelayDeadline(\n now: number,\n delaySeconds: unknown,\n minimum: 0 | 1 = 0,\n requiredHeadroomMs = 0,\n): number | null {\n if (!isNonNegativeSafeInteger(now) || !isValidQueueDelaySeconds(delaySeconds, minimum)) {\n return null;\n }\n return checkedQueueTimestampAdd(now, delaySeconds * 1000, requiredHeadroomMs);\n}\n\n/**\n * Add an offset to a queue timestamp while preserving its 13-digit key order.\n * Optional headroom reserves space for a later persisted derivative deadline.\n */\nexport function checkedQueueTimestampAdd(\n timestampMs: unknown,\n offsetMs: unknown,\n requiredHeadroomMs = 0,\n): number | null {\n if (\n !isNonNegativeSafeInteger(timestampMs) ||\n !isNonNegativeSafeInteger(offsetMs) ||\n !isNonNegativeSafeInteger(requiredHeadroomMs)\n ) {\n return null;\n }\n const timestamp = timestampMs;\n const offset = offsetMs;\n if (timestamp > MAX_QUEUE_TIMESTAMP_MS || offset > MAX_QUEUE_TIMESTAMP_MS - timestamp) {\n return null;\n }\n const result = timestamp + offset;\n return requiredHeadroomMs <= MAX_QUEUE_TIMESTAMP_MS - result ? result : null;\n}\n","import { MAX_QUEUE_TIMESTAMP_MS } from './validation.js';\n\n/**\n * Hard protocol bounds used by lifecycle leases and derivative-fence cleanup.\n *\n * These are not retention TTLs. They bound how long a request which was\n * admitted before expiry may still mutate a derivative Durable Object:\n *\n * - fleet calls have at most three 5-minute attempts plus bounded retry delay;\n * - one native Queue idempotency claim stays live for at most 15 minutes\n * without a successful delivery or explicit workflow suspension;\n * - hook admission spans reserve retries, one authoritative apply, and\n * finalize retries before the exact claim must be reacquired.\n */\nexport const MAX_FLEET_RPC_TIMEOUT_MS = 5 * 60 * 1000;\nexport const FLEET_IDEMPOTENT_ATTEMPTS = 3;\nexport const FLEET_RETRY_BACKOFF_MAX_MS = 900;\n\nexport const MAX_IDEMPOTENT_RPC_LIFETIME_MS =\n FLEET_IDEMPOTENT_ATTEMPTS * MAX_FLEET_RPC_TIMEOUT_MS + FLEET_RETRY_BACKOFF_MAX_MS;\nexport const MAX_RUN_INDEX_PUBLICATION_LIFETIME_MS =\n MAX_FLEET_RPC_TIMEOUT_MS + MAX_IDEMPOTENT_RPC_LIFETIME_MS;\n\nexport const MAX_QUEUE_DELIVERY_TIMEOUT_MS = 5 * 60 * 1000;\nexport const QUEUE_CLAIM_STALE_MS = 15 * 60 * 1000;\n/** A fresh delivery edge may advance an immediately-due message by one millisecond. */\nexport const QUEUE_ALARM_EDGE_MS = 1;\nexport const MAX_QUEUE_DERIVED_DEADLINE_HEADROOM_MS = QUEUE_CLAIM_STALE_MS + QUEUE_ALARM_EDGE_MS;\n/** Latest admissible due time with room for a fresh queue claim. */\nexport const MAX_QUEUE_SCHEDULE_TIMESTAMP_MS =\n MAX_QUEUE_TIMESTAMP_MS - MAX_QUEUE_DERIVED_DEADLINE_HEADROOM_MS;\n\nexport const CATALOG_FENCE_GRACE_MS = MAX_RUN_INDEX_PUBLICATION_LIFETIME_MS;\n\nexport const HOOK_CLAIM_LEASE_MS =\n (FLEET_IDEMPOTENT_ATTEMPTS * 2 + 1) * MAX_FLEET_RPC_TIMEOUT_MS + FLEET_RETRY_BACKOFF_MAX_MS * 2;\n\nexport const LIFECYCLE_COMPACTION_BATCH = 128;\nexport const LIFECYCLE_COMPACTION_RETRY_MS = 1000;\n","import type { WorkflowRun } from '@workflow/world';\nimport type { HookTokenOwner } from './config.js';\nimport type {\n ExpireRunIndexesRequest,\n ExpireRunIndexesResult,\n ReleaseHookIndexesRequest,\n ReleaseHookIndexesResult,\n} from './retention.js';\n\nexport const RUN_CATALOG_SHARDS = 16;\nexport const HOOK_TOKEN_SHARDS = 32;\nexport const HOOK_ID_SHARDS = 32;\n\nconst INDEX_PROTOCOL_VERSION = 'v1';\nconst HOOK_RELEASE_BATCH = 64;\n\nexport interface IndexListOptions {\n prefix?: string;\n cursor?: string;\n /** Exclusive upper bound for creation-time retention scans. */\n end?: string;\n limit?: number;\n reverse?: boolean;\n}\n\nexport interface IndexListPage {\n /** sourceShard is populated by the merged fleet index for exact follow-up mutation. */\n keys: Array<{ name: string; value: string; sourceShard?: string }>;\n list_complete: boolean;\n cursor?: string;\n}\n\nexport interface RunCatalogShardStub {\n upsertRun(\n runId: string,\n keys: string[],\n serializedMetadata: string,\n publicationExpiresAt: number,\n ): Promise<{ stored: boolean }>;\n list(options?: IndexListOptions): Promise<IndexListPage>;\n deleteStaleGlobalRun(\n runId: string,\n key: string,\n expectedValue: string,\n ): Promise<{ deleted: boolean }>;\n expireRun(runId: string, expiredAt: number): Promise<ExpireRunIndexesResult>;\n}\n\nexport type HookClaimResult =\n | { claimed: true; claimId: string }\n | { claimed: false; holder: HookTokenOwner };\n\nexport interface HookReservation {\n claimId: string;\n tokenClaimId?: string;\n hookIdClaimId?: string;\n}\n\nexport type HookReservationResult =\n | { admitted: true; reservation: HookReservation }\n | { admitted: false; holder: HookTokenOwner };\n\nexport interface HookTokenShardStub {\n get(token: string): Promise<string | null>;\n reserve(token: string, owner: HookTokenOwner, proposedClaimId: string): Promise<HookClaimResult>;\n finalize(\n token: string,\n serializedHook: string,\n owner: HookTokenOwner,\n claimId?: string,\n ): Promise<{ stored: boolean }>;\n releaseClaim(token: string, owner: HookTokenOwner, claimId: string): Promise<void>;\n releaseBatch(\n hooks: Array<{ token: string; owner: HookTokenOwner }>,\n ): Promise<{ deleted: number }>;\n}\n\nexport interface HookIdShardStub {\n get(hookId: string): Promise<string | null>;\n reserve(hookId: string, owner: HookTokenOwner, proposedClaimId: string): Promise<HookClaimResult>;\n publish(\n hookId: string,\n serializedHook: string,\n owner: HookTokenOwner,\n claimId?: string,\n ): Promise<{ stored: boolean }>;\n releaseClaim(hookId: string, owner: HookTokenOwner, claimId: string): Promise<void>;\n releaseBatch(\n hooks: Array<{ hookId: string; owner: HookTokenOwner }>,\n ): Promise<{ deleted: number }>;\n}\n\ninterface CellIdLike {\n toString(): string;\n}\n\nexport interface CellNamespaceLike<T> {\n idFromName(name: string): CellIdLike;\n get(id: CellIdLike): T;\n}\n\nexport interface WorkflowIndexBindings {\n runCatalog: CellNamespaceLike<RunCatalogShardStub>;\n hookTokens: CellNamespaceLike<HookTokenShardStub>;\n hookIds: CellNamespaceLike<HookIdShardStub>;\n}\n\n/** High-level client used by the World storage layer and retention coordinator. */\nexport interface WorkflowIndex {\n commitRun(\n run: WorkflowRun,\n serializedMetadata: string,\n publicationExpiresAt: number,\n ): Promise<{ stored: boolean }>;\n listRuns(options?: IndexListOptions): Promise<IndexListPage>;\n getHookByToken(token: string): Promise<string | null>;\n getHookById(hookId: string): Promise<string | null>;\n reserveHook(token: string, owner: HookTokenOwner): Promise<HookReservationResult>;\n finalizeHookIndexes(\n token: string,\n hookId: string,\n serializedHook: string,\n owner: HookTokenOwner,\n reservation?: HookReservation,\n ): Promise<void>;\n releaseHookReservation(\n token: string,\n owner: HookTokenOwner,\n reservation: HookReservation,\n ): Promise<void>;\n releaseHookIndexes(request: ReleaseHookIndexesRequest): Promise<ReleaseHookIndexesResult>;\n expireRun(request: ExpireRunIndexesRequest): Promise<ExpireRunIndexesResult>;\n}\n\n/** Stable FNV-1a routing hash. Collisions share a shard but never a storage key. */\nexport function stableIndexHash(value: string): number {\n let hash = 0x811c9dc5;\n for (const byte of new TextEncoder().encode(value)) {\n hash ^= byte;\n hash = Math.imul(hash, 0x01000193);\n }\n return hash >>> 0;\n}\n\nfunction shardName(domain: string, value: string, cardinality: number): string {\n const width = Math.max(2, (cardinality - 1).toString(16).length);\n const shard = stableIndexHash(value) % cardinality;\n return `${domain}:${INDEX_PROTOCOL_VERSION}:${shard.toString(16).padStart(width, '0')}`;\n}\n\nexport function runCatalogShardName(runId: string): string {\n return shardName('run-catalog', runId, RUN_CATALOG_SHARDS);\n}\n\nexport function hookTokenShardName(token: string): string {\n return shardName('hook-token', token, HOOK_TOKEN_SHARDS);\n}\n\nexport function hookIdShardName(hookId: string): string {\n return shardName('hook-id', hookId, HOOK_ID_SHARDS);\n}\n\nexport function allRunCatalogShardNames(): string[] {\n return Array.from(\n { length: RUN_CATALOG_SHARDS },\n (_, shard) => `run-catalog:${INDEX_PROTOCOL_VERSION}:${shard.toString(16).padStart(2, '0')}`,\n );\n}\n\nfunction stub<T>(namespace: CellNamespaceLike<T>, name: string): T {\n return namespace.get(namespace.idFromName(name));\n}\n\nfunction runKeys(run: Pick<WorkflowRun, 'runId' | 'workflowName' | 'createdAt'>): string[] {\n const timestamp = run.createdAt.getTime().toString().padStart(13, '0');\n return [`run:${run.workflowName}:${timestamp}:${run.runId}`, `runall:${timestamp}:${run.runId}`];\n}\n\nfunction groupByShard<T>(values: T[], route: (value: T) => string): Map<string, T[]> {\n const groups = new Map<string, T[]>();\n for (const value of values) {\n const name = route(value);\n const group = groups.get(name);\n if (group) group.push(value);\n else groups.set(name, [value]);\n }\n return groups;\n}\n\nfunction sameOwner(left: HookTokenOwner, right: HookTokenOwner): boolean {\n return left.runId === right.runId && left.hookId === right.hookId;\n}\n\nfunction compareBytes(left: Uint8Array, right: Uint8Array): number {\n const length = Math.min(left.byteLength, right.byteLength);\n for (let index = 0; index < length; index++) {\n const difference = left[index] - right[index];\n if (difference !== 0) return difference;\n }\n return left.byteLength - right.byteLength;\n}\n\nasync function inBatches<T>(\n values: T[],\n operation: (batch: T[]) => Promise<{ deleted: number }>,\n): Promise<number> {\n let deleted = 0;\n for (let offset = 0; offset < values.length; offset += HOOK_RELEASE_BATCH) {\n deleted += (await operation(values.slice(offset, offset + HOOK_RELEASE_BATCH))).deleted;\n }\n return deleted;\n}\n\nexport function createWorkflowIndex(bindings: WorkflowIndexBindings): WorkflowIndex {\n const releaseHooks = async (request: ReleaseHookIndexesRequest): Promise<number> => {\n const tokenGroups = groupByShard(request.hooks, (hook) => hookTokenShardName(hook.token));\n const idGroups = groupByShard(request.hooks, (hook) => hookIdShardName(hook.hookId));\n const releases: Array<Promise<number>> = [];\n for (const [name, hooks] of tokenGroups) {\n const target = stub(bindings.hookTokens, name);\n releases.push(\n inBatches(\n hooks.map((hook) => ({\n token: hook.token,\n owner: { runId: request.runId, hookId: hook.hookId },\n })),\n (batch) => target.releaseBatch(batch),\n ),\n );\n }\n for (const [name, hooks] of idGroups) {\n const target = stub(bindings.hookIds, name);\n releases.push(\n inBatches(\n hooks.map((hook) => ({\n hookId: hook.hookId,\n owner: { runId: request.runId, hookId: hook.hookId },\n })),\n (batch) => target.releaseBatch(batch),\n ),\n );\n }\n return (await Promise.all(releases)).reduce((total, count) => total + count, 0);\n };\n\n return {\n async commitRun(run, serializedMetadata, publicationExpiresAt) {\n return await stub(bindings.runCatalog, runCatalogShardName(run.runId)).upsertRun(\n run.runId,\n runKeys(run),\n serializedMetadata,\n publicationExpiresAt,\n );\n },\n\n async listRuns(options) {\n const suppliedLimit = options?.limit;\n const requestedLimit =\n typeof suppliedLimit === 'number' && Number.isFinite(suppliedLimit)\n ? Math.floor(suppliedLimit)\n : 1000;\n const limit = Math.min(1000, Math.max(1, requestedLimit));\n const shardNames = allRunCatalogShardNames();\n const pages = await Promise.all(\n shardNames.map((name) =>\n stub(bindings.runCatalog, name).list({ ...options, limit: limit + 1 }),\n ),\n );\n const encoder = new TextEncoder();\n const candidates = pages\n .flatMap((shardPage, shardIndex) =>\n shardPage.keys.map((entry) => ({\n entry: { ...entry, sourceShard: shardNames[shardIndex] },\n encodedName: encoder.encode(entry.name),\n })),\n )\n .toSorted((left, right) => {\n const compared = compareBytes(left.encodedName, right.encodedName);\n return options?.reverse ? -compared : compared;\n });\n const page = candidates.slice(0, limit).map(({ entry }) => entry);\n const listComplete =\n candidates.length <= limit && pages.every((entry) => entry.list_complete);\n return {\n keys: page,\n list_complete: listComplete,\n cursor: listComplete ? undefined : page.at(-1)?.name,\n };\n },\n\n getHookByToken(token) {\n return stub(bindings.hookTokens, hookTokenShardName(token)).get(token);\n },\n\n getHookById(hookId) {\n return stub(bindings.hookIds, hookIdShardName(hookId)).get(hookId);\n },\n\n async reserveHook(token, owner) {\n const proposedClaimId = crypto.randomUUID();\n const tokenTarget = stub(bindings.hookTokens, hookTokenShardName(token));\n const tokenClaim = await tokenTarget.reserve(token, owner, proposedClaimId);\n if (!tokenClaim.claimed && !sameOwner(tokenClaim.holder, owner)) {\n return { admitted: false, holder: tokenClaim.holder };\n }\n\n const tokenClaimId = tokenClaim.claimed ? tokenClaim.claimId : undefined;\n const hookIdTarget = stub(bindings.hookIds, hookIdShardName(owner.hookId));\n const sharedClaimId = tokenClaimId ?? proposedClaimId;\n let hookIdClaim: HookClaimResult;\n try {\n hookIdClaim = await hookIdTarget.reserve(owner.hookId, owner, sharedClaimId);\n } catch (error) {\n await Promise.allSettled([\n ...(tokenClaimId ? [tokenTarget.releaseClaim(token, owner, tokenClaimId)] : []),\n hookIdTarget.releaseClaim(owner.hookId, owner, sharedClaimId),\n ]);\n throw error;\n }\n if (!hookIdClaim.claimed) {\n if (tokenClaimId) await tokenTarget.releaseClaim(token, owner, tokenClaimId);\n return { admitted: false, holder: hookIdClaim.holder };\n }\n\n const reservation: HookReservation = {\n claimId: `${tokenClaimId ?? '-'}:${hookIdClaim.claimId}`,\n tokenClaimId,\n hookIdClaimId: hookIdClaim.claimId,\n };\n return { admitted: true, reservation };\n },\n\n async finalizeHookIndexes(token, hookId, serializedHook, owner, reservation) {\n const tokenResult = await stub(bindings.hookTokens, hookTokenShardName(token)).finalize(\n token,\n serializedHook,\n owner,\n reservation?.tokenClaimId,\n );\n if (!tokenResult.stored) return;\n await stub(bindings.hookIds, hookIdShardName(hookId)).publish(\n hookId,\n serializedHook,\n owner,\n reservation?.hookIdClaimId,\n );\n },\n\n async releaseHookReservation(token, owner, reservation) {\n const releases: Array<Promise<void>> = [];\n if (reservation.tokenClaimId) {\n releases.push(\n stub(bindings.hookTokens, hookTokenShardName(token)).releaseClaim(\n token,\n owner,\n reservation.tokenClaimId,\n ),\n );\n }\n if (reservation.hookIdClaimId) {\n releases.push(\n stub(bindings.hookIds, hookIdShardName(owner.hookId)).releaseClaim(\n owner.hookId,\n owner,\n reservation.hookIdClaimId,\n ),\n );\n }\n await Promise.all(releases);\n },\n\n async releaseHookIndexes(request) {\n return { deleted: await releaseHooks(request) };\n },\n\n async expireRun(request) {\n const [catalog, hookDeletes] = await Promise.all([\n stub(bindings.runCatalog, runCatalogShardName(request.runId)).expireRun(\n request.runId,\n request.expiredAt,\n ),\n releaseHooks({ runId: request.runId, hooks: request.hooks }),\n ]);\n return { deleted: catalog.deleted + hookDeletes };\n },\n };\n}\n","/** Standard Date fields used across all worlds */\nexport const DATE_FIELDS = new Set([\n 'createdAt',\n 'updatedAt',\n 'startedAt',\n 'completedAt',\n 'retryAfter',\n] as const);\n\n/**\n * JSON reviver that converts ISO date strings to Date objects.\n * Use with JSON.parse(json, dateReviver).\n */\nexport function dateReviver(key: string, value: unknown): unknown {\n if (\n DATE_FIELDS.has(key as typeof DATE_FIELDS extends Set<infer T> ? T : never) &&\n typeof value === 'string'\n ) {\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? value : date;\n }\n return value;\n}\n\n/**\n * Base64 helpers. Modified vs upstream: implemented over atob/btoa instead of\n * Buffer so this module is safe inside the celld worker bundle (workerd has\n * no Buffer global); atob/btoa are global in both workerd and Node >= 16.\n */\nexport function b64encode(bytes: Uint8Array): string {\n let bin = '';\n const CHUNK = 0x8000;\n for (let i = 0; i < bytes.length; i += CHUNK) {\n bin += String.fromCharCode(...bytes.subarray(i, i + CHUNK));\n }\n return btoa(bin);\n}\n\nexport function b64decode(text: string): Uint8Array {\n const bin = atob(text);\n const out = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) {\n out[i] = bin.charCodeAt(i);\n }\n return out;\n}\n\n/**\n * JSON replacer that encodes Uint8Array as a tagged object with base64 data.\n */\nexport function uint8ArrayReplacer(_key: string, value: unknown): unknown {\n if (value instanceof Uint8Array) {\n return {\n __type: 'Uint8Array',\n data: b64encode(value),\n };\n }\n return value;\n}\n\n/**\n * JSON reviver that decodes tagged Uint8Array objects and ISO date strings.\n * Supports both:\n * - New format: { __type: 'Uint8Array', data: '<base64>' }\n * - Legacy format: { __uint8array: true, data: [1, 2, 3] }\n */\nexport function uint8ArrayReviver(key: string, value: unknown): unknown {\n if (value && typeof value === 'object') {\n const obj = value as Record<string, unknown>;\n // New format: base64-encoded\n if (obj.__type === 'Uint8Array') {\n if (typeof obj.data !== 'string') throw new SyntaxError('Malformed Uint8Array tag');\n try {\n return b64decode(obj.data);\n } catch {\n throw new SyntaxError('Malformed Uint8Array tag');\n }\n }\n // Legacy format: number array (backwards compat with NATS JetStream data)\n if (obj.__uint8array === true) {\n if (\n !Array.isArray(obj.data) ||\n !obj.data.every((byte) => Number.isInteger(byte) && byte >= 0 && byte <= 255)\n ) {\n throw new SyntaxError('Malformed legacy Uint8Array tag');\n }\n return new Uint8Array(obj.data);\n }\n }\n return dateReviver(key, value);\n}\n\n/** Stringify with Uint8Array support. */\nexport function stringify(value: unknown): string {\n return JSON.stringify(value, uint8ArrayReplacer);\n}\n\n/** Parse with Uint8Array and Date support. */\nexport function parse<T>(text: string): T {\n return JSON.parse(text, uint8ArrayReviver) as T;\n}\n\n/** Deep clone using structuredClone */\nexport function deepClone<T>(value: T): T {\n return structuredClone(value);\n}\n","/**\n * RPC wire codec for the Node client <-> worker router HTTP boundary.\n *\n * Extends the shared tagged-JSON format (`{__type:'Uint8Array', data}` +\n * DATE_FIELDS revival) with explicitly tagged Dates: on Cloudflare the DO RPC\n * boundary is structured clone, which preserves EVERY Date — including ones\n * outside the well-known DATE_FIELDS (e.g. inside applyEvent outcomes and\n * user payloads). The HTTP hop must preserve them too, so Dates travel as\n * `{__type:'Date', iso}`.\n *\n * Runs on both sides of the wire, including inside the celld worker — so no\n * Node built-ins (Buffer): base64 goes through atob/btoa.\n */\nimport { b64decode, b64encode, dateReviver } from './vendor/shared/serialization.js';\n\nfunction rpcReplacer(this: unknown, key: string, value: unknown): unknown {\n // JSON.stringify invokes Date.toJSON before the replacer sees the value;\n // recover the original from the holder object.\n const original =\n this && typeof this === 'object' ? (this as Record<string, unknown>)[key] : undefined;\n if (original instanceof Date) {\n return { __type: 'Date', iso: original.toISOString() };\n }\n if (value instanceof Uint8Array) {\n return { __type: 'Uint8Array', data: b64encode(value) };\n }\n return value;\n}\n\nfunction rpcReviver(key: string, value: unknown): unknown {\n if (value && typeof value === 'object') {\n const obj = value as Record<string, unknown>;\n if (obj.__type === 'Date') {\n if (typeof obj.iso !== 'string') throw new SyntaxError('Malformed Date tag');\n const date = new Date(obj.iso);\n if (Number.isNaN(date.getTime())) throw new SyntaxError('Malformed Date tag');\n return date;\n }\n if (obj.__type === 'Uint8Array') {\n if (typeof obj.data !== 'string') throw new SyntaxError('Malformed Uint8Array tag');\n try {\n return b64decode(obj.data);\n } catch {\n throw new SyntaxError('Malformed Uint8Array tag');\n }\n }\n }\n // Fallback shared-codec semantics for untagged ISO strings in DATE_FIELDS.\n return dateReviver(key, value);\n}\n\nexport function rpcStringify(value: unknown): string {\n return JSON.stringify(value, rpcReplacer);\n}\n\nexport function rpcParse<T>(text: string): T {\n return JSON.parse(text, rpcReviver) as T;\n}\n","export class Mutex {\n promise: Promise<unknown> = Promise.resolve();\n andThen<T>(fn: () => Promise<T> | T): Promise<T> {\n this.promise = this.promise.then(\n () => fn(),\n () => fn(),\n );\n return this.promise as Promise<T>;\n }\n}\n\nexport function compact<T extends object>(obj: T) {\n const value = {} as {\n [key in keyof T]: null extends T[key] ? undefined | NonNullable<T[key]> : T[key];\n };\n for (const key in obj) {\n if (obj[key] !== null) {\n value[key] = obj[key] as any;\n } else {\n value[key] = undefined as any;\n }\n }\n return value;\n}\n\n/**\n * Reference counting helper for managing resource lifecycle\n */\nexport class Rc {\n private count = 0;\n private disposeFn?: () => void | Promise<void>;\n\n constructor(disposeFn?: () => void | Promise<void>) {\n this.disposeFn = disposeFn;\n }\n\n inc(): void {\n this.count++;\n }\n\n async dec(): Promise<void> {\n this.count--;\n if (this.count === 0 && this.disposeFn) {\n await this.disposeFn();\n }\n }\n\n getCount(): number {\n return this.count;\n }\n}\n","import { isNonNegativeSafeInteger, isRecord } from './validation.js';\nimport { MAX_QUEUE_SCHEDULE_TIMESTAMP_MS } from './lifecycle.js';\n\nexport { QUEUE_CLAIM_STALE_MS } from './lifecycle.js';\n\n/** celld v0.5.0 follows this native Queue producer-delay ceiling. */\nexport const NATIVE_QUEUE_MAX_DELAY_SECONDS = 86_400;\nexport const NATIVE_QUEUE_MAX_MESSAGE_BYTES = 128_000;\nexport const MAX_QUEUE_SUSPENSIONS = 256;\nexport const QUEUE_PAYLOAD_REGISTRY_PREFIX = 'queue-payload:';\n\nexport interface QueuePayloadRegistration {\n messageId: string;\n key: string;\n /** Delete an orphaned resilient-start payload after this deadline. */\n orphanExpiresAt: number;\n}\n\nexport interface QueuePayloadOrphan {\n messageId: string;\n runId: string;\n key: string;\n expiresAt: number;\n}\n\n/**\n * Small broker message carried by celld's native Queue.\n *\n * Run-bearing payload bytes live in object storage so the broker's 128 kB limit does not\n * narrow the Workflow World contract and run retention can delete those bytes\n * without waiting for the Queue's fixed four-day retention window.\n */\nexport interface NativeQueueEnvelope {\n version: 1;\n messageId: string;\n queueName: string;\n targetBaseUrl: string;\n runId?: string;\n idempotencyKey?: string;\n payloadKey?: string;\n /** Inline only for the small, run-less health-check envelope. */\n body?: string;\n /** Absolute workflow redelivery deadline; long waits are chained by the consumer. */\n notBefore?: number;\n /** Failed deliveries carried across suspension re-publishes. */\n deliveryFailures?: number;\n suspensionCount?: number;\n}\n\nexport interface NativeQueueSendOptions {\n delaySeconds?: number;\n}\n\nexport interface NativeQueueSendResult {\n messageId: string;\n}\n\nexport function queuePayloadRegistryKey(messageId: string): string {\n return `${QUEUE_PAYLOAD_REGISTRY_PREFIX}${messageId}`;\n}\n\nexport function queuePayloadObjectKey(runId: string, messageId: string): string {\n return `workflow-queue/${encodeURIComponent(runId)}/${encodeURIComponent(messageId)}`;\n}\n\nexport function queueClaimName(queueName: string, idempotencyKey: string): string {\n return `claim:${queueName.length}:${queueName}:${idempotencyKey}`;\n}\n\nexport function queueOrphanName(messageId: string): string {\n return `queue-orphan:${messageId}`;\n}\n\nexport function nativeQueueDelaySeconds(notBefore: number | undefined, now: number): number {\n if (notBefore === undefined || notBefore <= now) return 0;\n return Math.min(NATIVE_QUEUE_MAX_DELAY_SECONDS, Math.ceil((notBefore - now) / 1000));\n}\n\nexport function validateNativeQueueEnvelope(value: unknown): NativeQueueEnvelope {\n if (!isRecord(value)) throw new TypeError('world-celld native queue envelope must be an object');\n if (value.version !== 1)\n throw new TypeError('world-celld native queue envelope version must be 1');\n for (const field of ['messageId', 'queueName', 'targetBaseUrl'] as const) {\n if (typeof value[field] !== 'string' || value[field].length === 0) {\n throw new TypeError(`world-celld native queue envelope ${field} must be a non-empty string`);\n }\n }\n for (const field of ['runId', 'idempotencyKey', 'payloadKey', 'body'] as const) {\n if (value[field] !== undefined && typeof value[field] !== 'string') {\n throw new TypeError(`world-celld native queue envelope ${field} must be a string`);\n }\n }\n if ((value.payloadKey === undefined) === (value.body === undefined)) {\n throw new TypeError(\n 'world-celld native queue envelope must contain exactly one payload source',\n );\n }\n for (const field of ['notBefore', 'deliveryFailures', 'suspensionCount'] as const) {\n if (value[field] !== undefined && !isNonNegativeSafeInteger(value[field])) {\n throw new TypeError(\n `world-celld native queue envelope ${field} must be a non-negative safe integer`,\n );\n }\n }\n if (\n value.notBefore !== undefined &&\n (value.notBefore as number) > MAX_QUEUE_SCHEDULE_TIMESTAMP_MS\n ) {\n throw new TypeError(\n `world-celld native queue envelope notBefore must not exceed ${MAX_QUEUE_SCHEDULE_TIMESTAMP_MS}`,\n );\n }\n return value as unknown as NativeQueueEnvelope;\n}\n\nexport function validateNativeQueueSendOptions(value: unknown): NativeQueueSendOptions {\n if (value === undefined) return {};\n if (!isRecord(value)) throw new TypeError('world-celld native queue options must be an object');\n const delaySeconds = value.delaySeconds;\n if (\n delaySeconds !== undefined &&\n (!Number.isSafeInteger(delaySeconds) ||\n (delaySeconds as number) < 0 ||\n (delaySeconds as number) > NATIVE_QUEUE_MAX_DELAY_SECONDS)\n ) {\n throw new TypeError(\n `world-celld native queue delaySeconds must be an integer between 0 and ${NATIVE_QUEUE_MAX_DELAY_SECONDS}`,\n );\n }\n return delaySeconds === undefined ? {} : { delaySeconds: delaySeconds as number };\n}\n"],"mappings":"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"}
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
# celld v0.5.0 upgrade and feature review
|
|
2
|
-
|
|
3
|
-
Reviewed September 15, 2026. The previous runtime baseline was celld v0.4.0;
|
|
4
|
-
v0.5.0 is the latest published runtime release. world-celld 0.5.0 adopts it
|
|
5
|
-
as a breaking release: celld v0.4.x and node-level worker-variable overrides
|
|
6
|
-
are no longer supported. The npm and runtime versions are independent.
|
|
7
|
-
|
|
8
|
-
Sources: [v0.4.1 release](https://github.com/denoland/celld/releases/tag/v0.4.1),
|
|
9
|
-
[v0.5.0 release](https://github.com/denoland/celld/releases/tag/v0.5.0), and
|
|
10
|
-
[versioned runtime documentation](https://github.com/denoland/celld/blob/v0.5.0/docs/README.md).
|
|
11
|
-
|
|
12
|
-
## Required deployment changes
|
|
13
|
-
|
|
14
|
-
1. Schedule downtime and stop **every old celld node** before starting v0.5.0.
|
|
15
|
-
The v0.5.0 release requires a full fleet stop even from v0.4.1. Do not mix
|
|
16
|
-
v0.4.x and v0.5.0 nodes or assume an in-place downgrade is safe.
|
|
17
|
-
2. Move `CELLD_VAR_*` worker overrides into `vars` in private copies of the
|
|
18
|
-
Wrangler configs. Both scripts need the same `WORLD_SECRET`. The primary
|
|
19
|
-
worker also accepts `WORKFLOW_CALLBACK_SECRET`, `WORKFLOW_RETENTION_MS`, and
|
|
20
|
-
`WORKFLOW_RETENTION_BATCH_SIZE`. Preserve existing values when migrating.
|
|
21
|
-
Remove the old node variables: v0.5.0 rejects them at startup.
|
|
22
|
-
3. Keep these config copies outside source control and populate them from your
|
|
23
|
-
secret manager. Variables become part of the deployment in the fleet bucket;
|
|
24
|
-
restrict access accordingly. `.dev.vars` is only for `celld dev` and is not
|
|
25
|
-
uploaded by `celld deploy`.
|
|
26
|
-
4. Remove `CELLD_STORAGE_PROBE` and `CELLD_OUTPUT_GATE` if configured, including
|
|
27
|
-
values of `0`, `1`, or an empty string. Storage-contract checks and durability
|
|
28
|
-
gating are mandatory. Consult the versioned runtime documentation for other
|
|
29
|
-
removed tuning variables, including shutdown and Queue batching settings.
|
|
30
|
-
5. If using OTLP, remove `CELLD_OTEL_SINK` and set
|
|
31
|
-
`CELLD_OTEL=https://your-collector:4318` to the collector base URL. The old
|
|
32
|
-
`OTEL_EXPORTER_OTLP_ENDPOINT` variables no longer select the destination.
|
|
33
|
-
`CELLD_OTEL=1` still writes telemetry into the fleet bucket.
|
|
34
|
-
6. Using the v0.5.0 CLI, deploy the configured Queue consumer first, then the
|
|
35
|
-
primary worker. Start v0.5.0 nodes with the existing bucket, storage
|
|
36
|
-
credentials, and listener configuration. Check an authenticated World call
|
|
37
|
-
and a queued callback before reopening traffic.
|
|
38
|
-
|
|
39
|
-
There is no World data-model or binding-name migration in this change. Existing
|
|
40
|
-
native Queue attachments and payload keys retain their identities. The older
|
|
41
|
-
QueueDO-to-native-Queue cutover instructions apply only to installations that
|
|
42
|
-
still use QueueDO.
|
|
43
|
-
|
|
44
|
-
## Adopt telemetry
|
|
45
|
-
|
|
46
|
-
Enable bucket telemetry on every node to record runtime traces and logs without
|
|
47
|
-
adding a collector. Add these variables to the existing node service definition:
|
|
48
|
-
|
|
49
|
-
```sh
|
|
50
|
-
CELLD_OTEL=1
|
|
51
|
-
CELLD_OTEL_RETENTION=30d
|
|
52
|
-
OTEL_SERVICE_NAME=world-celld
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
celld writes Parquet files under `telemetry/` in the fleet bucket and expires
|
|
56
|
-
them after 30 days. The default flush interval is five minutes. Telemetry
|
|
57
|
-
retention is independent of World run retention. This is an opt-in operational
|
|
58
|
-
setting; installing the package does not change node environments.
|
|
59
|
-
|
|
60
|
-
For an existing OTLP collector, use these settings instead:
|
|
61
|
-
|
|
62
|
-
```sh
|
|
63
|
-
CELLD_OTEL=https://collector.example.com:4318
|
|
64
|
-
OTEL_SERVICE_NAME=world-celld
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
celld appends `/v1/traces` and `/v1/logs` to the collector base URL. Supply any
|
|
68
|
-
collector credentials through `OTEL_EXPORTER_OTLP_HEADERS` from your secret
|
|
69
|
-
manager. There is no fallback to the removed sink or endpoint configuration.
|
|
70
|
-
|
|
71
|
-
## Development variables
|
|
72
|
-
|
|
73
|
-
For `celld dev`, put local values in `.dev.vars` beside the Wrangler config:
|
|
74
|
-
|
|
75
|
-
```dotenv
|
|
76
|
-
WORLD_SECRET=local-development-secret
|
|
77
|
-
WORKFLOW_RETENTION_MS=0
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
Use the same local secret in the consumer and primary worker configurations.
|
|
81
|
-
Changes reload automatically. Both `.dev.vars` and `.celld/` are ignored by
|
|
82
|
-
this repository; add those ignore rules when copying the workers elsewhere.
|
|
83
|
-
These variables do not reach a production deployment. Keep using the native
|
|
84
|
-
smoke for verification of the complete two-script topology.
|
|
85
|
-
|
|
86
|
-
## Feature decisions
|
|
87
|
-
|
|
88
|
-
| Addition | Relevance to world-celld | Decision |
|
|
89
|
-
| ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
90
|
-
| v0.4.1 native Queue producer grouping and stronger durability gates | Existing `WORKFLOW_QUEUE.send()` calls gain runtime batching and durability improvements. | Adopt through the runtime upgrade. Keep pointer messages and application idempotency; runtime improvements do not imply exactly-once callbacks. Do not infer a project throughput gain from upstream benchmarks. |
|
|
91
|
-
| v0.4.1 fleet balancing, paged restore, restart recovery, and fewer false durability timeouts | Run, catalog, hook, and stream cells benefit without changing their addressing or schema. | Adopt defaults. The local restart smoke covers one process and bucket recovery, not multi-node balancing or handoff. |
|
|
92
|
-
| v0.4.1 alarm scheduling and v0.5.0 alarm/background-work fixes | Retention progress and native Queue delivery depend on durable alarms; stream code depends on asynchronous request handling. | Keep existing bounded alarm state machines and stream cancellation logic. Validate against the real runtime. |
|
|
93
|
-
| v0.5.0 lower per-cell memory, faster KV prefix listing, transaction/SQL and recovery fixes | Existing storage transactions and paginated prefix scans use these paths. | Adopt through the upgrade. Keep bounded cleanup and paging; these still limit application memory and work. |
|
|
94
|
-
| v0.4.1 Durable Object facets, with v0.5.0 rollback fixes | Facets could place related run/stream state under one owner, but current streams and global indexes have independent identities and lifecycles. | Defer. Adoption needs a data migration and contention/failure analysis; it is not a drop-in storage optimization. |
|
|
95
|
-
| v0.5.0 service-binding RPC | The companion calls `QueueDeliveryRpc.deliver()` with a shared secret and receives typed delivery outcomes. External Node clients continue using HTTP. | Adopted in the internal RPC spike. Delivery logic is extracted from the router, and the old HTTP delivery route is removed. Deploy matching worker scripts together. |
|
|
96
|
-
| v0.5.0 native Workflow retention and manual deletion | These APIs govern celld's Cloudflare-compatible Workflow instances, not Vercel Workflow records in `WorkflowRunDO`. | Do not substitute them for World retention. Keep run fences, catalog cleanup, stream deletion, and payload cleanup. |
|
|
97
|
-
| v0.5.0 `.dev.vars` with reload | Useful for local credentials and retention settings. | Ignore `.dev.vars` and `.celld/` in Git and document the development-only scope. The existing two-script smoke remains the complete local integration check. |
|
|
98
|
-
| v0.5.0 simplified telemetry configuration | Fleet traces and logs can help diagnose Queue delays, retention alarms, and recovery. | Recommend opt-in bucket telemetry or an existing OTLP collector. Document the new configuration without enabling telemetry by default. |
|
|
99
|
-
| v0.5.0 Containers/Sandbox SDK, worker loaders with service bindings/custom outbound fetch, and prebuilt WASM | Could support a future worker-hosted execution service. Today the World stores state and delivers callbacks to an external workflow application. | Defer until there is a concrete execution-hosting requirement; these do not improve the current storage adapter by themselves. |
|
|
100
|
-
| v0.4.1 expanded Node/web APIs and v0.5.0 host-state isolation | Broader compatibility and stronger runtime isolation. | Accept runtime fixes; no new API dependency is needed in the current worker bundle. |
|
|
101
|
-
|
|
102
|
-
The native Queue limits remain 128,000 bytes per message, 86,400 seconds of
|
|
103
|
-
producer delay, and four days of broker retention in
|
|
104
|
-
[the v0.5.0 queue policy](https://github.com/denoland/celld/blob/v0.5.0/crates/logic/queue.rs).
|
|
105
|
-
Keep external payload storage and long-delay republishing.
|
|
106
|
-
|
|
107
|
-
Telemetry migration details are in the
|
|
108
|
-
[v0.5.0 telemetry documentation](https://github.com/denoland/celld/blob/v0.5.0/docs/telemetry.md).
|
|
109
|
-
|
|
110
|
-
## Repository validation
|
|
111
|
-
|
|
112
|
-
The CI/native smoke and Docker performance harness pin v0.5.0. The native
|
|
113
|
-
runner verifies the release asset digests on Linux x86-64 and macOS arm64.
|
|
114
|
-
MinIO downloads use the official GitHub release assets with the existing
|
|
115
|
-
checksums because the former download host returns HTTP 410.
|
|
116
|
-
|
|
117
|
-
The native smoke deploys secrets in the temporary Worker configs and runs
|
|
118
|
-
without a storage-probe bypass. It tests acknowledged run/stream state and
|
|
119
|
-
accepted Queue delivery across a process restart, cancelled stream long polls,
|
|
120
|
-
and multi-page retention recovery after SIGKILL. These are fresh v0.5.0
|
|
121
|
-
deployments; they do not establish recovery of a production v0.4.0 bucket.
|
|
122
|
-
|
|
123
|
-
Run `pnpm check` and `pnpm test:integration:celld-smoke` for the repository and
|
|
124
|
-
runtime checks. Run `pnpm test:perf:minio` with Docker Compose to measure this
|
|
125
|
-
project's workload; compare against a separately preserved v0.4.0 baseline
|
|
126
|
-
before claiming a performance improvement.
|
|
127
|
-
|
|
128
|
-
Validation on macOS arm64: `pnpm check` passed (24 test files, 434 tests,
|
|
129
|
-
worker bundle checks, and package checks). All four real v0.5.0 smoke tests passed, including persisted bucket telemetry. The Docker performance harness could not run because the local
|
|
130
|
-
Docker installation lacks the Compose plugin; no performance gain is claimed.
|