@ewhauser/world-celld 0.8.0 → 0.9.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.9.0 (2026-10-06)
6
+
7
+ ## What's Changed
8
+ * feat: support World spec v8 and deliver demo queues by @ewhauser in https://github.com/ewhauser/world-celld/pull/90
9
+
10
+
11
+ **Full Changelog**: https://github.com/ewhauser/world-celld/compare/v0.8.1...v0.9.0
12
+
13
+ ## 0.8.1 (2026-10-02)
14
+
15
+ ## What's Changed
16
+ * fix: harden workflow fault recovery by @ewhauser in https://github.com/ewhauser/world-celld/pull/87
17
+ * chore(deps): bump hono from 4.13.2 to 4.13.7 by @dependabot[bot] in https://github.com/ewhauser/world-celld/pull/88
18
+
19
+
20
+ **Full Changelog**: https://github.com/ewhauser/world-celld/compare/v0.8.0...v0.8.1
21
+
5
22
  ## 0.8.0 (2026-09-27)
6
23
 
7
24
  ## What's Changed
package/README.md CHANGED
@@ -14,11 +14,18 @@ self-hosted alternative to platform-specific Workflow backends.
14
14
 
15
15
  ## World protocol compatibility
16
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.
17
+ This adapter supports World spec v8 with the Workflow 5 beta packages. This is
18
+ a breaking upgrade for applications using a v7 Workflow runtime: that runtime
19
+ rejects a World advertising v8. This adapter also rejects event writes to runs
20
+ stamped v7. Keep the v7 adapter and matching Workflow packages in place until
21
+ those runs finish, then upgrade the application and adapter together.
22
+
23
+ Event slots are allocated in the same transaction that stores each event, so
24
+ reads see a dense log and failed writes leave no gaps. No background sealing or
25
+ `noop` events are needed.
26
+
27
+ World v8 adds forced hook claims. This adapter does not advertise the optional
28
+ `hookForceClaim` capability, so `experimental_force` is unavailable.
22
29
 
23
30
  Run listing accepts either a single status or an array of statuses (OR matching).
24
31
  An empty status array matches no runs.
@@ -316,6 +323,11 @@ fleet-wide maximum age are both enabled, the earlier deadline wins.
316
323
 
317
324
  - Delivery is at least once. Workflow steps and other external side effects
318
325
  must be idempotent.
326
+ - A concurrent enqueue with the same idempotency key returns a retryable `503`
327
+ while broker publication is pending. Publication confirmation starts the
328
+ normal deduplication window. If a producer stops before publication completes,
329
+ its pending reservation can be replaced after five minutes. Broker delivery
330
+ also confirms publication when the producer's confirmation was interrupted.
319
331
  - Native Queue operations are administered with celld's `queue info`, `peek`,
320
332
  `pause`, `resume`, `purge`, and `redrive` commands.
321
333
  - Run cells expose retention status, scheduling, immediate cleanup, and alarm
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { n as NativeQueueSendOptions, r as NativeQueueSendResult, t as NativeQueueEnvelope } from "./queue-protocol-8iiZ8o_1.js";
1
2
  import { CreateEventParams, CreateEventRequest, Event, GetChunksOptions, Hook, RunCreatedEventRequest, Step, StreamChunksResponse, StreamInfoResponse, Streamer, TerminalWorkflowRunStatus, Wait, WorkflowRun, WorkflowRunStatus, World } from "@workflow/world";
2
3
  //#region src/retention.d.ts
3
4
  type CleanupPhase = 'retained' | 'index' | 'streams' | 'queues' | 'payload' | 'tombstoned';
@@ -175,6 +176,11 @@ interface WorkflowRunDOStub {
175
176
  }): Promise<{
176
177
  committed: boolean;
177
178
  }>;
179
+ resolveExpiredHookClaim(request: {
180
+ hookId: string;
181
+ claimId: string;
182
+ token?: string;
183
+ }): Promise<Hook | null>;
178
184
  getLifecycleStatus(): Promise<RunLifecycleStatus>;
179
185
  getRun(): Promise<RunReadOutcome<WorkflowRun | null>>;
180
186
  getStep(stepId: string): Promise<RunReadOutcome<Step | null>>;
@@ -307,46 +313,6 @@ interface StreamDOId {
307
313
  toString(): string;
308
314
  }
309
315
  //#endregion
