@ewhauser/world-celld 0.7.0 → 0.8.1

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,32 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## 0.8.1 (2026-10-02)
6
+
7
+ ## What's Changed
8
+ * fix: harden workflow fault recovery by @ewhauser in https://github.com/ewhauser/world-celld/pull/87
9
+ * chore(deps): bump hono from 4.13.2 to 4.13.7 by @dependabot[bot] in https://github.com/ewhauser/world-celld/pull/88
10
+
11
+
12
+ **Full Changelog**: https://github.com/ewhauser/world-celld/compare/v0.8.0...v0.8.1
13
+
14
+ ## 0.8.0 (2026-09-27)
15
+
16
+ ## What's Changed
17
+ * test(perf): add a run-bearing mode to the MinIO queue benchmark by @ewhauser in https://github.com/ewhauser/world-celld/pull/78
18
+ * feat(queue): accept inline run-bearing Queue bodies at delivery by @ewhauser in https://github.com/ewhauser/world-celld/pull/79
19
+ * feat(queue): support run-scoped Queue idempotency claims by @ewhauser in https://github.com/ewhauser/world-celld/pull/81
20
+ * perf(queue): keep small run-bearing bodies inline in the broker by @ewhauser in https://github.com/ewhauser/world-celld/pull/80
21
+ * perf(queue): keep new runs' idempotency claims in the run cell by @ewhauser in https://github.com/ewhauser/world-celld/pull/82
22
+ * feat(streams): read, append, and expire segment-layout streams by @ewhauser in https://github.com/ewhauser/world-celld/pull/83
23
+ * perf(streams): store new streams in segment rows by @ewhauser in https://github.com/ewhauser/world-celld/pull/84
24
+ * chore(deps): bump zizmorcore/zizmor-action from 0.6.3 to 0.6.4 in the github-actions group across 1 directory by @dependabot[bot] in https://github.com/ewhauser/world-celld/pull/62
25
+ * test(smoke): keep the retention-restart Queue payload in object storage by @ewhauser in https://github.com/ewhauser/world-celld/pull/85
26
+ * Limit Dependabot to security updates by @ewhauser in https://github.com/ewhauser/world-celld/pull/63
27
+
28
+
29
+ **Full Changelog**: https://github.com/ewhauser/world-celld/compare/v0.7.0...v0.8.0
30
+
5
31
  ## 0.7.0 (2026-09-27)
6
32
 
7
33
  ## What's Changed
package/README.md CHANGED
@@ -316,6 +316,11 @@ fleet-wide maximum age are both enabled, the earlier deadline wins.
316
316
 
317
317
  - Delivery is at least once. Workflow steps and other external side effects
318
318
  must be idempotent.
319
+ - A concurrent enqueue with the same idempotency key returns a retryable `503`
320
+ while broker publication is pending. Publication confirmation starts the
321
+ normal deduplication window. If a producer stops before publication completes,
322
+ its pending reservation can be replaced after five minutes. Broker delivery
323
+ also confirms publication when the producer's confirmation was interrupted.
319
324
  - Native Queue operations are administered with celld's `queue info`, `peek`,
320
325
  `pause`, `resume`, `purge`, and `redrive` commands.
321
326
  - Run cells expose retention status, scheduling, immediate cleanup, and alarm
package/dist/index.d.ts CHANGED
@@ -175,6 +175,11 @@ interface WorkflowRunDOStub {
175
175
  }): Promise<{
176
176
  committed: boolean;
177
177
  }>;
178
+ resolveExpiredHookClaim(request: {
179
+ hookId: string;
180
+ claimId: string;
181
+ token?: string;
182
+ }): Promise<Hook | null>;
178
183
  getLifecycleStatus(): Promise<RunLifecycleStatus>;
179
184
  getRun(): Promise<RunReadOutcome<WorkflowRun | null>>;
180
185
  getStep(stepId: string): Promise<RunReadOutcome<Step | null>>;
@@ -323,10 +328,19 @@ interface NativeQueueEnvelope {
323
328
  runId?: string;
324
329
  idempotencyKey?: string;
325
330
  payloadKey?: string;
326
- /** Inline only for the small, run-less health-check envelope. */
331
+ /**
332
+ * Inline body. Health checks always travel inline; a run-bearing body may
333
+ * travel inline when it is small and carries no user data, in which case
334
+ * delivery checks the run's expiry instead of reading a payload object.
335
+ */
327
336
  body?: string;
328
337
  /** Absolute workflow redelivery deadline; long waits are chained by the consumer. */
329
338
  notBefore?: number;
339
+ /**
340
+ * Where the idempotency claim lives. Absent: the claim cell named by queue
341
+ * and key. `run`: an entry in the run's own cell (requires `runId`).
342
+ */
343
+ claimScope?: 'run';
330
344
  /** Failed deliveries carried across suspension re-publishes. */
331
345
  deliveryFailures?: number;
332
346
  suspensionCount?: number;
@@ -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-protocol.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 STREAM_BATCH_CONTENT_TYPE, C as MAX_QUEUE_DELIVERY_TIMEOUT_MS, D as checkedQueueTimestampAdd, E as boundedIntegerOption, F as MAX_STREAM_ERROR_BYTES, H as decodeStreamReadResult, I as MAX_STREAM_LONG_POLL_MS, J as isChunkLimitRejection, K as encodeStreamWriteBatch, L as MAX_STREAM_READ_BYTES, M as strictIntegerSetting, N as MAX_STREAM_BATCH_BYTES, P as MAX_STREAM_CHUNK_BYTES, R as MAX_STREAM_READ_CHUNKS, S as MAX_FLEET_RPC_TIMEOUT_MS, V as STREAM_CHUNK_LIMIT_HEADER, W as decodeStreamWriteResult, Y as normalizeStreamError, _ as createWorkflowIndex, a as nativeQueueDelaySeconds, f as compact, g as stringify, h as parse, j as queueDelayDeadline, m as rpcStringify, n as listRunsPage, p as rpcParse, v as runIndexMetadata, w as MAX_QUEUE_DERIVED_DEADLINE_HEADROOM_MS, z as MAX_STREAM_WRITE_CHUNKS } from "./run-list-B__jKuE7.js";
1
+ import { A as checkedQueueTimestampAdd, B as MAX_STREAM_READ_BYTES, D as MAX_QUEUE_DERIVED_DEADLINE_HEADROOM_MS, E as MAX_QUEUE_DELIVERY_TIMEOUT_MS, F as strictIntegerSetting, G as decodeStreamReadResult, H as MAX_STREAM_WRITE_CHUNKS, I as MAX_STREAM_BATCH_BYTES, L as MAX_STREAM_CHUNK_BYTES, P as queueDelayDeadline, Q as normalizeStreamError, R as MAX_STREAM_ERROR_BYTES, T as MAX_FLEET_RPC_TIMEOUT_MS, U as STREAM_BATCH_CONTENT_TYPE, V as MAX_STREAM_READ_CHUNKS, W as STREAM_CHUNK_LIMIT_HEADER, Y as encodeStreamWriteBatch, Z as isChunkLimitRejection, _ as rpcStringify, b as createWorkflowIndex, c as nativeQueueDelaySeconds, g as rpcParse, h as compact, k as boundedIntegerOption, l as queueClaimName, n as listRunsPage, q as decodeStreamWriteResult, v as parse, x as runIndexMetadata, y as stringify, z as MAX_STREAM_LONG_POLL_MS } from "./run-list-DGsdv6Jf.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, {
@@ -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) {