@lostgradient/weft 0.23.0 → 0.24.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 (295) hide show
  1. package/README.md +72 -4
  2. package/dist/cli/codegen-emit-dedup.d.ts +81 -0
  3. package/dist/cli/codegen-emit-dedup.js +45 -0
  4. package/dist/cli/codegen-emit-registry.d.ts +90 -0
  5. package/dist/cli/codegen-emit-registry.js +50 -0
  6. package/dist/cli/codegen-emit.d.ts +26 -39
  7. package/dist/cli/codegen-emit.js +4 -27
  8. package/dist/cli/codegen-validate.d.ts +49 -0
  9. package/dist/cli/codegen-validate.js +186 -0
  10. package/dist/cli/codegen.js +6 -87
  11. package/dist/cli/generated/operation-catalog.snapshot.json +287 -3
  12. package/dist/cli/generated/operation-client.generated.d.ts +324 -10
  13. package/dist/cli/generated/operation-client.generated.js +12 -0
  14. package/dist/cli/output.js +1 -1
  15. package/dist/cli/storage-factory.js +2 -0
  16. package/dist/cli/workflow-commands.js +2 -0
  17. package/dist/cli-main.js +1 -1
  18. package/dist/core/application-payload-digest.d.ts +42 -0
  19. package/dist/core/application-payload-digest.js +76 -0
  20. package/dist/core/application-primitive-abort.d.ts +50 -0
  21. package/dist/core/application-primitive-abort.js +40 -0
  22. package/dist/core/application-primitive-attempt-registry.d.ts +74 -0
  23. package/dist/core/application-primitive-attempt-registry.js +73 -0
  24. package/dist/core/application-primitive-codec.d.ts +36 -0
  25. package/dist/core/application-primitive-codec.js +56 -0
  26. package/dist/core/application-primitive-commit.d.ts +67 -0
  27. package/dist/core/application-primitive-commit.js +66 -0
  28. package/dist/core/application-primitive-guards.d.ts +62 -0
  29. package/dist/core/application-primitive-guards.js +89 -0
  30. package/dist/core/application-primitive-payload.d.ts +29 -0
  31. package/dist/core/application-primitive-payload.js +65 -0
  32. package/dist/core/application-primitive-timing.d.ts +18 -0
  33. package/dist/core/application-primitive-timing.js +22 -0
  34. package/dist/core/atomic-state.js +2 -0
  35. package/dist/core/catalog/codec.d.ts +40 -0
  36. package/dist/core/catalog/codec.js +45 -0
  37. package/dist/core/catalog/errors.d.ts +104 -0
  38. package/dist/core/catalog/errors.js +41 -0
  39. package/dist/core/catalog/index.d.ts +25 -0
  40. package/dist/core/catalog/index.js +15 -0
  41. package/dist/core/catalog/reference-counts.d.ts +105 -0
  42. package/dist/core/catalog/reference-counts.js +28 -0
  43. package/dist/core/catalog/removal.d.ts +52 -0
  44. package/dist/core/catalog/removal.js +14 -0
  45. package/dist/core/catalog/storage-io.d.ts +78 -0
  46. package/dist/core/catalog/storage-io.js +106 -0
  47. package/dist/core/catalog/types.d.ts +108 -0
  48. package/dist/core/catalog/types.js +0 -0
  49. package/dist/core/catalog/workflow-catalog.d.ts +165 -0
  50. package/dist/core/catalog/workflow-catalog.js +198 -0
  51. package/dist/core/compare-codepoint.d.ts +13 -0
  52. package/dist/core/compare-codepoint.js +7 -0
  53. package/dist/core/compression.js +2 -0
  54. package/dist/core/context/attributes.d.ts +1 -1
  55. package/dist/core/context/durable-operations.d.ts +2 -2
  56. package/dist/core/context/index.js +5 -3
  57. package/dist/core/context/state-namespace.d.ts +1 -1
  58. package/dist/core/contract/build.d.ts +64 -0
  59. package/dist/core/contract/build.js +99 -0
  60. package/dist/core/contract/compatibility.d.ts +187 -0
  61. package/dist/core/contract/compatibility.js +37 -0
  62. package/dist/core/contract/failure.d.ts +64 -0
  63. package/dist/core/contract/failure.js +4 -0
  64. package/dist/core/contract/hash.d.ts +92 -0
  65. package/dist/core/contract/hash.js +34 -0
  66. package/dist/core/contract/index.d.ts +21 -0
  67. package/dist/core/contract/index.js +22 -0
  68. package/dist/core/contract/limits.d.ts +74 -0
  69. package/dist/core/contract/limits.js +1 -0
  70. package/dist/core/contract/manifest-parse-schema.d.ts +37 -0
  71. package/dist/core/contract/manifest-parse-schema.js +106 -0
  72. package/dist/core/contract/manifest-parse.d.ts +76 -0
  73. package/dist/core/contract/manifest-parse.js +189 -0
  74. package/dist/core/contract/manifest.d.ts +56 -0
  75. package/dist/core/contract/manifest.js +31 -0
  76. package/dist/core/contract/normalize.d.ts +97 -0
  77. package/dist/core/contract/normalize.js +127 -0
  78. package/dist/core/contract/revision.d.ts +29 -0
  79. package/dist/core/contract/revision.js +5 -0
  80. package/dist/core/contract/types.d.ts +274 -0
  81. package/dist/core/contract/types.js +1 -0
  82. package/dist/core/engine/catalog-activation.d.ts +40 -0
  83. package/dist/core/engine/catalog-activation.js +17 -0
  84. package/dist/core/engine/catalog-events.d.ts +36 -0
  85. package/dist/core/engine/catalog-events.js +15 -0
  86. package/dist/core/engine/catalog-readiness.d.ts +49 -0
  87. package/dist/core/engine/catalog-readiness.js +59 -0
  88. package/dist/core/engine/catalog-removal.d.ts +132 -0
  89. package/dist/core/engine/catalog-removal.js +70 -0
  90. package/dist/core/engine/construction.d.ts +1 -1
  91. package/dist/core/engine/construction.js +4 -2
  92. package/dist/core/engine/disposal.js +9 -0
  93. package/dist/core/engine/engine-workflows-namespace.d.ts +130 -0
  94. package/dist/core/engine/engine-workflows-namespace.js +49 -0
  95. package/dist/core/engine/finalizer-metadata.d.ts +22 -0
  96. package/dist/core/engine/finalizer-metadata.js +12 -0
  97. package/dist/core/engine/handle-result.js +2 -0
  98. package/dist/core/engine/index.d.ts +83 -0
  99. package/dist/core/engine/index.js +61 -0
  100. package/dist/core/engine/internals.d.ts +44 -1
  101. package/dist/core/engine/internals.js +6 -0
  102. package/dist/core/engine/lifecycle/start.js +3 -0
  103. package/dist/core/engine/registration.d.ts +76 -0
  104. package/dist/core/engine/registration.js +12 -6
  105. package/dist/core/engine/source-registration.d.ts +42 -0
  106. package/dist/core/engine/source-registration.js +36 -0
  107. package/dist/core/engine/source-resolution.d.ts +85 -0
  108. package/dist/core/engine/source-resolution.js +119 -0
  109. package/dist/core/engine/validation.js +2 -2
  110. package/dist/core/events/catalog-events.d.ts +117 -0
  111. package/dist/core/events/catalog-events.js +62 -0
  112. package/dist/core/events/event-map.d.ts +6 -0
  113. package/dist/core/events/index.d.ts +1 -0
  114. package/dist/core/events/index.js +1 -0
  115. package/dist/core/mailbox-admission.d.ts +26 -0
  116. package/dist/core/mailbox-admission.js +130 -0
  117. package/dist/core/mailbox-claims.d.ts +251 -0
  118. package/dist/core/mailbox-claims.js +0 -0
  119. package/dist/core/mailbox-codec.d.ts +32 -0
  120. package/dist/core/mailbox-codec.js +221 -0
  121. package/dist/core/mailbox-contract.d.ts +290 -0
  122. package/dist/core/mailbox-contract.js +0 -0
  123. package/dist/core/mailbox-delivery.d.ts +37 -0
  124. package/dist/core/mailbox-delivery.js +193 -0
  125. package/dist/core/mailbox-guards.d.ts +78 -0
  126. package/dist/core/mailbox-guards.js +58 -0
  127. package/dist/core/mailbox-index-codec.d.ts +41 -0
  128. package/dist/core/mailbox-index-codec.js +71 -0
  129. package/dist/core/mailbox-internals.d.ts +156 -0
  130. package/dist/core/mailbox-internals.js +154 -0
  131. package/dist/core/mailbox-maintenance.d.ts +29 -0
  132. package/dist/core/mailbox-maintenance.js +196 -0
  133. package/dist/core/mailbox-settlement.d.ts +69 -0
  134. package/dist/core/mailbox-settlement.js +206 -0
  135. package/dist/core/mailbox-storage.d.ts +117 -0
  136. package/dist/core/mailbox-storage.js +115 -0
  137. package/dist/core/mailbox-transition-helpers.d.ts +70 -0
  138. package/dist/core/mailbox-transition-helpers.js +43 -0
  139. package/dist/core/mailbox-transitions-recovery.d.ts +28 -0
  140. package/dist/core/mailbox-transitions-recovery.js +64 -0
  141. package/dist/core/mailbox-transitions.d.ts +138 -0
  142. package/dist/core/mailbox-transitions.js +180 -0
  143. package/dist/core/mailbox-types.d.ts +406 -0
  144. package/dist/core/mailbox-types.js +16 -0
  145. package/dist/core/mailbox-validation.d.ts +65 -0
  146. package/dist/core/mailbox-validation.js +73 -0
  147. package/dist/core/mailbox-waits.d.ts +50 -0
  148. package/dist/core/mailbox-waits.js +97 -0
  149. package/dist/core/mailbox.d.ts +163 -0
  150. package/dist/core/mailbox.js +211 -0
  151. package/dist/core/outbox-claims.d.ts +307 -0
  152. package/dist/core/outbox-claims.js +0 -0
  153. package/dist/core/outbox-codec.d.ts +24 -0
  154. package/dist/core/outbox-codec.js +257 -0
  155. package/dist/core/outbox-contract.d.ts +346 -0
  156. package/dist/core/outbox-contract.js +0 -0
  157. package/dist/core/outbox-delivery.d.ts +34 -0
  158. package/dist/core/outbox-delivery.js +149 -0
  159. package/dist/core/outbox-drain.d.ts +29 -0
  160. package/dist/core/outbox-drain.js +158 -0
  161. package/dist/core/outbox-enqueue.d.ts +24 -0
  162. package/dist/core/outbox-enqueue.js +115 -0
  163. package/dist/core/outbox-guards.d.ts +76 -0
  164. package/dist/core/outbox-guards.js +60 -0
  165. package/dist/core/outbox-index-codec.d.ts +33 -0
  166. package/dist/core/outbox-index-codec.js +71 -0
  167. package/dist/core/outbox-internals.d.ts +99 -0
  168. package/dist/core/outbox-internals.js +159 -0
  169. package/dist/core/outbox-listing.d.ts +12 -0
  170. package/dist/core/outbox-listing.js +38 -0
  171. package/dist/core/outbox-maintenance.d.ts +28 -0
  172. package/dist/core/outbox-maintenance.js +191 -0
  173. package/dist/core/outbox-operations.d.ts +18 -0
  174. package/dist/core/outbox-operations.js +46 -0
  175. package/dist/core/outbox-runner.d.ts +30 -0
  176. package/dist/core/outbox-runner.js +217 -0
  177. package/dist/core/outbox-settlement.d.ts +57 -0
  178. package/dist/core/outbox-settlement.js +183 -0
  179. package/dist/core/outbox-storage.d.ts +107 -0
  180. package/dist/core/outbox-storage.js +127 -0
  181. package/dist/core/outbox-transition-helpers.d.ts +83 -0
  182. package/dist/core/outbox-transition-helpers.js +63 -0
  183. package/dist/core/outbox-transitions-recovery.d.ts +30 -0
  184. package/dist/core/outbox-transitions-recovery.js +42 -0
  185. package/dist/core/outbox-transitions.d.ts +145 -0
  186. package/dist/core/outbox-transitions.js +236 -0
  187. package/dist/core/outbox-types.d.ts +400 -0
  188. package/dist/core/outbox-types.js +20 -0
  189. package/dist/core/outbox-validation.d.ts +101 -0
  190. package/dist/core/outbox-validation.js +127 -0
  191. package/dist/core/outbox-waits.d.ts +39 -0
  192. package/dist/core/outbox-waits.js +88 -0
  193. package/dist/core/outbox.d.ts +156 -0
  194. package/dist/core/outbox.js +234 -0
  195. package/dist/core/registry-limits.d.ts +49 -0
  196. package/dist/core/registry-limits.js +10 -0
  197. package/dist/core/registry-schema-conversion.d.ts +20 -0
  198. package/dist/core/registry-schema-conversion.js +22 -0
  199. package/dist/core/registry-snapshot.d.ts +91 -42
  200. package/dist/core/registry-snapshot.js +39 -77
  201. package/dist/core/registry-workflow-contract-draft.d.ts +32 -0
  202. package/dist/core/registry-workflow-contract-draft.js +28 -0
  203. package/dist/core/registry-workflow-manifest.d.ts +122 -0
  204. package/dist/core/registry-workflow-manifest.js +92 -0
  205. package/dist/core/source/errors.d.ts +55 -0
  206. package/dist/core/source/errors.js +13 -0
  207. package/dist/core/source/index.d.ts +19 -0
  208. package/dist/core/source/index.js +4 -0
  209. package/dist/core/source/resolvers.d.ts +45 -0
  210. package/dist/core/source/resolvers.js +9 -0
  211. package/dist/core/source/types.d.ts +211 -0
  212. package/dist/core/source/types.js +0 -0
  213. package/dist/core/source/validate.d.ts +91 -0
  214. package/dist/core/source/validate.js +62 -0
  215. package/dist/core/source/workflow-source.d.ts +43 -0
  216. package/dist/core/source/workflow-source.js +16 -0
  217. package/dist/core/types/workflow-registries.d.ts +7 -0
  218. package/dist/core/types/workflow-registry.d.ts +12 -0
  219. package/dist/core/versioning.js +2 -0
  220. package/dist/core/weft-error.d.ts +1 -1
  221. package/dist/core/weft-error.js +9 -1
  222. package/dist/http.js +2 -2
  223. package/dist/index.d.ts +23 -2
  224. package/dist/index.js +50 -0
  225. package/dist/indexeddb.js +1 -1
  226. package/dist/json-schema.js +3 -3
  227. package/dist/mcp/resources.js +2 -0
  228. package/dist/runtime/portable.d.ts +9 -1
  229. package/dist/server/fault-to-json-rpc.js +3 -1
  230. package/dist/server/handler/auth-context-principal.js +4 -0
  231. package/dist/server/json-rpc-http.js +2 -0
  232. package/dist/server/json-rpc-websocket.js +6 -4
  233. package/dist/server/operation-catalog/index.d.ts +1 -1
  234. package/dist/server/operation-catalog/pipeline-helpers.js +2 -0
  235. package/dist/server/operation-catalog/types.d.ts +17 -0
  236. package/dist/server/operation-fault.d.ts +3 -0
  237. package/dist/server/operation-fault.js +3 -1
  238. package/dist/server/operations/activate-workflow-revision.d.ts +30 -0
  239. package/dist/server/operations/activate-workflow-revision.js +69 -0
  240. package/dist/server/operations/fleet-events-subscription.js +1 -1
  241. package/dist/server/operations/fork-workflow.js +1 -1
  242. package/dist/server/operations/get-active-workflow-revision.d.ts +32 -0
  243. package/dist/server/operations/get-active-workflow-revision.js +46 -0
  244. package/dist/server/operations/get-catalog-diagnostics.d.ts +85 -0
  245. package/dist/server/operations/get-catalog-diagnostics.js +57 -0
  246. package/dist/server/operations/get-registry.js +17 -4
  247. package/dist/server/operations/get-workflow-revision.d.ts +25 -0
  248. package/dist/server/operations/get-workflow-revision.js +50 -0
  249. package/dist/server/operations/install-workflow-revision.d.ts +27 -0
  250. package/dist/server/operations/install-workflow-revision.js +46 -0
  251. package/dist/server/operations/list-workflow-revisions.d.ts +20 -0
  252. package/dist/server/operations/list-workflow-revisions.js +40 -0
  253. package/dist/server/operations/start-or-signal-workflow.js +1 -1
  254. package/dist/server/operations/start-workflow.js +1 -1
  255. package/dist/server/operations/static-registrations.js +36 -0
  256. package/dist/server/operations/storage.js +2 -2
  257. package/dist/server/operations/submit-review-decision.js +1 -1
  258. package/dist/server/operations/workflow-catalog-operation-helpers.d.ts +59 -0
  259. package/dist/server/operations/workflow-catalog-operation-helpers.js +135 -0
  260. package/dist/server/runtime/websocket-upgrade.js +2 -0
  261. package/dist/storage/bun-sql.js +49 -6
  262. package/dist/storage/catalog-keys.d.ts +45 -0
  263. package/dist/storage/catalog-keys.js +6 -0
  264. package/dist/storage/compressed-storage.js +1 -1
  265. package/dist/storage/derived-operations.js +1 -1
  266. package/dist/storage/index.d.ts +26 -1
  267. package/dist/storage/interface.d.ts +26 -1
  268. package/dist/storage/interface.js +1 -1
  269. package/dist/storage/key-encoding.d.ts +15 -0
  270. package/dist/storage/key-encoding.js +3 -0
  271. package/dist/storage/key-prefixes.d.ts +1 -1
  272. package/dist/storage/key-prefixes.js +15 -0
  273. package/dist/storage/lmdb.d.ts +26 -1
  274. package/dist/storage/lmdb.js +1 -1
  275. package/dist/storage/mailbox-keys.d.ts +80 -0
  276. package/dist/storage/mailbox-keys.js +14 -0
  277. package/dist/storage/memory.js +1 -1
  278. package/dist/storage/neon.js +2 -2
  279. package/dist/storage/node-sqlite-loader.d.ts +1 -1
  280. package/dist/storage/node-sqlite-loader.js +2 -2
  281. package/dist/storage/node-sqlite.js +51 -8
  282. package/dist/storage/outbox-keys.d.ts +77 -0
  283. package/dist/storage/outbox-keys.js +14 -0
  284. package/dist/storage/postgres.js +2 -2
  285. package/dist/storage/resolve.js +1 -1
  286. package/dist/storage/scoped-storage.js +1 -1
  287. package/dist/storage/storage-configuration.d.ts +6 -0
  288. package/dist/storage/testing.js +1 -1
  289. package/dist/storage/turso.js +2 -2
  290. package/dist/version.d.ts +1 -1
  291. package/dist/version.js +1 -1
  292. package/dist/web-extension.js +1 -1
  293. package/dist/worker/manifest/registry-contract-builder.d.ts +21 -3
  294. package/dist/worker/manifest/registry-contract-builder.js +40 -31
  295. package/package.json +1 -1
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Attempt-fenced settlement for the application command mailbox (WFT-84):
3
+ * lease renewal, acknowledgement, rejection, cancellation, and the bounded
4
+ * cleanup wait.
5
+ *
6
+ * Every function here proves the caller holds the current attempt before it
7
+ * writes anything, and every one re-reads durable state after a lost
8
+ * compare-and-swap rather than retrying with stale bytes. A stale claimant
9
+ * therefore cannot acknowledge, reject, cancel, extend, or heartbeat a newer
10
+ * attempt — it gets a `stale` result carrying the authoritative receipt.
11
+ *
12
+ * Renewal is the one mutation that emits no fleet event. It is liveness
13
+ * evidence, not a state transition: the record's disposition is unchanged, so
14
+ * publishing an event per heartbeat would flood the feed without telling a
15
+ * consumer anything a receipt read does not already say.
16
+ *
17
+ * @module core/mailbox-settlement
18
+ */
19
+ import type { JSONValue } from './json.ts';
20
+ import type { ApplicationCommandCancellationResult, ApplicationCommandCleanupResult, ApplicationCommandRenewalResult, ApplicationCommandSettleResult } from './mailbox-contract.ts';
21
+ import { type MailboxRuntime } from './mailbox-internals.ts';
22
+ import type { ApplicationCommandFailure } from './mailbox-types.ts';
23
+ /**
24
+ * Extend a lease and report liveness for the current attempt.
25
+ *
26
+ * The returned `cancellationRequested` flag is the cross-process cancellation
27
+ * channel. A claimant in another process never sees the attempt-scoped
28
+ * `AbortSignal`, so renewal is where it learns that cancellation was requested.
29
+ */
30
+ export declare function renewClaim(runtime: MailboxRuntime, options: {
31
+ readonly commandId: string;
32
+ readonly attemptToken: string;
33
+ readonly progress?: JSONValue | undefined;
34
+ }): Promise<ApplicationCommandRenewalResult>;
35
+ /** Settle a claimed command successfully. */
36
+ export declare function acknowledgeClaim(runtime: MailboxRuntime, options: {
37
+ readonly commandId: string;
38
+ readonly attemptToken: string;
39
+ readonly outcome?: JSONValue | undefined;
40
+ }): Promise<ApplicationCommandSettleResult>;
41
+ /**
42
+ * Settle a claimed command as failed, optionally scheduling a retry at the
43
+ * command's original FIFO position.
44
+ */
45
+ export declare function rejectClaim(runtime: MailboxRuntime, options: {
46
+ readonly commandId: string;
47
+ readonly attemptToken: string;
48
+ readonly failure: ApplicationCommandFailure;
49
+ readonly retry: boolean;
50
+ }): Promise<ApplicationCommandSettleResult>;
51
+ /**
52
+ * Record a durable cancellation request and, when the claimant is in this
53
+ * process, abort its attempt-scoped signal.
54
+ *
55
+ * Cancellation is durable before any acknowledgement: the abort fires only
56
+ * after the record commits, so a crash between the two cannot leave a claimant
57
+ * aborted without the request being persisted.
58
+ */
59
+ export declare function requestCancellation(runtime: MailboxRuntime, options: {
60
+ readonly commandId: string;
61
+ readonly reason?: string | undefined;
62
+ }): Promise<ApplicationCommandCancellationResult>;
63
+ /**
64
+ * Read whether a cancelled command's claimant has finished.
65
+ *
66
+ * `pending` means the mailbox has not seen the attempt settle. It never claims
67
+ * the handler stopped — only that cleanup is still outstanding.
68
+ */
69
+ export declare function readCleanupState(runtime: MailboxRuntime, commandId: string): Promise<ApplicationCommandCleanupResult>;
@@ -0,0 +1,206 @@
1
+ import { leaseCommitSerial } from "./application-primitive-attempt-registry.js";
2
+ import {
3
+ commitCommandTransition,
4
+ isTerminalRecord,
5
+ MailboxContentionError,
6
+ MAX_MAILBOX_TRANSITION_ATTEMPTS,
7
+ releaseAttemptController,
8
+ releaseAttemptsForCommand,
9
+ toApplicationCommandReceipt
10
+ } from "./mailbox-internals.js";
11
+ import { commitMailboxTransition, loadCommand, planCommandTransition } from "./mailbox-storage.js";
12
+ import { recoverExpiredCommand } from "./mailbox-transitions-recovery.js";
13
+ import {
14
+ acknowledgeCommand,
15
+ rejectCommand,
16
+ renewCommandLease,
17
+ requestCommandCancellation
18
+ } from "./mailbox-transitions.js";
19
+ import { isApplicationCommandLeased } from "./mailbox-types.js";
20
+ export async function renewClaim(runtime, options) {
21
+ for (let attempt = 1;attempt <= MAX_MAILBOX_TRANSITION_ATTEMPTS; attempt += 1) {
22
+ const loaded = await loadCommand(runtime.storage, runtime.keys, options.commandId);
23
+ if (loaded === null)
24
+ return { status: "unknown" };
25
+ const now = runtime.now(), transition = renewCommandLease(loaded.record, {
26
+ attemptToken: options.attemptToken,
27
+ now,
28
+ progress: options.progress
29
+ });
30
+ if (!transition.ok) {
31
+ releaseAttemptController(runtime, options.attemptToken, "This attempt is no longer current: its renewal was refused.", options.commandId);
32
+ return {
33
+ status: transition.reason === "deadline-exceeded" ? "deadline-exceeded" : "stale",
34
+ receipt: toApplicationCommandReceipt(loaded.record)
35
+ };
36
+ }
37
+ if (!await commitMailboxTransition(runtime.storage, runtime.events, {
38
+ ...planCommandTransition(runtime.keys, {
39
+ previous: loaded.record,
40
+ expectedBytes: loaded.bytes,
41
+ next: transition.next,
42
+ event: null,
43
+ now
44
+ })
45
+ }))
46
+ continue;
47
+ if (runtime.now() >= transition.next.absoluteDeadlineAt) {
48
+ const receipt = await deadLetterRenewed(runtime, options.commandId, toApplicationCommandReceipt(transition.next));
49
+ releaseAttemptController(runtime, options.attemptToken, "The application mailbox dead-lettered this command at its absolute deadline.", options.commandId);
50
+ return { status: "deadline-exceeded", receipt };
51
+ }
52
+ return {
53
+ status: "renewed",
54
+ visibilityExpiresAt: transition.next.visibilityExpiresAt,
55
+ cancellationRequested: transition.next.state === "cancellation-requested",
56
+ receipt: toApplicationCommandReceipt(transition.next)
57
+ };
58
+ }
59
+ throw new MailboxContentionError("renew", options.commandId);
60
+ }
61
+ async function deadLetterRenewed(runtime, commandId, renewed) {
62
+ const loaded = await loadCommand(runtime.storage, runtime.keys, commandId);
63
+ if (loaded === null)
64
+ return renewed;
65
+ const receipt = await deadLetterExpired(runtime, loaded, runtime.now());
66
+ if (receipt !== null)
67
+ return receipt;
68
+ const current = await loadCommand(runtime.storage, runtime.keys, commandId);
69
+ return current === null ? renewed : toApplicationCommandReceipt(current.record);
70
+ }
71
+ export async function acknowledgeClaim(runtime, options) {
72
+ return settle(runtime, options.commandId, options.attemptToken, "acknowledge", (record, now) => acknowledgeCommand(record, {
73
+ attemptToken: options.attemptToken,
74
+ now,
75
+ outcome: options.outcome
76
+ }));
77
+ }
78
+ export async function rejectClaim(runtime, options) {
79
+ return settle(runtime, options.commandId, options.attemptToken, "reject", (record, now) => rejectCommand(record, {
80
+ attemptToken: options.attemptToken,
81
+ now,
82
+ retry: options.retry,
83
+ failure: options.failure,
84
+ retryBackoffMs: runtime.policy.retryBackoffMs,
85
+ maxRetryBackoffMs: runtime.policy.maxRetryBackoffMs
86
+ }));
87
+ }
88
+ async function settle(runtime, commandId, attemptToken, operation, decide) {
89
+ for (let attempt = 1;attempt <= MAX_MAILBOX_TRANSITION_ATTEMPTS; attempt += 1) {
90
+ const loaded = await loadCommand(runtime.storage, runtime.keys, commandId);
91
+ if (loaded === null) {
92
+ releaseRefusedAttempt(runtime, commandId, attemptToken);
93
+ return { status: "unknown" };
94
+ }
95
+ const now = runtime.now(), transition = decide(loaded.record, now);
96
+ if (!transition.ok) {
97
+ releaseRefusedAttempt(runtime, commandId, attemptToken);
98
+ return {
99
+ status: transition.reason === "deadline-exceeded" ? "deadline-exceeded" : "stale",
100
+ receipt: toApplicationCommandReceipt(loaded.record)
101
+ };
102
+ }
103
+ if (!await commitCommandTransition(runtime, {
104
+ previous: loaded.record,
105
+ expectedBytes: loaded.bytes,
106
+ next: transition.next,
107
+ now
108
+ }))
109
+ continue;
110
+ if (isApplicationCommandLeased(loaded.record))
111
+ releaseAttemptController(runtime, loaded.record.attemptToken, "The application mailbox released this attempt when the command settled.");
112
+ return {
113
+ status: isTerminalRecord(transition.next) ? "settled" : "retrying",
114
+ receipt: toApplicationCommandReceipt(transition.next)
115
+ };
116
+ }
117
+ throw new MailboxContentionError(operation, commandId);
118
+ }
119
+ function releaseTerminalAttempts(runtime, commandId, observedAt) {
120
+ releaseAttemptsForCommand(runtime, commandId, "This command is terminal; its attempt is over.", void 0, observedAt);
121
+ }
122
+ function releaseRefusedAttempt(runtime, commandId, attemptToken) {
123
+ releaseAttemptController(runtime, attemptToken, "This attempt is no longer current: its settlement was refused.", commandId);
124
+ }
125
+ export async function requestCancellation(runtime, options) {
126
+ for (let attempt = 1;attempt <= MAX_MAILBOX_TRANSITION_ATTEMPTS; attempt += 1) {
127
+ const observedAt = leaseCommitSerial(), loaded = await loadCommand(runtime.storage, runtime.keys, options.commandId);
128
+ if (loaded === null) {
129
+ releaseTerminalAttempts(runtime, options.commandId, observedAt);
130
+ return { status: "unknown" };
131
+ }
132
+ const now = runtime.now(), transition = requestCommandCancellation(loaded.record, { now, reason: options.reason });
133
+ if (!transition.ok) {
134
+ if (transition.reason === "deadline-exceeded") {
135
+ const expired = await deadLetterExpired(runtime, loaded, now);
136
+ if (expired === null)
137
+ continue;
138
+ releaseTerminalAttempts(runtime, options.commandId, observedAt);
139
+ return { status: "already-terminal", receipt: expired };
140
+ }
141
+ if (transition.reason === "not-leased" && isApplicationCommandLeased(loaded.record)) {
142
+ abortLocalClaimant(runtime, loaded.record.attemptToken);
143
+ return {
144
+ status: "requested",
145
+ receipt: toApplicationCommandReceipt(loaded.record),
146
+ cleanupPending: !0
147
+ };
148
+ }
149
+ releaseTerminalAttempts(runtime, options.commandId, observedAt);
150
+ return { status: "already-terminal", receipt: toApplicationCommandReceipt(loaded.record) };
151
+ }
152
+ if (!await commitCommandTransition(runtime, {
153
+ previous: loaded.record,
154
+ expectedBytes: loaded.bytes,
155
+ next: transition.next,
156
+ now
157
+ }))
158
+ continue;
159
+ if (transition.next.state === "cancellation-requested") {
160
+ abortLocalClaimant(runtime, transition.next.attemptToken);
161
+ return {
162
+ status: "requested",
163
+ receipt: toApplicationCommandReceipt(transition.next),
164
+ cleanupPending: !0
165
+ };
166
+ }
167
+ return { status: "cancelled", receipt: toApplicationCommandReceipt(transition.next) };
168
+ }
169
+ throw new MailboxContentionError("cancel", options.commandId);
170
+ }
171
+ async function deadLetterExpired(runtime, loaded, now) {
172
+ const transition = recoverExpiredCommand(loaded.record, {
173
+ now,
174
+ retryBackoffMs: runtime.policy.retryBackoffMs,
175
+ maxRetryBackoffMs: runtime.policy.maxRetryBackoffMs
176
+ });
177
+ if (!transition.ok)
178
+ return null;
179
+ if (!await commitCommandTransition(runtime, {
180
+ previous: loaded.record,
181
+ expectedBytes: loaded.bytes,
182
+ next: transition.next,
183
+ now
184
+ }))
185
+ return null;
186
+ if (isApplicationCommandLeased(loaded.record))
187
+ releaseAttemptController(runtime, loaded.record.attemptToken, "The application mailbox dead-lettered this command at its absolute deadline.");
188
+ return toApplicationCommandReceipt(transition.next);
189
+ }
190
+ function abortLocalClaimant(runtime, attemptToken) {
191
+ const registration = runtime.attemptControllers.get(attemptToken);
192
+ if (registration !== void 0 && !registration.controller.signal.aborted)
193
+ registration.controller.abort(Error("The application mailbox cancelled this command."));
194
+ }
195
+ export async function readCleanupState(runtime, commandId) {
196
+ const observedAt = leaseCommitSerial(), loaded = await loadCommand(runtime.storage, runtime.keys, commandId);
197
+ if (loaded === null) {
198
+ releaseAttemptsForCommand(runtime, commandId, "This command no longer exists; its receipt was retired.", void 0, observedAt);
199
+ return { status: "unknown" };
200
+ }
201
+ releaseAttemptsForCommand(runtime, commandId, "This attempt is no longer the current lease on its command.", isApplicationCommandLeased(loaded.record) ? loaded.record.attemptToken : void 0, observedAt);
202
+ const receipt = toApplicationCommandReceipt(loaded.record);
203
+ if (receipt.terminalAt !== void 0 && receipt.cleanupPending !== !0)
204
+ return { status: "settled", receipt };
205
+ return { status: "pending", receipt };
206
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Durable reads, index maintenance, and the atomic state-plus-event commit for
3
+ * the application command mailbox (WFT-84).
4
+ *
5
+ * Every mutation goes through {@link commitMailboxTransition}, the mailbox's binding
6
+ * of the shared `commitApplicationTransition`. When the mailbox
7
+ * was built with an event sink, the caller's state operations and the sink's own
8
+ * event write land in one `conditionalBatch`, so no restart can expose the state
9
+ * transition without its event or the other way round. Without a sink the same
10
+ * operations commit through `storageConditionalBatch` directly, which keeps the
11
+ * atomicity guarantee and makes a lost compare-and-swap exactly detectable.
12
+ *
13
+ * @module core/mailbox-storage
14
+ */
15
+ import { type BatchOperation, type ConditionalBatchCondition, type Storage } from '../storage/interface.ts';
16
+ import { type ApplicationCommitPlan, type ApplicationEventSink } from './application-primitive-commit.ts';
17
+ import type { LoadedCommandRecord } from './mailbox-contract.ts';
18
+ import { type ApplicationCommandIdempotencyRecord, type ApplicationCommandRecord, type MailboxRecord } from './mailbox-types.ts';
19
+ /** Every key builder for one `(namespace, resourceId)` mailbox, bound once. */
20
+ export type MailboxKeys = Readonly<{
21
+ header: string;
22
+ sinkProbe: (nonce: string) => string;
23
+ commandPrefix: string;
24
+ command: (commandId: string) => string;
25
+ readyPrefix: string;
26
+ ready: (sequence: number) => string;
27
+ bySequencePrefix: string;
28
+ bySequence: (sequence: number) => string;
29
+ idempotency: (key: string) => string;
30
+ terminalPrefix: string;
31
+ terminal: (terminalAt: number, commandId: string) => string;
32
+ }>;
33
+ /**
34
+ * Bind every mailbox storage key to one namespace and resource.
35
+ */
36
+ export declare function createMailboxKeys(namespace: string, resourceId: string): MailboxKeys;
37
+ /** The empty header a mailbox starts from, so a first admission has something to compare against. */
38
+ export declare function emptyMailboxRecord(namespace: string, resourceId: string): MailboxRecord;
39
+ /** A header read together with the exact bytes it decoded from, for compare-and-swap. */
40
+ export type LoadedMailboxRecord = {
41
+ readonly record: MailboxRecord;
42
+ /** `null` when the mailbox has never been written — the condition for a first admission. */
43
+ readonly bytes: Uint8Array | null;
44
+ };
45
+ /**
46
+ * Read the per-mailbox header, treating an absent key as a fresh mailbox.
47
+ *
48
+ * @throws {PersistedDataCorruptError} When the stored header is malformed.
49
+ */
50
+ export declare function loadMailboxHeader(storage: Storage, keys: MailboxKeys, namespace: string, resourceId: string): Promise<LoadedMailboxRecord>;
51
+ /**
52
+ * Read one command record with the exact bytes it decoded from.
53
+ *
54
+ * @throws {PersistedDataCorruptError} When the stored record is malformed.
55
+ */
56
+ export declare function loadCommand(storage: Storage, keys: MailboxKeys, commandId: string): Promise<LoadedCommandRecord | null>;
57
+ /**
58
+ * Read the idempotency binding for a retry key.
59
+ *
60
+ * @throws {PersistedDataCorruptError} When the stored binding is malformed.
61
+ */
62
+ export declare function loadIdempotencyBinding(storage: Storage, keys: MailboxKeys, idempotencyKey: string): Promise<{
63
+ readonly record: ApplicationCommandIdempotencyRecord;
64
+ readonly bytes: Uint8Array;
65
+ } | null>;
66
+ /**
67
+ * Read the FIFO head: the lowest-sequence entry still in the delivery index.
68
+ *
69
+ * Strict FIFO is the mailbox's ordering contract, so the head is the only entry
70
+ * a claim may consider. A later command never overtakes a delayed head.
71
+ *
72
+ * @throws {PersistedDataCorruptError} When an index entry is malformed.
73
+ */
74
+ export declare function loadDeliveryHead(storage: Storage, keys: MailboxKeys): Promise<{
75
+ readonly key: string;
76
+ readonly bytes: Uint8Array;
77
+ readonly commandId: string;
78
+ } | null>;
79
+ /**
80
+ * Put/delete operations that keep the delivery and terminal indexes consistent
81
+ * with a record's new state.
82
+ *
83
+ * The delivery entry is keyed by the command's original admission sequence, so
84
+ * a redelivered command re-enters the queue at the position it was first
85
+ * admitted to rather than at the back.
86
+ */
87
+ export declare function indexOperationsFor(keys: MailboxKeys, previous: ApplicationCommandRecord | null, next: ApplicationCommandRecord): BatchOperation[];
88
+ /** Whether a record is in the delivery index: admitted or released, not yet claimed or settled. */
89
+ export declare function isWaitingState(record: ApplicationCommandRecord): boolean;
90
+ /**
91
+ * Commit one mailbox transition, atomically with its fleet event when a sink is
92
+ * configured. The mailbox's binding of the shared commit: see
93
+ * `application-primitive-commit.ts` for the compare-and-swap classification and
94
+ * the sink probe.
95
+ */
96
+ export declare function commitMailboxTransition(storage: Storage, events: ApplicationEventSink | undefined, plan: ApplicationCommitPlan): Promise<boolean>;
97
+ /**
98
+ * Persist a command record plus its index maintenance as one plan.
99
+ *
100
+ * `expectedBytes` must be the exact bytes the record was read as — not a
101
+ * re-encoding of the decoded value, which `conditionalBatch`'s whole-value byte
102
+ * comparison would reject.
103
+ */
104
+ export declare function planCommandTransition(keys: MailboxKeys, options: {
105
+ readonly previous: ApplicationCommandRecord | null;
106
+ readonly expectedBytes: Uint8Array | null;
107
+ readonly next: ApplicationCommandRecord;
108
+ readonly event: {
109
+ readonly kind: string;
110
+ readonly payload: unknown;
111
+ } | null;
112
+ readonly now: number;
113
+ readonly extraConditions?: readonly ConditionalBatchCondition[] | undefined;
114
+ readonly extraOperations?: readonly BatchOperation[] | undefined;
115
+ }): ApplicationCommitPlan;
116
+ /** The put operation that advances the mailbox header. */
117
+ export declare function headerOperation(keys: MailboxKeys, record: MailboxRecord): BatchOperation;
@@ -0,0 +1,115 @@
1
+ import {
2
+ KEYS
3
+ } from "../storage/interface.js";
4
+ import {
5
+ commitApplicationTransition
6
+ } from "./application-primitive-commit.js";
7
+ import { decodeApplicationCommandRecord, encodeApplicationCommandRecord } from "./mailbox-codec.js";
8
+ import {
9
+ decodeApplicationCommandIdempotencyRecord,
10
+ decodeApplicationReadyEntry,
11
+ decodeMailboxRecord,
12
+ encodeApplicationReadyEntry,
13
+ encodeMailboxRecord
14
+ } from "./mailbox-index-codec.js";
15
+ import {
16
+ MAILBOX_RECORD_VERSION
17
+ } from "./mailbox-types.js";
18
+ export function createMailboxKeys(namespace, resourceId) {
19
+ return {
20
+ header: KEYS.applicationMailbox(namespace, resourceId),
21
+ sinkProbe: (nonce) => KEYS.applicationMailboxSinkProbe(namespace, resourceId, nonce),
22
+ commandPrefix: KEYS.applicationCommandPrefix(namespace, resourceId),
23
+ command: (commandId) => KEYS.applicationCommand(namespace, resourceId, commandId),
24
+ readyPrefix: KEYS.applicationCommandReadyPrefix(namespace, resourceId),
25
+ ready: (sequence) => KEYS.applicationCommandReady(namespace, resourceId, sequence),
26
+ bySequencePrefix: KEYS.applicationCommandBySequencePrefix(namespace, resourceId),
27
+ bySequence: (sequence) => KEYS.applicationCommandBySequence(namespace, resourceId, sequence),
28
+ idempotency: (key) => KEYS.applicationCommandIdempotency(namespace, resourceId, key),
29
+ terminalPrefix: KEYS.applicationCommandTerminalPrefix(namespace, resourceId),
30
+ terminal: (terminalAt, commandId) => KEYS.applicationCommandTerminal(namespace, resourceId, terminalAt, commandId)
31
+ };
32
+ }
33
+ export function emptyMailboxRecord(namespace, resourceId) {
34
+ return {
35
+ recordVersion: MAILBOX_RECORD_VERSION,
36
+ namespace,
37
+ resourceId,
38
+ nextSequence: 0,
39
+ openCount: 0,
40
+ admittedCount: 0
41
+ };
42
+ }
43
+ export async function loadMailboxHeader(storage, keys, namespace, resourceId) {
44
+ const bytes = await storage.get(keys.header);
45
+ if (bytes === null)
46
+ return { record: emptyMailboxRecord(namespace, resourceId), bytes: null };
47
+ return { record: decodeMailboxRecord(bytes, keys.header), bytes };
48
+ }
49
+ export async function loadCommand(storage, keys, commandId) {
50
+ const key = keys.command(commandId), bytes = await storage.get(key);
51
+ if (bytes === null)
52
+ return null;
53
+ return { record: decodeApplicationCommandRecord(bytes, key), bytes };
54
+ }
55
+ export async function loadIdempotencyBinding(storage, keys, idempotencyKey) {
56
+ const key = keys.idempotency(idempotencyKey), bytes = await storage.get(key);
57
+ if (bytes === null)
58
+ return null;
59
+ return { record: decodeApplicationCommandIdempotencyRecord(bytes, key), bytes };
60
+ }
61
+ export async function loadDeliveryHead(storage, keys) {
62
+ for await (const [key, value] of storage.scan(keys.readyPrefix, { limit: 1 }))
63
+ return { key, bytes: value, commandId: decodeApplicationReadyEntry(value, key) };
64
+ return null;
65
+ }
66
+ export function indexOperationsFor(keys, previous, next) {
67
+ const operations = [], wasWaiting = previous !== null && isWaitingState(previous), willWait = isWaitingState(next);
68
+ if (wasWaiting && !willWait)
69
+ operations.push({ type: "delete", key: keys.ready(next.sequence) });
70
+ if (willWait && !wasWaiting)
71
+ operations.push({
72
+ type: "put",
73
+ key: keys.ready(next.sequence),
74
+ value: encodeApplicationReadyEntry(next.commandId)
75
+ });
76
+ if (isTerminalState(next) && (previous === null || !isTerminalState(previous)))
77
+ operations.push({
78
+ type: "put",
79
+ key: keys.terminal(next.terminalAt, next.commandId),
80
+ value: encodeApplicationReadyEntry(next.commandId)
81
+ });
82
+ return operations;
83
+ }
84
+ export function isWaitingState(record) {
85
+ return record.state === "accepted" || record.state === "available";
86
+ }
87
+ function isTerminalState(record) {
88
+ return record.state === "applied" || record.state === "rejected" || record.state === "cancelled" || record.state === "dead-lettered";
89
+ }
90
+ export function commitMailboxTransition(storage, events, plan) {
91
+ return commitApplicationTransition(storage, events, plan, "application mailbox");
92
+ }
93
+ export function planCommandTransition(keys, options) {
94
+ return {
95
+ sinkProbeKey: keys.sinkProbe(crypto.randomUUID()),
96
+ conditions: [
97
+ { key: keys.command(options.next.commandId), expectedValue: options.expectedBytes },
98
+ ...options.extraConditions ?? []
99
+ ],
100
+ operations: [
101
+ {
102
+ type: "put",
103
+ key: keys.command(options.next.commandId),
104
+ value: encodeApplicationCommandRecord(options.next)
105
+ },
106
+ ...indexOperationsFor(keys, options.previous, options.next),
107
+ ...options.extraOperations ?? []
108
+ ],
109
+ event: options.event,
110
+ now: options.now
111
+ };
112
+ }
113
+ export function headerOperation(keys, record) {
114
+ return { type: "put", key: keys.header, value: encodeMailboxRecord(record) };
115
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * The primitives every mailbox transition shares (WFT-84):
3
+ * the rejection vocabulary, the transition result shape, non-terminal
4
+ * narrowing, identity-field carry-over, and retry backoff.
5
+ *
6
+ * Extracted so `mailbox-transitions.ts` and
7
+ * `mailbox-transitions-recovery.ts` can both use them without
8
+ * importing each other — a runtime cycle this repository forbids outright.
9
+ *
10
+ * @module core/mailbox-transition-helpers
11
+ */
12
+ import { computeRetryBackoffMs } from './application-primitive-timing.ts';
13
+ import type { ApplicationCommandAccepted, ApplicationCommandAvailable, ApplicationCommandCancelling, ApplicationCommandClaimed, ApplicationCommandRecord, ApplicationCommandTerminalRecord } from './mailbox-types.ts';
14
+ export { computeRetryBackoffMs };
15
+ /**
16
+ * Why a proposed transition is illegal. Stable and low-cardinality, so callers
17
+ * can map each reason onto a discriminated result without string matching.
18
+ */
19
+ export type MailboxTransitionRejection = 'stale-attempt' | 'not-leased' | 'not-waiting' | 'not-due' | 'deadline-exceeded' | 'already-terminal';
20
+ /**
21
+ * The outcome of a proposed transition: the record to persist, or why the edge
22
+ * is illegal.
23
+ */
24
+ export type MailboxTransition<TNext> = {
25
+ readonly ok: true;
26
+ readonly next: TNext;
27
+ } | {
28
+ readonly ok: false;
29
+ readonly reason: MailboxTransitionRejection;
30
+ };
31
+ export declare function rejectedTransition<TNext>(reason: MailboxTransitionRejection): MailboxTransition<TNext>;
32
+ export declare function succeededTransition<TNext>(next: TNext): MailboxTransition<TNext>;
33
+ /**
34
+ * Narrow a record to the four non-terminal states.
35
+ *
36
+ * `isApplicationCommandTerminalState` narrows the `state` string but not the
37
+ * record it came from, so every transition below needs this to stay cast-free.
38
+ */
39
+ export declare function isTerminalCommandRecord(record: ApplicationCommandRecord): record is ApplicationCommandTerminalRecord;
40
+ export declare function nonTerminalCommandRecord(record: ApplicationCommandRecord): ApplicationCommandAccepted | ApplicationCommandAvailable | ApplicationCommandClaimed | ApplicationCommandCancelling | null;
41
+ /** Strip the state-specific fields so a transition rebuilds a record from identity alone. */
42
+ export declare function applicationCommandIdentityFields(record: ApplicationCommandRecord): {
43
+ readonly recordVersion: 1;
44
+ readonly namespace: string;
45
+ readonly resourceId: string;
46
+ readonly commandId: string;
47
+ readonly sequence: number;
48
+ readonly idempotencyKey: string | undefined;
49
+ readonly caller: string;
50
+ readonly target: string;
51
+ readonly kind: string;
52
+ readonly payload: import("./mailbox-types.ts").ApplicationCommandPayload;
53
+ readonly payloadDigest: string;
54
+ readonly payloadMediaType: string | undefined;
55
+ readonly payloadSchema: string | undefined;
56
+ readonly causation: Readonly<{
57
+ correlationId?: string | undefined;
58
+ causationId?: string | undefined;
59
+ traceparent?: string | undefined;
60
+ }> | undefined;
61
+ readonly acceptedAt: number;
62
+ readonly absoluteDeadlineAt: number;
63
+ readonly maxAttempts: number;
64
+ readonly visibilityTimeoutMs: number;
65
+ readonly generation: number;
66
+ readonly attempt: number;
67
+ readonly retryCount: number;
68
+ readonly firstClaimedAt: number | undefined;
69
+ readonly availableAt: number;
70
+ };
@@ -0,0 +1,43 @@
1
+ import { computeRetryBackoffMs } from "./application-primitive-timing.js";
2
+ import { MAILBOX_RECORD_VERSION, isApplicationCommandTerminalState } from "./mailbox-types.js";
3
+
4
+ export { computeRetryBackoffMs };
5
+ export function rejectedTransition(reason) {
6
+ return { ok: !1, reason };
7
+ }
8
+ export function succeededTransition(next) {
9
+ return { ok: !0, next };
10
+ }
11
+ export function isTerminalCommandRecord(record) {
12
+ return isApplicationCommandTerminalState(record.state);
13
+ }
14
+ export function nonTerminalCommandRecord(record) {
15
+ return isTerminalCommandRecord(record) ? null : record;
16
+ }
17
+ export function applicationCommandIdentityFields(record) {
18
+ return {
19
+ recordVersion: MAILBOX_RECORD_VERSION,
20
+ namespace: record.namespace,
21
+ resourceId: record.resourceId,
22
+ commandId: record.commandId,
23
+ sequence: record.sequence,
24
+ idempotencyKey: record.idempotencyKey,
25
+ caller: record.caller,
26
+ target: record.target,
27
+ kind: record.kind,
28
+ payload: record.payload,
29
+ payloadDigest: record.payloadDigest,
30
+ payloadMediaType: record.payloadMediaType,
31
+ payloadSchema: record.payloadSchema,
32
+ causation: record.causation,
33
+ acceptedAt: record.acceptedAt,
34
+ absoluteDeadlineAt: record.absoluteDeadlineAt,
35
+ maxAttempts: record.maxAttempts,
36
+ visibilityTimeoutMs: record.visibilityTimeoutMs,
37
+ generation: record.generation + 1,
38
+ attempt: record.attempt,
39
+ retryCount: record.retryCount,
40
+ firstClaimedAt: record.firstClaimedAt,
41
+ availableAt: record.availableAt
42
+ };
43
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The time-driven recovery transitions for the application command mailbox
3
+ * (WFT-84): reclaiming an expired lease and terminalizing a command past its
4
+ * absolute deadline.
5
+ *
6
+ * Split out of `mailbox-transitions.ts` only to keep both files
7
+ * under this repository's file-size ceiling; `recoverExpiredCommand` is
8
+ * re-exported from there so callers still have one import.
9
+ *
10
+ * @module core/mailbox-transitions-recovery
11
+ */
12
+ import { type MailboxTransition } from './mailbox-transition-helpers.ts';
13
+ import type { ApplicationCommandAccepted, ApplicationCommandRecord, ApplicationCommandTerminalRecord } from './mailbox-types.ts';
14
+ /**
15
+ * Recover a command whose lease expired or whose absolute deadline passed.
16
+ *
17
+ * A claimed command returns to `accepted` at its original FIFO position while
18
+ * attempts remain, and is dead-lettered once they are gone. A
19
+ * cancellation-requested command becomes `cancelled` with `cleanupPending:
20
+ * true` — the mailbox stopped waiting for the claimant, which is not the same
21
+ * as the claimant having stopped. Any non-terminal command past its absolute
22
+ * deadline is dead-lettered regardless of remaining attempts.
23
+ */
24
+ export declare function recoverExpiredCommand(record: ApplicationCommandRecord, options: {
25
+ readonly now: number;
26
+ readonly retryBackoffMs: number;
27
+ readonly maxRetryBackoffMs: number;
28
+ }): MailboxTransition<ApplicationCommandAccepted | ApplicationCommandTerminalRecord>;