@ewhauser/world-celld 0.4.1 → 0.6.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,39 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## 0.6.0 (2026-09-16)
6
+
7
+ ## What's Changed
8
+ * feat!: upgrade Workflow packages and require World spec v7 by @ewhauser in https://github.com/ewhauser/world-celld/pull/59
9
+
10
+
11
+ **Full Changelog**: https://github.com/ewhauser/world-celld/compare/v0.5.0...v0.6.0
12
+
13
+ ## 0.5.0 (unreleased)
14
+
15
+ ### Breaking changes
16
+
17
+ - Internal Queue delivery now requires the `QueueDeliveryRpc` named entrypoint.
18
+ Deploy matching consumer and primary worker scripts together; the former
19
+ `/v1/queue/deliver` route is removed with no fallback.
20
+
21
+ - Require celld v0.5.0. Stop the entire old fleet before starting v0.5.0 nodes;
22
+ mixed-version fleets and older runtime compatibility are not supported.
23
+ - Configure worker variables in deployment `vars`. Removed `CELLD_VAR_*` node
24
+ overrides are not supported; both scripts must deploy the same `WORLD_SECRET`.
25
+ - Remove `CELLD_STORAGE_PROBE`, `CELLD_OUTPUT_GATE`, and `CELLD_OTEL_SINK` from
26
+ node environments. Use `CELLD_OTEL=1` for bucket telemetry or a collector base
27
+ URL for OTLP. Storage checks and durability gating are mandatory.
28
+
29
+ ### Improvements
30
+
31
+ - Adopt the runtime's native Queue batching, alarm, storage, and recovery
32
+ improvements with the existing World retention and payload-storage design.
33
+ - Document opt-in telemetry and development variables; verify persisted bucket
34
+ telemetry in the native runtime smoke.
35
+ - Update runtime pins and verified downloads, and ship the
36
+ [upgrade guide](docs/celld-v0.5-upgrade.md) in the npm package.
37
+
5
38
  ## 0.4.1 (2026-08-24)
6
39
 
7
40
  ## 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,18 +4,30 @@ 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]
11
12
  > `world-celld` is experimental and has not been proven in production. Its API
12
13
  > and storage layout may change before 1.0.
13
14
 
15
+ ## World protocol compatibility
16
+
17
+ This adapter supports World spec v7 with the Workflow 5 beta packages. Event
18
+ slots are allocated in the same transaction that stores each event, so reads
19
+ see a dense log and failed writes leave no gaps. No background sealing or
20
+ `noop` events are needed. Runs stamped with earlier spec versions are rejected
21
+ when applying events; finish existing runs before upgrading.
22
+
23
+ Run listing accepts either a single status or an array of statuses (OR matching).
24
+ An empty status array matches no runs.
25
+
14
26
  ## What it provides
15
27
 
16
28
  - Workflow run, step, event, and hook persistence
17
29
  - Durable streams
18
- - Delayed work, retries, deduplication, and dead-letter storage
30
+ - Delayed work, retries, consumer-side deduplication, and dead-letter routing
19
31
  - Configurable cleanup of terminal workflow payloads
20
32
  - An authenticated HTTP connection between a Node application and a celld fleet
21
33
  - An in-process fleet for local development and conformance testing
@@ -70,20 +82,47 @@ const world = createCelldWorld({
70
82
  });
