@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,162 @@
1
+ import { calculateBackoff } from "../core/scheduler.js";
2
+ import {
3
+ pickAttemptFields,
4
+ pickBase,
5
+ pickLeaseHolderFields
6
+ } from "./task-ledger-transition-helpers.js";
7
+ export {
8
+ canDeleteRetainedTerminalTask,
9
+ commitCancellation,
10
+ markWorkflowResultAdopted,
11
+ recordCancellationIntent
12
+ } from "./task-ledger-transitions-cancellation.js";
13
+ export function createQueued(current, input, now) {
14
+ if (current !== null)
15
+ return {
16
+ ok: !1,
17
+ reason: `operation "${input.operationId}" already has a task record in state "${current.state}"`
18
+ };
19
+ return { ok: !0, nextRecord: {
20
+ ...pickBase({ ...input, generation: 0 }),
21
+ state: "queued",
22
+ attempt: 1,
23
+ availableAt: input.availableAt ?? now,
24
+ firstQueuedAt: now,
25
+ lastQueuedAt: now,
26
+ retryCount: 0,
27
+ requeueCount: 0
28
+ } };
29
+ }
30
+ export function claimQueued(current, input, now) {
31
+ if (current === null || current.state !== "queued")
32
+ return { ok: !1, reason: 'expected task state "queued"' };
33
+ if (current.generation !== input.expectedGeneration)
34
+ return { ok: !1, reason: "generation mismatch" };
35
+ if (current.availableAt > now)
36
+ return { ok: !1, reason: "task is not yet available" };
37
+ return { ok: !0, nextRecord: {
38
+ ...pickBase(current),
39
+ ...pickAttemptFields(current),
40
+ generation: current.generation + 1,
41
+ state: "leased",
42
+ attemptToken: input.attemptToken,
43
+ workerSessionId: input.workerSessionId,
44
+ ...input.executionIdentity !== void 0 ? { executionIdentity: input.executionIdentity } : {},
45
+ attempt: current.attempt,
46
+ leaseDeadline: now + input.leaseDurationMilliseconds,
47
+ firstQueuedAt: current.firstQueuedAt,
48
+ lastQueuedAt: current.lastQueuedAt,
49
+ startedAt: current.startedAt ?? now,
50
+ lastHeartbeatAt: now
51
+ } };
52
+ }
53
+ export function renewAttemptLease(current, input, now) {
54
+ if (current === null || current.state !== "leased")
55
+ return { ok: !1, reason: 'expected task state "leased"' };
56
+ if (current.attemptToken !== input.attemptToken || current.workerSessionId !== input.workerSessionId)
57
+ return { ok: !1, reason: "attempt token or worker session mismatch" };
58
+ return { ok: !0, nextRecord: {
59
+ ...current,
60
+ generation: current.generation + 1,
61
+ leaseDeadline: now + input.leaseDurationMilliseconds,
62
+ lastHeartbeatAt: now
63
+ } };
64
+ }
65
+ export function beginCompletion(current, input) {
66
+ if (current === null || current.state !== "leased")
67
+ return { ok: !1, reason: 'expected task state "leased"' };
68
+ if (current.attemptToken !== input.attemptToken)
69
+ return { ok: !1, reason: "attempt token mismatch" };
70
+ return { ok: !0, nextRecord: {
71
+ ...pickBase(current),
72
+ ...pickAttemptFields(current),
73
+ ...pickLeaseHolderFields(current),
74
+ generation: current.generation + 1,
75
+ state: "completing",
76
+ pendingStatus: input.pendingStatus,
77
+ pendingResultDigest: input.pendingResultDigest
78
+ } };
79
+ }
80
+ export function commitTerminalResult(current, input, now) {
81
+ if (current === null || current.state !== "completing")
82
+ return { ok: !1, reason: 'expected task state "completing"' };
83
+ if (current.attemptToken !== input.attemptToken || current.pendingResultDigest !== input.resultDigest)
84
+ return { ok: !1, reason: "attempt token or result digest mismatch" };
85
+ return { ok: !0, nextRecord: {
86
+ ...pickBase(current),
87
+ generation: current.generation + 1,
88
+ state: "terminal",
89
+ disposition: "resolved",
90
+ attempt: current.attempt,
91
+ attemptToken: current.attemptToken,
92
+ status: current.pendingStatus,
93
+ ...input.error !== void 0 ? { error: input.error } : {},
94
+ resultDigest: current.pendingResultDigest,
95
+ terminalAt: now,
96
+ adopted: !1,
97
+ retentionGeneration: 0
98
+ } };
99
+ }
100
+ export function requeueExpiredAttempt(current, input, now) {
101
+ if (current === null || current.state !== "leased")
102
+ return { ok: !1, reason: 'expected task state "leased"' };
103
+ if (current.attemptToken !== input.attemptToken)
104
+ return { ok: !1, reason: "attempt token mismatch" };
105
+ if (input.skipDeadlineCheck !== !0 && current.leaseDeadline > now)
106
+ return { ok: !1, reason: "lease has not expired" };
107
+ const nextAttempt = current.attempt + 1, policy = current.retryPolicy;
108
+ if (policy !== void 0 && nextAttempt > policy.maxAttempts)
109
+ return { ok: !0, nextRecord: {
110
+ ...pickBase(current),
111
+ generation: current.generation + 1,
112
+ state: "terminal",
113
+ disposition: "retryExhausted",
114
+ attempt: current.attempt,
115
+ attemptToken: current.attemptToken,
116
+ error: `Activity "${current.activityName}" exhausted all ${String(policy.maxAttempts)} retry attempts`,
117
+ resultDigest: `retry-exhausted:${current.operationId}:${current.attemptToken}`,
118
+ terminalAt: now,
119
+ adopted: !1,
120
+ retentionGeneration: 0
121
+ } };
122
+ const availableAt = policy === void 0 ? now : now + calculateBackoff(nextAttempt - 1, policy);
123
+ return { ok: !0, nextRecord: {
124
+ ...pickBase(current),
125
+ generation: current.generation + 1,
126
+ state: "queued",
127
+ attempt: nextAttempt,
128
+ availableAt,
129
+ firstQueuedAt: current.firstQueuedAt,
130
+ lastQueuedAt: now,
131
+ lastDispatchedAt: current.lastQueuedAt,
132
+ startedAt: current.startedAt,
133
+ retryCount: Math.max(current.retryCount, nextAttempt - 1),
134
+ requeueCount: current.requeueCount + 1,
135
+ lastRequeueReason: input.requeueReason
136
+ } };
137
+ }
138
+ export function commitDeadLetter(current, input, now) {
139
+ if (current === null || current.state !== "completing")
140
+ return { ok: !1, reason: 'expected task state "completing"' };
141
+ if (current.attemptToken !== input.attemptToken || current.pendingResultDigest !== input.resultDigest)
142
+ return { ok: !1, reason: "attempt token or result digest mismatch" };
143
+ return { ok: !0, nextRecord: {
144
+ ...pickBase(current),
145
+ ...pickAttemptFields(current),
146
+ generation: current.generation + 1,
147
+ state: "deadLettered",
148
+ attemptToken: current.attemptToken,
149
+ attempt: current.attempt,
150
+ pendingStatus: current.pendingStatus,
151
+ pendingResultDigest: current.pendingResultDigest,
152
+ ...input.value !== void 0 ? { value: input.value } : {},
153
+ ...input.error !== void 0 ? { error: input.error } : {},
154
+ deadLetteredAt: now,
155
+ persistenceFailureReason: input.persistenceFailureReason
156
+ } };
157
+ }
158
+ export function canClearDeadLetteredTask(current) {
159
+ if (current === null || current.state !== "deadLettered")
160
+ return { ok: !1, reason: 'expected task state "deadLettered"' };
161
+ return { ok: !0 };
162
+ }
@@ -0,0 +1,220 @@
1
+ /**
2
+ * The canonical durable remote task ledger type union (WFT-25).
3
+ *
4
+ * Replaces the fragmented `op:queued:` / `op:inflight:` / `op:resolved:` /
5
+ * `op:dead-letter:` records in `task-state.ts` with one authoritative current
6
+ * state per operation. Every transition between these states proves the
7
+ * expected prior state and attempt identity through `storage.conditionalBatch`
8
+ * — see `task-ledger-transitions.ts` for the pure precondition functions that
9
+ * decide whether a transition is legal.
10
+ *
11
+ * This module defines the record shapes only. It does not read or write
12
+ * storage, and it is not wired into the live dispatch path — `TaskQueue`,
13
+ * `WorkerRegistry`, and the WebSocket/long-poll task handlers keep using
14
+ * `task-state.ts` until "Queue and Claim Coordinator" (WFT-22), the project
15
+ * slice that owns replacing that memory authority, lands.
16
+ *
17
+ * @module server/task-ledger-types
18
+ */
19
+ import type { JSONValue } from '../core/json.ts';
20
+ import type { RetryPolicy } from '../core/types.ts';
21
+ import type { WorkerExecutionIdentity } from '../worker/manifest/types.ts';
22
+ /**
23
+ * Fields common to every state a task record can occupy — the "complete
24
+ * dispatch envelope" the project brief requires to survive queueing, lease,
25
+ * retry, recovery, resolution, and dead letter.
26
+ *
27
+ * `workflowId` is optional, diverging from the project brief's literal
28
+ * `workflowId: string`. `TaskDispatch.workflowId` (`server/index.ts`) and
29
+ * every existing task record (`QueuedRecord`, `InflightRecord` in
30
+ * `task-state.ts`) already treat it as optional, and `serve-internals.ts`'s
31
+ * `rebuildWorkflowIndex` has a real, non-test code path that tolerates a
32
+ * restored in-flight record with no `workflowId`. Making it required here
33
+ * would silently break that existing, exercised behavior.
34
+ *
35
+ * `workflowExecutionToken` is optional for the same reason: the public
36
+ * `TaskDispatch.workflowExecutionToken` (`server/index.ts`) is already
37
+ * `string | undefined` for standalone remote-activity dispatch outside any
38
+ * durable workflow run, and every other declaration of this field in the
39
+ * codebase (`task-state.ts`, `task-queue-types.ts`, the core execution
40
+ * strategies) is optional too. Absence means "not workflow-bound" and must
41
+ * never be defaulted to an empty string — this field is documented
42
+ * elsewhere as an external write fence, so a shared `''` sentinel would let
43
+ * unrelated standalone tasks appear to hold the same fence.
44
+ */
45
+ export type RemoteTaskBase = Readonly<{
46
+ recordVersion: 1;
47
+ operationId: string;
48
+ workflowId?: string;
49
+ workflowType: string;
50
+ workflowExecutionToken?: string;
51
+ activityName: string;
52
+ queue: string;
53
+ input: JSONValue;
54
+ headers: Readonly<Record<string, string>>;
55
+ priority?: number;
56
+ fairShareKey?: string;
57
+ /** Preferred worker-affinity routing hint, derived from `sticky ? workflowId : undefined` at dispatch time. */
58
+ stickyWorkflowId?: string;
59
+ visibilityTimeoutMilliseconds: number;
60
+ retryPolicy?: RetryPolicy;
61
+ scheduleToCloseDeadline?: number;
62
+ executionRequirement?: WorkerExecutionRequirementInput;
63
+ createdAt: number;
64
+ /** Monotonic counter incremented on every transition. Diagnostic/provenance surface — `conditionalBatch`'s whole-record byte equality is what actually enforces the CAS, but the precondition functions still check `generation` explicitly because it is the documented contract. */
65
+ generation: number;
66
+ }>;
67
+ /**
68
+ * Local alias for the manifest project's `WorkerExecutionRequirement` — kept
69
+ * as a named alias (rather than importing it directly into every downstream
70
+ * signature) so this module has one place documenting that the ledger reuses
71
+ * WFT-26's routing-input vocabulary rather than defining its own.
72
+ */
73
+ export type WorkerExecutionRequirementInput = Readonly<{
74
+ deploymentName?: string;
75
+ buildId?: string;
76
+ artifactDigest?: string;
77
+ workflowRevision?: string;
78
+ activityContractHash?: string;
79
+ }>;
80
+ /**
81
+ * Retry/requeue provenance carried by every non-terminal state so it survives
82
+ * a lease → requeue → re-lease cycle intact — matching the existing
83
+ * `TaskLifecycleFields.retryCount` / `requeueCount` semantics in
84
+ * `task-state.ts` (`reassignOrExpireTask`'s `nextRetryCount` /
85
+ * `requeueCount + 1` computation), which this ledger's requeue transition
86
+ * reproduces.
87
+ */
88
+ export type RemoteTaskAttemptFields = Readonly<{
89
+ retryCount: number;
90
+ requeueCount: number;
91
+ lastRequeueReason?: string;
92
+ }>;
93
+ export type RemoteTaskQueued = RemoteTaskBase & RemoteTaskAttemptFields & Readonly<{
94
+ state: 'queued';
95
+ attempt: number;
96
+ /** Delayed retries are represented here, not by a process timer — `Queued` includes work delayed until `availableAt`. */
97
+ availableAt: number;
98
+ firstQueuedAt: number;
99
+ lastQueuedAt: number;
100
+ lastDispatchedAt?: number;
101
+ /** First time any attempt of this operation began executing. Absent until the first claim; preserved across requeues. */
102
+ startedAt?: number;
103
+ }>;
104
+ export type RemoteTaskLeased = RemoteTaskBase & RemoteTaskAttemptFields & Readonly<{
105
+ state: 'leased';
106
+ attemptToken: string;
107
+ /**
108
+ * Identifies the live worker connection/session holding the lease,
109
+ * distinct from `executionIdentity.workerId` (the durable identity of
110
+ * the worker *process*). No durable session concept exists in the
111
+ * codebase today — `WorkerRegistry`'s grace-period reconnect logic
112
+ * deliberately preserves in-flight ownership across sockets under one
113
+ * `workerId`. This field exists so a later slice (WFT-22) can decide
114
+ * whether lease renewal survives reconnect; that policy is out of this
115
+ * slice's scope.
116
+ */
117
+ workerSessionId: string;
118
+ /**
119
+ * Complete observed identity of the worker holding the lease — see
120
+ * `WorkerExecutionIdentity` in `worker/manifest/types.ts`. Optional
121
+ * because `buildWorkerExecutionIdentity` returns `undefined` whenever the
122
+ * claiming worker has no registered manifest entry for this
123
+ * workflowType/activityName pair — which is the *only* case for every
124
+ * long-poll claim (long-poll workers never call `WorkerRegistry.register`,
125
+ * so they never have a manifest) and can also occur for a WebSocket
126
+ * worker whose manifest doesn't cover the dispatched activity. Absence
127
+ * here means "no verifiable provenance was available at claim time," not
128
+ * "provenance was skipped" — nothing fabricates a placeholder identity.
129
+ */
130
+ executionIdentity?: WorkerExecutionIdentity;
131
+ attempt: number;
132
+ leaseDeadline: number;
133
+ firstQueuedAt: number;
134
+ lastQueuedAt: number;
135
+ startedAt: number;
136
+ lastHeartbeatAt: number;
137
+ }>;
138
+ export type RemoteTaskCompleting = RemoteTaskBase & RemoteTaskAttemptFields & Readonly<{
139
+ state: 'completing';
140
+ attemptToken: string;
141
+ workerSessionId: string;
142
+ executionIdentity?: WorkerExecutionIdentity;
143
+ attempt: number;
144
+ leaseDeadline: number;
145
+ firstQueuedAt: number;
146
+ lastQueuedAt: number;
147
+ startedAt: number;
148
+ lastHeartbeatAt: number;
149
+ pendingStatus: 'completed' | 'failed';
150
+ /** Digest of the pending result, matched by "Commit terminal result" before it is applied. */
151
+ pendingResultDigest: string;
152
+ }>;
153
+ export type RemoteTaskCancelling = RemoteTaskBase & RemoteTaskAttemptFields & Readonly<{
154
+ state: 'cancelling';
155
+ /**
156
+ * Always present. Per the state diagram, `Cancelling` is reached only
157
+ * from `Leased` (`Leased --> Cancelling: cancellation intent`) — a
158
+ * cancellation recorded while a task is still `Queued` transitions
159
+ * directly to `Terminal` (disposition `cancelled`, no attempt ever
160
+ * existed) without passing through this state. The brief's transition
161
+ * table phrase "attempt token matches when one exists" describes that
162
+ * generic precondition-checking function across both origins; this
163
+ * type only represents the leased-origin case.
164
+ */
165
+ attemptToken: string;
166
+ workerSessionId: string;
167
+ executionIdentity?: WorkerExecutionIdentity;
168
+ attempt: number;
169
+ leaseDeadline: number;
170
+ firstQueuedAt: number;
171
+ lastQueuedAt: number;
172
+ startedAt: number;
173
+ lastHeartbeatAt: number;
174
+ cancellationReason: string;
175
+ cancellationRequestedAt: number;
176
+ }>;
177
+ /** Which lineage produced a terminal record. Every lineage carries `resultDigest` so "Mark workflow result adopted" can match uniformly regardless of disposition. */
178
+ export type RemoteTaskTerminalDisposition = 'resolved' | 'cancelled' | 'retryExhausted';
179
+ type RemoteTaskTerminalCommon = RemoteTaskBase & Readonly<{
180
+ state: 'terminal';
181
+ attempt: number;
182
+ resultDigest: string;
183
+ terminalAt: number;
184
+ adopted: boolean;
185
+ adoptedAt?: number;
186
+ /** Matched by "Delete retained terminal task"; incremented if a record is re-terminalized (e.g. adoption retried after a partial cleanup). */
187
+ retentionGeneration: number;
188
+ }>;
189
+ export type RemoteTaskTerminalResolved = RemoteTaskTerminalCommon & Readonly<{
190
+ disposition: 'resolved';
191
+ attemptToken: string;
192
+ status: 'completed' | 'failed';
193
+ error?: string;
194
+ }>;
195
+ export type RemoteTaskTerminalCancelled = RemoteTaskTerminalCommon & Readonly<{
196
+ disposition: 'cancelled';
197
+ /** Present when cancelled mid-attempt (via `Cancelling`); absent when cancelled directly from `Queued`. */
198
+ attemptToken?: string;
199
+ cancellationReason: string;
200
+ }>;
201
+ export type RemoteTaskTerminalRetryExhausted = RemoteTaskTerminalCommon & Readonly<{
202
+ disposition: 'retryExhausted';
203
+ attemptToken: string;
204
+ error: string;
205
+ }>;
206
+ export type RemoteTaskTerminal = RemoteTaskTerminalResolved | RemoteTaskTerminalCancelled | RemoteTaskTerminalRetryExhausted;
207
+ export type RemoteTaskDeadLettered = RemoteTaskBase & RemoteTaskAttemptFields & Readonly<{
208
+ state: 'deadLettered';
209
+ attemptToken: string;
210
+ attempt: number;
211
+ pendingStatus: 'completed' | 'failed';
212
+ pendingResultDigest: string;
213
+ value?: JSONValue;
214
+ error?: string;
215
+ deadLetteredAt: number;
216
+ persistenceFailureReason: string;
217
+ }>;
218
+ export type RemoteTaskRecord = RemoteTaskQueued | RemoteTaskLeased | RemoteTaskCompleting | RemoteTaskCancelling | RemoteTaskTerminal | RemoteTaskDeadLettered;
219
+ export declare const REMOTE_TASK_RECORD_VERSION = 1;
220
+ export {};
@@ -0,0 +1 @@
1
+ export const REMOTE_TASK_RECORD_VERSION = 1;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Durable remote task ledger — public surface for this subsystem (WFT-25).
3
+ *
4
+ * Barrel re-exporting the record types, storage key, and codec split across
5
+ * `task-ledger-types.ts`, `task-ledger-limits.ts`, `task-ledger-keys.ts`, and
6
+ * `task-ledger-codec.ts` so callers (and `task-ledger.test.ts`, named by the
7
+ * project's verification gate) import from one module. Server-internal only
8
+ * — not re-exported from `src/index.ts`. See `task-ledger-transitions.ts`
9
+ * for the pure conditional-transition precondition functions.
10
+ *
11
+ * @module server/task-ledger
12
+ */
13
+ export { decodeRemoteTaskRecord, encodeRemoteTaskRecord, isRemoteTaskCancelling, isRemoteTaskCompleting, isRemoteTaskDeadLettered, isRemoteTaskLeased, isRemoteTaskQueued, isRemoteTaskRecord, isRemoteTaskTerminal, isRemoteTaskTerminalCancelled, isRemoteTaskTerminalResolved, isRemoteTaskTerminalRetryExhausted, isValidTaskHeaders, } from './task-ledger-codec.ts';
14
+ export { taskLedgerKey } from './task-ledger-keys.ts';
15
+ export { MAX_TASK_HEADER_COUNT, MAX_TASK_HEADER_VALUE_BYTES, MAX_TASK_IDENTIFIER_BYTES, MAX_TASK_REASON_BYTES, utf8ByteLength, } from './task-ledger-limits.ts';
16
+ export { REMOTE_TASK_RECORD_VERSION } from './task-ledger-types.ts';
17
+ export type { RemoteTaskAttemptFields, RemoteTaskBase, RemoteTaskCancelling, RemoteTaskCompleting, RemoteTaskDeadLettered, RemoteTaskLeased, RemoteTaskQueued, RemoteTaskRecord, RemoteTaskTerminal, RemoteTaskTerminalCancelled, RemoteTaskTerminalDisposition, RemoteTaskTerminalResolved, RemoteTaskTerminalRetryExhausted, WorkerExecutionRequirementInput, } from './task-ledger-types.ts';
@@ -0,0 +1,24 @@
1
+ export {
2
+ decodeRemoteTaskRecord,
3
+ encodeRemoteTaskRecord,
4
+ isRemoteTaskCancelling,
5
+ isRemoteTaskCompleting,
6
+ isRemoteTaskDeadLettered,
7
+ isRemoteTaskLeased,
8
+ isRemoteTaskQueued,
9
+ isRemoteTaskRecord,
10
+ isRemoteTaskTerminal,
11
+ isRemoteTaskTerminalCancelled,
12
+ isRemoteTaskTerminalResolved,
13
+ isRemoteTaskTerminalRetryExhausted,
14
+ isValidTaskHeaders
15
+ } from "./task-ledger-codec.js";
16
+ export { taskLedgerKey } from "./task-ledger-keys.js";
17
+ export {
18
+ MAX_TASK_HEADER_COUNT,
19
+ MAX_TASK_HEADER_VALUE_BYTES,
20
+ MAX_TASK_IDENTIFIER_BYTES,
21
+ MAX_TASK_REASON_BYTES,
22
+ utf8ByteLength
23
+ } from "./task-ledger-limits.js";
24
+ export { REMOTE_TASK_RECORD_VERSION } from "./task-ledger-types.js";
@@ -1,27 +1,21 @@
1
1
  /**
2
- * Task state tracking for the remote worker dispatch system.
2
+ * Lifecycle vocabulary and timing calculators shared by the durable task
3
+ * ledger and its diagnostics/metrics consumers.
3
4
  *
4
- * Every dispatched task exists in exactly one of three durable states:
5
- * - **queued**: Waiting in storage for a worker to claim it.
6
- * - **inflight**: Assigned to a worker with a visibility deadline.
7
- * - **resolved**: Completed or permanently failed.
8
- *
9
- * State transitions use `storage.batch()` to atomically delete the old
10
- * key and write the new key, preventing any window where a task is in
11
- * zero or two states simultaneously.
5
+ * The `op:queued:`/`op:inflight:`/`op:resolved:`/`op:dead-letter:` record
6
+ * shapes and type guards this module once exported alongside these
7
+ * calculators are gone: WFT-22 replaced the key scheme with the durable
8
+ * task ledger (`task-ledger.ts`), and WFT-24 migrated `get-task-diagnostics.ts`
9
+ * (the last production reader of those legacy shapes) onto the ledger.
10
+ * What remains is still load-bearing: `TaskLifecycleFields` and
11
+ * `TaskRequeueReason` back the ledger's own record types, and the
12
+ * queue-latency, execution-latency, and heartbeat-staleness calculators
13
+ * back `task-metrics.ts` and the ledger diagnostics.
12
14
  *
13
15
  * @module task-state
14
16
  */
15
- import type { RetryPolicy } from '../core/types.ts';
16
- import type { Storage } from '../storage/interface.ts';
17
- /** The three exclusive states a dispatched task can occupy. */
18
- export type TaskState = 'queued' | 'inflight' | 'resolved';
19
17
  /** Why a task was requeued before another dispatch attempt. */
20
18
  export type TaskRequeueReason = 'visibility-timeout' | 'worker-disconnect';
21
- /** Final reason captured when a task reaches the resolved state. */
22
- export type TaskResolutionReason = 'completed' | 'failed' | 'cancelled' | 'max-attempts-exceeded';
23
- /** Why a task-result transition was moved to the operator dead-letter guard. */
24
- export type TaskDeadLetterReason = 'result-resolution-storage-exhausted';
25
19
  /** Lifecycle evidence persisted with task records for diagnostics. */
26
20
  export interface TaskLifecycleFields {
27
21
  /** First time this operation entered a task queue. */
@@ -43,146 +37,25 @@ export interface TaskLifecycleFields {
43
37
  /** Most recent reason this task moved back to queued. */
44
38
  lastRequeueReason?: TaskRequeueReason | undefined;
45
39
  }
46
- /** Persisted record for a task in the queued state. */
47
- export interface QueuedRecord extends TaskLifecycleFields {
48
- operationId: string;
49
- activityName: string;
50
- input: unknown;
51
- queue: string;
52
- attempt: number;
53
- visibilityTimeout: number;
54
- retryPolicy?: RetryPolicy | undefined;
55
- queuedAt: number;
56
- /** Workflow that dispatched this activity. Present when the dispatch included a workflowId. */
57
- workflowId?: string | undefined;
58
- /** Durable token for the workflow run that dispatched this activity, when known. */
59
- workflowExecutionToken?: string | undefined;
60
- }
61
- /** Persisted record for a task in the inflight state. */
62
- export interface InflightRecord extends TaskLifecycleFields {
63
- operationId: string;
64
- workerId: string;
65
- deadline: number;
66
- activityName: string;
67
- queue: string;
68
- input: unknown;
69
- attempt: number;
70
- visibilityTimeout: number;
71
- retryPolicy?: RetryPolicy | undefined;
72
- /** Workflow that dispatched this activity. Present when the dispatch included a workflowId. */
73
- workflowId?: string | undefined;
74
- /** Durable token for the workflow run that dispatched this activity, when known. */
75
- workflowExecutionToken?: string | undefined;
76
- /** Unique, unguessable token identifying this dispatch attempt. */
77
- attemptToken: string;
78
- }
79
- /** Persisted record for a task in the resolved state. */
80
- export interface ResolvedRecord {
81
- operationId: string;
82
- status: 'completed' | 'failed';
83
- resolvedAt: number;
84
- value?: unknown;
85
- error?: string | undefined;
86
- activityName?: string | undefined;
87
- queue?: string | undefined;
88
- workerId?: string | undefined;
89
- attempt?: number | undefined;
90
- visibilityTimeout?: number | undefined;
91
- /** Workflow that dispatched this activity. Present when the dispatch included a workflowId. */
92
- workflowId?: string | undefined;
93
- firstQueuedAt?: number | undefined;
40
+ /**
41
+ * The subset of {@link TaskLifecycleFields} the queue-latency, execution-latency,
42
+ * and heartbeat-staleness calculations actually read. Deliberately narrower
43
+ * than `TaskLifecycleFields` so these calculations also accept the durable
44
+ * remote task ledger's records (`RemoteTaskLeased`, `RemoteTaskQueued` —
45
+ * WFT-22), which carry the same timing fields but a free-text
46
+ * `lastRequeueReason` rather than the fixed {@link TaskRequeueReason} enum.
47
+ */
48
+ export type TaskTimingFields = Readonly<{
94
49
  lastQueuedAt?: number | undefined;
95
50
  lastDispatchedAt?: number | undefined;
96
51
  startedAt?: number | undefined;
97
- completedAt?: number | undefined;
98
52
  lastHeartbeatAt?: number | undefined;
99
- retryCount?: number | undefined;
100
- requeueCount?: number | undefined;
101
- lastRequeueReason?: TaskRequeueReason | undefined;
102
- resolutionReason?: TaskResolutionReason | undefined;
103
- queueLatencyMs?: number | undefined;
104
- executionLatencyMs?: number | undefined;
105
- }
106
- /** Durable operator guard for a task result whose resolved write exhausted retries. */
107
- export interface DeadLetteredTaskRecord {
108
- operationId: string;
109
- reason: TaskDeadLetterReason;
110
- deadLetteredAt: number;
111
- errorMessage: string;
112
- retryAttempts: number;
113
- status: 'completed' | 'failed';
114
- activityName?: string | undefined;
115
- queue?: string | undefined;
116
- workerId?: string | undefined;
117
- attempt?: number | undefined;
118
- visibilityTimeout?: number | undefined;
119
- workflowId?: string | undefined;
120
- retryCount?: number | undefined;
121
- requeueCount?: number | undefined;
122
- lastRequeueReason?: TaskRequeueReason | undefined;
123
- }
124
- export interface TransitionInflightToResolvedOptions {
125
- resolutionReason?: TaskResolutionReason;
126
- resolvedAt?: number;
127
- record?: InflightRecord;
128
- value?: unknown;
129
- error?: string | undefined;
130
- }
131
- /**
132
- * Look up the current durable state of a task.
133
- *
134
- * Returns the state name if the task is found in any of the three
135
- * states, or `null` if no record exists (the task was never dispatched
136
- * or its resolved record has been garbage-collected).
137
- */
138
- export declare function getTaskState(storage: Storage, operationId: string): Promise<TaskState | null>;
139
- /**
140
- * Return the task state and verify it occupies exactly one state.
141
- *
142
- * Throws if the task is found in multiple states simultaneously —
143
- * this indicates a bug in the state machine.
144
- */
145
- export declare function getExclusiveTaskState(storage: Storage, operationId: string): Promise<TaskState | null>;
146
- /** Type guard for decoded storage records in the queued state. */
147
- export declare function isQueuedRecord(value: unknown): value is QueuedRecord;
148
- /** Type guard for decoded storage records in the inflight state. */
149
- export declare function isInflightRecord(value: unknown): value is InflightRecord;
150
- /** Type guard for decoded storage records in the resolved state. */
151
- export declare function isResolvedRecord(value: unknown): value is ResolvedRecord;
152
- /** Type guard for decoded task-result dead-letter records. */
153
- export declare function isDeadLetteredTaskRecord(value: unknown): value is DeadLetteredTaskRecord;
154
- /** Decode the queued record for an operation, returning null when absent or malformed. */
155
- export declare function readQueuedRecord(storage: Storage, operationId: string): Promise<QueuedRecord | null>;
156
- /** Decode the inflight record for an operation, returning null when absent or malformed. */
157
- export declare function readInflightRecord(storage: Storage, operationId: string): Promise<InflightRecord | null>;
158
- /** Decode the task-result dead-letter record for an operation. */
159
- export declare function readDeadLetteredTaskRecord(storage: Storage, operationId: string): Promise<DeadLetteredTaskRecord | null>;
160
- /** True when reconciliation should not re-dispatch the operation. */
161
- export declare function isTaskDeadLettered(storage: Storage, operationId: string): Promise<boolean>;
162
- /** Persist a task-result dead-letter guard. */
163
- export declare function writeDeadLetteredTaskRecord(storage: Storage, record: DeadLetteredTaskRecord): Promise<void>;
164
- /** Clear a task-result dead-letter guard so reconciliation may handle the inflight record again. */
165
- export declare function clearDeadLetteredTaskRecord(storage: Storage, operationId: string): Promise<void>;
166
- export declare function calculateQueueLatencyMs(record: TaskLifecycleFields): number | undefined;
167
- export declare function calculateExecutionLatencyMs(record: TaskLifecycleFields, completedAt: number): number | undefined;
168
- export declare function calculateHeartbeatAgeMs(record: TaskLifecycleFields & {
53
+ }>;
54
+ export declare function calculateQueueLatencyMs(record: TaskTimingFields): number | undefined;
55
+ export declare function calculateExecutionLatencyMs(record: TaskTimingFields, completedAt: number): number | undefined;
56
+ export declare function calculateHeartbeatAgeMs(record: TaskTimingFields & {
169
57
  deadline?: number | undefined;
170
58
  }, currentTime: number): number | undefined;
171
- export declare function isHeartbeatStale(record: TaskLifecycleFields & {
59
+ export declare function isHeartbeatStale(record: TaskTimingFields & {
172
60
  deadline?: number | undefined;
173
61
  }, currentTime: number, staleAfterMs: number): boolean;
174
- /** Write the initial queued record for a newly dispatched task. */
175
- export declare function markQueued(storage: Storage, record: QueuedRecord): Promise<QueuedRecord>;
176
- type TransitionQueuedToInflightOptions = {
177
- readonly queuedRecord?: QueuedRecord | null;
178
- readonly now?: number | undefined;
179
- };
180
- /** Atomically transition a task from queued → inflight. */
181
- export declare function transitionQueuedToInflight(storage: Storage, operationId: string, inflightRecord: InflightRecord, options?: TransitionQueuedToInflightOptions): Promise<InflightRecord>;
182
- /** Write the initial inflight record (for tasks dispatched directly to a WS worker). */
183
- export declare function markInflight(storage: Storage, record: InflightRecord): Promise<void>;
184
- /** Atomically transition a task from inflight → resolved. */
185
- export declare function transitionInflightToResolved(storage: Storage, operationId: string, status: 'completed' | 'failed', options?: TransitionInflightToResolvedOptions): Promise<void>;
186
- /** Atomically transition a task from inflight → queued (requeue on disconnect/timeout). */
187
- export declare function transitionInflightToQueued(storage: Storage, operationId: string, queuedRecord: QueuedRecord): Promise<void>;
188
- export {};