310
- //#region src/queue-protocol.d.ts
311
- /**
312
- * Small broker message carried by celld's native Queue.
313
- *
314
- * Run-bearing payload bytes live in object storage so the broker's 128 kB limit does not
315
- * narrow the Workflow World contract and run retention can delete those bytes
316
- * without waiting for the Queue's fixed four-day retention window.
317
- */
318
- interface NativeQueueEnvelope {
319
- version: 1;
320
- messageId: string;
321
- queueName: string;
322
- targetBaseUrl: string;
323
- runId?: string;
324
- idempotencyKey?: string;
325
- payloadKey?: string;
326
- /**
327
- * Inline body. Health checks always travel inline; a run-bearing body may
328
- * travel inline when it is small and carries no user data, in which case
329
- * delivery checks the run's expiry instead of reading a payload object.
330
- */
331
- body?: string;
332
- /** Absolute workflow redelivery deadline; long waits are chained by the consumer. */
333
- notBefore?: number;
334
- /**
335
- * Where the idempotency claim lives. Absent: the claim cell named by queue
336
- * and key. `run`: an entry in the run's own cell (requires `runId`).
337
- */
338
- claimScope?: 'run';
339
- /** Failed deliveries carried across suspension re-publishes. */
340
- deliveryFailures?: number;
341
- suspensionCount?: number;
342
- }
343
- interface NativeQueueSendOptions {
344
- delaySeconds?: number;
345
- }
346
- interface NativeQueueSendResult {
347
- messageId: string;
348
- }
349
- //#endregion
350
316
  //#region src/queue.d.ts