71
83
  ```
72
84
 
73
- `WORKFLOW_BASE_URL` (or `baseUrl`) is where queue cells deliver flow and step
85
+ `WORKFLOW_BASE_URL` (or `baseUrl`) is where the native Queue bridge delivers flow
74
86
  requests. It must be reachable from every celld node.
75
87
 
76
88
  ## Deploy the worker
77
89
 
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
90
+ Before deploying, you need a celld v0.5.0 fleet, `esbuild` on `PATH`, and an
91
+ object store that meets celld's conditional-write requirements. Refer to the
81
92
  [celld documentation](https://github.com/denoland/celld) for fleet and storage
82
93
  setup.
83
94
 
95
+ **world-celld 0.5.0 is a breaking release and requires celld v0.5.0.**
96
+ Older runtimes and node-level worker-variable overrides are unsupported.
97
+
98
+ For an existing fleet, follow the [v0.5.0 upgrade and feature review](docs/celld-v0.5-upgrade.md).
99
+ Stop every old celld node before starting v0.5.0; this is not a rolling upgrade.
100
+
101
+ Before either deploy, set `vars.WORLD_SECRET` in both copied Wrangler configs
102
+ to the same secret from your secret manager. Set optional
103
+ `vars.WORKFLOW_CALLBACK_SECRET` in the primary config and
104
+ `vars.WORKFLOW_RETENTION_MS` there to the desired maximum workflow age
105
+ (for example, `"7776000000"` for 90 days). Use private deployment copies outside
106
+ source control: celld persists these variables in the deployment stored in the
107
+ fleet bucket. v0.5.0 rejects `CELLD_VAR_*`; `.dev.vars` is read only by
108
+ `celld dev`, not by `celld deploy`.
109
+
110
+ No second storage service is required for queue payloads. celld v0.5.0 serves
111
+ the `WORKFLOW_QUEUE_PAYLOADS` binding from the existing fleet bucket under
112
+ `r2/workflow-world-queue-payloads/`. `r2_buckets` is the Wrangler-compatible
113
+ configuration key for that binding; it does not require Cloudflare R2.
114
+
115
+ Deploy the Queue consumer first, then the primary HTTP worker. The consumer
116
+ binds the primary worker's `QueueDeliveryRpc` named entrypoint; deploy matching
117
+ versions of both scripts during a maintenance window. The former internal
118
+ `/v1/queue/deliver` route is removed. The order matters:
119
+ the consumer deploy creates the Queue attachment; the primary deploy must go last
120
+ so it remains the fleet's public application.
121
+
84
122
  From a source checkout:
85
123
 
86
124
  ```sh
125
+ celld deploy ./celld-queue-worker --bucket s3://my-cells-bucket
87
126
  celld deploy ./celld-worker --bucket s3://my-cells-bucket
88
127
  ```
89
128
 
@@ -91,29 +130,40 @@ From an installed package, first copy the deployable worker into your project:
91
130
 
92
131
  ```sh
93
132
  cp -R node_modules/@ewhauser/world-celld/celld-worker ./workflow-world
133
+ cp -R node_modules/@ewhauser/world-celld/celld-queue-worker ./workflow-world-queue
134
+ celld deploy ./workflow-world-queue --bucket s3://my-cells-bucket
94
135
  celld deploy ./workflow-world --bucket s3://my-cells-bucket
95
136
  ```
96
137
 
97
- The worker rejects stateful requests unless `WORLD_SECRET` is configured. Pass
98
- the same secret to the fleet and the application:
138
+ If upgrading from the former QueueDO implementation, this is a hard queue
139
+ cutover. Existing `QueueDO` messages and dead letters are
140
+ not migrated into celld's native Queue; drain or otherwise account for them
141
+ before upgrading an existing fleet. The old QueueDO cell data becomes
142
+ unreachable after the binding is removed.
143
+
144
+ The worker rejects stateful requests unless `WORLD_SECRET` is configured in its
145
+ deployment. Use that same secret for the application
146
+ (`CELLD_WORLD_SECRET`). Start nodes with the storage and listener settings:
99
147
 
100
148
  ```sh
101
- CELLD_VAR_WORLD_SECRET="$CELLD_WORLD_SECRET" \
102
- CELLD_VAR_WORKFLOW_RETENTION_MS=7776000000 \
103
149
  celld --bucket s3://my-cells-bucket \
104
150
  --listen 0.0.0.0:8080 \
105
151
  --internal-listen 10.0.0.12:8081 \
106
152
  --advertise 10.0.0.12:8081
