@ewhauser/world-celld 0.4.0 → 0.5.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 CHANGED
@@ -2,6 +2,45 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## 0.5.0 (unreleased)
6
+
7
+ ### Breaking changes
8
+
9
+ - Internal Queue delivery now requires the `QueueDeliveryRpc` named entrypoint.
10
+ Deploy matching consumer and primary worker scripts together; the former
11
+ `/v1/queue/deliver` route is removed with no fallback.
12
+
13
+ - Require celld v0.5.0. Stop the entire old fleet before starting v0.5.0 nodes;
14
+ mixed-version fleets and older runtime compatibility are not supported.
15
+ - Configure worker variables in deployment `vars`. Removed `CELLD_VAR_*` node
16
+ overrides are not supported; both scripts must deploy the same `WORLD_SECRET`.
17
+ - Remove `CELLD_STORAGE_PROBE`, `CELLD_OUTPUT_GATE`, and `CELLD_OTEL_SINK` from
18
+ node environments. Use `CELLD_OTEL=1` for bucket telemetry or a collector base
19
+ URL for OTLP. Storage checks and durability gating are mandatory.
20
+
21
+ ### Improvements
22
+
23
+ - Adopt the runtime's native Queue batching, alarm, storage, and recovery
24
+ improvements with the existing World retention and payload-storage design.
25
+ - Document opt-in telemetry and development variables; verify persisted bucket
26
+ telemetry in the native runtime smoke.
27
+ - Update runtime pins and verified downloads, and ship the
28
+ [upgrade guide](docs/celld-v0.5-upgrade.md) in the npm package.
29
+
30
+ ## 0.4.1 (2026-08-24)
31
+
32
+ ## What's Changed
33
+ * fix: harden remote transport failure handling by @ewhauser in https://github.com/ewhauser/world-celld/pull/34
34
+ * test: enforce real celld restart recovery by @ewhauser in https://github.com/ewhauser/world-celld/pull/36
35
+ * test: cover index compaction alarm recovery by @ewhauser in https://github.com/ewhauser/world-celld/pull/37
36
+ * test: harden index compaction rollback coverage by @ewhauser in https://github.com/ewhauser/world-celld/pull/38
37
+ * test: harden StreamDO corruption handling by @ewhauser in https://github.com/ewhauser/world-celld/pull/39
38
+ * test: harden configuration and malformed input handling by @ewhauser in https://github.com/ewhauser/world-celld/pull/40
39
+ * fix: enforce negative apply-event contracts by @ewhauser in https://github.com/ewhauser/world-celld/pull/41
40
+
41
+
42
+ **Full Changelog**: https://github.com/ewhauser/world-celld/compare/v0.4.0...v0.4.1
43
+
5
44
  ## 0.4.0 (2026-08-22)
6
45
 
7
46
  ## What's Changed
package/NOTICE CHANGED
@@ -12,7 +12,7 @@ vinnymac/worlds:
12
12
  src/apply-event.ts
13
13
  src/storage.ts
14
14
  src/streamer.ts
15
- src/queue.ts (modified: Cloudflare Queues replaced by QueueDO)
15
+ src/queue.ts (modified: Cloudflare Queues adapted to celld Queues and R2)
16
16
  src/test-mocks.ts (modified)
17
17
  src/util.ts
18
18
  src/worker/durable-objects/WorkflowRunDO.ts
