@ewhauser/world-celld 0.3.1 → 0.4.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 +17 -0
- package/README.md +70 -28
- package/celld-worker/README.md +25 -3
- package/celld-worker/worker.ts +0 -1
- package/celld-worker/wrangler.jsonc +12 -2
- package/dist/index.d.ts +36 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -21
- package/dist/index.js.map +1 -1
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +625 -241
- package/dist/testing.js.map +1 -1
- package/dist/{util-DmO0XkFw.js → util-ya3V_FTA.js} +32 -19
- package/dist/util-ya3V_FTA.js.map +1 -0
- package/dist/worker.d.ts +95 -28
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +768 -291
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
- package/dist/util-DmO0XkFw.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.4.0 (2026-08-22)
|
|
6
|
+
|
|
7
|
+
## What's Changed
|
|
8
|
+
* chore: test against celld v0.3.0 by @ewhauser in https://github.com/ewhauser/world-celld/pull/31
|
|
9
|
+
* feat: add configurable workflow retention by @ewhauser in https://github.com/ewhauser/world-celld/pull/33
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
**Full Changelog**: https://github.com/ewhauser/world-celld/compare/v0.3.2...v0.4.0
|
|
13
|
+
|
|
14
|
+
## 0.3.2 (2026-08-19)
|
|
15
|
+
|
|
16
|
+
## What's Changed
|
|
17
|
+
* fix: compact expired-run lifecycle fences by @ewhauser in https://github.com/ewhauser/world-celld/pull/29
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
**Full Changelog**: https://github.com/ewhauser/world-celld/compare/v0.3.1...v0.3.2
|
|
21
|
+
|
|
5
22
|
## 0.3.1 (2026-08-19)
|
|
6
23
|
|
|
7
24
|
## What's Changed
|
package/README.md
CHANGED
|
@@ -75,8 +75,9 @@ requests. It must be reachable from every celld node.
|
|
|
75
75
|
|
|
76
76
|
## Deploy the worker
|
|
77
77
|
|
|
78
|
-
Before deploying, you need a celld fleet, `esbuild` on `PATH`, and an
|
|
79
|
-
store that meets celld's conditional-write requirements.
|
|
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
|
|
80
81
|
[celld documentation](https://github.com/denoland/celld) for fleet and storage
|
|
81
82
|
setup.
|
|
82
83
|
|
|
@@ -98,6 +99,7 @@ the same secret to the fleet and the application:
|
|
|
98
99
|
|
|
99
100
|
```sh
|
|
100
101
|
CELLD_VAR_WORLD_SECRET="$CELLD_WORLD_SECRET" \
|
|
102
|
+
CELLD_VAR_WORKFLOW_RETENTION_MS=7776000000 \
|
|
101
103
|
celld --bucket s3://my-cells-bucket \
|
|
102
104
|
--listen 0.0.0.0:8080 \
|
|
103
105
|
--internal-listen 10.0.0.12:8081 \
|
|
@@ -108,6 +110,9 @@ Use a secret manager rather than putting the value in `wrangler.jsonc`. Keep
|
|
|
108
110
|
celld's internal listener on a trusted network; the World bearer token protects
|
|
109
111
|
the worker RPC routes, not celld's administrative endpoints.
|
|
110
112
|
|
|
113
|
+
The example sets a fleet-wide maximum workflow age of 90 days. Leave
|
|
114
|
+
`WORKFLOW_RETENTION_MS` at `0` to disable that policy.
|
|
115
|
+
|
|
111
116
|
More deployment detail is in [`celld-worker/README.md`](./celld-worker/README.md).
|
|
112
117
|
|
|
113
118
|
## How it works
|
|
@@ -118,9 +123,9 @@ Workflow application
|
|
|
118
123
|
| authenticated HTTP RPC + binary stream batches
|
|
119
124
|
v
|
|
120
125
|
celld worker router
|
|
126
|
+
|-- scheduled hourly maximum-age retention discovery
|
|
121
127
|
|-- WorkflowRunDO one cell per workflow run
|
|
122
128
|
|-- RunCatalogDO 16 stable run-id shards; merged ordered listing
|
|
123
|
-
|-- RunFenceDO one lifecycle fence cell per workflow run
|
|
124
129
|
|-- HookTokenDO 32 stable token-ownership shards
|
|
125
130
|
|-- HookIdDO 32 stable hook-id lookup shards
|
|
126
131
|
|-- StreamDO stream chunks and run/stream indexes
|
|
@@ -141,17 +146,20 @@ Index routing is a versioned hard-cutover protocol. Run catalog writes hash the
|
|
|
141
146
|
`runId` across 16 shards, while hook ownership and hook-ID lookup hash their
|
|
142
147
|
natural keys across 32 shards each. A single stateless worker request queries
|
|
143
148
|
all 16 catalog shards in parallel and merges their lexicographic pages before
|
|
144
|
-
authoritative RunDO reads.
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
fence resolve ambiguous delivery without pretending
|
|
149
|
-
form a cross-Durable-Object transaction. Stateless cohesive index routes keep
|
|
149
|
+
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
|
|
152
|
+
each transactional inside their natural-key shards. Exact claim IDs plus a
|
|
153
|
+
serialized cancellation fence resolve ambiguous delivery without pretending
|
|
154
|
+
the two ownership shards form a cross-Durable-Object transaction. Stateless cohesive index routes keep
|
|
150
155
|
catalog listing, terminal commits, hook finalization, and hook release to one
|
|
151
156
|
public request each while exposing the internal shard work in measurements.
|
|
152
157
|
|
|
153
158
|
The routing rationale and before/after RPC, storage, latency, and contention
|
|
154
159
|
evidence are recorded in [`docs/index-sharding.md`](./docs/index-sharding.md).
|
|
160
|
+
The expiry authority, producer/consumer map, bounded horizons, and
|
|
161
|
+
post-compaction costs are recorded in
|
|
162
|
+
[`docs/lifecycle-compaction.md`](./docs/lifecycle-compaction.md).
|
|
155
163
|
|
|
156
164
|
## Configuration
|
|
157
165
|
|
|
@@ -168,22 +176,51 @@ variable is shown below.
|
|
|
168
176
|
| `runRetentionMs` | `CELLD_RUN_RETENTION_MS` | `0` (disabled) |
|
|
169
177
|
| `streamLongPollMs` | — | `20000` |
|
|
170
178
|
| `streamFlushIntervalMs` | — | `0` |
|
|
171
|
-
| `rpcTimeoutMs` | — | `30000`
|
|
179
|
+
| `rpcTimeoutMs` | — | `30000` (max `300000`) |
|
|
172
180
|
|
|
173
181
|
The deployed worker also accepts these celld variables:
|
|
174
182
|
|
|
175
|
-
| Variable
|
|
176
|
-
|
|
|
177
|
-
| `WORLD_SECRET`
|
|
178
|
-
| `WORKFLOW_CALLBACK_SECRET`
|
|
179
|
-
| `QUEUE_MAX_ATTEMPTS`
|
|
180
|
-
| `QUEUE_MAX_INFLIGHT`
|
|
181
|
-
| `QUEUE_DELIVERY_TIMEOUT_MS`
|
|
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 |
|
|
182
193
|
|
|
183
194
|
`queueShards` is part of queue placement and is pinned when a queue cell is
|
|
184
195
|
first used. Drain pending work before changing it.
|
|
185
196
|
|
|
186
|
-
##
|
|
197
|
+
## Workflow retention
|
|
198
|
+
|
|
199
|
+
### Fleet-wide maximum age
|
|
200
|
+
|
|
201
|
+
Set the deployed worker's `WORKFLOW_RETENTION_MS` variable to expire every
|
|
202
|
+
workflow after a maximum age measured from `createdAt`. For example,
|
|
203
|
+
`7776000000` is 90 days. This policy applies to pending, running, completed,
|
|
204
|
+
failed, and cancelled runs.
|
|
205
|
+
|
|
206
|
+
The packaged worker declares an hourly UTC celld cron trigger. Each occurrence
|
|
207
|
+
scans one bounded creation-time catalog page and asks the authoritative run
|
|
208
|
+
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.
|
|
211
|
+
Repeated cron invocations and alarm retries are idempotent.
|
|
212
|
+
|
|
213
|
+
One occurrence admits at most `WORKFLOW_RETENTION_BATCH_SIZE` runs (default
|
|
214
|
+
`128`, maximum `1000`). If the fleet was down or a shorter policy creates a
|
|
215
|
+
backlog, later occurrences continue with the next oldest entries. Edit
|
|
216
|
+
`triggers.crons` in the copied `celld-worker/wrangler.jsonc` if hourly discovery
|
|
217
|
+
does not provide the desired expiration resolution or catch-up rate.
|
|
218
|
+
|
|
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
|
+
### Terminal payload retention
|
|
187
224
|
|
|
188
225
|
When `runRetentionMs` is greater than zero, a terminal transition atomically
|
|
189
226
|
records the run's `expiredAt` and arms its cell alarm. The complete run, event,
|
|
@@ -217,7 +254,8 @@ await world.retention.rearm(runId); // recover a missed or abandoned alarm
|
|
|
217
254
|
|
|
218
255
|
The retention deadline is pinned when the run becomes terminal. Changing the
|
|
219
256
|
configuration affects newly terminal runs; call `schedule()` explicitly for
|
|
220
|
-
an existing terminal run that has no cleanup record.
|
|
257
|
+
an existing terminal run that has no cleanup record. If terminal retention and
|
|
258
|
+
fleet-wide maximum age are both enabled, the earlier deadline wins.
|
|
221
259
|
|
|
222
260
|
## Operational notes
|
|
223
261
|
|
|
@@ -263,7 +301,7 @@ message reaches a successful callback, including forced `503` redeliveries. A
|
|
|
263
301
|
second workload creates terminal runs with streams and delayed queue messages,
|
|
264
302
|
then verifies complete payload cleanup without resurrection. Results include
|
|
265
303
|
queue and cleanup throughput plus p50, p95, p99, and maximum latency and are
|
|
266
|
-
saved under `.perf-results/`.
|
|
304
|
+
saved under `.perf-results/`. The harness currently pins celld v0.3.0.
|
|
267
305
|
|
|
268
306
|
> MinIO Community is **not a supported celld production store**. It does not
|
|
269
307
|
> implement the conditional writes celld needs for ownership fencing. This
|
|
@@ -282,7 +320,7 @@ and force every twentieth message through one `503` redelivery.
|
|
|
282
320
|
|
|
283
321
|
#### Reference result
|
|
284
322
|
|
|
285
|
-
The following directional baseline was recorded on August
|
|
323
|
+
The following directional baseline was recorded on August 21, 2026. MinIO and
|
|
286
324
|
celld ran as native arm64 processes because Docker was unavailable on the test
|
|
287
325
|
machine; container results will differ.
|
|
288
326
|
|
|
@@ -290,21 +328,25 @@ machine; container results will differ.
|
|
|
290
328
|
| ----------- | -------------------------------------------------------------------------------------------------------- |
|
|
291
329
|
| Machine | MacBook Pro (Mac15,8), Apple M3 Max, 16 cores (12 performance, 4 efficiency), 64 GB RAM |
|
|
292
330
|
| OS | macOS 26.5.2 (25F84), arm64 |
|
|
293
|
-
| Services | celld v0.
|
|
331
|
+
| Services | celld v0.3.0; MinIO RELEASE.2025-09-07T16-13-09Z |
|
|
294
332
|
| Workload | 1,000 messages; concurrency 32; 2 queue shards; 256-byte target payload; every 20th message retried once |
|
|
295
333
|
|
|
296
334
|
| Metric | Result |
|
|
297
335
|
| ----------------------- | --------------------------------------------------------------------------------: |
|
|
298
336
|
| Delivery integrity | 1,000 accepted, 1,000 delivered, 0 missing, 0 duplicate successes, 0 dead letters |
|
|
299
337
|
| Callback attempts | 1,050, including 50 forced redeliveries |
|
|
300
|
-
| Enqueue throughput |
|
|
301
|
-
| Delivery throughput |
|
|
302
|
-
| Enqueue latency | p50
|
|
303
|
-
| All delivery latency | p50
|
|
304
|
-
| First-attempt latency | p50
|
|
305
|
-
| Retried-message latency |
|
|
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 |
|
|
306
344
|
| Final queue state | 0 pending, 0 in flight, 0 dead letters across both shards |
|
|
307
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
|
+
|
|
308
350
|
Use these numbers as a smoke-test reference, not a portable performance
|
|
309
351
|
guarantee. For regression tracking, compare repeated runs on the same machine
|
|
310
352
|
and runtime configuration. Two shards gave the best latency balance for this
|
package/celld-worker/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# world-celld worker
|
|
2
2
|
|
|
3
|
-
The celld-deployable half of `@ewhauser/world-celld`:
|
|
4
|
-
(WorkflowRunDO, RunCatalogDO,
|
|
5
|
-
|
|
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
6
|
Storage, control, and queue methods use fixed JSON RPC routes; stream chunks use
|
|
7
7
|
bounded binary batch writes and binary long-poll reads.
|
|
8
8
|
|
|
@@ -19,11 +19,33 @@ celld deploy ./workflow-world --bucket s3://my-cells-bucket
|
|
|
19
19
|
|
|
20
20
|
Requirements:
|
|
21
21
|
|
|
22
|
+
- celld v0.3.0 (the currently tested runtime baseline).
|
|
22
23
|
- `esbuild` on PATH (celld shells out to it).
|
|
23
24
|
- A bucket with conditional-write support (celld's fencing requirement).
|
|
24
25
|
- `WORLD_SECRET` injected at the node level (`CELLD_VAR_WORLD_SECRET=...`) —
|
|
25
26
|
the router fails closed with 503 while it is empty.
|
|
26
27
|
|
|
28
|
+
## Fleet-wide retention
|
|
29
|
+
|
|
30
|
+
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
|
+
```
|
|
38
|
+
|
|
39
|
+
`7776000000` is 90 days. The policy includes pending and running workflows as
|
|
40
|
+
well as terminal ones. Each cron occurrence admits at most
|
|
41
|
+
`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.
|
|
48
|
+
|
|
27
49
|
Point the app at any node's public listener:
|
|
28
50
|
|
|
29
51
|
```ts
|
package/celld-worker/worker.ts
CHANGED
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
{ "name": "WORKFLOW_DB", "class_name": "WorkflowRunDO" },
|
|
11
11
|
{ "name": "WORKFLOW_STREAMS", "class_name": "StreamDO" },
|
|
12
12
|
{ "name": "WORKFLOW_RUN_CATALOG", "class_name": "RunCatalogDO" },
|
|
13
|
-
{ "name": "WORKFLOW_RUN_FENCES", "class_name": "RunFenceDO" },
|
|
14
13
|
{ "name": "WORKFLOW_HOOK_TOKENS", "class_name": "HookTokenDO" },
|
|
15
14
|
{ "name": "WORKFLOW_HOOK_IDS", "class_name": "HookIdDO" },
|
|
16
15
|
{ "name": "WORKFLOW_QUEUE", "class_name": "QueueDO" },
|
|
@@ -23,13 +22,16 @@
|
|
|
23
22
|
"WorkflowRunDO",
|
|
24
23
|
"StreamDO",
|
|
25
24
|
"RunCatalogDO",
|
|
26
|
-
"RunFenceDO",
|
|
27
25
|
"HookTokenDO",
|
|
28
26
|
"HookIdDO",
|
|
29
27
|
"QueueDO",
|
|
30
28
|
],
|
|
31
29
|
},
|
|
32
30
|
],
|
|
31
|
+
// Fleet-wide maximum-age retention is discovered hourly. The handler is a
|
|
32
|
+
// no-op while WORKFLOW_RETENTION_MS is "0". Change this expression if a
|
|
33
|
+
// different sweep cadence is required; celld cron schedules are UTC.
|
|
34
|
+
"triggers": { "crons": ["0 * * * *"] },
|
|
33
35
|
"vars": {
|
|
34
36
|
// REQUIRED: bearer secret for the world's RPC and binary stream surface.
|
|
35
37
|
// Authenticated routes fail closed with 503 while this is empty. Inject the real value
|
|
@@ -37,6 +39,14 @@
|
|
|
37
39
|
"WORLD_SECRET": "",
|
|
38
40
|
// Optional: forwarded as x-workflow-callback-secret on queue deliveries.
|
|
39
41
|
"WORKFLOW_CALLBACK_SECRET": "",
|
|
42
|
+
// Optional fleet-wide maximum workflow age, measured from run creation.
|
|
43
|
+
// Applies to pending, running, and terminal runs. "0" disables it.
|
|
44
|
+
"WORKFLOW_RETENTION_MS": "0",
|
|
45
|
+
// Maximum runs admitted to cleanup by one hourly sweep (1..1000).
|
|
46
|
+
"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",
|
|
40
50
|
// Optional queue tuning (strings): QUEUE_MAX_ATTEMPTS, QUEUE_MAX_INFLIGHT,
|
|
41
51
|
// QUEUE_DELIVERY_TIMEOUT_MS.
|
|
42
52
|
},
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CreateEventParams, CreateEventRequest, Event, GetChunksOptions, Hook, RunCreatedEventRequest, Step, StreamChunksResponse, StreamInfoResponse, Streamer, TerminalWorkflowRunStatus, ValidQueueName, Wait, WorkflowRun, World } from "@workflow/world";
|
|
1
|
+
import { CreateEventParams, CreateEventRequest, Event, GetChunksOptions, Hook, RunCreatedEventRequest, Step, StreamChunksResponse, StreamInfoResponse, Streamer, TerminalWorkflowRunStatus, ValidQueueName, Wait, WorkflowRun, WorkflowRunStatus, World } from "@workflow/world";
|
|
2
2
|
//#region src/retention.d.ts
|
|
3
3
|
type CleanupPhase = 'retained' | 'index' | 'streams' | 'queues' | 'payload' | 'tombstoned';
|
|
4
4
|
interface CleanupRecord {
|
|
@@ -6,8 +6,13 @@ interface CleanupRecord {
|
|
|
6
6
|
runId: string;
|
|
7
7
|
workflowName: string;
|
|
8
8
|
createdAt: Date;
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
/** Last status observed when cleanup was scheduled. */
|
|
10
|
+
status?: WorkflowRunStatus;
|
|
11
|
+
/** Present when cleanup was scheduled for a terminal run. */
|
|
12
|
+
completedAt?: Date;
|
|
13
|
+
/** Retained for persisted terminal-retention records created before max-age cleanup. */
|
|
14
|
+
terminalStatus?: TerminalWorkflowRunStatus;
|
|
15
|
+
reason?: 'terminal-retention' | 'maximum-age' | 'manual';
|
|
11
16
|
dueAt: Date;
|
|
12
17
|
queueShards: number;
|
|
13
18
|
phase: CleanupPhase;
|
|
@@ -24,11 +29,16 @@ interface CleanupRecord {
|
|
|
24
29
|
interface RunTombstone {
|
|
25
30
|
version: 1;
|
|
26
31
|
runId: string;
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
/** Last status observed before the run expired. */
|
|
33
|
+
status?: WorkflowRunStatus;
|
|
34
|
+
completedAt?: Date;
|
|
35
|
+
terminalStatus?: TerminalWorkflowRunStatus;
|
|
29
36
|
expiredAt: Date;
|
|
30
37
|
tombstonedAt: Date;
|
|
38
|
+
/** Final cleanup accounting retained inside the single authoritative record. */
|
|
39
|
+
cleanup: CleanupRecord;
|
|
31
40
|
}
|
|
41
|
+
type RunLifecycleStatus = 'active' | 'terminal' | 'expired' | 'missing';
|
|
32
42
|
type RunReadOutcome<T> = {
|
|
33
43
|
ok: true;
|
|
34
44
|
value: T;
|
|
@@ -45,8 +55,6 @@ interface HookIndexReference {
|
|
|
45
55
|
interface ReleaseHookIndexesRequest {
|
|
46
56
|
runId: string;
|
|
47
57
|
hooks: HookIndexReference[];
|
|
48
|
-
/** Install a fence that prevents delayed hook finalization after terminal state. */
|
|
49
|
-
terminal: boolean;
|
|
50
58
|
}
|
|
51
59
|
interface ReleaseHookIndexesResult {
|
|
52
60
|
deleted: number;
|
|
@@ -77,10 +85,22 @@ interface ExpireStreamResult {
|
|
|
77
85
|
bytes: number;
|
|
78
86
|
done: boolean;
|
|
79
87
|
}
|
|
80
|
-
interface
|
|
88
|
+
interface QueueExpiryReceipt {
|
|
89
|
+
expiredAt: number;
|
|
90
|
+
deleted: number;
|
|
91
|
+
}
|
|
92
|
+
type ExpireQueueRunResult = {
|
|
81
93
|
/** Cumulative messages removed for this run in this queue shard. */
|
|
82
94
|
deleted: number;
|
|
83
|
-
|
|
95
|
+
} & ({
|
|
96
|
+
done: false;
|
|
97
|
+
} | {
|
|
98
|
+
done: true;
|
|
99
|
+
/** Durable receipt which the cleanup coordinator must acknowledge. */
|
|
100
|
+
receipt: QueueExpiryReceipt;
|
|
101
|
+
});
|
|
102
|
+
interface AcknowledgeQueueExpiryResult {
|
|
103
|
+
acknowledged: boolean;
|
|
84
104
|
}
|
|
85
105
|
interface ScheduleCleanupRequest {
|
|
86
106
|
retentionMs: number;
|
|
@@ -146,6 +166,8 @@ interface ApplyEventSuccess {
|
|
|
146
166
|
hasMore?: boolean;
|
|
147
167
|
/** Server-owned event ceiling advertised on run lifecycle writes. */
|
|
148
168
|
maxEvents?: number;
|
|
169
|
+
/** Deadline for publishing this authoritative mutation to derivative indexes. */
|
|
170
|
+
indexPublicationExpiresAt?: number;
|
|
149
171
|
}
|
|
150
172
|
type ApplyEventOutcome = ApplyEventSuccess | ApplyEventFailure;
|
|
151
173
|
//#endregion
|
|
@@ -163,6 +185,7 @@ interface WorkflowRunDOStub {
|
|
|
163
185
|
}): Promise<{
|
|
164
186
|
committed: boolean;
|
|
165
187
|
}>;
|
|
188
|
+
getLifecycleStatus(): Promise<RunLifecycleStatus>;
|
|
166
189
|
getRun(): Promise<RunReadOutcome<WorkflowRun | null>>;
|
|
167
190
|
getStep(stepId: string): Promise<RunReadOutcome<Step | null>>;
|
|
168
191
|
getEvent(eventId: string): Promise<RunReadOutcome<Event | null>>;
|
|
@@ -326,6 +349,7 @@ interface QueueCellStub {
|
|
|
326
349
|
expireRun(runId: string, expiredAt: number, options?: {
|
|
327
350
|
limit?: number;
|
|
328
351
|
}): Promise<ExpireQueueRunResult>;
|
|
352
|
+
acknowledgeExpireRun(runId: string, receipt: QueueExpiryReceipt): Promise<AcknowledgeQueueExpiryResult>;
|
|
329
353
|
}
|
|
330
354
|
interface QueueCellNamespace {
|
|
331
355
|
idFromName(name: string): {
|
|
@@ -340,6 +364,8 @@ interface QueueCellNamespace {
|
|
|
340
364
|
interface IndexListOptions {
|
|
341
365
|
prefix?: string;
|
|
342
366
|
cursor?: string;
|
|
367
|
+
/** Exclusive upper bound for creation-time retention scans. */
|
|
368
|
+
end?: string;
|
|
343
369
|
limit?: number;
|
|
344
370
|
reverse?: boolean;
|
|
345
371
|
}
|
|
@@ -365,7 +391,7 @@ type HookReservationResult = {
|
|
|
365
391
|
};
|
|
366
392
|
/** High-level client used by the World storage layer and retention coordinator. */
|
|
367
393
|
interface WorkflowIndex {
|
|
368
|
-
commitRun(run: WorkflowRun, serializedMetadata: string): Promise<{
|
|
394
|
+
commitRun(run: WorkflowRun, serializedMetadata: string, publicationExpiresAt: number): Promise<{
|
|
369
395
|
stored: boolean;
|
|
370
396
|
}>;
|
|
371
397
|
listRuns(options?: IndexListOptions): Promise<IndexListPage>;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/retention.ts","../src/apply-event.ts","../src/storage.ts","../src/stream-protocol.ts","../src/streamer.ts","../src/queue.ts","../src/indexes.ts","../src/config.ts","../src/index.ts"],"mappings":";;KAOY;UAEK;EACf;EACA;EACA;EACA,WAAW
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/retention.ts","../src/apply-event.ts","../src/storage.ts","../src/stream-protocol.ts","../src/streamer.ts","../src/queue.ts","../src/indexes.ts","../src/config.ts","../src/index.ts"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { C as decodeStreamReadResult, D as encodeStreamWriteBatch, S as STREAM_BATCH_CONTENT_TYPE, T as decodeStreamWriteResult, _ as MAX_STREAM_ERROR_BYTES, a as stringify, b as MAX_STREAM_READ_CHUNKS, g as MAX_STREAM_CHUNK_BYTES, h as MAX_STREAM_BATCH_BYTES, i as parse, k as normalizeStreamError, n as rpcParse, o as createWorkflowIndex, r as rpcStringify, t as compact, u as MAX_FLEET_RPC_TIMEOUT_MS, v as MAX_STREAM_LONG_POLL_MS, x as MAX_STREAM_WRITE_CHUNKS, y as MAX_STREAM_READ_BYTES } from "./util-ya3V_FTA.js";
|
|
2
2
|
import { EventSchema, HookSchema, MessageId, SPEC_VERSION_CURRENT, StepSchema, WorkflowRunSchema, isTerminalWorkflowRunStatus, parseQueueName } from "@workflow/world";
|
|
3
3
|
import { EntityConflictError, HookNotFoundError, RunExpiredError, RunNotSupportedError, TooEarlyError, WorkflowRunNotFoundError, WorkflowWorldError } from "@workflow/errors";
|
|
4
4
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
5
5
|
import { monotonicFactory } from "ulid";
|
|
6
6
|
//#region src/config.ts
|
|
7
7
|
function resolveConfig(config) {
|
|
8
|
+
const queueShards = config?.queueShards ?? 1;
|
|
9
|
+
if (!Number.isSafeInteger(queueShards) || queueShards < 1) throw new Error("world-celld: queueShards must be a positive safe integer");
|
|
8
10
|
const retentionRaw = config?.runRetentionMs ?? process.env.CELLD_RUN_RETENTION_MS ?? 0;
|
|
9
11
|
const runRetentionMs = typeof retentionRaw === "number" ? retentionRaw : Number.parseInt(retentionRaw, 10);
|
|
10
12
|
if (!Number.isSafeInteger(runRetentionMs) || runRetentionMs < 0) throw new Error("world-celld: runRetentionMs must be a non-negative safe integer");
|
|
11
13
|
const rpcTimeoutMs = config?.rpcTimeoutMs ?? 3e4;
|
|
12
|
-
if (!Number.isSafeInteger(rpcTimeoutMs) || rpcTimeoutMs < 1) throw new Error(
|
|
14
|
+
if (!Number.isSafeInteger(rpcTimeoutMs) || rpcTimeoutMs < 1 || rpcTimeoutMs > 3e5) throw new Error(`world-celld: rpcTimeoutMs must be between 1 and ${MAX_FLEET_RPC_TIMEOUT_MS}`);
|
|
13
15
|
const streamLongPollMs = config?.streamLongPollMs ?? Math.min(2e4, Math.max(1, rpcTimeoutMs - 1e3));
|
|
14
16
|
if (!Number.isSafeInteger(streamLongPollMs) || streamLongPollMs < 1 || streamLongPollMs > 2e4) throw new Error(`world-celld: streamLongPollMs must be between 1 and ${MAX_STREAM_LONG_POLL_MS}`);
|
|
15
17
|
if (streamLongPollMs >= rpcTimeoutMs) throw new Error("world-celld: streamLongPollMs must be less than rpcTimeoutMs");
|
|
@@ -21,7 +23,7 @@ function resolveConfig(config) {
|
|
|
21
23
|
env: config?.env,
|
|
22
24
|
deploymentId: config?.deploymentId ?? process.env.CELLD_DEPLOYMENT_ID ?? "celld-default",
|
|
23
25
|
baseUrl: config?.baseUrl,
|
|
24
|
-
queueShards
|
|
26
|
+
queueShards,
|
|
25
27
|
runRetentionMs,
|
|
26
28
|
streamLongPollMs,
|
|
27
29
|
streamFlushIntervalMs,
|
|
@@ -79,6 +81,8 @@ async function callFleetRoute(transport, path, args, opts) {
|
|
|
79
81
|
const doFetch = transport.fetchImpl ?? fetch;
|
|
80
82
|
const url = `${transport.fleetUrl.replace(/\/$/, "")}${path}`;
|
|
81
83
|
const attempts = opts?.idempotent ? 3 : 1;
|
|
84
|
+
const timeoutMs = transport.timeoutMs ?? DEFAULT_TIMEOUT_MS$1;
|
|
85
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 3e5) throw new Error(`world-celld: fleet RPC timeout must be between 1 and ${MAX_FLEET_RPC_TIMEOUT_MS}`);
|
|
82
86
|
let lastError;
|
|
83
87
|
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
84
88
|
if (attempt > 1) await delayMs$1(100 * attempt + Math.random() * 200);
|
|
@@ -91,7 +95,7 @@ async function callFleetRoute(transport, path, args, opts) {
|
|
|
91
95
|
authorization: `Bearer ${transport.secret}`
|
|
92
96
|
},
|
|
93
97
|
body: rpcStringify(args),
|
|
94
|
-
signal: AbortSignal.timeout(
|
|
98
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
95
99
|
});
|
|
96
100
|
} catch (error) {
|
|
97
101
|
lastError = new FleetTransportError(`world-celld: fleet unreachable at ${url}`, error);
|
|
@@ -258,6 +262,7 @@ async function readStreamChunks(transport, name, request, signal) {
|
|
|
258
262
|
const RUNS = {
|
|
259
263
|
methods: [
|
|
260
264
|
"applyEvent",
|
|
265
|
+
"getLifecycleStatus",
|
|
261
266
|
"getRun",
|
|
262
267
|
"getStep",
|
|
263
268
|
"getEvent",
|
|
@@ -318,14 +323,16 @@ const QUEUE = {
|
|
|
318
323
|
"redriveDeadLetter",
|
|
319
324
|
"purgeDeadLetters",
|
|
320
325
|
"rearmAlarm",
|
|
321
|
-
"expireRun"
|
|
326
|
+
"expireRun",
|
|
327
|
+
"acknowledgeExpireRun"
|
|
322
328
|
],
|
|
323
329
|
mutating: /* @__PURE__ */ new Set([
|
|
324
330
|
"enqueue",
|
|
325
331
|
"redriveDeadLetter",
|
|
326
332
|
"purgeDeadLetters",
|
|
327
333
|
"rearmAlarm",
|
|
328
|
-
"expireRun"
|
|
334
|
+
"expireRun",
|
|
335
|
+
"acknowledgeExpireRun"
|
|
329
336
|
])
|
|
330
337
|
};
|
|
331
338
|
function makeStub(transport, binding, name, spec) {
|
|
@@ -351,14 +358,6 @@ const RUN_CATALOG = {
|
|
|
351
358
|
],
|
|
352
359
|
mutating: /* @__PURE__ */ new Set()
|
|
353
360
|
};
|
|
354
|
-
const RUN_FENCES = {
|
|
355
|
-
methods: [
|
|
356
|
-
"getStatus",
|
|
357
|
-
"fenceTerminal",
|
|
358
|
-
"fenceExpired"
|
|
359
|
-
],
|
|
360
|
-
mutating: /* @__PURE__ */ new Set()
|
|
361
|
-
};
|
|
362
361
|
const HOOK_TOKENS = {
|
|
363
362
|
methods: [
|
|
364
363
|
"get",
|
|
@@ -381,12 +380,10 @@ const HOOK_IDS = {
|
|
|
381
380
|
};
|
|
382
381
|
function createRemoteEnv(transport) {
|
|
383
382
|
const runCatalog = makeNamespace(transport, "run-catalog", RUN_CATALOG);
|
|
384
|
-
const runFences = makeNamespace(transport, "run-fences", RUN_FENCES);
|
|
385
383
|
const hookTokens = makeNamespace(transport, "hook-tokens", HOOK_TOKENS);
|
|
386
384
|
const hookIds = makeNamespace(transport, "hook-ids", HOOK_IDS);
|
|
387
385
|
const workflowIndex = createWorkflowIndex({
|
|
388
386
|
runCatalog,
|
|
389
|
-
runFences,
|
|
390
387
|
hookTokens,
|
|
391
388
|
hookIds
|
|
392
389
|
});
|
|
@@ -395,7 +392,11 @@ function createRemoteEnv(transport) {
|
|
|
395
392
|
WORKFLOW_STREAMS: makeStreamNamespace(transport),
|
|
396
393
|
WORKFLOW_INDEX: {
|
|
397
394
|
...workflowIndex,
|
|
398
|
-
commitRun: (run, serializedMetadata) => callFleetRoute(transport, "/v1/index/runs/commit", [
|
|
395
|
+
commitRun: (run, serializedMetadata, publicationExpiresAt) => callFleetRoute(transport, "/v1/index/runs/commit", [
|
|
396
|
+
run,
|
|
397
|
+
serializedMetadata,
|
|
398
|
+
publicationExpiresAt
|
|
399
|
+
], { idempotent: true }),
|
|
399
400
|
listRuns: (options) => callFleetRoute(transport, "/v1/index/runs/list", [options], { idempotent: true }),
|
|
400
401
|
expireRun: (request) => callFleetRoute(transport, "/v1/index/runs/expire", [request], { idempotent: true }),
|
|
401
402
|
reserveHook: (token, owner) => callFleetRoute(transport, "/v1/index/hooks/reserve", [token, owner], { idempotent: true }),
|
|
@@ -940,17 +941,16 @@ function createStorage(config) {
|
|
|
940
941
|
createdAt: outcome.run.createdAt.toISOString(),
|
|
941
942
|
status: outcome.run.status
|
|
942
943
|
});
|
|
943
|
-
|
|
944
|
+
if (outcome.indexPublicationExpiresAt === void 0) throw new Error("world-celld: authoritative run mutation omitted its index lease");
|
|
945
|
+
await env.WORKFLOW_INDEX.commitRun(outcome.run, meta, outcome.indexPublicationExpiresAt);
|
|
944
946
|
}
|
|
945
947
|
if (outcome.hookToIndex) {
|
|
946
948
|
const serialized = stringify(outcome.hookToIndex);
|
|
947
949
|
await env.WORKFLOW_INDEX.finalizeHookIndexes(outcome.hookToIndex.token, outcome.hookToIndex.hookId, serialized, hookOwner(outcome.hookToIndex), hookAdmission?.reservation);
|
|
948
950
|
} else if (hookAdmission) await env.WORKFLOW_INDEX.releaseHookReservation(hookAdmission.token, hookAdmission.owner, hookAdmission.reservation);
|
|
949
|
-
const terminal = Boolean(outcome.run && isTerminalWorkflowRunStatus(outcome.run.status));
|
|
950
951
|
if (outcome.releasedHooks.length > 0) await env.WORKFLOW_INDEX.releaseHookIndexes({
|
|
951
952
|
runId: effectiveRunId,
|
|
952
|
-
hooks: outcome.releasedHooks
|
|
953
|
-
terminal
|
|
953
|
+
hooks: outcome.releasedHooks
|
|
954
954
|
});
|
|
955
955
|
const eventPage = outcome.events === void 0 ? {} : {
|
|
956
956
|
events: outcome.events,
|