107
153
  ```
108
154
 
109
- Use a secret manager rather than putting the value in `wrangler.jsonc`. Keep
155
+ Keep deployment configs containing secrets out of source control. Keep
110
156
  celld's internal listener on a trusted network; the World bearer token protects
111
157
  the worker RPC routes, not celld's administrative endpoints.
112
158
 
113
- The example sets a fleet-wide maximum workflow age of 90 days. Leave
114
- `WORKFLOW_RETENTION_MS` at `0` to disable that policy.
159
+ Setting `vars.WORKFLOW_RETENTION_MS` to `"7776000000"` enables a fleet-wide
160
+ maximum workflow age of 90 days. Leave `WORKFLOW_RETENTION_MS` at `0` to disable that policy.
161
+
162
+ More deployment detail is in [`celld-worker/README.md`](./celld-worker/README.md)
163
+ and [`celld-queue-worker/README.md`](./celld-queue-worker/README.md).
115
164
 
116
- More deployment detail is in [`celld-worker/README.md`](./celld-worker/README.md).
165
+ For opt-in bucket or collector telemetry and local `.dev.vars` setup, see
166
+ the [operational examples](docs/celld-v0.5-upgrade.md#adopt-telemetry).
117
167
 
118
168
  ## How it works
119
169
 
@@ -129,11 +179,14 @@ celld worker router
129
179
  |-- HookTokenDO 32 stable token-ownership shards
130
180
  |-- HookIdDO 32 stable hook-id lookup shards
131
181
  |-- StreamDO stream chunks and run/stream indexes
132
- `-- QueueDO delayed delivery, retries, and dead letters
182
+ |-- object storage run-bearing queue payload bodies in the fleet bucket
183
+ `-- native Queue producer
133
184
  |
134
- | HTTP callbacks
135
185
  v
136
- Workflow application /.well-known/workflow/v1/flow
186
+ native Queue --> companion consumer --> QueueDeliveryRpc.deliver()
187
+ |
188
+ v
189
+ Workflow application /.well-known/workflow/v1/flow
137
190
  ```
138
191
 
139
192
  The application-side package implements the Workflow `World` interface and
@@ -147,8 +200,8 @@ Index routing is a versioned hard-cutover protocol. Run catalog writes hash the
147
200
  natural keys across 32 shards each. A single stateless worker request queries
148
201
  all 16 catalog shards in parallel and merges their lexicographic pages before
149
202
  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
203
+ authority: hook shards and queue idempotency claims consult it directly, while
204
+ catalog expiry fences compact after bounded protocol leases. Token and hook-ID admission are
152
205
  each transactional inside their natural-key shards. Exact claim IDs plus a
153
206
  serialized cancellation fence resolve ambiguous delivery without pretending
154
207
  the two ownership shards form a cross-Durable-Object transaction. Stateless cohesive index routes keep
@@ -172,7 +225,6 @@ variable is shown below.
172
225
  | `secret` | `CELLD_WORLD_SECRET` | required with `fleetUrl` |
173
226
  | `baseUrl` | `WORKFLOW_BASE_URL` | `http://localhost:$PORT` |
174
227
  | `deploymentId` | `CELLD_DEPLOYMENT_ID` | `celld-default` |
175
- | `queueShards` | — | `1` |
176
228
  | `runRetentionMs` | `CELLD_RUN_RETENTION_MS` | `0` (disabled) |
177
229
  | `streamLongPollMs` | — | `20000` |
178
230
  | `streamFlushIntervalMs` | — | `0` |
@@ -180,30 +232,22 @@ variable is shown below.
180
232
 
181
233
  The deployed worker also accepts these celld variables:
182
234
 
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 older runs |
193
-
194
- `queueShards` is part of queue placement and is pinned when a queue cell is
195
- first used. It may be any positive safe integer; the `128` storage-operation
196
- batch limit does not cap the fleet's total shard count. Drain pending work
197
- before changing it.
235
+ | Variable | Default | Purpose |
236
+ | ------------------------------- | ------- | ---------------------------------------------------- |
237
+ | `WORLD_SECRET` | none | Required bearer secret for RPC routes |
238
+ | `WORKFLOW_CALLBACK_SECRET` | none | Sent with deliveries as `x-workflow-callback-secret` |
239
+ | `WORKFLOW_RETENTION_MS` | `0` | Maximum run age from creation; includes active runs |
240
+ | `WORKFLOW_RETENTION_BATCH_SIZE` | `128` | Runs admitted by each cron sweep (maximum `1000`) |
198
241
 
199
242
  Queue deadlines use fixed-width 13-digit epoch-millisecond keys. A requested
200
243
  `delaySeconds` or handler redelivery timeout is accepted only when its deadline
