@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 +33 -0
- package/NOTICE +1 -1
- package/README.md +117 -106
- package/celld-queue-worker/README.md +26 -0
- package/celld-queue-worker/worker.ts +3 -0
- package/celld-queue-worker/wrangler.jsonc +26 -0
- package/celld-worker/README.md +33 -25
- package/celld-worker/worker.ts +1 -1
- package/celld-worker/wrangler.jsonc +12 -8
- package/dist/index.d.ts +32 -70
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +33 -90
- package/dist/index.js.map +1 -1
- package/dist/queue-consumer.d.ts +33 -0
- package/dist/queue-consumer.d.ts.map +1 -0
- package/dist/queue-consumer.js +75 -0
- package/dist/queue-consumer.js.map +1 -0
- package/dist/queue-delivery-DzNOtCYA.d.ts +49 -0
- package/dist/queue-delivery-DzNOtCYA.d.ts.map +1 -0
- package/dist/{util-Cu09mVzK.js → queue-protocol-CmJNMH_5.js} +60 -12
- package/dist/queue-protocol-CmJNMH_5.js.map +1 -0
- package/dist/queue-protocol-Ds44uuND.js +104 -0
- package/dist/queue-protocol-Ds44uuND.js.map +1 -0
- package/dist/testing.d.ts +3 -1
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +420 -833
- package/dist/testing.js.map +1 -1
- package/dist/worker.d.ts +106 -181
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +602 -955
- package/dist/worker.js.map +1 -1
- package/docs/celld-v0.5-upgrade.md +130 -0
- package/package.json +14 -6
- package/dist/util-Cu09mVzK.js.map +0 -1
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
79
|
-
object store that meets celld's conditional-write requirements.
|
|
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
|
-
|
|
98
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
184
|
-
|
|
|
185
|
-
| `WORLD_SECRET`
|
|
186
|
-
| `WORKFLOW_CALLBACK_SECRET`
|
|
187
|
-
| `
|
|
188
|
-
| `
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
are chunked at the host timer limit without changing this
|
|
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
|
|
221
|
-
bounded stream, queue, and payload cleanup phases through
|
|
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,
|
|
244
|
-
|
|
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
|
|
277
|
-
`
|
|
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.
|
|
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
|
|
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.
|
|
330
|
-
|
|
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
|
|
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
|
-
>
|
|
346
|
-
>
|
|
347
|
-
>
|
|
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
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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`,
|
|
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,
|
|
412
|
-
|
|
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,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
|
+
}
|
package/celld-worker/README.md
CHANGED
|
@@ -1,52 +1,60 @@
|
|
|
1
1
|
# world-celld worker
|
|
2
2
|
|
|
3
|
-
The celld-deployable half of `@ewhauser/world-celld`:
|
|
4
|
-
(WorkflowRunDO, RunCatalogDO, HookTokenDO, HookIdDO, StreamDO
|
|
5
|
-
|
|
6
|
-
Storage
|
|
7
|
-
bounded binary batch writes and binary long-poll
|
|
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
|
|
12
|
-
|
|
13
|
-
|
|
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.
|
|
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`
|
|
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 `
|
|
32
|
-
enable a maximum workflow age
|
|
33
|
-
|
|
34
|
-
|
|
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
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
package/celld-worker/worker.ts
CHANGED
|
@@ -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
|
-
//
|
|
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
|
}
|