@lostgradient/weft 0.2.0 → 0.3.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.
Files changed (207) hide show
  1. package/README.md +47 -22
  2. package/dist/cli/generated/operation-client.generated.d.ts +28 -1
  3. package/dist/cli/generated/operation-client.generated.js +2 -0
  4. package/dist/cli-main.js +79 -79
  5. package/dist/client/handle-delegation.d.ts +4 -0
  6. package/dist/client/handle-delegation.js +6 -0
  7. package/dist/client/http-client-requests.d.ts +2 -0
  8. package/dist/client/http-client-requests.js +3 -0
  9. package/dist/client/http-client.d.ts +4 -1
  10. package/dist/client/http-client.js +9 -1
  11. package/dist/client/interface.d.ts +57 -2
  12. package/dist/client/local.d.ts +4 -1
  13. package/dist/client/local.js +7 -0
  14. package/dist/client/start-body.d.ts +7 -1
  15. package/dist/client/start-body.js +13 -4
  16. package/dist/core/codec/extension-codec.js +4 -2
  17. package/dist/core/codec/index.d.ts +1 -0
  18. package/dist/core/codec/index.js +1 -0
  19. package/dist/core/codec/serializer-registry.d.ts +122 -0
  20. package/dist/core/codec/serializer-registry.js +51 -0
  21. package/dist/core/context/index.d.ts +10 -0
  22. package/dist/core/context/index.js +3 -0
  23. package/dist/core/context/internals.d.ts +10 -0
  24. package/dist/core/context/internals.js +5 -1
  25. package/dist/core/context/run-operation.d.ts +16 -3
  26. package/dist/core/context/run-operation.js +16 -7
  27. package/dist/core/context/speculative-child.js +2 -0
  28. package/dist/core/context/types.d.ts +6 -0
  29. package/dist/core/engine/bulk-operations-purge.js +1 -0
  30. package/dist/core/engine/bulk-operations.js +1 -1
  31. package/dist/core/engine/callback-creators-bundles.js +2 -1
  32. package/dist/core/engine/callback-creators-core.js +2 -1
  33. package/dist/core/engine/construction.d.ts +1 -1
  34. package/dist/core/engine/construction.js +15 -3
  35. package/dist/core/engine/disposal.js +12 -0
  36. package/dist/core/engine/engine-create-types.d.ts +0 -14
  37. package/dist/core/engine/engine-internal-types.d.ts +17 -0
  38. package/dist/core/engine/engine-leak-warnings.d.ts +6 -0
  39. package/dist/core/engine/engine-leak-warnings.js +4 -0
  40. package/dist/core/engine/engine-runtime-helpers.d.ts +17 -0
  41. package/dist/core/engine/engine-runtime-helpers.js +26 -5
  42. package/dist/core/engine/errors.d.ts +74 -0
  43. package/dist/core/engine/errors.js +25 -1
  44. package/dist/core/engine/handle-result.js +1 -1
  45. package/dist/core/engine/handles.d.ts +89 -40
  46. package/dist/core/engine/handles.js +25 -27
  47. package/dist/core/engine/index.d.ts +122 -4
  48. package/dist/core/engine/index.js +82 -5
  49. package/dist/core/engine/inline-launch-queue.d.ts +14 -0
  50. package/dist/core/engine/inline-launch-queue.js +32 -7
  51. package/dist/core/engine/internals.d.ts +26 -10
  52. package/dist/core/engine/lifecycle/fork-helpers.js +1 -7
  53. package/dist/core/engine/lifecycle/persist.js +5 -20
  54. package/dist/core/engine/lifecycle/recovered-services.d.ts +45 -0
  55. package/dist/core/engine/lifecycle/recovered-services.js +34 -0
  56. package/dist/core/engine/lifecycle/resume.js +33 -5
  57. package/dist/core/engine/lifecycle/shared.d.ts +8 -0
  58. package/dist/core/engine/lifecycle/start-batch.js +23 -12
  59. package/dist/core/engine/lifecycle/start-commit.d.ts +47 -0
  60. package/dist/core/engine/lifecycle/start-commit.js +27 -0
  61. package/dist/core/engine/lifecycle/start-exec.d.ts +30 -2
  62. package/dist/core/engine/lifecycle/start-exec.js +38 -0
  63. package/dist/core/engine/lifecycle/start-or-signal-resolution.d.ts +79 -0
  64. package/dist/core/engine/lifecycle/start-or-signal-resolution.js +60 -0
  65. package/dist/core/engine/lifecycle/start-or-signal.d.ts +45 -0
  66. package/dist/core/engine/lifecycle/start-or-signal.js +141 -0
  67. package/dist/core/engine/lifecycle/start.d.ts +3 -3
  68. package/dist/core/engine/lifecycle/start.js +42 -37
  69. package/dist/core/engine/lifecycle.d.ts +3 -2
  70. package/dist/core/engine/lifecycle.js +9 -2
  71. package/dist/core/engine/listing.js +1 -1
  72. package/dist/core/engine/operations-data.d.ts +16 -0
  73. package/dist/core/engine/operations-data.js +6 -0
  74. package/dist/core/engine/operations-time.d.ts +3 -2
  75. package/dist/core/engine/operations-time.js +6 -1
  76. package/dist/core/engine/persisted-data-version.d.ts +5 -9
  77. package/dist/core/engine/persisted-data-version.js +4 -5
  78. package/dist/core/engine/schedule-handle.d.ts +45 -0
  79. package/dist/core/engine/schedule-handle.js +26 -0
  80. package/dist/core/engine/schedules.d.ts +1 -1
  81. package/dist/core/engine/schedules.js +7 -3
  82. package/dist/core/engine/second-instance-detector.d.ts +96 -0
  83. package/dist/core/engine/second-instance-detector.js +108 -0
  84. package/dist/core/engine/signals.d.ts +22 -0
  85. package/dist/core/engine/signals.js +15 -0
  86. package/dist/core/engine/termination/cleanup.d.ts +25 -0
  87. package/dist/core/engine/termination/cleanup.js +21 -1
  88. package/dist/core/engine/termination/complete.js +4 -3
  89. package/dist/core/engine/termination/suspend.d.ts +68 -0
  90. package/dist/core/engine/termination/suspend.js +41 -0
  91. package/dist/core/engine/termination.d.ts +4 -2
  92. package/dist/core/engine/termination.js +2 -0
  93. package/dist/core/engine/validation.js +25 -1
  94. package/dist/core/engine/workflow-feed.d.ts +5 -3
  95. package/dist/core/events/event-map.d.ts +2 -1
  96. package/dist/core/events/workflow-events.d.ts +23 -0
  97. package/dist/core/events/workflow-events.js +9 -0
  98. package/dist/core/inline-execution-strategy.d.ts +5 -0
  99. package/dist/core/inline-execution-strategy.js +2 -1
  100. package/dist/core/list-filter-validation.js +2 -1
  101. package/dist/core/start-workflow-validation.d.ts +22 -0
  102. package/dist/core/start-workflow-validation.js +11 -1
  103. package/dist/core/step-context.d.ts +10 -6
  104. package/dist/core/step-context.js +7 -15
  105. package/dist/core/types/activity.d.ts +6 -3
  106. package/dist/core/types/identity.d.ts +8 -1
  107. package/dist/core/types/launch-metadata.d.ts +33 -0
  108. package/dist/core/types/launch-metadata.js +0 -0
  109. package/dist/core/types/message-handles.d.ts +25 -0
  110. package/dist/core/types/options.d.ts +90 -7
  111. package/dist/core/types/reviews.d.ts +2 -1
  112. package/dist/core/types/services-resolution.d.ts +47 -0
  113. package/dist/core/types/services-resolution.js +0 -0
  114. package/dist/core/types/state.d.ts +11 -11
  115. package/dist/core/types/workflow-builder.d.ts +5 -4
  116. package/dist/core/types/workflow-context.d.ts +25 -0
  117. package/dist/core/types/workflow-function.d.ts +17 -0
  118. package/dist/core/types/workflow-snapshot.d.ts +29 -0
  119. package/dist/core/types/workflow-snapshot.js +0 -0
  120. package/dist/core/types.d.ts +3 -0
  121. package/dist/core/types.js +3 -0
  122. package/dist/core/weft-error.d.ts +46 -14
  123. package/dist/core/weft-error.js +12 -1
  124. package/dist/diagnostics/doctor.js +6 -3
  125. package/dist/diagnostics/format.js +2 -2
  126. package/dist/diagnostics/types.d.ts +1 -0
  127. package/dist/diagnostics/version-check.js +6 -4
  128. package/dist/index.d.ts +10 -5
  129. package/dist/index.js +11 -2
  130. package/dist/json-schema.js +3 -3
  131. package/dist/mcp/cli.js +35 -35
  132. package/dist/mcp/list-filter.js +2 -1
  133. package/dist/mcp/session.js +1 -0
  134. package/dist/observability/index.js +2 -2
  135. package/dist/server/handler.js +30 -30
  136. package/dist/server/index.js +33 -33
  137. package/dist/server/interactive-operations.js +1 -0
  138. package/dist/server/operations/resume-workflow.js +2 -2
  139. package/dist/server/operations/start-or-signal-workflow.d.ts +39 -0
  140. package/dist/server/operations/start-or-signal-workflow.js +140 -0
  141. package/dist/server/operations/start-workflow-options.d.ts +32 -0
  142. package/dist/server/operations/start-workflow-options.js +63 -0
  143. package/dist/server/operations/start-workflow.js +7 -69
  144. package/dist/server/operations/suspend-workflow.d.ts +13 -0
  145. package/dist/server/operations/suspend-workflow.js +36 -0
  146. package/dist/server/rest-binding.d.ts +18 -7
  147. package/dist/server/rest-bindings.js +12 -0
  148. package/dist/server/runtime/task-dispatch.js +5 -3
  149. package/dist/server/runtime/task-polling.d.ts +16 -2
  150. package/dist/server/runtime/task-polling.js +20 -5
  151. package/dist/server/runtime/websocket-worker.js +8 -0
  152. package/dist/server/serve-internals.d.ts +8 -0
  153. package/dist/server/serve-internals.js +4 -2
  154. package/dist/server/task-state.d.ts +8 -0
  155. package/dist/service-worker/index.js +28 -28
  156. package/dist/storage/capabilities.d.ts +10 -2
  157. package/dist/storage/capabilities.js +2 -2
  158. package/dist/storage/http.js +2 -2
  159. package/dist/storage/index.d.ts +7 -1
  160. package/dist/storage/indexeddb.js +1 -1
  161. package/dist/storage/interface.d.ts +40 -0
  162. package/dist/storage/interface.js +1 -1
  163. package/dist/storage/key-prefixes.d.ts +1 -1
  164. package/dist/storage/key-prefixes.js +3 -0
  165. package/dist/storage/lmdb.js +1 -1
  166. package/dist/storage/memory.js +1 -1
  167. package/dist/storage/neon-value-mapping.d.ts +47 -0
  168. package/dist/storage/neon-value-mapping.js +11 -0
  169. package/dist/storage/neon.d.ts +108 -0
  170. package/dist/storage/neon.js +10 -0
  171. package/dist/storage/node-sqlite-loader.d.ts +71 -0
  172. package/dist/storage/node-sqlite-loader.js +41 -0
  173. package/dist/storage/node-sqlite.d.ts +1 -19
  174. package/dist/storage/node-sqlite.js +38 -32
  175. package/dist/storage/postgres-key-value-queries.d.ts +79 -0
  176. package/dist/storage/postgres-key-value-queries.js +63 -0
  177. package/dist/storage/resolve.d.ts +2 -165
  178. package/dist/storage/resolve.js +1 -1
  179. package/dist/storage/scoped-storage.js +1 -1
  180. package/dist/storage/storage-configuration.d.ts +209 -0
  181. package/dist/storage/storage-configuration.js +0 -0
  182. package/dist/storage/text-value-store.d.ts +13 -10
  183. package/dist/storage/turso.js +2 -2
  184. package/dist/storage/typed-storage.js +1 -1
  185. package/dist/storage/web-extension.js +1 -1
  186. package/dist/testing/event-loop.d.ts +36 -2
  187. package/dist/testing/index.d.ts +31 -1
  188. package/dist/testing/index.js +33 -33
  189. package/dist/version.d.ts +1 -1
  190. package/dist/version.js +1 -1
  191. package/dist/worker/index.js +9 -5
  192. package/dist/worker/long-poll.js +4 -0
  193. package/dist/worker/protocol-messages.d.ts +20 -0
  194. package/dist/worker/protocol-schemas.d.ts +32 -0
  195. package/dist/worker/protocol-schemas.js +8 -4
  196. package/dist/worker/protocol-task-result.d.ts +28 -0
  197. package/dist/worker/protocol-task-result.js +76 -0
  198. package/dist/worker/protocol.d.ts +4 -15
  199. package/dist/worker/protocol.js +1 -1
  200. package/dist/worker/registry/fair-share.d.ts +29 -0
  201. package/dist/worker/registry/fair-share.js +30 -0
  202. package/dist/worker/registry/routing.d.ts +18 -0
  203. package/dist/worker/registry/routing.js +14 -0
  204. package/dist/worker/registry/types.d.ts +7 -0
  205. package/dist/worker/registry.d.ts +16 -1
  206. package/dist/worker/registry.js +24 -36
  207. package/package.json +17 -4
