@lostgradient/weft 0.18.0 → 0.20.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 (171) hide show
  1. package/README.md +3 -1
  2. package/dist/cli/conformance.js +14 -18
  3. package/dist/cli/generated/operation-catalog.snapshot.json +1597 -21
  4. package/dist/cli/generated/operation-client.generated.d.ts +37 -6
  5. package/dist/cli/generated/operation-client.generated.js +6 -0
  6. package/dist/core/context/child-workflow-pipe.d.ts +1 -1
  7. package/dist/core/context/child-workflow-pipe.js +1 -1
  8. package/dist/core/engine/construction.d.ts +2 -0
  9. package/dist/core/engine/construction.js +3 -0
  10. package/dist/core/engine/index.js +1 -0
  11. package/dist/core/engine/listing.d.ts +1 -0
  12. package/dist/core/engine/listing.js +6 -4
  13. package/dist/core/weft-error.d.ts +1 -1
  14. package/dist/core/weft-error.js +2 -1
  15. package/dist/core/worker-execution-dispatcher.d.ts +8 -0
  16. package/dist/core/worker-execution-dispatcher.js +4 -0
  17. package/dist/core/worker-execution-strategy-options.d.ts +16 -0
  18. package/dist/core/worker-execution-strategy.js +71 -89
  19. package/dist/core/worker-fault-handling.d.ts +45 -0
  20. package/dist/core/worker-fault-handling.js +88 -0
  21. package/dist/core/worker-protocol.d.ts +18 -1
  22. package/dist/core/worker-protocol.js +1 -1
  23. package/dist/core/worker-realm-readiness.d.ts +78 -0
  24. package/dist/core/worker-realm-readiness.js +127 -0
  25. package/dist/index.d.ts +3 -1
  26. package/dist/index.js +28 -1
  27. package/dist/json-schema.js +1 -1
  28. package/dist/runtime/portable.d.ts +14 -0
  29. package/dist/runtime/portable.js +10 -0
  30. package/dist/server/index.d.ts +71 -8
  31. package/dist/server/index.js +7 -3
  32. package/dist/server/operations/get-task-detail-schema.d.ts +341 -0
  33. package/dist/server/operations/get-task-detail-schema.js +118 -0
  34. package/dist/server/operations/get-task-detail.d.ts +346 -0
  35. package/dist/server/operations/get-task-detail.js +215 -0
  36. package/dist/server/operations/get-task-diagnostics.d.ts +43 -18
  37. package/dist/server/operations/get-task-diagnostics.js +87 -126
  38. package/dist/server/operations/get-worker-diagnostics.d.ts +177 -0
  39. package/dist/server/operations/get-worker-diagnostics.js +117 -0
  40. package/dist/server/operations/list-worker-registration-rejections.d.ts +79 -0
  41. package/dist/server/operations/list-worker-registration-rejections.js +60 -0
  42. package/dist/server/operations/list-workers.js +1 -3
  43. package/dist/server/operations/static-registrations.js +8 -20
  44. package/dist/server/rest-bindings.js +31 -2
  45. package/dist/server/runtime/authentication-bridge.js +6 -14
  46. package/dist/server/runtime/context.d.ts +23 -0
  47. package/dist/server/runtime/task-dispatch.d.ts +7 -1
  48. package/dist/server/runtime/task-dispatch.js +126 -55
  49. package/dist/server/runtime/task-ledger-completion.d.ts +68 -0
  50. package/dist/server/runtime/task-ledger-completion.js +72 -0
  51. package/dist/server/runtime/task-ledger-recovery.d.ts +55 -0
  52. package/dist/server/runtime/task-ledger-recovery.js +73 -0
  53. package/dist/server/runtime/task-ledger-runtime.d.ts +64 -0
  54. package/dist/server/runtime/task-ledger-runtime.js +34 -0
  55. package/dist/server/runtime/task-metrics.d.ts +6 -4
  56. package/dist/server/runtime/task-polling.d.ts +14 -12
  57. package/dist/server/runtime/task-polling.js +106 -82
  58. package/dist/server/runtime/task-reconciliation.d.ts +35 -8
  59. package/dist/server/runtime/task-reconciliation.js +75 -74
  60. package/dist/server/runtime/task-result-resolution.d.ts +2 -10
  61. package/dist/server/runtime/task-result-resolution.js +0 -67
  62. package/dist/server/runtime/task-result-view.d.ts +82 -0
  63. package/dist/server/runtime/task-result-view.js +48 -0
  64. package/dist/server/runtime/websocket-worker-messaging.d.ts +16 -0
  65. package/dist/server/runtime/websocket-worker-messaging.js +32 -0
  66. package/dist/server/runtime/websocket-worker-registration.d.ts +41 -0
  67. package/dist/server/runtime/websocket-worker-registration.js +134 -0
  68. package/dist/server/runtime/websocket-worker.d.ts +0 -1
  69. package/dist/server/runtime/websocket-worker.js +64 -116
  70. package/dist/server/serve-internals.d.ts +9 -6
  71. package/dist/server/serve-internals.js +14 -40
  72. package/dist/server/task-ledger-codec.d.ts +40 -0
  73. package/dist/server/task-ledger-codec.js +195 -0
  74. package/dist/server/task-ledger-keys.d.ts +13 -0
  75. package/dist/server/task-ledger-keys.js +5 -0
  76. package/dist/server/task-ledger-limits.d.ts +34 -0
  77. package/dist/server/task-ledger-limits.js +5 -0
  78. package/dist/server/task-ledger-transition-helpers.d.ts +38 -0
  79. package/dist/server/task-ledger-transition-helpers.js +42 -0
  80. package/dist/server/task-ledger-transitions-cancellation.d.ts +31 -0
  81. package/dist/server/task-ledger-transitions-cancellation.js +73 -0
  82. package/dist/server/task-ledger-transitions.d.ts +129 -0
  83. package/dist/server/task-ledger-transitions.js +162 -0
  84. package/dist/server/task-ledger-types.d.ts +220 -0
  85. package/dist/server/task-ledger-types.js +1 -0
  86. package/dist/server/task-ledger.d.ts +17 -0
  87. package/dist/server/task-ledger.js +24 -0
  88. package/dist/server/task-state.d.ts +25 -152
  89. package/dist/server/task-state.js +0 -188
  90. package/dist/server/worker-admission-policy.d.ts +70 -0
  91. package/dist/server/worker-admission-policy.js +0 -0
  92. package/dist/storage/compressed-storage.js +1 -1
  93. package/dist/storage/index.d.ts +0 -2
  94. package/dist/storage/interface.d.ts +0 -2
  95. package/dist/storage/interface.js +1 -1
  96. package/dist/storage/lmdb.js +1 -1
  97. package/dist/storage/memory.js +1 -1
  98. package/dist/storage/neon.js +1 -1
  99. package/dist/storage/postgres.js +1 -1
  100. package/dist/storage/resolve.js +1 -1
  101. package/dist/storage/scoped-storage.js +1 -1
  102. package/dist/storage/testing.js +1 -1
  103. package/dist/storage/turso.js +1 -1
  104. package/dist/storage/web-extension.js +2 -2
  105. package/dist/version.d.ts +1 -1
  106. package/dist/version.js +1 -1
  107. package/dist/web-extension.js +1 -1
  108. package/dist/worker/index.d.ts +2 -0
  109. package/dist/worker/index.js +30 -4
  110. package/dist/worker/manifest/canonical-json.d.ts +20 -0
  111. package/dist/worker/manifest/canonical-json.js +13 -0
  112. package/dist/worker/manifest/capabilities.d.ts +24 -0
  113. package/dist/worker/manifest/capabilities.js +74 -0
  114. package/dist/worker/manifest/content-digest.d.ts +20 -0
  115. package/dist/worker/manifest/content-digest.js +13 -0
  116. package/dist/worker/manifest/declared-shape-digest.d.ts +14 -0
  117. package/dist/worker/manifest/declared-shape-digest.js +4 -0
  118. package/dist/worker/manifest/digest.d.ts +82 -0
  119. package/dist/worker/manifest/digest.js +9 -0
  120. package/dist/worker/manifest/execution-identity.d.ts +92 -0
  121. package/dist/worker/manifest/execution-identity.js +36 -0
  122. package/dist/worker/manifest/failure.d.ts +59 -0
  123. package/dist/worker/manifest/failure.js +4 -0
  124. package/dist/worker/manifest/index.d.ts +22 -0
  125. package/dist/worker/manifest/index.js +23 -0
  126. package/dist/worker/manifest/internal-realm.d.ts +50 -0
  127. package/dist/worker/manifest/internal-realm.js +39 -0
  128. package/dist/worker/manifest/is-record.d.ts +12 -0
  129. package/dist/worker/manifest/is-record.js +6 -0
  130. package/dist/worker/manifest/json-scan.d.ts +26 -0
  131. package/dist/worker/manifest/json-scan.js +88 -0
  132. package/dist/worker/manifest/limits.d.ts +138 -0
  133. package/dist/worker/manifest/limits.js +1 -0
  134. package/dist/worker/manifest/normalize.d.ts +63 -0
  135. package/dist/worker/manifest/normalize.js +73 -0
  136. package/dist/worker/manifest/parse-json.d.ts +24 -0
  137. package/dist/worker/manifest/parse-json.js +15 -0
  138. package/dist/worker/manifest/parse.d.ts +86 -0
  139. package/dist/worker/manifest/parse.js +188 -0
  140. package/dist/worker/manifest/registry-contract-builder.d.ts +120 -0
  141. package/dist/worker/manifest/registry-contract-builder.js +65 -0
  142. package/dist/worker/manifest/types.d.ts +263 -0
  143. package/dist/worker/manifest/types.js +1 -0
  144. package/dist/worker/manifest/utf8.d.ts +13 -0
  145. package/dist/worker/manifest/utf8.js +7 -0
  146. package/dist/worker/options.d.ts +55 -8
  147. package/dist/worker/options.js +65 -9
  148. package/dist/worker/protocol-internals.d.ts +1 -2
  149. package/dist/worker/protocol-internals.js +0 -5
  150. package/dist/worker/protocol-messages.d.ts +33 -17
  151. package/dist/worker/protocol-schemas.d.ts +36 -74
  152. package/dist/worker/protocol-schemas.js +25 -18
  153. package/dist/worker/protocol-version.d.ts +18 -14
  154. package/dist/worker/protocol-version.js +1 -1
  155. package/dist/worker/protocol.js +1 -1
  156. package/dist/worker/registry/deployment-consistency.d.ts +78 -0
  157. package/dist/worker/registry/deployment-consistency.js +27 -0
  158. package/dist/worker/registry/drain.d.ts +14 -0
  159. package/dist/worker/registry/drain.js +24 -0
  160. package/dist/worker/registry/rejections.d.ts +31 -0
  161. package/dist/worker/registry/rejections.js +10 -0
  162. package/dist/worker/registry/summary.d.ts +0 -3
  163. package/dist/worker/registry/summary.js +2 -5
  164. package/dist/worker/registry/types.d.ts +10 -3
  165. package/dist/worker/registry.d.ts +47 -2
  166. package/dist/worker/registry.js +45 -32
  167. package/dist/workers/workflow-worker-entry.d.ts +12 -7
  168. package/dist/workers/workflow-worker-entry.js +9 -2
  169. package/package.json +3 -3
  170. package/dist/server/task-resolved-record.d.ts +0 -12
  171. package/dist/server/task-resolved-record.js +0 -35