package/README.md CHANGED
@@ -4,7 +4,8 @@ A [Workflow DevKit](https://useworkflow.dev) `World` backed by
4
4
  [celld](https://github.com/denoland/celld).
5
5
 
6
6
  `world-celld` stores workflow runs, hooks, and streams in celld cells. Scheduled
7
- work is delivered with durable cell alarms. This gives Node applications a
7
+ work is delivered through celld's native Queues, with run-bearing message bodies
8
+ stored in the fleet's object store. This gives Node applications a
8
9
  self-hosted alternative to platform-specific Workflow backends.
9
10
 
10
11
  > [!WARNING]
@@ -15,7 +16,7 @@ self-hosted alternative to platform-specific Workflow backends.
15
16
 
16
17
  - Workflow run, step, event, and hook persistence
17
18
  - Durable streams
18
- - Delayed work, retries, deduplication, and dead-letter storage
19
+ - Delayed work, retries, consumer-side deduplication, and dead-letter routing
19
20
  - Configurable cleanup of terminal workflow payloads
20
21
  - An authenticated HTTP connection between a Node application and a celld fleet
21
22
  - An in-process fleet for local development and conformance testing
@@ -70,20 +71,47 @@ const world = createCelldWorld({
70
71
  });
71
72
  ```
72
73
 
73
- `WORKFLOW_BASE_URL` (or `baseUrl`) is where queue cells deliver flow and step
74
+ `WORKFLOW_BASE_URL` (or `baseUrl`) is where the native Queue bridge delivers flow
74
75
  requests. It must be reachable from every celld node.
75
76
 
76
77
  ## Deploy the worker
77
78
 
78
- Before deploying, you need a celld v0.3.0 fleet, `esbuild` on `PATH`, and an
79
- object store that meets celld's conditional-write requirements. v0.3.0 is the
80
- currently tested runtime baseline. Refer to the
79
+ Before deploying, you need a celld v0.5.0 fleet, `esbuild` on `PATH`, and an
80
+ object store that meets celld's conditional-write requirements. Refer to the
81
81
  [celld documentation](https://github.com/denoland/celld) for fleet and storage
82
82
  setup.
83
83
 
84
+ **world-celld 0.5.0 is a breaking release and requires celld v0.5.0.**
85
+ Older runtimes and node-level worker-variable overrides are unsupported.
86
+
87
+ For an existing fleet, follow the [v0.5.0 upgrade and feature review](docs/celld-v0.5-upgrade.md).
88
+ Stop every old celld node before starting v0.5.0; this is not a rolling upgrade.
89
+
90
+ Before either deploy, set `vars.WORLD_SECRET` in both copied Wrangler configs
91
+ to the same secret from your secret manager. Set optional
92
+ `vars.WORKFLOW_CALLBACK_SECRET` in the primary config and
93
+ `vars.WORKFLOW_RETENTION_MS` there to the desired maximum workflow age
94
+ (for example, `"7776000000"` for 90 days). Use private deployment copies outside
95
+ source control: celld persists these variables in the deployment stored in the
96
+ fleet bucket. v0.5.0 rejects `CELLD_VAR_*`; `.dev.vars` is read only by
97
+ `celld dev`, not by `celld deploy`.
98
+
99
+ No second storage service is required for queue payloads. celld v0.5.0 serves
100
+ the `WORKFLOW_QUEUE_PAYLOADS` binding from the existing fleet bucket under
101
+ `r2/workflow-world-queue-payloads/`. `r2_buckets` is the Wrangler-compatible
102
+ configuration key for that binding; it does not require Cloudflare R2.
103
+
104
+ Deploy the Queue consumer first, then the primary HTTP worker. The consumer
105
+ binds the primary worker's `QueueDeliveryRpc` named entrypoint; deploy matching
106
+ versions of both scripts during a maintenance window. The former internal
107
+ `/v1/queue/deliver` route is removed. The order matters:
108
+ the consumer deploy creates the Queue attachment; the primary deploy must go last
109
+ so it remains the fleet's public application.
110
+
84
111
  From a source checkout:
85
112
 
86
113
  ```sh
114
+ celld deploy ./celld-queue-worker --bucket s3://my-cells-bucket
87
115
  celld deploy ./celld-worker --bucket s3://my-cells-bucket
88
116
  ```
89
117
 
@@ -91,29 +119,40 @@ From an installed package, first copy the deployable worker into your project:
91
119
 
92
120
  ```sh
93
121
  cp -R node_modules/@ewhauser/world-celld/celld-worker ./workflow-world
122
+ cp -R node_modules/@ewhauser/world-celld/celld-queue-worker ./workflow-world-queue
123
+ celld deploy ./workflow-world-queue --bucket s3://my-cells-bucket
94
124
  celld deploy ./workflow-world --bucket s3://my-cells-bucket
95
125
  ```
96
126
 
97
- The worker rejects stateful requests unless `WORLD_SECRET` is configured. Pass
98
- the same secret to the fleet and the application:
127
+ If upgrading from the former QueueDO implementation, this is a hard queue
128
+ cutover. Existing `QueueDO` messages and dead letters are
129
+ not migrated into celld's native Queue; drain or otherwise account for them
130
+ before upgrading an existing fleet. The old QueueDO cell data becomes
131
+ unreachable after the binding is removed.
132
+
133
+ The worker rejects stateful requests unless `WORLD_SECRET` is configured in its
134
+ deployment. Use that same secret for the application
135
+ (`CELLD_WORLD_SECRET`). Start nodes with the storage and listener settings:
99
136
 
100
137
  ```sh
101
- CELLD_VAR_WORLD_SECRET="$CELLD_WORLD_SECRET" \
102
- CELLD_VAR_WORKFLOW_RETENTION_MS=7776000000 \
103
138
  celld --bucket s3://my-cells-bucket \
104
139
  --listen 0.0.0.0:8080 \
105
140
  --internal-listen 10.0.0.12:8081 \
106
141
  --advertise 10.0.0.12:8081
107
142
  ```
108
143
 
109
- Use a secret manager rather than putting the value in `wrangler.jsonc`. Keep
144
+ Keep deployment configs containing secrets out of source control. Keep
110
145
  celld's internal listener on a trusted network; the World bearer token protects
111
146
  the worker RPC routes, not celld's administrative endpoints.
112
147
 
113
- The example sets a fleet-wide maximum workflow age of 90 days. Leave
114
- `WORKFLOW_RETENTION_MS` at `0` to disable that policy.
148
+ Setting `vars.WORKFLOW_RETENTION_MS` to `"7776000000"` enables a fleet-wide
149
+ maximum workflow age of 90 days. Leave `WORKFLOW_RETENTION_MS` at `0` to disable that policy.
150
+
151
+ More deployment detail is in [`celld-worker/README.md`](./celld-worker/README.md)
152
+ and [`celld-queue-worker/README.md`](./celld-queue-worker/README.md).
115
153
 
116
- More deployment detail is in [`celld-worker/README.md`](./celld-worker/README.md).
154
+ For opt-in bucket or collector telemetry and local `.dev.vars` setup, see
155
+ the [operational examples](docs/celld-v0.5-upgrade.md#adopt-telemetry).
117
156
 
118
157
  ## How it works
119
158
 
@@ -129,11 +168,14 @@ celld worker router
129
168
  |-- HookTokenDO 32 stable token-ownership shards
130
169
  |-- HookIdDO 32 stable hook-id lookup shards
131
170
  |-- StreamDO stream chunks and run/stream indexes
132
- `-- QueueDO delayed delivery, retries, and dead letters
171
+ |-- object storage run-bearing queue payload bodies in the fleet bucket
172
+ `-- native Queue producer
133
173
  |
134
- | HTTP callbacks
135
174
  v
136
- Workflow application /.well-known/workflow/v1/flow
175
+ native Queue --> companion consumer --> QueueDeliveryRpc.deliver()
176
+ |
177
+ v
178
+ Workflow application /.well-known/workflow/v1/flow
137
179
  ```
138
180
 
139
181
  The application-side package implements the Workflow `World` interface and
@@ -147,8 +189,8 @@ Index routing is a versioned hard-cutover protocol. Run catalog writes hash the
147
189
  natural keys across 32 shards each. A single stateless worker request queries
148
190
  all 16 catalog shards in parallel and merges their lexicographic pages before
149
191
  authoritative RunDO reads. The RunDO is also the only permanent lifecycle
150
- authority: hook shards consult it directly, while queue and catalog expiry
151
- fences compact after bounded protocol leases. Token and hook-ID admission are
192
+ authority: hook shards and queue idempotency claims consult it directly, while
193
+ catalog expiry fences compact after bounded protocol leases. Token and hook-ID admission are
152
194
  each transactional inside their natural-key shards. Exact claim IDs plus a
153
195
  serialized cancellation fence resolve ambiguous delivery without pretending
154
196
  the two ownership shards form a cross-Durable-Object transaction. Stateless cohesive index routes keep
@@ -172,7 +214,6 @@ variable is shown below.
172
214
  | `secret` | `CELLD_WORLD_SECRET` | required with `fleetUrl` |
173
215
  | `baseUrl` | `WORKFLOW_BASE_URL` | `http://localhost:$PORT` |
174
216
  | `deploymentId` | `CELLD_DEPLOYMENT_ID` | `celld-default` |
175
- | `queueShards` | — | `1` |
176
217
  | `runRetentionMs` | `CELLD_RUN_RETENTION_MS` | `0` (disabled) |
177
218
  | `streamLongPollMs` | — | `20000` |
178
219
  | `streamFlushIntervalMs` | — | `0` |
@@ -180,19 +221,22 @@ variable is shown below.
180
221
 
181
222
  The deployed worker also accepts these celld variables:
182
223
 
183
- | Variable | Default | Purpose |
184
- | --------------------------------- | -------- | -------------------------------------------------------- |
185
- | `WORLD_SECRET` | none | Required bearer secret for RPC routes |
186
- | `WORKFLOW_CALLBACK_SECRET` | none | Sent with deliveries as `x-workflow-callback-secret` |
187
- | `QUEUE_MAX_ATTEMPTS` | `5` | Attempts before a message is dead-lettered |
188
- | `QUEUE_MAX_INFLIGHT` | `5` | Concurrent deliveries per queue cell (maximum `128`) |
189
- | `QUEUE_DELIVERY_TIMEOUT_MS` | `300000` | Callback timeout (maximum `300000`) |
190
- | `WORKFLOW_RETENTION_MS` | `0` | Maximum run age from creation; includes active runs |
191
- | `WORKFLOW_RETENTION_BATCH_SIZE` | `128` | Runs admitted by each cron sweep (maximum `1000`) |
192
- | `WORKFLOW_RETENTION_QUEUE_SHARDS` | `1` | Queue-shard fallback for runs created before this policy |
193
-
194
- `queueShards` is part of queue placement and is pinned when a queue cell is
195
- first used. Drain pending work before changing it.
224
+ | Variable | Default | Purpose |
225
+ | ------------------------------- | ------- | ---------------------------------------------------- |
226
+ | `WORLD_SECRET` | none | Required bearer secret for RPC routes |
227
+ | `WORKFLOW_CALLBACK_SECRET` | none | Sent with deliveries as `x-workflow-callback-secret` |
228
+ | `WORKFLOW_RETENTION_MS` | `0` | Maximum run age from creation; includes active runs |
229
+ | `WORKFLOW_RETENTION_BATCH_SIZE` | `128` | Runs admitted by each cron sweep (maximum `1000`) |
230
+
231
+ Queue deadlines use fixed-width 13-digit epoch-millisecond keys. A requested
232
+ `delaySeconds` or handler redelivery timeout is accepted only when its deadline
233
+ leaves room for the 15-minute idempotency-claim window below the absolute
234
+ `9999999999999` limit. A suspension that cannot preserve that headroom becomes
235
+ a normal broker retry instead of being clamped. celld's native
236
+ producer delay is capped at 86,400 seconds, so the consumer chains longer waits
237
+ while preserving the absolute deadline and stable message identity. Long
238
+ test-mode waits are chunked at the host timer limit without changing this
239
+ deadline contract.
196
240
 
197
241
  ## Workflow retention
198
242
 
@@ -206,8 +250,9 @@ failed, and cancelled runs.
206
250
  The packaged worker declares an hourly UTC celld cron trigger. Each occurrence
207
251
  scans one bounded creation-time catalog page and asks the authoritative run
208
252
  cells to enforce the cutoff. A run cell immediately fences reads, writes, stream
209
- activity, and queue work, removes its catalog entry, then finishes the existing
210
- bounded stream, queue, and payload cleanup phases through its durable alarm.
253
+ activity, and queue payload work, removes its catalog entry, then finishes the
254
+ existing bounded stream, object-store queue-payload, and run-payload cleanup phases through
255
+ its durable alarm.
211
256
  Repeated cron invocations and alarm retries are idempotent.
212
257
 
213
258
  One occurrence admits at most `WORKFLOW_RETENTION_BATCH_SIZE` runs (default
@@ -216,10 +261,6 @@ backlog, later occurrences continue with the next oldest entries. Edit
216
261
  `triggers.crons` in the copied `celld-worker/wrangler.jsonc` if hourly discovery
217
262
  does not provide the desired expiration resolution or catch-up rate.
218
263
 
219
- New runs persist the application's `queueShards` value for complete queue
220
- cleanup. `WORKFLOW_RETENTION_QUEUE_SHARDS` is the fallback for runs created
221
- before that metadata existed and must match the placement used by those runs.
222
-
223
264
  ### Terminal payload retention
224
265
 
225
266
  When `runRetentionMs` is greater than zero, a terminal transition atomically
@@ -228,8 +269,10 @@ step, hook, and stream data remains readable until that deadline. Active and
228
269
  pending runs are never eligible for automatic cleanup.
229
270
 
230
271
  At expiration, the run cell fences new writes and removes the run's derived
231
- indexes, stream chunks, pending and dead-letter queue messages, and durable run
232
- payloads. Cleanup is a persisted, idempotent state machine. Each alarm processes
272
+ indexes, stream chunks, object-store queue payloads, and durable run payloads.
273
+ Native Queue pointer messages can remain until celld's fixed four-day retention
274
+ expires; a pointer whose object-store body was removed is acknowledged as permanently gone
275
+ and cannot resurrect the run. Cleanup is a persisted, idempotent state machine. Each alarm processes
233
276
  one bounded page, persists its progress, and re-arms the next alarm; an
234
277
  interrupted phase records its error and retries with capped backoff.
235
278
 
@@ -261,10 +304,8 @@ fleet-wide maximum age are both enabled, the earlier deadline wins.
261
304
 
262
305
  - Delivery is at least once. Workflow steps and other external side effects
263
306
  must be idempotent.
264
- - Queue cells expose `stats`, `listDeadLetters`, `redriveDeadLetter`,
265
- `purgeDeadLetters`, and `rearmAlarm` through the authenticated RPC endpoint.
266
- `purgeDeadLetters` deletes at most 128 entries per call; repeat it while its
267
- result reports `hasMore`.
307
+ - Native Queue operations are administered with celld's `queue info`, `peek`,
308
+ `pause`, `resume`, `purge`, and `redrive` commands.
268
309
  - Run cells expose retention status, scheduling, immediate cleanup, and alarm
269
310
  recovery through `world.retention`.
270
311
  - A new application URL applies to newly enqueued messages. Existing messages
@@ -293,6 +334,39 @@ CELLD_WORLD_SECRET=replace-with-a-secret \
293
334
  pnpm test:integration
294
335
  ```
295
336
 
337
+ ### Real celld restart smoke
338
+
339
+ The required CI smoke owns native celld v0.5.0 and MinIO processes on loopback,
340
+ uses fresh temporary bucket and runtime state, and kills celld with `SIGKILL`
341
+ before deleting its local working state and starting a new process against the
342
+ bucket-backed state. It checks that:
343
+
344
+ - opt-in runtime telemetry is persisted as nonempty Parquet files in the fleet bucket;
345
+ - acknowledged run and stream state survives the process restart;
346
+ - an accepted delayed queue message that becomes due while celld is down is
347
+ delivered once after the native broker is restored;
348
+ - the companion Queue consumer can call the named `QueueDeliveryRpc` service
349
+ entrypoint and recover run-bearing payloads from the fleet object store;
350
+ - transient callbacks increment attempts while suspension preserves the attempt
351
+ count and message identity;
352
+ - invalid credentials, envelopes, and attempts are rejected across real RPC;
353
+ - the former HTTP delivery route returns 404;
354
+ - multi-page retention cleanup continues from a persisted nonterminal phase;
355
+ - cancelling an in-flight HTTP long poll leaves the stream writable and
356
+ readable.
357
+
358
+ Run the same bounded smoke locally on Linux x86-64 or macOS arm64:
359
+
360
+ ```sh
361
+ pnpm test:integration:celld-smoke
362
+ ```
363
+
364
+ The runner downloads celld and MinIO artifacts at pinned versions and verifies
365
+ their SHA-256 digests before use. celld performs its required storage-contract
366
+ probe at startup; the smoke runs exactly one celld process at a time. It proves the
367
+ single-process restart boundaries above, not multi-node ownership, handoff, or
368
+ failover correctness.
369
+
296
370
  ### Local MinIO performance and loss test
297
371
 
298
372
  The opt-in performance harness starts a fresh MinIO bucket and a single celld
@@ -301,13 +375,12 @@ message reaches a successful callback, including forced `503` redeliveries. A
301
375
  second workload creates terminal runs with streams and delayed queue messages,
302
376
  then verifies complete payload cleanup without resurrection. Results include
303
377
  queue and cleanup throughput plus p50, p95, p99, and maximum latency and are
304
- saved under `.perf-results/`. The harness currently pins celld v0.3.0.
378
+ saved under `.perf-results/`. The harness pins celld v0.5.0 and deploys the same
379
+ two-script native Queue topology as the restart smoke.
305
380
 
306
- > MinIO Community is **not a supported celld production store**. It does not
307
- > implement the conditional writes celld needs for ownership fencing. This
308
- > deliberately single-node harness disables celld's storage probe and tests
309
- > the queue's persistence, redelivery, and performance paths only; it does not
310
- > validate multi-node ownership or failover correctness.
381
+ > This harness runs a single celld node and leaves the required storage-contract
382
+ > probe enabled. Passing it does not establish multi-node fencing, ownership
383
+ > handoff, or production suitability for a particular object-store deployment.
311
384
 
312
385
  With Docker and the Compose plugin installed, run:
313
386
 
@@ -315,43 +388,10 @@ With Docker and the Compose plugin installed, run:
315
388
  pnpm test:perf:minio
316
389
  ```
317
390
 
318
- The defaults send 1,000 messages with concurrency 32 across two queue cells,
319
- and force every twentieth message through one `503` redelivery.
320
-
321
- #### Reference result
322
-
323
- The following directional baseline was recorded on August 21, 2026. MinIO and
324
- celld ran as native arm64 processes because Docker was unavailable on the test
325
- machine; container results will differ.
326
-
327
- | Environment | Value |
328
- | ----------- | -------------------------------------------------------------------------------------------------------- |
329
- | Machine | MacBook Pro (Mac15,8), Apple M3 Max, 16 cores (12 performance, 4 efficiency), 64 GB RAM |
330
- | OS | macOS 26.5.2 (25F84), arm64 |
331
- | Services | celld v0.3.0; MinIO RELEASE.2025-09-07T16-13-09Z |
332
- | Workload | 1,000 messages; concurrency 32; 2 queue shards; 256-byte target payload; every 20th message retried once |
333
-
334
- | Metric | Result |
335
- | ----------------------- | --------------------------------------------------------------------------------: |
336
- | Delivery integrity | 1,000 accepted, 1,000 delivered, 0 missing, 0 duplicate successes, 0 dead letters |
337
- | Callback attempts | 1,050, including 50 forced redeliveries |
338
- | Enqueue throughput | 865.66 messages/s |
339
- | Delivery throughput | 794.60 messages/s |
340
- | Enqueue latency | p50 26.06 ms; p95 70.72 ms; p99 146.59 ms; max 157.70 ms |
341
- | All delivery latency | p50 65.05 ms; p95 184.47 ms; p99 225.10 ms; max 328.75 ms |
342
- | First-attempt latency | p50 63.65 ms; p95 177.93 ms; p99 209.38 ms; max 226.86 ms |
343
- | Retried-message latency | p50 114.81 ms; p95 286.35 ms; p99 328.75 ms; max 328.75 ms |
344
- | Final queue state | 0 pending, 0 in flight, 0 dead letters across both shards |
345
-
346
- The retention workload in the same run tombstoned 100 of 100 terminal runs,
347
- expired all 100 reads, deleted 400 payload keys, 100 streams, and 100 delayed
348
- queue messages, and left both queue shards empty.
349
-
350
- Use these numbers as a smoke-test reference, not a portable performance
351
- guarantee. For regression tracking, compare repeated runs on the same machine
352
- and runtime configuration. Two shards gave the best latency balance for this
353
- workload; benchmark your own traffic before changing the shard count. A fleet
354
- pins its shard count on first use, so changing it requires draining the queues.
391
+ The defaults send 1,000 messages with concurrency 32 and force every twentieth
392
+ message through one `503` redelivery. The old QueueDO/shard baseline is not
393
+ comparable to the native celld Queue path and was intentionally removed; record
394
+ a fresh machine-specific baseline before enabling budgets.
355
395
 
356
396
  Override the workload or set machine-specific regression budgets with
357
397
  environment variables:
@@ -359,18 +399,17 @@ environment variables:
359
399
  ```sh
360
400
  PERF_MESSAGES=10000 \
361
401
  PERF_CONCURRENCY=64 \
362
- PERF_QUEUE_SHARDS=16 \
363
402
  PERF_MIN_DELIVERY_PER_SECOND=100 \
364
403
  PERF_MAX_DELIVERY_P99_MS=5000 \
365
404
  pnpm test:perf:minio
366
405
  ```
367
406
 
368
407
  Useful controls are `PERF_PAYLOAD_BYTES`, `PERF_RETRY_EVERY`,
369
- `PERF_TIMEOUT_MS`, `PERF_QUEUE_MAX_INFLIGHT`,
408
+ `PERF_TIMEOUT_MS`,
370
409
  `PERF_MIN_ENQUEUE_PER_SECOND`, `PERF_MIN_DELIVERY_PER_SECOND`, and
371
410
  `PERF_MAX_DELIVERY_P99_MS`. Throughput and latency budgets default to disabled
372
- because local machines vary. Message-loss, dead-letter, message-ID, queue-drain,
373
- and duplicate-success checks are always enforced.
411
+ because local machines vary. Message-loss, message-ID, callback validity, and
412
+ duplicate-success checks are always enforced.
374
413
 
375
414
  Bug reports and focused pull requests are welcome. Please include a regression
376
415
  test for behavior changes and run the checks above before submitting a PR.
@@ -0,0 +1,26 @@
1
+ # world-celld Queue consumer
2
+
3
+ This companion celld v0.5.0 script consumes the native `workflow-world` Queue
4
+ and forwards each delivery to the primary `workflow-world` script through a
5
+ `QueueDeliveryRpc` named service binding. The consumer calls
6
+ `deliver(secret, envelope, attempt)` and handles `complete`, `suspend`, or `retry`
7
+ results. RPC exceptions retry without acknowledgment. The separate script keeps the broker consumer attachment independent of the
8
+ public HTTP worker.
9
+
10
+ Deploy it before the primary worker so the Queue consumer attachment exists:
11
+
12
+ ```sh
13
+ cp -r node_modules/@ewhauser/world-celld/celld-queue-worker ./workflow-world-queue
14
+ celld deploy ./workflow-world-queue --bucket s3://my-cells-bucket
15
+ ```
16
+
17
+ Before deploying, set `vars.WORLD_SECRET` in the copied Wrangler config to the
18
+ same bearer secret used by the primary worker. Keep that private deployment
19
+ config out of source control. v0.5.0 rejects `CELLD_VAR_*` node overrides.
20
+ Deploy the primary worker after this script so it remains the fleet's
21
+ public application. Native Queue operations are available through `celld queue
22
+ info`, `peek`, `pause`, `resume`, `purge`, and `redrive`.
23
+
24
+ The primary worker must export `QueueDeliveryRpc`, and this script's service
25
+ binding must set `entrypoint` to that name. Deploy matching worker versions
26
+ during a maintenance window; there is no fallback to `/v1/queue/deliver`.
@@ -0,0 +1,3 @@
1
+ // Companion native Queue consumer. It intentionally exports no fetch handler:
2
+ // Keep the Queue consumer attachment independent of the public HTTP worker.
3
+ export { default } from '@ewhauser/world-celld/queue-consumer';
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "workflow-world-queue-consumer",
3
+ "main": "worker.ts",
4
+ "compatibility_date": "2026-01-01",
5
+ "services": [
6
+ { "binding": "WORLD_SERVICE", "service": "workflow-world", "entrypoint": "QueueDeliveryRpc" },
7
+ ],
8
+ "queues": {
9
+ "producers": [{ "binding": "WORKFLOW_QUEUE", "queue": "workflow-world" }],
10
+ "consumers": [
11
+ {
12
+ "queue": "workflow-world",
13
+ "max_batch_size": 10,
14
+ "max_batch_timeout": 1,
15
+ "max_retries": 100,
16
+ "dead_letter_queue": "workflow-world-dlq",
17
+ "max_concurrency": 25,
18
+ },
19
+ ],
20
+ },
21
+ "vars": {
22
+ // REQUIRED. Inject the same value used by the primary worker through
23
+ // vars in a private deployment copy of this config; never commit secrets.
24
+ "WORLD_SECRET": "",
25
+ },
26
+ }
@@ -1,50 +1,60 @@
1
1
  # world-celld worker
2
2
 
3
- The celld-deployable half of `@ewhauser/world-celld`: six cell classes
4
- (WorkflowRunDO, RunCatalogDO, HookTokenDO, HookIdDO, StreamDO, and QueueDO)
5
- behind an authenticated HTTP router.
6
- Storage, control, and queue methods use fixed JSON RPC routes; stream chunks use
7
- bounded binary batch writes and binary long-poll reads.
3
+ The primary celld-deployable half of `@ewhauser/world-celld`: five cell classes
4
+ (WorkflowRunDO, RunCatalogDO, HookTokenDO, HookIdDO, and StreamDO) behind an
5
+ authenticated HTTP router, plus the `QueueDeliveryRpc` named entrypoint
6
+ for the companion consumer. Storage and control methods use fixed JSON RPC
7
+ routes; stream chunks use bounded binary batch writes and binary long-poll
8
+ reads. Queue producers use celld's native Queue binding, with run-bearing
9
+ payload bodies stored in the fleet's object store.
8
10
 
9
11
  ## Deploy
10
12
 
11
- Copy this directory out of `node_modules` so `celld deploy` can bundle it
12
- (its `main` must live inside the project directory), then deploy against your
13
- fleet bucket:
13
+ Copy this directory and the companion Queue consumer out of `node_modules` so
14
+ `celld deploy` can bundle them. Deploy the consumer first and this primary
15
+ worker last. Before deploying, set `vars.WORLD_SECRET` in both copied
16
+ Wrangler configs to the same secret from your secret manager. Keep those
17
+ private deployment copies out of source control; celld stores their variables
18
+ in the fleet bucket. Stop the whole old fleet before starting v0.5.0 nodes:
14
19
 
15
20
  ```sh
16
21
  cp -r node_modules/@ewhauser/world-celld/celld-worker ./workflow-world
22
+ cp -r node_modules/@ewhauser/world-celld/celld-queue-worker ./workflow-world-queue
23
+ celld deploy ./workflow-world-queue --bucket s3://my-cells-bucket
17
24
  celld deploy ./workflow-world --bucket s3://my-cells-bucket
18
25
  ```
19
26
 
27
+ Upgrading from the former QueueDO implementation is a hard cutover. Drain or
28
+ account for its pending messages and dead letters first; they are not migrated
29
+ to the native Queue.
30
+
20
31
  Requirements:
21
32
 
22
- - celld v0.3.0 (the currently tested runtime baseline).
33
+ - celld v0.5.0 (the currently tested runtime baseline).
23
34
  - `esbuild` on PATH (celld shells out to it).
24
35
  - A bucket with conditional-write support (celld's fencing requirement).
25
- - `WORLD_SECRET` injected at the node level (`CELLD_VAR_WORLD_SECRET=...`) —
36
+ - `WORLD_SECRET` set in the deployment config (`vars.WORLD_SECRET`) —
26
37
  the router fails closed with 503 while it is empty.
27
38
 
39
+ Queue payloads do not require another provider or another set of credentials.
40
+ celld maps the `WORKFLOW_QUEUE_PAYLOADS` binding into the fleet bucket under
41
+ `r2/workflow-world-queue-payloads/`. The `r2_buckets` key in `wrangler.jsonc`
42
+ names the Workers-compatible binding API; it does not require Cloudflare R2.
43
+
28
44
  ## Fleet-wide retention
29
45
 
30
46
  The bundled `wrangler.jsonc` declares an hourly UTC cron trigger. It does no
31
- catalog work by default. Set `CELLD_VAR_WORKFLOW_RETENTION_MS` on every node to
32
- enable a maximum workflow age measured from run creation:
33
-
34
- ```sh
35
- CELLD_VAR_WORKFLOW_RETENTION_MS=7776000000 \
36
- celld --bucket s3://my-cells-bucket
37
- ```
47
+ catalog work by default. Set `vars.WORKFLOW_RETENTION_MS` to `"7776000000"`
48
+ in the primary Wrangler config and redeploy to enable a maximum workflow age
49
+ measured from run creation. v0.5.0 rejects `CELLD_VAR_*` node overrides.
50
+ `.dev.vars` overrides apply only to `celld dev`, not production deployments.
38
51
 
39
52
  `7776000000` is 90 days. The policy includes pending and running workflows as
40
53
  well as terminal ones. Each cron occurrence admits at most
41
54
  `WORKFLOW_RETENTION_BATCH_SIZE` runs (default `128`); the existing per-run alarm
42
- state machine finishes bounded index, stream, queue, and payload cleanup.
43
-
44
- New runs persist the application's `queueShards` placement. Set
45
- `WORKFLOW_RETENTION_QUEUE_SHARDS` to the historical queue-shard count when
46
- cleaning runs created before this worker version. Edit `triggers.crons` in the
47
- copied config if hourly discovery is not the desired resolution.
55
+ state machine finishes bounded index, stream, object-store queue-payload, and run-payload
56
+ cleanup. Edit `triggers.crons` in the copied config if hourly discovery is not
57
+ the desired resolution.
48
58
 
49
59
  Point the app at any node's public listener:
50
60
 
@@ -3,9 +3,9 @@
3
3
  // application's node_modules so the deployed bundle stays in step with the
4
4
  // installed @ewhauser/world-celld version.
5
5
  export {
6
+ QueueDeliveryRpc,
6
7
  HookIdDO,
7
8
  HookTokenDO,
8
- QueueDO,
9
9
  RunCatalogDO,
10
10
  StreamDO,
11
11
  WorkflowRunDO,
@@ -12,7 +12,6 @@
12
12
  { "name": "WORKFLOW_RUN_CATALOG", "class_name": "RunCatalogDO" },
13
13
  { "name": "WORKFLOW_HOOK_TOKENS", "class_name": "HookTokenDO" },
14
14
  { "name": "WORKFLOW_HOOK_IDS", "class_name": "HookIdDO" },
15
- { "name": "WORKFLOW_QUEUE", "class_name": "QueueDO" },
16
15
  ],
17
16
  },
18
17
  "migrations": [
@@ -24,10 +23,20 @@
24
23
  "RunCatalogDO",
25
24
  "HookTokenDO",
26
25
  "HookIdDO",
27
- "QueueDO",
28
26
  ],
29
27
  },
30
28
  ],