201
- is at most `9999999669998`, leaving 330,001 ms for a fresh alarm edge and the
202
- maximum delivery lease. Every derived due, retry, inflight, and GC deadline is
203
- checked against the absolute `9999999999999` limit. A production retry which
204
- cannot preserve that headroom is dead-lettered instead of clamped or rescheduled;
205
- the test pump terminates the equivalent in-memory message. Long test-mode waits
206
- are chunked at the host timer limit without changing this deadline contract.
244
+ leaves room for the 15-minute idempotency-claim window below the absolute
245
+ `9999999999999` limit. A suspension that cannot preserve that headroom becomes
246
+ a normal broker retry instead of being clamped. celld's native
247
+ producer delay is capped at 86,400 seconds, so the consumer chains longer waits
248
+ while preserving the absolute deadline and stable message identity. Long
249
+ test-mode waits are chunked at the host timer limit without changing this
250
+ deadline contract.
207
251
 
208
252
  ## Workflow retention
209
253
 
@@ -217,8 +261,9 @@ failed, and cancelled runs.
217
261
  The packaged worker declares an hourly UTC celld cron trigger. Each occurrence
218
262
  scans one bounded creation-time catalog page and asks the authoritative run
219
263
  cells to enforce the cutoff. A run cell immediately fences reads, writes, stream
220
- activity, and queue work, removes its catalog entry, then finishes the existing
221
- bounded stream, queue, and payload cleanup phases through its durable alarm.
264
+ activity, and queue payload work, removes its catalog entry, then finishes the
265
+ existing bounded stream, object-store queue-payload, and run-payload cleanup phases through
266
+ its durable alarm.
222
267
  Repeated cron invocations and alarm retries are idempotent.
223
268
 