@@ -0,0 +1,195 @@
1
+ import { decode, encode } from "../core/codec.js";
2
+ import { isJSONValue } from "../core/json.js";
3
+ import {
4
+ MAX_TASK_HEADER_COUNT,
5
+ MAX_TASK_HEADER_VALUE_BYTES,
6
+ MAX_TASK_IDENTIFIER_BYTES,
7
+ MAX_TASK_REASON_BYTES,
8
+ MAX_TASK_RESULT_DIGEST_BYTES,
9
+ utf8ByteLength
10
+ } from "./task-ledger-limits.js";
11
+ function isRecord(value) {
12
+ return typeof value === "object" && value !== null && !Array.isArray(value);
13
+ }
14
+ function isBoundedIdentifier(value, maxBytes = MAX_TASK_IDENTIFIER_BYTES) {
15
+ return typeof value === "string" && value.length > 0 && utf8ByteLength(value) <= maxBytes;
16
+ }
17
+ function isBoundedOptionalIdentifier(value, maxBytes = MAX_TASK_IDENTIFIER_BYTES) {
18
+ return value === void 0 || isBoundedIdentifier(value, maxBytes);
19
+ }
20
+ function isBoundedReason(value) {
21
+ return typeof value === "string" && value.length > 0 && utf8ByteLength(value) <= MAX_TASK_REASON_BYTES;
22
+ }
23
+ function isFiniteNumber(value) {
24
+ return typeof value === "number" && Number.isFinite(value);
25
+ }
26
+ function isOptionalFiniteNumber(value) {
27
+ return value === void 0 || isFiniteNumber(value);
28
+ }
29
+ function isOptionalBoundedReason(value) {
30
+ return value === void 0 || isBoundedReason(value);
31
+ }
32
+ function isCompletedOrFailedStatus(value) {
33
+ return value === "completed" || value === "failed";
34
+ }
35
+ function isOptionalJSONValue(value) {
36
+ return value === void 0 || isJSONValue(value);
37
+ }
38
+ function isDurationLike(value) {
39
+ return typeof value === "number" || typeof value === "string";
40
+ }
41
+ export function isValidTaskHeaders(value) {
42
+ if (!isRecord(value))
43
+ return !1;
44
+ const entries = Object.entries(value);
45
+ if (entries.length > MAX_TASK_HEADER_COUNT)
46
+ return !1;
47
+ return entries.every(([key, headerValue]) => utf8ByteLength(key) <= MAX_TASK_HEADER_VALUE_BYTES && typeof headerValue === "string" && utf8ByteLength(headerValue) <= MAX_TASK_HEADER_VALUE_BYTES);
48
+ }
49
+ function isValidNonRetryableErrors(value) {
50
+ return value === void 0 || Array.isArray(value) && value.every((entry) => typeof entry === "string");
51
+ }
52
+ function isValidRetryPolicy(value) {
53
+ if (value === void 0)
54
+ return !0;
55
+ if (!isRecord(value))
56
+ return !1;
57
+ return isFiniteNumber(value.maxAttempts) && isDurationLike(value.initialBackoff) && isFiniteNumber(value.backoffMultiplier) && isDurationLike(value.maxBackoff) && isValidNonRetryableErrors(value.nonRetryableErrors);
58
+ }
59
+ function isValidExecutionRequirement(value) {
60
+ if (value === void 0)
61
+ return !0;
62
+ if (!isRecord(value))
63
+ return !1;
64
+ return isBoundedOptionalIdentifier(value.deploymentName) && isBoundedOptionalIdentifier(value.buildId) && isBoundedOptionalIdentifier(value.artifactDigest) && isBoundedOptionalIdentifier(value.workflowRevision) && isBoundedOptionalIdentifier(value.activityContractHash);
65
+ }
66
+ const EXECUTION_IDENTITY_STRING_FIELDS = [
67
+ "workerId",
68
+ "manifestDigest",
69
+ "sdkVersion",
70
+ "runtimeName",
71
+ "runtimeVersion",
72
+ "deploymentName",
73
+ "buildId",
74
+ "artifactDigest",
75
+ "workflowType",
76
+ "workflowRevision",
77
+ "activityName",
78
+ "activityContractHash"
79
+ ];
80
+ function isValidExecutionIdentity(value) {
81
+ if (!isRecord(value))
82
+ return !1;
83
+ return EXECUTION_IDENTITY_STRING_FIELDS.every((field) => isBoundedIdentifier(value[field])) && isFiniteNumber(value.protocolVersion);
84
+ }
85
+ function isOptionalExecutionIdentity(value) {
86
+ return value === void 0 || isValidExecutionIdentity(value);
87
+ }
88
+ function isValidTaskIdentityFields(value) {
89
+ return value.recordVersion === 1 && isBoundedIdentifier(value.operationId) && isBoundedOptionalIdentifier(value.workflowId) && isBoundedIdentifier(value.workflowType) && isBoundedOptionalIdentifier(value.workflowExecutionToken) && isBoundedIdentifier(value.activityName) && isBoundedIdentifier(value.queue);
90
+ }
91
+ function isValidTaskRoutingFields(value) {
92
+ return isOptionalFiniteNumber(value.priority) && isBoundedOptionalIdentifier(value.fairShareKey) && isBoundedOptionalIdentifier(value.stickyWorkflowId) && isValidExecutionRequirement(value.executionRequirement);
93
+ }
94
+ function isValidTaskPayloadFields(value) {
95
+ return isJSONValue(value.input) && isValidTaskHeaders(value.headers) && isValidTaskRoutingFields(value) && isFiniteNumber(value.visibilityTimeoutMilliseconds) && isValidRetryPolicy(value.retryPolicy) && isOptionalFiniteNumber(value.scheduleToCloseDeadline) && isFiniteNumber(value.createdAt) && isFiniteNumber(value.generation);
96
+ }
97
+ function isValidTaskBase(value) {
98
+ return isValidTaskIdentityFields(value) && isValidTaskPayloadFields(value);
99
+ }
100
+ function isValidAttemptFields(value) {
101
+ return isFiniteNumber(value.retryCount) && isFiniteNumber(value.requeueCount) && (value.lastRequeueReason === void 0 || isBoundedReason(value.lastRequeueReason));
102
+ }
103
+ function isValidLeaseHolderShape(value) {
104
+ return isValidAttemptFields(value) && isBoundedIdentifier(value.attemptToken) && isBoundedIdentifier(value.workerSessionId) && isOptionalExecutionIdentity(value.executionIdentity) && isFiniteNumber(value.attempt) && isFiniteNumber(value.leaseDeadline) && isFiniteNumber(value.firstQueuedAt) && isFiniteNumber(value.lastQueuedAt) && isFiniteNumber(value.startedAt) && isFiniteNumber(value.lastHeartbeatAt);
105
+ }
106
+ function isValidQueuedTimingFields(value) {
107
+ return isFiniteNumber(value.attempt) && isFiniteNumber(value.availableAt) && isFiniteNumber(value.firstQueuedAt) && isFiniteNumber(value.lastQueuedAt) && isOptionalFiniteNumber(value.lastDispatchedAt) && isOptionalFiniteNumber(value.startedAt);
108
+ }
109
+ export function isRemoteTaskQueued(value) {
110
+ if (!isRecord(value) || value.state !== "queued")
111
+ return !1;
112
+ return isValidTaskBase(value) && isValidAttemptFields(value) && isValidQueuedTimingFields(value);
113
+ }
114
+ export function isRemoteTaskLeased(value) {
115
+ if (!isRecord(value) || value.state !== "leased" || !isValidTaskBase(value))
116
+ return !1;
117
+ return isValidLeaseHolderShape(value);
118
+ }
119
+ export function isRemoteTaskCompleting(value) {
120
+ if (!isRecord(value) || value.state !== "completing")
121
+ return !1;
122
+ return isValidTaskBase(value) && isValidLeaseHolderShape(value) && isCompletedOrFailedStatus(value.pendingStatus) && isBoundedIdentifier(value.pendingResultDigest);
123
+ }
124
+ export function isRemoteTaskCancelling(value) {
125
+ if (!isRecord(value) || value.state !== "cancelling" || !isValidTaskBase(value))
126
+ return !1;
127
+ return isValidLeaseHolderShape(value) && isBoundedReason(value.cancellationReason) && isFiniteNumber(value.cancellationRequestedAt);
128
+ }
129
+ function isValidTerminalCommon(value) {
130
+ return isFiniteNumber(value.attempt) && isBoundedIdentifier(value.resultDigest, MAX_TASK_RESULT_DIGEST_BYTES) && isFiniteNumber(value.terminalAt) && typeof value.adopted === "boolean" && isOptionalFiniteNumber(value.adoptedAt) && isFiniteNumber(value.retentionGeneration);
131
+ }
132
+ function isTerminalDisposition(value, disposition) {
133
+ return value.state === "terminal" && value.disposition === disposition;
134
+ }
135
+ export function isRemoteTaskTerminalResolved(value) {
136
+ if (!isRecord(value) || !isTerminalDisposition(value, "resolved"))
137
+ return !1;
138
+ if (!isValidTaskBase(value) || !isValidTerminalCommon(value))
139
+ return !1;
140
+ return isBoundedIdentifier(value.attemptToken) && isCompletedOrFailedStatus(value.status) && isOptionalBoundedReason(value.error);
141
+ }
142
+ export function isRemoteTaskTerminalCancelled(value) {
143
+ if (!isRecord(value) || !isTerminalDisposition(value, "cancelled"))
144
+ return !1;
145
+ if (!isValidTaskBase(value) || !isValidTerminalCommon(value))
146
+ return !1;
147
+ return isBoundedOptionalIdentifier(value.attemptToken) && isBoundedReason(value.cancellationReason);
148
+ }
149
+ export function isRemoteTaskTerminalRetryExhausted(value) {
150
+ if (!isRecord(value) || !isTerminalDisposition(value, "retryExhausted"))
151
+ return !1;
152
+ if (!isValidTaskBase(value) || !isValidTerminalCommon(value))
153
+ return !1;
154
+ return isBoundedIdentifier(value.attemptToken) && isBoundedReason(value.error);
155
+ }
156
+ export function isRemoteTaskTerminal(value) {
157
+ return isRemoteTaskTerminalResolved(value) || isRemoteTaskTerminalCancelled(value) || isRemoteTaskTerminalRetryExhausted(value);
158
+ }
159
+ function isValidDeadLetteredResultFields(value) {
160
+ return isBoundedIdentifier(value.attemptToken) && isFiniteNumber(value.attempt) && isCompletedOrFailedStatus(value.pendingStatus) && isBoundedIdentifier(value.pendingResultDigest) && isOptionalJSONValue(value.value) && isOptionalBoundedReason(value.error);
161
+ }
162
+ export function isRemoteTaskDeadLettered(value) {
163
+ if (!isRecord(value) || value.state !== "deadLettered")
164
+ return !1;
165
+ return isValidTaskBase(value) && isValidAttemptFields(value) && isValidDeadLetteredResultFields(value) && isFiniteNumber(value.deadLetteredAt) && isBoundedReason(value.persistenceFailureReason);
166
+ }
167
+ export function isRemoteTaskRecord(value) {
168
+ if (!isRecord(value) || typeof value.state !== "string")
169
+ return !1;
170
+ switch (value.state) {
171
+ case "queued":
172
+ return isRemoteTaskQueued(value);
173
+ case "leased":
174
+ return isRemoteTaskLeased(value);
175
+ case "completing":
176
+ return isRemoteTaskCompleting(value);
177
+ case "cancelling":
178
+ return isRemoteTaskCancelling(value);
179
+ case "terminal":
180
+ return isRemoteTaskTerminal(value);
181
+ case "deadLettered":
182
+ return isRemoteTaskDeadLettered(value);
183
+ default:
184
+ return !1;
185
+ }
186
+ }
187
+ export function encodeRemoteTaskRecord(record) {
188
+ return encode(record);
189
+ }
190
+ export function decodeRemoteTaskRecord(bytes) {
191
+ if (bytes === null)
192
+ return null;
193
+ const decoded = decode(bytes);
194
+ return isRemoteTaskRecord(decoded) ? decoded : null;
195
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Canonical storage key for the durable remote task ledger (WFT-25).
3
+ *
4
+ * One current-state key is authoritative per operation. `operationId` is
5
+ * caller-controlled (it flows from `TaskDispatch.operationId`), so it is
6
+ * encoded through {@link encodeStorageKeyComponent} the same way every other
7
+ * hostile key component in this codebase is — see `signalStorageKey` in
8
+ * `storage/interface.ts` for the established precedent.
9
+ *
10
+ * @module server/task-ledger-keys
11
+ */
12
+ /** The single authoritative current-state key for one operation's task record. */
13
+ export declare function taskLedgerKey(operationId: string): string;
@@ -0,0 +1,5 @@
1
+ import { encodeStorageKeyComponent } from "../storage/interface.js";
2
+ const TASK_LEDGER_KEY_PREFIX = "task-ledger:";
3
+ export function taskLedgerKey(operationId) {
4
+ return `${TASK_LEDGER_KEY_PREFIX}${encodeStorageKeyComponent(operationId)}`;
5
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Semantic bounds applied to a {@link RemoteTaskRecord} before it is decoded
3
+ * or admitted to storage (WFT-25).
4
+ *
5
+ * These bound the *semantic* shape a persisted or wire-derived task record
6
+ * may assert — operation/workflow identity strings, header counts and sizes,
7
+ * retry fields — independent of any transport-level frame ceiling. Server
8
+ * policy brief: "Bound operation IDs, workflow IDs, names, queues, header
9
+ * counts, header bytes, retry fields, and payload sizes before storage
10
+ * writes."
11
+ *
12
+ * @module server/task-ledger-limits
13
+ */
14
+ /** UTF-8 byte length of `value`. */
15
+ export declare function utf8ByteLength(value: string): number;
16
+ /** Maximum UTF-8 byte length of any single identifier-shaped ledger string. */
17
+ export declare const MAX_TASK_IDENTIFIER_BYTES = 512;
18
+ /** Maximum number of header entries a single task record may carry. */
19
+ export declare const MAX_TASK_HEADER_COUNT = 64;
20
+ /** Maximum UTF-8 byte length of a single header name or value. */
21
+ export declare const MAX_TASK_HEADER_VALUE_BYTES = 4096;
22
+ /** Maximum UTF-8 byte length of a single free-text reason/error string. */
23
+ export declare const MAX_TASK_REASON_BYTES = 4096;
24
+ /**
25
+ * Maximum UTF-8 byte length of a terminal record's `resultDigest`.
26
+ *
27
+ * Wider than {@link MAX_TASK_IDENTIFIER_BYTES} because the ledger's own
28
+ * cancellation and retry-exhaustion transitions synthesize this field by
29
+ * concatenating an `operationId` and an `attemptToken` — each independently
30
+ * bounded to {@link MAX_TASK_IDENTIFIER_BYTES} — plus a short literal prefix.
31
+ * A tighter bound here would let two individually-valid identifiers combine
32
+ * into a `resultDigest` the codec's own decoder then rejects.
33
+ */
34
+ export declare const MAX_TASK_RESULT_DIGEST_BYTES: number;
@@ -0,0 +1,5 @@
1
+ const textEncoder = new TextEncoder;
2
+ export function utf8ByteLength(value) {
3
+ return textEncoder.encode(value).byteLength;
4
+ }
5
+ export const MAX_TASK_IDENTIFIER_BYTES = 512, MAX_TASK_HEADER_COUNT = 64, MAX_TASK_HEADER_VALUE_BYTES = 4096, MAX_TASK_REASON_BYTES = 4096, MAX_TASK_RESULT_DIGEST_BYTES = 2 * MAX_TASK_IDENTIFIER_BYTES + 64;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Shared result types and field pickers for the durable remote task ledger's
3
+ * pure conditional-transition functions (WFT-25) — split out of
4
+ * `task-ledger-transitions.ts` / `task-ledger-transitions-cancellation.ts`
5
+ * purely to keep both under this repository's file-size ceiling; there is no
6
+ * behavioral reason to import this module directly instead of one of those.
7
+ *
8
+ * @module server/task-ledger-transition-helpers
9
+ */
10
+ import type { WorkerExecutionIdentity } from '../worker/manifest/types.ts';
11
+ import type { RemoteTaskAttemptFields, RemoteTaskBase } from './task-ledger-types.ts';
12
+ export type TaskLedgerTransitionResult<T> = Readonly<{
13
+ ok: true;
14
+ nextRecord: T;
15
+ }> | Readonly<{
16
+ ok: false;
17
+ reason: string;
18
+ }>;
19
+ export type TaskLedgerPreconditionResult = Readonly<{
20
+ ok: true;
21
+ }> | Readonly<{
22
+ ok: false;
23
+ reason: string;
24
+ }>;
25
+ export declare function pickBase(record: RemoteTaskBase): RemoteTaskBase;
26
+ export declare function pickAttemptFields(record: RemoteTaskAttemptFields): RemoteTaskAttemptFields;
27
+ export type LeaseHolderFields = Readonly<{
28
+ attemptToken: string;
29
+ workerSessionId: string;
30
+ executionIdentity?: WorkerExecutionIdentity;
31
+ attempt: number;
32
+ leaseDeadline: number;
33
+ firstQueuedAt: number;
34
+ lastQueuedAt: number;
35
+ startedAt: number;
36
+ lastHeartbeatAt: number;
37
+ }>;
38
+ export declare function pickLeaseHolderFields(record: LeaseHolderFields): LeaseHolderFields;
@@ -0,0 +1,42 @@
1
+ export function pickBase(record) {
2
+ return {
3
+ recordVersion: record.recordVersion,
4
+ operationId: record.operationId,
5
+ ...record.workflowId !== void 0 ? { workflowId: record.workflowId } : {},
6
+ workflowType: record.workflowType,
7
+ ...record.workflowExecutionToken !== void 0 ? { workflowExecutionToken: record.workflowExecutionToken } : {},
8
+ activityName: record.activityName,
9
+ queue: record.queue,
10
+ input: record.input,
11
+ headers: record.headers,
12
+ ...record.priority !== void 0 ? { priority: record.priority } : {},
13
+ ...record.fairShareKey !== void 0 ? { fairShareKey: record.fairShareKey } : {},
14
+ ...record.stickyWorkflowId !== void 0 ? { stickyWorkflowId: record.stickyWorkflowId } : {},
15
+ visibilityTimeoutMilliseconds: record.visibilityTimeoutMilliseconds,
16
+ ...record.retryPolicy !== void 0 ? { retryPolicy: record.retryPolicy } : {},
17
+ ...record.scheduleToCloseDeadline !== void 0 ? { scheduleToCloseDeadline: record.scheduleToCloseDeadline } : {},
18
+ ...record.executionRequirement !== void 0 ? { executionRequirement: record.executionRequirement } : {},
19
+ createdAt: record.createdAt,
20
+ generation: record.generation
21
+ };
22
+ }
23
+ export function pickAttemptFields(record) {
24
+ return {
25
+ retryCount: record.retryCount,
26
+ requeueCount: record.requeueCount,
27
+ ...record.lastRequeueReason !== void 0 ? { lastRequeueReason: record.lastRequeueReason } : {}
28
+ };
29
+ }
30
+ export function pickLeaseHolderFields(record) {
31
+ return {
32
+ attemptToken: record.attemptToken,
33
+ workerSessionId: record.workerSessionId,
34
+ ...record.executionIdentity !== void 0 ? { executionIdentity: record.executionIdentity } : {},
35
+ attempt: record.attempt,
36
+ leaseDeadline: record.leaseDeadline,
37
+ firstQueuedAt: record.firstQueuedAt,
38
+ lastQueuedAt: record.lastQueuedAt,
39
+ startedAt: record.startedAt,
40
+ lastHeartbeatAt: record.lastHeartbeatAt
41
+ };
42
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Cancellation and terminal-adoption transitions for the durable remote task
3
+ * ledger (WFT-25) — rows 7-10 of the project brief's "Conditional transition
4
+ * contract" table. Split out of `task-ledger-transitions.ts` purely to keep
5
+ * both files under this repository's file-size ceiling; re-exported from
6
+ * `task-ledger-transitions.ts` so callers have one import surface. See that
7
+ * module's doc comment for the shared design notes (pure, storage-agnostic,
8
+ * `expectedValue` bytes are the caller's responsibility).
9
+ *
10
+ * @module server/task-ledger-transitions-cancellation
11
+ */
12
+ import { type TaskLedgerPreconditionResult, type TaskLedgerTransitionResult } from './task-ledger-transition-helpers.ts';
13
+ import type { RemoteTaskCancelling, RemoteTaskRecord, RemoteTaskTerminal, RemoteTaskTerminalCancelled } from './task-ledger-types.ts';
14
+ export type RecordCancellationIntentInput = Readonly<{
15
+ expectedGeneration: number;
16
+ expectedAttempt: number;
17
+ cancellationReason: string;
18
+ }>;
19
+ export declare function recordCancellationIntent(current: RemoteTaskRecord | null, input: RecordCancellationIntentInput, now: number): TaskLedgerTransitionResult<RemoteTaskCancelling | RemoteTaskTerminalCancelled>;
20
+ export type CommitCancellationInput = Readonly<{
21
+ attemptToken: string;
22
+ }>;
23
+ export declare function commitCancellation(current: RemoteTaskRecord | null, input: CommitCancellationInput, now: number): TaskLedgerTransitionResult<RemoteTaskTerminalCancelled>;
24
+ export type MarkWorkflowResultAdoptedInput = Readonly<{
25
+ expectedResultDigest: string;
26
+ }>;
27
+ export declare function markWorkflowResultAdopted(current: RemoteTaskRecord | null, input: MarkWorkflowResultAdoptedInput, now: number): TaskLedgerTransitionResult<RemoteTaskTerminal>;
28
+ export type DeleteRetainedTerminalTaskInput = Readonly<{
29
+ expectedRetentionGeneration: number;
30
+ }>;
31
+ export declare function canDeleteRetainedTerminalTask(current: RemoteTaskRecord | null, input: DeleteRetainedTerminalTaskInput): TaskLedgerPreconditionResult;
@@ -0,0 +1,73 @@
1
+ import {
2
+ pickAttemptFields,
3
+ pickBase,
4
+ pickLeaseHolderFields
5
+ } from "./task-ledger-transition-helpers.js";
6
+ export function recordCancellationIntent(current, input, now) {
7
+ if (current === null || current.state !== "queued" && current.state !== "leased")
8
+ return { ok: !1, reason: 'expected task state "queued" or "leased"' };
9
+ if (current.generation !== input.expectedGeneration || current.attempt !== input.expectedAttempt)
10
+ return { ok: !1, reason: "generation or attempt mismatch" };
11
+ if (current.state === "queued")
12
+ return { ok: !0, nextRecord: {
13
+ ...pickBase(current),
14
+ generation: current.generation + 1,
15
+ state: "terminal",
16
+ disposition: "cancelled",
17
+ attempt: current.attempt,
18
+ cancellationReason: input.cancellationReason,
19
+ resultDigest: `cancelled:${current.operationId}:${String(current.generation)}`,
20
+ terminalAt: now,
21
+ adopted: !1,
22
+ retentionGeneration: 0
23
+ } };
24
+ return { ok: !0, nextRecord: {
25
+ ...pickBase(current),
26
+ ...pickAttemptFields(current),
27
+ ...pickLeaseHolderFields(current),
28
+ generation: current.generation + 1,
29
+ state: "cancelling",
30
+ cancellationReason: input.cancellationReason,
31
+ cancellationRequestedAt: now
32
+ } };
33
+ }
34
+ export function commitCancellation(current, input, now) {
35
+ if (current === null || current.state !== "cancelling")
36
+ return { ok: !1, reason: 'expected task state "cancelling"' };
37
+ if (current.attemptToken !== input.attemptToken)
38
+ return { ok: !1, reason: "attempt token mismatch" };
39
+ return { ok: !0, nextRecord: {
40
+ ...pickBase(current),
41
+ generation: current.generation + 1,
42
+ state: "terminal",
43
+ disposition: "cancelled",
44
+ attempt: current.attempt,
45
+ attemptToken: current.attemptToken,
46
+ cancellationReason: current.cancellationReason,
47
+ resultDigest: `cancelled:${current.operationId}:${current.attemptToken}`,
48
+ terminalAt: now,
49
+ adopted: !1,
50
+ retentionGeneration: 0
51
+ } };
52
+ }
53
+ export function markWorkflowResultAdopted(current, input, now) {
54
+ if (current === null || current.state !== "terminal")
55
+ return { ok: !1, reason: 'expected task state "terminal"' };
56
+ if (current.resultDigest !== input.expectedResultDigest)
57
+ return { ok: !1, reason: "result digest mismatch" };
58
+ return { ok: !0, nextRecord: {
59
+ ...current,
60
+ generation: current.generation + 1,
61
+ adopted: !0,
62
+ adoptedAt: now
63
+ } };
64
+ }
65
+ export function canDeleteRetainedTerminalTask(current, input) {
66
+ if (current === null || current.state !== "terminal")
67
+ return { ok: !1, reason: 'expected task state "terminal"' };
68
+ if (!current.adopted)
69
+ return { ok: !1, reason: "terminal task is not yet adopted" };
70
+ if (current.retentionGeneration !== input.expectedRetentionGeneration)
71
+ return { ok: !1, reason: "retention generation mismatch" };
72
+ return { ok: !0 };
73
+ }
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Pure conditional-transition precondition functions for the durable remote
3
+ * task ledger (WFT-25) — one function per row of the project brief's
4
+ * "Conditional transition contract" table.
5
+ *
6
+ * These functions are storage-agnostic by design: they take the currently
7
+ * decoded {@link RemoteTaskRecord} (or `null` when no record exists) and
8
+ * proposed inputs, and return either the next record to persist or a
9
+ * rejection reason. They never read or write storage, and they never see raw
10
+ * encoded bytes.
11
+ *
12
+ * That last point is deliberate. `storage.conditionalBatch`'s
13
+ * `ConditionalBatchCondition.expectedValue` is whole-value byte equality —
14
+ * `encode(decode(bytes))` is not guaranteed to reproduce the exact original
15
+ * bytes. The caller that actually commits a transition (the "Queue and Claim
16
+ * Coordinator" work, WFT-22) must keep the raw `Uint8Array` it read from
17
+ * storage and pass those exact bytes as the CAS `expectedValue`, using the
18
+ * `nextRecord` this module returns only for what to write, never for what to
19
+ * compare against.
20
+ *
21
+ * Twelve rows are covered. This module holds rows 1-6 (create through
22
+ * requeue) plus two WFT-24 additions at the end: `Completing --> DeadLettered`
23
+ * (dead-letter creation on exhausted result-persistence retries) and "Clear
24
+ * dead letter" (an operator discarding a dead-lettered diagnostic). Both
25
+ * appear in the state diagram but not the original ten-row
26
+ * transition-contract table — WFT-25 deliberately left them out as WFT-24
27
+ * ("Adoption, retention, and diagnostics") scope; see `commitDeadLetter`'s
28
+ * own doc comment for why they belong beside `commitTerminalResult` rather
29
+ * than in the cancellation file. Rows 7-10 (cancellation and terminal
30
+ * adoption) live in `task-ledger-transitions-cancellation.ts` and are
31
+ * re-exported below — the split exists only to keep both files under this
32
+ * repository's file-size ceiling.
33
+ *
34
+ * @module server/task-ledger-transitions
35
+ */
36
+ import type { JSONValue } from '../core/json.ts';
37
+ import type { WorkerExecutionIdentity } from '../worker/manifest/types.ts';
38
+ import { type TaskLedgerPreconditionResult, type TaskLedgerTransitionResult } from './task-ledger-transition-helpers.ts';
39
+ import type { RemoteTaskBase, RemoteTaskCompleting, RemoteTaskDeadLettered, RemoteTaskLeased, RemoteTaskQueued, RemoteTaskRecord, RemoteTaskTerminal, RemoteTaskTerminalRetryExhausted } from './task-ledger-types.ts';
40
+ export type { TaskLedgerPreconditionResult, TaskLedgerTransitionResult, } from './task-ledger-transition-helpers.ts';
41
+ export { canDeleteRetainedTerminalTask, commitCancellation, markWorkflowResultAdopted, recordCancellationIntent, type CommitCancellationInput, type DeleteRetainedTerminalTaskInput, type MarkWorkflowResultAdoptedInput, type RecordCancellationIntentInput, } from './task-ledger-transitions-cancellation.ts';
42
+ export type CreateQueuedInput = Omit<RemoteTaskBase, 'generation'> & Readonly<{
43
+ availableAt?: number;
44
+ }>;
45
+ export declare function createQueued(current: RemoteTaskRecord | null, input: CreateQueuedInput, now: number): TaskLedgerTransitionResult<RemoteTaskQueued>;
46
+ export type ClaimQueuedInput = Readonly<{
47
+ expectedGeneration: number;
48
+ attemptToken: string;
49
+ workerSessionId: string;
50
+ /** Omitted when the claiming worker has no registered manifest entry for this workflowType/activityName — see `RemoteTaskLeased.executionIdentity`'s doc comment. */
51
+ executionIdentity?: WorkerExecutionIdentity;
52
+ leaseDurationMilliseconds: number;
53
+ }>;
54
+ export declare function claimQueued(current: RemoteTaskRecord | null, input: ClaimQueuedInput, now: number): TaskLedgerTransitionResult<RemoteTaskLeased>;
55
+ export type RenewAttemptLeaseInput = Readonly<{
56
+ attemptToken: string;
57
+ workerSessionId: string;
58
+ leaseDurationMilliseconds: number;
59
+ }>;
60
+ export declare function renewAttemptLease(current: RemoteTaskRecord | null, input: RenewAttemptLeaseInput, now: number): TaskLedgerTransitionResult<RemoteTaskLeased>;
61
+ export type BeginCompletionInput = Readonly<{
62
+ attemptToken: string;
63
+ pendingStatus: 'completed' | 'failed';
64
+ pendingResultDigest: string;
65
+ }>;
66
+ export declare function beginCompletion(current: RemoteTaskRecord | null, input: BeginCompletionInput): TaskLedgerTransitionResult<RemoteTaskCompleting>;
67
+ export type CommitTerminalResultInput = Readonly<{
68
+ attemptToken: string;
69
+ resultDigest: string;
70
+ error?: string;
71
+ }>;
72
+ export declare function commitTerminalResult(current: RemoteTaskRecord | null, input: CommitTerminalResultInput, now: number): TaskLedgerTransitionResult<RemoteTaskTerminal>;
73
+ export type RequeueExpiredAttemptInput = Readonly<{
74
+ attemptToken: string;
75
+ requeueReason: string;
76
+ /**
77
+ * Skip the lease-deadline precondition. Worker disconnect forfeits the
78
+ * lease immediately rather than waiting for it to expire — the brief's
79
+ * "Disconnect races heartbeat -> One attempt-token transition wins" licenses
80
+ * a requeue here as long as the attempt token still matches, independent of
81
+ * whether the deadline has actually passed. Visibility-timeout-origin
82
+ * requeues never set this, since expiry genuinely requires the deadline
83
+ * precondition.
84
+ */
85
+ skipDeadlineCheck?: boolean;
86
+ }>;
87
+ export declare function requeueExpiredAttempt(current: RemoteTaskRecord | null, input: RequeueExpiredAttemptInput, now: number): TaskLedgerTransitionResult<RemoteTaskQueued | RemoteTaskTerminalRetryExhausted>;
88
+ /**
89
+ * Terminal-adjacent, not terminal: a `deadLettered` record is not a
90
+ * `RemoteTaskTerminal` disposition. `commitTaskLedgerCompletion`
91
+ * (`server/runtime/task-ledger-completion.ts`) reaches this only after
92
+ * `beginCompletion` already landed the record durably in `completing` (or
93
+ * found it already there) and the *second* write — `commitTerminalResult` —
94
+ * exhausted its own CAS retry budget. That is a genuine, sustained storage
95
+ * write failure on this operation specifically, not a benign lost race
96
+ * against a legitimate concurrent writer: `commitTerminalResult`'s own
97
+ * precondition (state `completing`, matching attempt token and result
98
+ * digest) already guards against clobbering a result some other writer
99
+ * legitimately resolved out from under this one, so a caller reaching this
100
+ * transition has already confirmed, moments earlier, that nothing else
101
+ * should have been able to land instead.
102
+ *
103
+ * Preconditions mirror `commitTerminalResult`'s exactly (state, attempt
104
+ * token, result digest) rather than trusting the caller's exhausted-retry
105
+ * report at face value — a fresh read here could observe that some other
106
+ * writer *did* land a terminal result in the interim (e.g. the worker's own
107
+ * retried submission, resumed through `commitTaskLedgerCompletion`'s
108
+ * `resuming` branch, racing ahead of the exhausted attempt), in which case
109
+ * this correctly rejects rather than dead-lettering an already-resolved
110
+ * task.
111
+ */
112
+ export type CommitDeadLetterInput = Readonly<{
113
+ attemptToken: string;
114
+ resultDigest: string;
115
+ value?: JSONValue;
116
+ error?: string;
117
+ persistenceFailureReason: string;
118
+ }>;
119
+ export declare function commitDeadLetter(current: RemoteTaskRecord | null, input: CommitDeadLetterInput, now: number): TaskLedgerTransitionResult<RemoteTaskDeadLettered>;
120
+ /**
121
+ * Precondition for `weft.tasks.diagnostics.deadletters.clear`
122
+ * (`server/operations/get-task-diagnostics.ts`): an operator has seen the
123
+ * dead-lettered diagnostic and is discarding it, freeing the operationId
124
+ * for reuse (a later `createQueued` for the same operationId is otherwise
125
+ * rejected — its own precondition requires the current key be absent).
126
+ * Precondition-only, like `canDeleteRetainedTerminalTask` — the caller
127
+ * issues the delete.
128
+ */
129
+ export declare function canClearDeadLetteredTask(current: RemoteTaskRecord | null): TaskLedgerPreconditionResult;