351
317
  interface CelldQueueProducer {
352
318
  send(envelope: NativeQueueEnvelope, options?: NativeQueueSendOptions): Promise<NativeQueueSendResult>;
@@ -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-protocol.ts","../src/queue.ts","../src/run-list.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/run-list.ts","../src/indexes.ts","../src/config.ts","../src/index.ts"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { B as MAX_STREAM_WRITE_CHUNKS, C as MAX_FLEET_RPC_TIMEOUT_MS, D as boundedIntegerOption, F as MAX_STREAM_CHUNK_BYTES, G as decodeStreamWriteResult, H as STREAM_CHUNK_LIMIT_HEADER, I as MAX_STREAM_ERROR_BYTES, L as MAX_STREAM_LONG_POLL_MS, M as queueDelayDeadline, N as strictIntegerSetting, O as checkedQueueTimestampAdd, P as MAX_STREAM_BATCH_BYTES, R as MAX_STREAM_READ_BYTES, T as MAX_QUEUE_DERIVED_DEADLINE_HEADROOM_MS, U as decodeStreamReadResult, V as STREAM_BATCH_CONTENT_TYPE, X as normalizeStreamError, Y as isChunkLimitRejection, _ as stringify, g as parse, h as rpcStringify, m as rpcParse, n as listRunsPage, o as nativeQueueDelaySeconds, p as compact, q as encodeStreamWriteBatch, v as createWorkflowIndex, w as MAX_QUEUE_DELIVERY_TIMEOUT_MS, y as runIndexMetadata, z as MAX_STREAM_READ_CHUNKS } from "./run-list-C987PkJ2.js";
1
+ import { $ as normalizeStreamError, A as boundedIntegerOption, B as MAX_STREAM_LONG_POLL_MS, D as MAX_QUEUE_DERIVED_DEADLINE_HEADROOM_MS, E as MAX_QUEUE_DELIVERY_TIMEOUT_MS, F as queueDelayDeadline, G as STREAM_CHUNK_LIMIT_HEADER, H as MAX_STREAM_READ_CHUNKS, I as strictIntegerSetting, J as decodeStreamWriteResult, K as decodeStreamReadResult, L as MAX_STREAM_BATCH_BYTES, Q as isChunkLimitRejection, R as MAX_STREAM_CHUNK_BYTES, T as MAX_FLEET_RPC_TIMEOUT_MS, U as MAX_STREAM_WRITE_CHUNKS, V as MAX_STREAM_READ_BYTES, W as STREAM_BATCH_CONTENT_TYPE, X as encodeStreamWriteBatch, _ as rpcStringify, b as createWorkflowIndex, c as nativeQueueDelaySeconds, g as rpcParse, h as compact, j as checkedQueueTimestampAdd, l as queueClaimName, n as listRunsPage, v as parse, x as runIndexMetadata, y as stringify, z as MAX_STREAM_ERROR_BYTES } from "./run-list-C4B4SBYW.js";
2
2
  import { CreateEventSchema, EventSchema, HookSchema, MessageId, QueuePayloadSchema, SPEC_VERSION_CURRENT, StepSchema, WaitSchema, WorkflowRunSchema, parseQueueName } from "@workflow/world";
3
3
  import { EntityConflictError, HookNotFoundError, RunExpiredError, RunNotSupportedError, TooEarlyError, WorkflowRunNotFoundError, WorkflowWorldError } from "@workflow/errors";
4
4
  import { monotonicFactory } from "ulid";
@@ -106,7 +106,13 @@ async function callFleetRoute(transport, path, args, opts) {
106
106
  if (attempt < attempts) continue;
107
107
  throw lastError;
108
108
  }
109
- const text = await response.text();
109
+ let text;
110
+ try {
111
+ text = await response.text();
112
+ } catch (error) {
113
+ lastError = new FleetTransportError(`world-celld: interrupted error response from ${url}`, error);
114
+ continue;
115
+ }
110
116
  let wire;
111
117
  try {
112
118
  wire = JSON.parse(text).error;
@@ -240,7 +246,9 @@ async function writeStreamChunks(transport, name, runId, chunks) {
240
246
  if (!response.ok) throw await errorFromResponse(response);
241
247
  recordStreamChunkLimit(transport, response);
242
248
  try {
243
- return decodeStreamWriteResult(await readBoundedResponse(response, MAX_WRITE_RESPONSE_BYTES));
249
+ const result = decodeStreamWriteResult(await readBoundedResponse(response, MAX_WRITE_RESPONSE_BYTES));
250
+ if (result.count !== chunks.length) throw new Error("Stream write acknowledgement does not match the requested chunk count");
251
+ return result;
244
252
  } catch (error) {
245
253
  if (error instanceof FleetTransportError) throw error;
246
254
  throw new FleetTransportError(`world-celld: malformed stream response from ${url.href}`, error);
@@ -256,6 +264,7 @@ async function readStreamChunks(transport, name, request, signal) {
256
264
  url.searchParams.set("waitMs", String(request.waitMs));
257
265
  const doFetch = transport.fetchImpl ?? fetch;
258
266
  let lastError;
267
+ let maxChunks = request.maxChunks;
259
268
  for (let attempt = 1; attempt <= READ_ATTEMPTS; attempt++) {
260
269
  if (signal?.aborted) throw aborted(signal);
261
270
  if (attempt > 1) await delayMs(100 * attempt + Math.random() * 200, signal);
@@ -278,7 +287,9 @@ async function readStreamChunks(transport, name, request, signal) {
278
287
  if (response.ok) {
279
288
  recordStreamChunkLimit(transport, response);
280
289
  try {
281
- return decodeStreamReadResult(await readBoundedResponse(response, MAX_READ_RESPONSE_BYTES));
290
+ const result = decodeStreamReadResult(await readBoundedResponse(response, MAX_READ_RESPONSE_BYTES));
291
+ if (result.startIndex !== request.startIndex || result.chunks.length > maxChunks || result.chunks.reduce((bytes, chunk) => bytes + chunk.byteLength, 0) > request.maxBytes || result.chunks.length > 0 && result.startIndex + result.chunks.length - 1 > result.tailIndex || maxChunks > 0 && result.chunks.length === 0 && result.startIndex <= result.tailIndex && result.state !== "expired") throw new Error("Stream read response does not match the requested range and bounds");
292
+ return result;
282
293
  } catch (error) {
283
294
  if (signal?.aborted) throw aborted(signal);
284
295
  lastError = error instanceof FleetTransportError ? error : new FleetTransportError(`world-celld: malformed stream response from ${url.href}`, error);
@@ -299,6 +310,7 @@ async function readStreamChunks(transport, name, request, signal) {
299
310
  }
300
311
  if (request.maxChunks > 32 && isChunkLimitRejection(error) && attempt < READ_ATTEMPTS) {
301
312
  resetStreamChunkLimit(transport);
313
+ maxChunks = 32;
302
314
  url.searchParams.set("maxChunks", String(32));
303
315
  lastError = error;
304
316
  continue;
@@ -323,14 +335,16 @@ const RUNS = {
323
335
  "scheduleCleanup",
324
336
  "cleanupNow",
325
337
  "rearmCleanup",
326
- "resolveHookTokenClaim"
338
+ "resolveHookTokenClaim",
339
+ "resolveExpiredHookClaim"
327
340
  ],
328
341
  mutating: /* @__PURE__ */ new Set([
329
342
  "applyEvent",
330
343
  "scheduleCleanup",
331
344
  "cleanupNow",
332
345
  "rearmCleanup",
333
- "resolveHookTokenClaim"
346
+ "resolveHookTokenClaim",
347
+ "resolveExpiredHookClaim"
334
348
  ])
335
349
  };
336
350
  const STREAMS = {
@@ -581,7 +595,7 @@ function createTestPump(config) {
581
595
  const inflightMessages = /* @__PURE__ */ new Map();
582
596
  let running = false;
583
597
  function release(envelope) {
584
- if (envelope.idempotencyKey) inflightMessages.delete(envelope.idempotencyKey);
598
+ if (envelope.idempotencyKey) inflightMessages.delete(queueClaimName(envelope.queueName, envelope.idempotencyKey));
585
599
  }
586
600
  function enqueue(pathname, envelope) {
587
601
  queues[pathname].push(envelope);
@@ -594,6 +608,24 @@ function createTestPump(config) {
594
608
  wakers[pathname].push(() => resolve(queues[pathname].shift() ?? null));
595
609
  });
596
610
  }
611
+ function retry(envelope, pathname, reason) {
612
+ if (envelope.attempt >= maxAttempts) {
613
+ release(envelope);
614
+ console.error(`[world-celld test pump] dropping ${envelope.messageId} after ${envelope.attempt} attempts: ${reason}`);
615
+ return;
616
+ }
617
+ const next = {
618
+ ...envelope,
619
+ attempt: envelope.attempt + 1
620
+ };
621
+ const backoff = Math.min(MAX_TEST_PUMP_BACKOFF_MS, baseBackoffMs * 2 ** Math.min(next.attempt - 1, 30));
622
+ if (checkedQueueTimestampAdd(Date.now(), backoff, 900001) === null) {
623
+ release(envelope);
624
+ console.error(`[world-celld test pump] dropping ${envelope.messageId}: retry deadline exceeds the queue timestamp horizon`);
625
+ return;
626
+ }
627
+ delayFor(backoff).then(() => enqueue(pathname, next));
628
+ }
597
629
  async function dispatch(envelope, pathname) {
598
630
  const url = `${resolveBaseUrl(config)}/.well-known/workflow/v1/${pathname}`;
599
631
  const response = await fetch(url, {
@@ -607,11 +639,12 @@ function createTestPump(config) {
607
639
  body: stringify(envelope.message),
608
640
  signal: AbortSignal.timeout(httpTimeoutMs)
609
641
  });
610
- if (response.ok) {
642
+ if (response.ok || PERMANENT_ERROR_STATUSES.has(response.status)) {
611
643
  try {
612
644
  await response.body?.cancel();
613
645
  } catch {}
614
646
  release(envelope);
647
+ if (!response.ok) debug(`[world-celld test pump] dropping ${envelope.messageId}: permanent HTTP ${response.status}`);
615
648
  return;
616
649
  }
617
650
  const text = await response.text();
@@ -630,27 +663,7 @@ function createTestPump(config) {
630
663
  return;
631
664
  }
632
665
  }
633
- if (PERMANENT_ERROR_STATUSES.has(response.status)) {
634
- release(envelope);
635
- debug(`[world-celld test pump] dropping ${envelope.messageId}: permanent HTTP ${response.status}: ${text}`);
636
- return;
637
- }
638
- if (envelope.attempt < maxAttempts) {
639
- const next = {
640
- ...envelope,
641
- attempt: envelope.attempt + 1
642
- };
643
- const backoff = Math.min(MAX_TEST_PUMP_BACKOFF_MS, baseBackoffMs * 2 ** Math.min(next.attempt - 1, 30));
644
- if (checkedQueueTimestampAdd(Date.now(), backoff, 900001) === null) {
645
- release(envelope);
646
- console.error(`[world-celld test pump] dropping ${envelope.messageId}: retry deadline exceeds the queue timestamp horizon`);
647
- return;
648
- }
649
- delayFor(backoff).then(() => enqueue(pathname, next));
650
- } else {
651
- release(envelope);
652
- console.error(`[world-celld test pump] dropping ${envelope.messageId} after ${envelope.attempt} attempts: HTTP ${response.status}: ${text}`);
653
- }
666
+ retry(envelope, pathname, `HTTP ${response.status}: ${text}`);
654
667
  }
655
668
  async function loop(pathname) {
656
669
  while (true) {
@@ -660,19 +673,18 @@ function createTestPump(config) {
660
673
  try {
661
674
  await dispatch(envelope, pathname);
662
675
  } catch (err) {
663
- release(envelope);
664
- console.error(`[world-celld test pump] dispatch error on ${pathname}:`, err);
676
+ retry(envelope, pathname, err instanceof Error ? err.message : String(err));
665
677
  }
666
678
  }
667
679
  }
668
680
  return {
669
681
  /** Returns the inflight messageId when the key is already claimed. */
670
- inflight(idempotencyKey) {
671
- return idempotencyKey ? inflightMessages.get(idempotencyKey) : void 0;
682
+ inflight(queueName, idempotencyKey) {
683
+ return idempotencyKey ? inflightMessages.get(queueClaimName(queueName, idempotencyKey)) : void 0;
672
684
  },
673
685
  push(pathname, envelope, delaySeconds) {
674
686
  if (queueDelayDeadline(Date.now(), delaySeconds ?? 0, 0, 900001) === null) throw new Error("world-celld queue delaySeconds lacks required delivery headroom");
675
- if (envelope.idempotencyKey) inflightMessages.set(envelope.idempotencyKey, MessageId.parse(envelope.messageId));
687
+ if (envelope.idempotencyKey) inflightMessages.set(queueClaimName(envelope.queueName, envelope.idempotencyKey), MessageId.parse(envelope.messageId));
676
688
  if (delaySeconds && delaySeconds > 0) delayFor(delaySeconds * 1e3).then(() => enqueue(pathname, envelope));
677
689
  else enqueue(pathname, envelope);
678
690
  },
@@ -700,7 +712,7 @@ function createQueue(config) {
700
712
  if (queueDelayDeadline(Date.now(), opts?.delaySeconds ?? 0, 0, 900001) === null) throw new Error("world-celld queue delaySeconds lacks required delivery headroom");
701
713
  const runId = "runId" in parsedMessage.data && typeof parsedMessage.data.runId === "string" ? parsedMessage.data.runId : void 0;
702
714
  if (isTestMode()) {
703
- const existing = testPump.inflight(opts?.idempotencyKey);
715
+ const existing = testPump.inflight(queueName, opts?.idempotencyKey);
704
716
  if (existing) return { messageId: existing };
705
717
  const messageId = MessageId.parse(`msg_${generateMessageId()}`);
706
718
  testPump.push(QUEUE_PATHNAME, {
@@ -757,7 +769,7 @@ function createQueue(config) {
757
769
  queueName: reqQueueName,
758
770
  messageId: reqMessageId
759
771
  });
760
- if (result && typeof result.timeoutSeconds === "number") {
772
+ if (!("invoke" in parsedBody.data && parsedBody.data.invoke) && result !== null && typeof result === "object" && "timeoutSeconds" in result && typeof result.timeoutSeconds === "number") {
761
773
  if (queueDelayDeadline(Date.now(), result.timeoutSeconds, 1, 900001) === null) throw new Error("Queue handler timeoutSeconds is out of range");
762
774
  return Response.json({ timeoutSeconds: result.timeoutSeconds }, {
763
775
  status: 503,
@@ -836,7 +848,9 @@ function parseSuccessEntity(outcome, key, schema) {
836
848
  const value = outcome[key];
837
849
  if (value === void 0) return void 0;
838
850
  if (!isRecord(value)) malformedApplyEventOutcome(`${key} is invalid`);
839
- const result = schema.safeParse(compact(value));
851
+ const entity = compact(value);
852
+ if (key === "step") entity.error = value.error;
853
+ const result = schema.safeParse(entity);
840
854
  if (!result.success) malformedApplyEventOutcome(`${key} is invalid`);
841
855
  return result.data;
842
856
  }
@@ -991,7 +1005,10 @@ function throwOutcomeError(outcome, runId, data) {
991
1005
  }
992
1006
  }
993
1007
  const parseRun = (run) => WorkflowRunSchema.parse(compact(run));
994
- const parseStep = (step) => StepSchema.parse(compact(step));
1008
+ const parseStep = (step) => StepSchema.parse({
1009
+ ...compact(step),
1010
+ error: step.error
1011
+ });
995
1012
  const parseHook = (hook) => HookSchema.parse(compact(hook));
996
1013
  const parseEvent = (event) => EventSchema.parse(compact(event));
997
1014
  function unwrapRead(outcome) {