@@ -30,12 +30,16 @@ function validateTaskResultBody(body) {
30
30
  return Response.json({ error: "Missing required fields: operationId, status" }, { status: 400 });
31
31
  if (status !== "completed" && status !== "failed")
32
32
  return Response.json({ error: 'status must be "completed" or "failed"' }, { status: 400 });
33
+ const rawAttemptToken = body.attemptToken;
34
+ if (rawAttemptToken !== void 0 && (typeof rawAttemptToken !== "string" || rawAttemptToken === ""))
35
+ return Response.json({ error: "attemptToken must be a non-empty string when present" }, { status: 400 });
33
36
  return {
34
37
  operationId,
35
38
  status,
36
39
  workerId: typeof body.workerId === "string" ? body.workerId : void 0,
37
40
  value: body.value,
38
- error: typeof body.error === "string" ? body.error : void 0
41
+ error: typeof body.error === "string" ? body.error : void 0,
42
+ attemptToken: rawAttemptToken
39
43
  };
40
44
  }
41
45
  async function applyTaskResult(context, options, result, inflightRecord) {
@@ -69,6 +73,7 @@ export function createLongPollInflightRecord(queue, task) {
69
73
  visibilityTimeout,
70
74
  retryPolicy: task.retryPolicy,
71
75
  workflowId: task.workflowId,
76
+ attemptToken: crypto.randomUUID(),
72
77
  firstQueuedAt: task.firstQueuedAt ?? task.enqueuedAt ?? now,
73
78
  lastQueuedAt: task.lastQueuedAt ?? task.enqueuedAt ?? now,
74
79
  lastDispatchedAt: now,
@@ -87,7 +92,10 @@ export async function markTaskClaimedByLongPollWorker(context, options, queue, t
87
92
  const normalizedInflightRecord = await transitionQueuedToInflight(options.engine.storage, task.operationId, inflightRecord);
88
93
  recordTaskQueueLatencyMetric(context.metricsCollector, normalizedInflightRecord);
89
94
  recordTaskBacklogMetric(context.metricsCollector, context.taskQueue);
90
- return normalizedInflightRecord.workerId;
95
+ return {
96
+ workerId: normalizedInflightRecord.workerId,
97
+ attemptToken: inflightRecord.attemptToken
98
+ };
91
99
  }
92
100
  export async function handleTaskPollRequest(context, options, request, url, principal) {
93
101
  if (request.method !== "GET")
@@ -105,8 +113,8 @@ export async function handleTaskPollRequest(context, options, request, url, prin
105
113
  return Response.json({ error: 'At least one "activity" query parameter is required' }, { status: 400 });
106
114
  const rawTimeout = url.searchParams.get("timeout"), timeout = rawTimeout !== null ? Math.min(Math.max(0, Number(rawTimeout)), MAX_POLL_TIMEOUT) : DEFAULT_POLL_TIMEOUT, task = await context.taskQueue.poll(queue, activities, timeout, request.signal);
107
115
  if (task !== null) {
108
- const workerId = await markTaskClaimedByLongPollWorker(context, options, queue, task);
109
- return Response.json({ ...task, workerId });
116
+ const claim = await markTaskClaimedByLongPollWorker(context, options, queue, task);
117
+ return Response.json({ ...task, workerId: claim.workerId, attemptToken: claim.attemptToken });
110
118
  }
111
119
  return new Response(null, { status: 204 });
112
120
  }
@@ -125,8 +133,15 @@ export async function handleTaskResultRequest(context, options, request, url, pr
125
133
  if (validated instanceof Response)
126
134
  return validated;
127
135
  const inflightRecord = await readInflightRecord(options.engine.storage, validated.operationId);
128
- if (inflightRecord !== null && (validated.workerId === void 0 || inflightRecord.workerId !== validated.workerId))
136
+ if (inflightRecord !== null && !isLongPollCompletionAuthorized(inflightRecord, validated))
129
137
  return Response.json({ error: "Forbidden" }, { status: 403 });
130
138
  await applyTaskResult(context, options, validated, inflightRecord);
131
139
  return Response.json({ ok: !0 });
132
140
  }
141
+ function isLongPollCompletionAuthorized(inflightRecord, validated) {
142
+ if (validated.workerId === void 0 || inflightRecord.workerId !== validated.workerId)
143
+ return !1;
144
+ if (inflightRecord.attemptToken !== void 0 && validated.attemptToken !== void 0 && validated.attemptToken !== inflightRecord.attemptToken)
145
+ return !1;
146
+ return !0;
147
+ }
@@ -120,6 +120,14 @@ function onTaskResultMessage(context, options, ws, message, cleanupWorkflowIndex
120
120
  });
121
121
  return;
122
122
  }
123
+ if (!context.registry.isAssignedToAttempt(operationId, workerId, message.attemptToken)) {
124
+ sendWorkerProtocolMessage(ws, {
125
+ type: "protocolError",
126
+ code: "invalid_message",
127
+ message: `taskResult for operation "${operationId}" rejected \u2014 stale attempt token`
128
+ });
129
+ return;
130
+ }
123
131
  context.registry.completeTask(operationId);
124
132
  context.deadlineTracker.remove(operationId);
125
133
  cleanupWorkflowIndex(operationId);
@@ -11,6 +11,14 @@ import type { WebSocketData } from './json-rpc-websocket-runtime.ts';
11
11
  import { createServerWebSocketHandlers } from './runtime/authentication-bridge.ts';
12
12
  import type { ServerContext } from './runtime/context.ts';
13
13
  import { type EventBroadcastingHandle } from './runtime/event-broadcasting.ts';
14
+ /**
15
+ * @internal
16
+ *
17
+ * Warning emitted when a server starts with no authentication and no explicit
18
+ * `unauthenticatedAccess` policy. Exported only for internal tests (see
19
+ * `tests/auth-warning-filter.test.ts`) and not part of the public API surface.
20
+ */
21
+ export declare const NO_AUTHENTICATION_WARNING = "[weft] WARNING: server started with NO authentication; all non-public operations are publicly accessible. Configure serve({ auth }) to lock down, or set unauthenticatedAccess: \"reject\" in production to fail closed.";
14
22
  /**
15
23
  * Clamp a user-supplied `workerReconnectGracePeriodMs` into `[0, 5_000]`.
16
24
  * Returns the default when undefined or non-finite.
@@ -26,7 +26,9 @@ import { reconcileOrphanedRecords, scanExpiredTasks } from "./runtime/task-recon
26
26
  import { isInflightRecord, withRetry } from "./runtime/websocket-worker.js";
27
27
  import { TaskQueue } from "./task-queue.js";
28
28
  import { createWorkflowEventFeed } from "./workflow-event-feed.js";
29
- const RECONCILIATION_MULTIPLIER = 12, DEFAULT_WORKER_RECONNECT_GRACE_PERIOD_MS = 100, MAX_WORKER_RECONNECT_GRACE_PERIOD_MS = 5000, AUTHENTICATION_REQUIRED_ENVIRONMENT_VARIABLE = "WEFT_SERVER_AUTHENTICATION_REQUIRED", NO_AUTHENTICATION_WARNING = '[weft] WARNING: server started with NO authentication; all non-public operations are publicly accessible. Configure serve({ auth }) to lock down, or set unauthenticatedAccess: "reject" in production to fail closed.', NO_AUTHENTICATION_REJECT_ERROR = '[weft] Refusing to start server with no authentication. Configure serve({ auth }) or set unauthenticatedAccess: "allow" only for trusted local development.', NO_AUTHENTICATION_ENVIRONMENT_ERROR = "[weft] Refusing to start server with no authentication because WEFT_SERVER_AUTHENTICATION_REQUIRED requires authentication. Configure serve({ auth }) or unset WEFT_SERVER_AUTHENTICATION_REQUIRED only for trusted local development.", TRUTHY_AUTHENTICATION_REQUIREMENT_VALUES = new Set(["1", "true", "yes", "on"]), FALSY_AUTHENTICATION_REQUIREMENT_VALUES = new Set(["0", "false", "no", "off"]);
29
+ const RECONCILIATION_MULTIPLIER = 12, DEFAULT_WORKER_RECONNECT_GRACE_PERIOD_MS = 100, MAX_WORKER_RECONNECT_GRACE_PERIOD_MS = 5000, AUTHENTICATION_REQUIRED_ENVIRONMENT_VARIABLE = "WEFT_SERVER_AUTHENTICATION_REQUIRED";
30
+ export const NO_AUTHENTICATION_WARNING = '[weft] WARNING: server started with NO authentication; all non-public operations are publicly accessible. Configure serve({ auth }) to lock down, or set unauthenticatedAccess: "reject" in production to fail closed.';
31
+ const NO_AUTHENTICATION_REJECT_ERROR = '[weft] Refusing to start server with no authentication. Configure serve({ auth }) or set unauthenticatedAccess: "allow" only for trusted local development.', NO_AUTHENTICATION_ENVIRONMENT_ERROR = "[weft] Refusing to start server with no authentication because WEFT_SERVER_AUTHENTICATION_REQUIRED requires authentication. Configure serve({ auth }) or unset WEFT_SERVER_AUTHENTICATION_REQUIRED only for trusted local development.", TRUTHY_AUTHENTICATION_REQUIREMENT_VALUES = new Set(["1", "true", "yes", "on"]), FALSY_AUTHENTICATION_REQUIREMENT_VALUES = new Set(["0", "false", "no", "off"]);
30
32
  export function clampWorkerReconnectGracePeriod(value) {
31
33
  if (value === void 0 || !Number.isFinite(value))
32
34
  return DEFAULT_WORKER_RECONNECT_GRACE_PERIOD_MS;
@@ -179,7 +181,7 @@ export function restoreInflightTasks(context, options) {
179
181
  continue;
180
182
  }
181
183
  const remaining = record.deadline - now;
182
- context.registry.assignTask(record.workerId, record.operationId, remaining);
184
+ context.registry.assignTask(record.workerId, record.operationId, remaining, void 0, record.attemptToken);
183
185
  context.deadlineTracker.add({ operationId: record.operationId, deadline: record.deadline });
184
186
  const tracked = context.registry.getWorkerTasks(record.workerId).find((t) => t.operationId === record.operationId);
185
187
  if (tracked)
@@ -67,6 +67,14 @@ export interface InflightRecord extends TaskLifecycleFields {
67
67
  retryPolicy?: RetryPolicy | undefined;
68
68
  /** Workflow that dispatched this activity. Present when the dispatch included a workflowId. */
69
69
  workflowId?: string | undefined;
70
+ /**
71
+ * Unique, unguessable token identifying this dispatch attempt. Rotated on every
72
+ * (re-)dispatch because each dispatch writes a fresh InflightRecord. The
73
+ * long-poll completion handler rejects a result whose echoed token does not
74
+ * match this value. Optional for back-compatible decoding of records written
75
+ * before this field existed; a missing token disables the check for that record.
76
+ */
77
+ attemptToken?: string | undefined;
70
78
  }
71
79
  /** Persisted record for a task in the resolved state. */
72
80
  export interface ResolvedRecord {