224
269
  One occurrence admits at most `WORKFLOW_RETENTION_BATCH_SIZE` runs (default
@@ -227,11 +272,6 @@ backlog, later occurrences continue with the next oldest entries. Edit
227
272
  `triggers.crons` in the copied `celld-worker/wrangler.jsonc` if hourly discovery
228
273
  does not provide the desired expiration resolution or catch-up rate.
229
274
 
230
- New runs persist the application's `queueShards` value for complete queue
231
- cleanup. `WORKFLOW_RETENTION_QUEUE_SHARDS` is the fallback for runs created
232
- before that metadata existed and must match the placement used by those runs.
233
- Both values may be any positive safe integer.
234
-
235
275
  ### Terminal payload retention
236
276
 
237
277
  When `runRetentionMs` is greater than zero, a terminal transition atomically
@@ -240,8 +280,10 @@ step, hook, and stream data remains readable until that deadline. Active and
240
280
  pending runs are never eligible for automatic cleanup.
241
281
 
242
282
  At expiration, the run cell fences new writes and removes the run's derived
243
- indexes, stream chunks, pending and dead-letter queue messages, and durable run
244
- payloads. Cleanup is a persisted, idempotent state machine. Each alarm processes
283
+ indexes, stream chunks, object-store queue payloads, and durable run payloads.
284
+ Native Queue pointer messages can remain until celld's fixed four-day retention
285
+ expires; a pointer whose object-store body was removed is acknowledged as permanently gone
286
+ and cannot resurrect the run. Cleanup is a persisted, idempotent state machine. Each alarm processes
245
287
  one bounded page, persists its progress, and re-arms the next alarm; an
246
288
  interrupted phase records its error and retries with capped backoff.
247
289
 
@@ -273,10 +315,8 @@ fleet-wide maximum age are both enabled, the earlier deadline wins.
273
315
 
274
316
  - Delivery is at least once. Workflow steps and other external side effects
275
317
  must be idempotent.
276
- - Queue cells expose `stats`, `listDeadLetters`, `redriveDeadLetter`,
277
- `purgeDeadLetters`, and `rearmAlarm` through the authenticated RPC endpoint.
278
- `purgeDeadLetters` deletes at most 128 entries per call; repeat it while its
279
- result reports `hasMore`.
318
+ - Native Queue operations are administered with celld's `queue info`, `peek`,
319
+ `pause`, `resume`, `purge`, and `redrive` commands.
280
320
  - Run cells expose retention status, scheduling, immediate cleanup, and alarm
281
321
  recovery through `world.retention`.
282
322
  - A new application URL applies to newly enqueued messages. Existing messages
@@ -307,14 +347,21 @@ pnpm test:integration
307
347
 
308
348
  ### Real celld restart smoke
309
349
 
310
- The required CI smoke owns native celld v0.3.0 and MinIO processes on loopback,
350
+ The required CI smoke owns native celld v0.5.0 and MinIO processes on loopback,
311
351
  uses fresh temporary bucket and runtime state, and kills celld with `SIGKILL`
312
352
  before deleting its local working state and starting a new process against the
313
353
  bucket-backed state. It checks that:
314
354
 
355
+ - opt-in runtime telemetry is persisted as nonempty Parquet files in the fleet bucket;
315
356
  - acknowledged run and stream state survives the process restart;
316
357
  - an accepted delayed queue message that becomes due while celld is down is
317
- delivered once after the alarm is restored;
358
+ delivered once after the native broker is restored;
359
+ - the companion Queue consumer can call the named `QueueDeliveryRpc` service
360
+ entrypoint and recover run-bearing payloads from the fleet object store;
361
+ - transient callbacks increment attempts while suspension preserves the attempt
362
+ count and message identity;
363
+ - invalid credentials, envelopes, and attempts are rejected across real RPC;
364
+ - the former HTTP delivery route returns 404;
318
365
  - multi-page retention cleanup continues from a persisted nonterminal phase;
319
366
  - cancelling an in-flight HTTP long poll leaves the stream writable and
320
367
  readable.
@@ -326,9 +373,8 @@ pnpm test:integration:celld-smoke
326
373
  ```
327
374
 
328
375
  The runner downloads celld and MinIO artifacts at pinned versions and verifies
329
- their SHA-256 digests before use. MinIO does not implement the conditional-write
330
- contract celld requires for production ownership fencing, so the smoke disables
331
- the storage probe and runs exactly one celld process at a time. It proves the
376
+ their SHA-256 digests before use. celld performs its required storage-contract
377
+ probe at startup; the smoke runs exactly one celld process at a time. It proves the
332
378
  single-process restart boundaries above, not multi-node ownership, handoff, or
333
379
  failover correctness.
334
380
 
@@ -340,13 +386,12 @@ message reaches a successful callback, including forced `503` redeliveries. A
340
386
  second workload creates terminal runs with streams and delayed queue messages,
341
387
  then verifies complete payload cleanup without resurrection. Results include
342
388
  queue and cleanup throughput plus p50, p95, p99, and maximum latency and are
343
- saved under `.perf-results/`. The harness currently pins celld v0.3.0.
389
+ saved under `.perf-results/`. The harness pins celld v0.5.0 and deploys the same
390
+ two-script native Queue topology as the restart smoke.
344
391
 
345
- > MinIO Community is **not a supported celld production store**. It does not
346
- > implement the conditional writes celld needs for ownership fencing. This
347
- > deliberately single-node harness disables celld's storage probe and tests
348
- > the queue's persistence, redelivery, and performance paths only; it does not
349
- > validate multi-node ownership or failover correctness.
392
+ > This harness runs a single celld node and leaves the required storage-contract
393
+ > probe enabled. Passing it does not establish multi-node fencing, ownership
394
+ > handoff, or production suitability for a particular object-store deployment.
350
395
 
351
396
  With Docker and the Compose plugin installed, run:
352
397
 
@@ -354,43 +399,10 @@ With Docker and the Compose plugin installed, run:
354
399
  pnpm test:perf:minio
355
400
  ```
356
401
 
357
- The defaults send 1,000 messages with concurrency 32 across two queue cells,
358
- and force every twentieth message through one `503` redelivery.
359
-
360
- #### Reference result
361
-
362
- The following directional baseline was recorded on August 21, 2026. MinIO and
363
- celld ran as native arm64 processes because Docker was unavailable on the test
364
- machine; container results will differ.
365
-
366
- | Environment | Value |
367
- | ----------- | -------------------------------------------------------------------------------------------------------- |
368
- | Machine | MacBook Pro (Mac15,8), Apple M3 Max, 16 cores (12 performance, 4 efficiency), 64 GB RAM |
369
- | OS | macOS 26.5.2 (25F84), arm64 |
370
- | Services | celld v0.3.0; MinIO RELEASE.2025-09-07T16-13-09Z |
371
- | Workload | 1,000 messages; concurrency 32; 2 queue shards; 256-byte target payload; every 20th message retried once |
372
-
373
- | Metric | Result |
374
- | ----------------------- | --------------------------------------------------------------------------------: |
375
- | Delivery integrity | 1,000 accepted, 1,000 delivered, 0 missing, 0 duplicate successes, 0 dead letters |
376
- | Callback attempts | 1,050, including 50 forced redeliveries |
377
- | Enqueue throughput | 865.66 messages/s |
378
- | Delivery throughput | 794.60 messages/s |
379
- | Enqueue latency | p50 26.06 ms; p95 70.72 ms; p99 146.59 ms; max 157.70 ms |
380
- | All delivery latency | p50 65.05 ms; p95 184.47 ms; p99 225.10 ms; max 328.75 ms |
381
- | First-attempt latency | p50 63.65 ms; p95 177.93 ms; p99 209.38 ms; max 226.86 ms |
382
- | Retried-message latency | p50 114.81 ms; p95 286.35 ms; p99 328.75 ms; max 328.75 ms |
383
- | Final queue state | 0 pending, 0 in flight, 0 dead letters across both shards |
384
-
385
- The retention workload in the same run tombstoned 100 of 100 terminal runs,
386
- expired all 100 reads, deleted 400 payload keys, 100 streams, and 100 delayed
387
- queue messages, and left both queue shards empty.
388
-
389
- Use these numbers as a smoke-test reference, not a portable performance
390
- guarantee. For regression tracking, compare repeated runs on the same machine
391
- and runtime configuration. Two shards gave the best latency balance for this
392
- workload; benchmark your own traffic before changing the shard count. A fleet
393
- pins its shard count on first use, so changing it requires draining the queues.
402
+ The defaults send 1,000 messages with concurrency 32 and force every twentieth
403
+ message through one `503` redelivery. The old QueueDO/shard baseline is not
404
+ comparable to the native celld Queue path and was intentionally removed; record
405
+ a fresh machine-specific baseline before enabling budgets.
394
406
 
395
407
  Override the workload or set machine-specific regression budgets with
396
408
  environment variables:
@@ -398,18 +410,17 @@ environment variables:
398
410
  ```sh
399
411
  PERF_MESSAGES=10000 \
400
412
  PERF_CONCURRENCY=64 \
401
- PERF_QUEUE_SHARDS=16 \
402
413
  PERF_MIN_DELIVERY_PER_SECOND=100 \
403
414
  PERF_MAX_DELIVERY_P99_MS=5000 \
404
415
  pnpm test:perf:minio
405
416
  ```
406
417
 
407
418
  Useful controls are `PERF_PAYLOAD_BYTES`, `PERF_RETRY_EVERY`,
408
- `PERF_TIMEOUT_MS`, `PERF_QUEUE_MAX_INFLIGHT`,
419
+ `PERF_TIMEOUT_MS`,
409
420
  `PERF_MIN_ENQUEUE_PER_SECOND`, `PERF_MIN_DELIVERY_PER_SECOND`, and
410
421
  `PERF_MAX_DELIVERY_P99_MS`. Throughput and latency budgets default to disabled
411
- because local machines vary. Message-loss, dead-letter, message-ID, queue-drain,
412
- and duplicate-success checks are always enforced.
422
+ because local machines vary. Message-loss, message-ID, callback validity, and
423
+ duplicate-success checks are always enforced.
413
424
 
414
425
  Bug reports and focused pull requests are welcome. Please include a regression
415
426
  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,52 +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 positive-safe-integer
46
- queue-shard count when cleaning runs created before this worker version. The
47
- `128` multikey storage-operation limit is not a total-shard ceiling. Edit
48
- `triggers.crons` in the copied config if hourly discovery is not the desired
49
- 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.
50
58
 
51
59
  Point the app at any node's public listener:
52
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
  }