29
+ "queues": {
30
+ "producers": [{ "binding": "WORKFLOW_QUEUE", "queue": "workflow-world" }],
31
+ },
32
+ // celld maps this Workers-compatible binding into the existing fleet bucket
33
+ // at r2/<bucket_name>/; it does not require a Cloudflare R2 deployment.
34
+ "r2_buckets": [
35
+ {
36
+ "binding": "WORKFLOW_QUEUE_PAYLOADS",
37
+ "bucket_name": "workflow-world-queue-payloads",
38
+ },
39
+ ],
31
40
  // Fleet-wide maximum-age retention is discovered hourly. The handler is a
32
41
  // no-op while WORKFLOW_RETENTION_MS is "0". Change this expression if a
33
42
  // different sweep cadence is required; celld cron schedules are UTC.
@@ -35,7 +44,7 @@
35
44
  "vars": {
36
45
  // REQUIRED: bearer secret for the world's RPC and binary stream surface.
37
46
  // Authenticated routes fail closed with 503 while this is empty. Inject the real value
38
- // at the node level with CELLD_VAR_WORLD_SECRET instead of committing it.
47
+ // into vars in a private deployment copy of this config; never commit secrets.
39
48
  "WORLD_SECRET": "",
40
49
  // Optional: forwarded as x-workflow-callback-secret on queue deliveries.
41
50
  "WORKFLOW_CALLBACK_SECRET": "",
@@ -44,10 +53,5 @@
44
53
  "WORKFLOW_RETENTION_MS": "0",
45
54
  // Maximum runs admitted to cleanup by one hourly sweep (1..1000).
46
55
  "WORKFLOW_RETENTION_BATCH_SIZE": "128",
47
- // Must match createCelldWorld({ queueShards }) so every queue shard is
48
- // included in cleanup for active runs discovered by the sweep.
49
- "WORKFLOW_RETENTION_QUEUE_SHARDS": "1",
50
- // Optional queue tuning (strings): QUEUE_MAX_ATTEMPTS, QUEUE_MAX_INFLIGHT,
51
- // QUEUE_DELIVERY_TIMEOUT_MS.
52
56
  },
53
57
  }