@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 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 object
79
- store that meets celld's conditional-write requirements. Refer to the
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. A terminal or expired run is hidden through its own
145
- `RunFenceDO`, so no global fence cell remains. Token and hook-ID admission are
146
- each transactional inside their natural-key shards. The RunDO remains the
147
- authoritative commit domain, and exact claim IDs plus a serialized cancellation
148
- fence resolve ambiguous delivery without pretending the two ownership shards
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 | Default | Purpose |
176
- | --------------------------- | -------- | ---------------------------------------------------- |
177
- | `WORLD_SECRET` | none | Required bearer secret for RPC routes |
178
- | `WORKFLOW_CALLBACK_SECRET` | none | Sent with deliveries as `x-workflow-callback-secret` |
179
- | `QUEUE_MAX_ATTEMPTS` | `5` | Attempts before a message is dead-lettered |
180
- | `QUEUE_MAX_INFLIGHT` | `5` | Concurrent deliveries per queue cell (maximum `128`) |
181
- | `QUEUE_DELIVERY_TIMEOUT_MS` | `300000` | Timeout for an application callback |
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
- ## Terminal run retention
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 15, 2026. MinIO and
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.2.1; MinIO RELEASE.2025-09-07T16-13-09Z |
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 | 882.55 messages/s |
301
- | Delivery throughput | 808.81 messages/s |
302
- | Enqueue latency | p50 24.37 ms; p95 75.60 ms; p99 176.67 ms; max 200.36 ms |
303
- | All delivery latency | p50 53.95 ms; p95 167.86 ms; p99 210.84 ms; max 312.03 ms |
304
- | First-attempt latency | p50 52.55 ms; p95 166.35 ms; p99 209.80 ms; max 212.01 ms |
305
- | Retried-message latency | p50 82.52 ms; p95 262.63 ms; p99 312.03 ms; max 312.03 ms |
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
@@ -1,8 +1,8 @@
1
1
  # world-celld worker
2
2
 
3
- The celld-deployable half of `@ewhauser/world-celld`: seven cell classes
4
- (WorkflowRunDO, RunCatalogDO, RunFenceDO, HookTokenDO, HookIdDO, StreamDO, and
5
- QueueDO) behind an authenticated HTTP router.
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
@@ -7,7 +7,6 @@ export {
7
7
  HookTokenDO,
8
8
  QueueDO,
9
9
  RunCatalogDO,
10
- RunFenceDO,
11
10
  StreamDO,
12
11
  WorkflowRunDO,
13
12
  default,
@@ -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
- completedAt: Date;
10
- terminalStatus: TerminalWorkflowRunStatus;
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
- terminalStatus: TerminalWorkflowRunStatus;
28
- completedAt: Date;
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 ExpireQueueRunResult {
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
- done: boolean;
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>;
@@ -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":"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"}
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 { S as normalizeStreamError, a as stringify, b as encodeStreamWriteBatch, c as MAX_STREAM_BATCH_BYTES, d as MAX_STREAM_LONG_POLL_MS, f as MAX_STREAM_READ_BYTES, g as decodeStreamReadResult, h as STREAM_BATCH_CONTENT_TYPE, i as parse, l as MAX_STREAM_CHUNK_BYTES, m as MAX_STREAM_WRITE_CHUNKS, n as rpcParse, o as createWorkflowIndex, p as MAX_STREAM_READ_CHUNKS, r as rpcStringify, t as compact, u as MAX_STREAM_ERROR_BYTES, v as decodeStreamWriteResult } from "./util-DmO0XkFw.js";
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("world-celld: rpcTimeoutMs must be a positive safe integer");
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: config?.queueShards ?? 1,
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(Math.max(1, transport.timeoutMs ?? DEFAULT_TIMEOUT_MS$1))
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", [run, serializedMetadata], { idempotent: true }),
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
- await env.WORKFLOW_INDEX.commitRun(outcome.run, meta);
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,