@lostgradient/weft 0.23.1 → 0.24.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (469) 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/conformance.js +5 -2
  12. package/dist/cli/generated/operation-catalog.snapshot.json +381 -3
  13. package/dist/cli/generated/operation-client.generated.d.ts +350 -10
  14. package/dist/cli/generated/operation-client.generated.js +14 -0
  15. package/dist/cli/output.js +1 -1
  16. package/dist/cli/parse-schedule-arguments.js +15 -3
  17. package/dist/cli/schedule.js +2 -1
  18. package/dist/cli/storage-factory.js +2 -0
  19. package/dist/cli/types.d.ts +2 -1
  20. package/dist/cli/workflow-commands.js +2 -0
  21. package/dist/cli-main.js +1 -1
  22. package/dist/client/http-client-requests.js +6 -9
  23. package/dist/client/start-body.js +1 -0
  24. package/dist/core/application-payload-digest.d.ts +42 -0
  25. package/dist/core/application-payload-digest.js +76 -0
  26. package/dist/core/application-primitive-abort.d.ts +50 -0
  27. package/dist/core/application-primitive-abort.js +40 -0
  28. package/dist/core/application-primitive-attempt-registry.d.ts +74 -0
  29. package/dist/core/application-primitive-attempt-registry.js +73 -0
  30. package/dist/core/application-primitive-codec.d.ts +36 -0
  31. package/dist/core/application-primitive-codec.js +56 -0
  32. package/dist/core/application-primitive-commit.d.ts +67 -0
  33. package/dist/core/application-primitive-commit.js +66 -0
  34. package/dist/core/application-primitive-guards.d.ts +62 -0
  35. package/dist/core/application-primitive-guards.js +89 -0
  36. package/dist/core/application-primitive-payload.d.ts +29 -0
  37. package/dist/core/application-primitive-payload.js +65 -0
  38. package/dist/core/application-primitive-timing.d.ts +18 -0
  39. package/dist/core/application-primitive-timing.js +22 -0
  40. package/dist/core/atomic-state.js +2 -0
  41. package/dist/core/catalog/codec.d.ts +40 -0
  42. package/dist/core/catalog/codec.js +45 -0
  43. package/dist/core/catalog/errors.d.ts +104 -0
  44. package/dist/core/catalog/errors.js +41 -0
  45. package/dist/core/catalog/index.d.ts +25 -0
  46. package/dist/core/catalog/index.js +22 -0
  47. package/dist/core/catalog/reference-counts.d.ts +120 -0
  48. package/dist/core/catalog/reference-counts.js +28 -0
  49. package/dist/core/catalog/removal.d.ts +105 -0
  50. package/dist/core/catalog/removal.js +30 -0
  51. package/dist/core/catalog/storage-io.d.ts +101 -0
  52. package/dist/core/catalog/storage-io.js +106 -0
  53. package/dist/core/catalog/types.d.ts +108 -0
  54. package/dist/core/catalog/types.js +0 -0
  55. package/dist/core/catalog/workflow-catalog.d.ts +165 -0
  56. package/dist/core/catalog/workflow-catalog.js +198 -0
  57. package/dist/core/compare-codepoint.d.ts +13 -0
  58. package/dist/core/compare-codepoint.js +7 -0
  59. package/dist/core/compression.js +2 -0
  60. package/dist/core/context/attributes.d.ts +1 -1
  61. package/dist/core/context/durable-operations.d.ts +2 -2
  62. package/dist/core/context/index.js +5 -3
  63. package/dist/core/context/state-namespace.d.ts +1 -1
  64. package/dist/core/contract/build.d.ts +64 -0
  65. package/dist/core/contract/build.js +99 -0
  66. package/dist/core/contract/compatibility.d.ts +187 -0
  67. package/dist/core/contract/compatibility.js +37 -0
  68. package/dist/core/contract/failure.d.ts +64 -0
  69. package/dist/core/contract/failure.js +4 -0
  70. package/dist/core/contract/hash.d.ts +92 -0
  71. package/dist/core/contract/hash.js +34 -0
  72. package/dist/core/contract/index.d.ts +21 -0
  73. package/dist/core/contract/index.js +22 -0
  74. package/dist/core/contract/limits.d.ts +74 -0
  75. package/dist/core/contract/limits.js +1 -0
  76. package/dist/core/contract/manifest-parse-schema.d.ts +37 -0
  77. package/dist/core/contract/manifest-parse-schema.js +106 -0
  78. package/dist/core/contract/manifest-parse.d.ts +76 -0
  79. package/dist/core/contract/manifest-parse.js +189 -0
  80. package/dist/core/contract/manifest.d.ts +56 -0
  81. package/dist/core/contract/manifest.js +31 -0
  82. package/dist/core/contract/normalize.d.ts +97 -0
  83. package/dist/core/contract/normalize.js +127 -0
  84. package/dist/core/contract/revision.d.ts +29 -0
  85. package/dist/core/contract/revision.js +5 -0
  86. package/dist/core/contract/types.d.ts +274 -0
  87. package/dist/core/contract/types.js +1 -0
  88. package/dist/core/engine/activity-resolution.js +17 -17
  89. package/dist/core/engine/bulk-operations-purge.js +14 -20
  90. package/dist/core/engine/bulk-operations-retry.d.ts +9 -0
  91. package/dist/core/engine/bulk-operations-retry.js +177 -0
  92. package/dist/core/engine/bulk-operations-shared.d.ts +18 -0
  93. package/dist/core/engine/bulk-operations-shared.js +14 -0
  94. package/dist/core/engine/bulk-operations.js +8 -167
  95. package/dist/core/engine/callback-creators-bundles.js +4 -2
  96. package/dist/core/engine/callback-creators-core.js +9 -1
  97. package/dist/core/engine/callback-creators-schedule.js +2 -2
  98. package/dist/core/engine/catalog-activation.d.ts +40 -0
  99. package/dist/core/engine/catalog-activation.js +17 -0
  100. package/dist/core/engine/catalog-events.d.ts +36 -0
  101. package/dist/core/engine/catalog-events.js +15 -0
  102. package/dist/core/engine/catalog-readiness.d.ts +49 -0
  103. package/dist/core/engine/catalog-readiness.js +61 -0
  104. package/dist/core/engine/catalog-removal.d.ts +236 -0
  105. package/dist/core/engine/catalog-removal.js +123 -0
  106. package/dist/core/engine/catalog-tombstone-recovery.d.ts +56 -0
  107. package/dist/core/engine/catalog-tombstone-recovery.js +39 -0
  108. package/dist/core/engine/constraints.js +5 -1
  109. package/dist/core/engine/construction.d.ts +2 -2
  110. package/dist/core/engine/construction.js +4 -2
  111. package/dist/core/engine/decode-revision.d.ts +32 -0
  112. package/dist/core/engine/decode-revision.js +9 -0
  113. package/dist/core/engine/disposal.d.ts +6 -2
  114. package/dist/core/engine/disposal.js +26 -3
  115. package/dist/core/engine/dynamic-source-errors.d.ts +81 -0
  116. package/dist/core/engine/dynamic-source-errors.js +25 -0
  117. package/dist/core/engine/dynamic-source-execution.d.ts +173 -0
  118. package/dist/core/engine/dynamic-source-execution.js +108 -0
  119. package/dist/core/engine/engine-internal-types.d.ts +16 -0
  120. package/dist/core/engine/engine-workflows-namespace.d.ts +140 -0
  121. package/dist/core/engine/engine-workflows-namespace.js +51 -0
  122. package/dist/core/engine/finalizer-metadata.d.ts +22 -0
  123. package/dist/core/engine/finalizer-metadata.js +12 -0
  124. package/dist/core/engine/handle-result.js +2 -0
  125. package/dist/core/engine/index.d.ts +91 -0
  126. package/dist/core/engine/index.js +78 -6
  127. package/dist/core/engine/inline-launch-queue.js +1 -1
  128. package/dist/core/engine/internals.d.ts +54 -15
  129. package/dist/core/engine/internals.js +6 -0
  130. package/dist/core/engine/lifecycle/checkpoint-launch.d.ts +29 -0
  131. package/dist/core/engine/lifecycle/checkpoint-launch.js +75 -0
  132. package/dist/core/engine/lifecycle/delayed-start-registration.d.ts +30 -0
  133. package/dist/core/engine/lifecycle/delayed-start-registration.js +11 -0
  134. package/dist/core/engine/lifecycle/fork-helpers.d.ts +33 -1
  135. package/dist/core/engine/lifecycle/fork-helpers.js +3 -1
  136. package/dist/core/engine/lifecycle/recovery-revision-groups.d.ts +75 -0
  137. package/dist/core/engine/lifecycle/recovery-revision-groups.js +64 -0
  138. package/dist/core/engine/lifecycle/resume-generation-guard.d.ts +58 -0
  139. package/dist/core/engine/lifecycle/resume-generation-guard.js +11 -0
  140. package/dist/core/engine/lifecycle/resume.js +18 -9
  141. package/dist/core/engine/lifecycle/shared.d.ts +43 -0
  142. package/dist/core/engine/lifecycle/standalone-claim-acquire.d.ts +12 -0
  143. package/dist/core/engine/lifecycle/standalone-claim-acquire.js +6 -0
  144. package/dist/core/engine/lifecycle/start-commit-errors.d.ts +12 -0
  145. package/dist/core/engine/lifecycle/start-commit-errors.js +6 -0
  146. package/dist/core/engine/lifecycle/start-commit.d.ts +22 -7
  147. package/dist/core/engine/lifecycle/start-commit.js +49 -15
  148. package/dist/core/engine/lifecycle/start-exec.d.ts +18 -2
  149. package/dist/core/engine/lifecycle/start-exec.js +7 -5
  150. package/dist/core/engine/lifecycle/start-or-signal-create.js +1 -3
  151. package/dist/core/engine/lifecycle/start-or-signal.js +1 -1
  152. package/dist/core/engine/lifecycle/start-precondition-attribution.d.ts +49 -0
  153. package/dist/core/engine/lifecycle/start-precondition-attribution.js +13 -0
  154. package/dist/core/engine/lifecycle/start-revision-resolution.d.ts +34 -0
  155. package/dist/core/engine/lifecycle/start-revision-resolution.js +11 -0
  156. package/dist/core/engine/lifecycle/start-state.d.ts +16 -0
  157. package/dist/core/engine/lifecycle/start-state.js +60 -0
  158. package/dist/core/engine/lifecycle/start-terminal-conflict-purge.d.ts +66 -3
  159. package/dist/core/engine/lifecycle/start-terminal-conflict-purge.js +13 -3
  160. package/dist/core/engine/lifecycle/start.d.ts +16 -6
  161. package/dist/core/engine/lifecycle/start.js +39 -66
  162. package/dist/core/engine/lifecycle/transition.d.ts +3 -4
  163. package/dist/core/engine/lifecycle/transition.js +30 -80
  164. package/dist/core/engine/lifecycle.d.ts +4 -2
  165. package/dist/core/engine/lifecycle.js +6 -6
  166. package/dist/core/engine/listing.js +15 -1
  167. package/dist/core/engine/memo-durable-activity.js +4 -1
  168. package/dist/core/engine/nonterminal-revision-count.d.ts +25 -0
  169. package/dist/core/engine/nonterminal-revision-count.js +16 -0
  170. package/dist/core/engine/operations-time.d.ts +5 -23
  171. package/dist/core/engine/operations-time.js +13 -11
  172. package/dist/core/engine/ownership-bootstrap.d.ts +1 -1
  173. package/dist/core/engine/pinned-schedule-revision-count.d.ts +26 -0
  174. package/dist/core/engine/pinned-schedule-revision-count.js +19 -0
  175. package/dist/core/engine/pinned-schedule-revision.d.ts +76 -0
  176. package/dist/core/engine/pinned-schedule-revision.js +53 -0
  177. package/dist/core/engine/registration.d.ts +106 -0
  178. package/dist/core/engine/registration.js +28 -7
  179. package/dist/core/engine/retention.js +9 -2
  180. package/dist/core/engine/revision-errors.d.ts +71 -0
  181. package/dist/core/engine/revision-errors.js +13 -0
  182. package/dist/core/engine/schedule-revision-fire.d.ts +17 -0
  183. package/dist/core/engine/schedule-revision-fire.js +3 -0
  184. package/dist/core/engine/schedule-run.js +2 -1
  185. package/dist/core/engine/schedules.d.ts +2 -1
  186. package/dist/core/engine/schedules.js +28 -9
  187. package/dist/core/engine/source-diagnostics.d.ts +76 -0
  188. package/dist/core/engine/source-diagnostics.js +105 -0
  189. package/dist/core/engine/source-registration.d.ts +42 -0
  190. package/dist/core/engine/source-registration.js +38 -0
  191. package/dist/core/engine/source-resolution.d.ts +101 -0
  192. package/dist/core/engine/source-resolution.js +154 -0
  193. package/dist/core/engine/source-runtime-state.d.ts +92 -0
  194. package/dist/core/engine/source-runtime-state.js +36 -0
  195. package/dist/core/engine/storage-io.d.ts +19 -2
  196. package/dist/core/engine/storage-io.js +21 -2
  197. package/dist/core/engine/termination/finalizer-registration.d.ts +18 -0
  198. package/dist/core/engine/termination/finalizer-registration.js +21 -0
  199. package/dist/core/engine/termination/finalizer.d.ts +16 -19
  200. package/dist/core/engine/termination/finalizer.js +3 -3
  201. package/dist/core/engine/time-operation-callbacks.d.ts +32 -0
  202. package/dist/core/engine/time-operation-callbacks.js +0 -0
  203. package/dist/core/engine/validation/schedule-cadence.d.ts +16 -0
  204. package/dist/core/engine/validation/schedule-cadence.js +20 -0
  205. package/dist/core/engine/validation/schedule-options.d.ts +4 -1
  206. package/dist/core/engine/validation/schedule-options.js +14 -0
  207. package/dist/core/engine/validation/schedule-revision.d.ts +24 -0
  208. package/dist/core/engine/validation/schedule-revision.js +21 -0
  209. package/dist/core/engine/validation/schedule-warnings.d.ts +11 -0
  210. package/dist/core/engine/validation/schedule-warnings.js +5 -0
  211. package/dist/core/engine/validation/schedule.d.ts +4 -3
  212. package/dist/core/engine/validation/schedule.js +15 -26
  213. package/dist/core/engine/validation.js +5 -2
  214. package/dist/core/engine/workflow-claim-reclaim-target.d.ts +1 -1
  215. package/dist/core/engine/workflow-claim-reclaim-target.js +16 -2
  216. package/dist/core/engine/workflow-retention-deadline.d.ts +16 -0
  217. package/dist/core/engine/workflow-retention-deadline.js +33 -0
  218. package/dist/core/events/catalog-events.d.ts +117 -0
  219. package/dist/core/events/catalog-events.js +62 -0
  220. package/dist/core/events/event-map.d.ts +11 -0
  221. package/dist/core/events/index.d.ts +2 -0
  222. package/dist/core/events/index.js +2 -0
  223. package/dist/core/events/workflow-source-events.d.ts +102 -0
  224. package/dist/core/events/workflow-source-events.js +57 -0
  225. package/dist/core/execution-strategy.d.ts +7 -0
  226. package/dist/core/inline-execution-strategy.context-options.d.ts +1 -1
  227. package/dist/core/inline-execution-strategy.js +1 -1
  228. package/dist/core/mailbox-admission.d.ts +26 -0
  229. package/dist/core/mailbox-admission.js +130 -0
  230. package/dist/core/mailbox-claims.d.ts +251 -0
  231. package/dist/core/mailbox-claims.js +0 -0
  232. package/dist/core/mailbox-codec.d.ts +32 -0
  233. package/dist/core/mailbox-codec.js +221 -0
  234. package/dist/core/mailbox-contract.d.ts +290 -0
  235. package/dist/core/mailbox-contract.js +0 -0
  236. package/dist/core/mailbox-delivery.d.ts +37 -0
  237. package/dist/core/mailbox-delivery.js +193 -0
  238. package/dist/core/mailbox-guards.d.ts +78 -0
  239. package/dist/core/mailbox-guards.js +58 -0
  240. package/dist/core/mailbox-index-codec.d.ts +41 -0
  241. package/dist/core/mailbox-index-codec.js +71 -0
  242. package/dist/core/mailbox-internals.d.ts +156 -0
  243. package/dist/core/mailbox-internals.js +154 -0
  244. package/dist/core/mailbox-maintenance.d.ts +29 -0
  245. package/dist/core/mailbox-maintenance.js +196 -0
  246. package/dist/core/mailbox-settlement.d.ts +69 -0
  247. package/dist/core/mailbox-settlement.js +206 -0
  248. package/dist/core/mailbox-storage.d.ts +117 -0
  249. package/dist/core/mailbox-storage.js +115 -0
  250. package/dist/core/mailbox-transition-helpers.d.ts +70 -0
  251. package/dist/core/mailbox-transition-helpers.js +43 -0
  252. package/dist/core/mailbox-transitions-recovery.d.ts +28 -0
  253. package/dist/core/mailbox-transitions-recovery.js +64 -0
  254. package/dist/core/mailbox-transitions.d.ts +138 -0
  255. package/dist/core/mailbox-transitions.js +180 -0
  256. package/dist/core/mailbox-types.d.ts +406 -0
  257. package/dist/core/mailbox-types.js +16 -0
  258. package/dist/core/mailbox-validation.d.ts +65 -0
  259. package/dist/core/mailbox-validation.js +73 -0
  260. package/dist/core/mailbox-waits.d.ts +50 -0
  261. package/dist/core/mailbox-waits.js +97 -0
  262. package/dist/core/mailbox.d.ts +163 -0
  263. package/dist/core/mailbox.js +211 -0
  264. package/dist/core/outbox-claims.d.ts +307 -0
  265. package/dist/core/outbox-claims.js +0 -0
  266. package/dist/core/outbox-codec.d.ts +24 -0
  267. package/dist/core/outbox-codec.js +257 -0
  268. package/dist/core/outbox-contract.d.ts +346 -0
  269. package/dist/core/outbox-contract.js +0 -0
  270. package/dist/core/outbox-delivery.d.ts +34 -0
  271. package/dist/core/outbox-delivery.js +149 -0
  272. package/dist/core/outbox-drain.d.ts +29 -0
  273. package/dist/core/outbox-drain.js +158 -0
  274. package/dist/core/outbox-enqueue.d.ts +24 -0
  275. package/dist/core/outbox-enqueue.js +115 -0
  276. package/dist/core/outbox-guards.d.ts +76 -0
  277. package/dist/core/outbox-guards.js +60 -0
  278. package/dist/core/outbox-index-codec.d.ts +33 -0
  279. package/dist/core/outbox-index-codec.js +71 -0
  280. package/dist/core/outbox-internals.d.ts +99 -0
  281. package/dist/core/outbox-internals.js +159 -0
  282. package/dist/core/outbox-listing.d.ts +12 -0
  283. package/dist/core/outbox-listing.js +38 -0
  284. package/dist/core/outbox-maintenance.d.ts +28 -0
  285. package/dist/core/outbox-maintenance.js +191 -0
  286. package/dist/core/outbox-operations.d.ts +18 -0
  287. package/dist/core/outbox-operations.js +46 -0
  288. package/dist/core/outbox-runner.d.ts +30 -0
  289. package/dist/core/outbox-runner.js +217 -0
  290. package/dist/core/outbox-settlement.d.ts +57 -0
  291. package/dist/core/outbox-settlement.js +183 -0
  292. package/dist/core/outbox-storage.d.ts +107 -0
  293. package/dist/core/outbox-storage.js +127 -0
  294. package/dist/core/outbox-transition-helpers.d.ts +83 -0
  295. package/dist/core/outbox-transition-helpers.js +63 -0
  296. package/dist/core/outbox-transitions-recovery.d.ts +30 -0
  297. package/dist/core/outbox-transitions-recovery.js +42 -0
  298. package/dist/core/outbox-transitions.d.ts +145 -0
  299. package/dist/core/outbox-transitions.js +236 -0
  300. package/dist/core/outbox-types.d.ts +400 -0
  301. package/dist/core/outbox-types.js +20 -0
  302. package/dist/core/outbox-validation.d.ts +101 -0
  303. package/dist/core/outbox-validation.js +127 -0
  304. package/dist/core/outbox-waits.d.ts +39 -0
  305. package/dist/core/outbox-waits.js +88 -0
  306. package/dist/core/outbox.d.ts +156 -0
  307. package/dist/core/outbox.js +234 -0
  308. package/dist/core/registry-limits.d.ts +49 -0
  309. package/dist/core/registry-limits.js +10 -0
  310. package/dist/core/registry-schema-conversion.d.ts +20 -0
  311. package/dist/core/registry-schema-conversion.js +22 -0
  312. package/dist/core/registry-snapshot.d.ts +91 -42
  313. package/dist/core/registry-snapshot.js +39 -77
  314. package/dist/core/registry-workflow-contract-draft.d.ts +32 -0
  315. package/dist/core/registry-workflow-contract-draft.js +28 -0
  316. package/dist/core/registry-workflow-manifest.d.ts +122 -0
  317. package/dist/core/registry-workflow-manifest.js +92 -0
  318. package/dist/core/source/errors.d.ts +55 -0
  319. package/dist/core/source/errors.js +13 -0
  320. package/dist/core/source/index.d.ts +19 -0
  321. package/dist/core/source/index.js +4 -0
  322. package/dist/core/source/resolvers.d.ts +45 -0
  323. package/dist/core/source/resolvers.js +9 -0
  324. package/dist/core/source/types.d.ts +211 -0
  325. package/dist/core/source/types.js +0 -0
  326. package/dist/core/source/validate.d.ts +91 -0
  327. package/dist/core/source/validate.js +62 -0
  328. package/dist/core/source/workflow-source.d.ts +43 -0
  329. package/dist/core/source/workflow-source.js +16 -0
  330. package/dist/core/types/checkpoint.d.ts +14 -0
  331. package/dist/core/types/schedules.d.ts +52 -1
  332. package/dist/core/types/state.d.ts +16 -0
  333. package/dist/core/types/workflow-registries.d.ts +7 -0
  334. package/dist/core/types/workflow-registry.d.ts +12 -0
  335. package/dist/core/versioning.js +2 -0
  336. package/dist/core/weft-error.d.ts +1 -1
  337. package/dist/core/weft-error.js +12 -1
  338. package/dist/core/worker-execution-strategy.d.ts +1 -0
  339. package/dist/core/worker-execution-strategy.js +13 -1
  340. package/dist/core/worker-fault-handling.d.ts +10 -0
  341. package/dist/core/worker-fault-handling.js +1 -0
  342. package/dist/core/worker-inbound-message.d.ts +1 -0
  343. package/dist/core/worker-inbound-message.js +1 -0
  344. package/dist/core/worker-protocol-guard.js +4 -0
  345. package/dist/core/worker-protocol.d.ts +7 -0
  346. package/dist/core/worker-protocol.js +2 -1
  347. package/dist/core/worker-turn-watchdog.d.ts +8 -1
  348. package/dist/core/worker-turn-watchdog.js +3 -2
  349. package/dist/diagnostics/format.js +5 -0
  350. package/dist/diagnostics/types.d.ts +24 -0
  351. package/dist/diagnostics/version-check.js +8 -2
  352. package/dist/http.js +2 -2
  353. package/dist/index.d.ts +25 -4
  354. package/dist/index.js +57 -0
  355. package/dist/indexeddb.js +1 -1
  356. package/dist/json-schema.js +3 -3
  357. package/dist/mcp/resources.js +2 -0
  358. package/dist/runtime/portable.d.ts +9 -1
  359. package/dist/server/fault-to-json-rpc.js +4 -1
  360. package/dist/server/handler/auth-context-principal.js +4 -0
  361. package/dist/server/index.d.ts +11 -0
  362. package/dist/server/json-rpc-http.js +2 -0
  363. package/dist/server/json-rpc-websocket.js +6 -4
  364. package/dist/server/operation-catalog/index.d.ts +1 -1
  365. package/dist/server/operation-catalog/pipeline-helpers.js +2 -0
  366. package/dist/server/operation-catalog/types.d.ts +17 -0
  367. package/dist/server/operation-fault.d.ts +5 -0
  368. package/dist/server/operation-fault.js +4 -1
  369. package/dist/server/operations/activate-workflow-revision.d.ts +30 -0
  370. package/dist/server/operations/activate-workflow-revision.js +69 -0
  371. package/dist/server/operations/create-schedule.d.ts +2 -0
  372. package/dist/server/operations/create-schedule.js +10 -6
  373. package/dist/server/operations/fleet-events-subscription.js +1 -1
  374. package/dist/server/operations/fork-workflow.js +1 -1
  375. package/dist/server/operations/get-active-workflow-revision.d.ts +32 -0
  376. package/dist/server/operations/get-active-workflow-revision.js +46 -0
  377. package/dist/server/operations/get-catalog-diagnostics.d.ts +113 -0
  378. package/dist/server/operations/get-catalog-diagnostics.js +65 -0
  379. package/dist/server/operations/get-registry.js +17 -4
  380. package/dist/server/operations/get-task-detail-schema.d.ts +8 -0
  381. package/dist/server/operations/get-task-detail-schema.js +1 -0
  382. package/dist/server/operations/get-task-detail.d.ts +8 -0
  383. package/dist/server/operations/get-task-detail.js +1 -0
  384. package/dist/server/operations/get-task-diagnostics.d.ts +4 -4
  385. package/dist/server/operations/get-workflow-revision.d.ts +25 -0
  386. package/dist/server/operations/get-workflow-revision.js +50 -0
  387. package/dist/server/operations/install-workflow-revision.d.ts +27 -0
  388. package/dist/server/operations/install-workflow-revision.js +46 -0
  389. package/dist/server/operations/list-workflow-revisions.d.ts +20 -0
  390. package/dist/server/operations/list-workflow-revisions.js +40 -0
  391. package/dist/server/operations/preload-workflow-revision.d.ts +28 -0
  392. package/dist/server/operations/preload-workflow-revision.js +60 -0
  393. package/dist/server/operations/schedule-faults.d.ts +1 -0
  394. package/dist/server/operations/schedule-faults.js +32 -4
  395. package/dist/server/operations/schedule-rest-body.d.ts +1 -0
  396. package/dist/server/operations/schedule-rest-body.js +2 -1
  397. package/dist/server/operations/start-or-signal-workflow.js +1 -1
  398. package/dist/server/operations/start-workflow.js +1 -1
  399. package/dist/server/operations/static-registrations.js +42 -0
  400. package/dist/server/operations/storage.js +2 -2
  401. package/dist/server/operations/submit-review-decision.js +1 -1
  402. package/dist/server/operations/update-schedule.d.ts +2 -0
  403. package/dist/server/operations/update-schedule.js +4 -2
  404. package/dist/server/operations/workflow-catalog-operation-helpers.d.ts +59 -0
  405. package/dist/server/operations/workflow-catalog-operation-helpers.js +162 -0
  406. package/dist/server/runtime/task-dispatch-envelope.d.ts +14 -0
  407. package/dist/server/runtime/task-dispatch-envelope.js +32 -0
  408. package/dist/server/runtime/task-dispatch-revision.d.ts +29 -0
  409. package/dist/server/runtime/task-dispatch-revision.js +13 -0
  410. package/dist/server/runtime/task-dispatch.js +12 -37
  411. package/dist/server/runtime/task-ledger-recovery.js +1 -1
  412. package/dist/server/runtime/task-ledger-runtime.d.ts +14 -2
  413. package/dist/server/runtime/task-ledger-runtime.js +5 -3
  414. package/dist/server/runtime/task-polling.js +9 -3
  415. package/dist/server/runtime/task-reconciliation.js +2 -1
  416. package/dist/server/runtime/websocket-upgrade.js +2 -0
  417. package/dist/server/runtime/websocket-worker.js +9 -0
  418. package/dist/server/task-ledger-codec.d.ts +9 -0
  419. package/dist/server/task-ledger-codec.js +4 -1
  420. package/dist/server/task-ledger-transition-helpers.js +1 -0
  421. package/dist/server/task-ledger-types.d.ts +9 -0
  422. package/dist/server/task-ledger.d.ts +1 -1
  423. package/dist/server/task-ledger.js +2 -1
  424. package/dist/server/task-queue-types.d.ts +2 -0
  425. package/dist/storage/bun-sql.js +51 -6
  426. package/dist/storage/catalog-keys.d.ts +64 -0
  427. package/dist/storage/catalog-keys.js +8 -0
  428. package/dist/storage/compressed-storage.js +1 -1
  429. package/dist/storage/derived-operations.js +1 -1
  430. package/dist/storage/index.d.ts +28 -1
  431. package/dist/storage/interface.d.ts +28 -1
  432. package/dist/storage/interface.js +1 -1
  433. package/dist/storage/key-encoding.d.ts +15 -0
  434. package/dist/storage/key-encoding.js +3 -0
  435. package/dist/storage/key-prefixes.d.ts +1 -1
  436. package/dist/storage/key-prefixes.js +16 -0
  437. package/dist/storage/lmdb.d.ts +54 -1
  438. package/dist/storage/lmdb.js +1 -1
  439. package/dist/storage/mailbox-keys.d.ts +80 -0
  440. package/dist/storage/mailbox-keys.js +14 -0
  441. package/dist/storage/memory.js +1 -1
  442. package/dist/storage/neon.js +2 -2
  443. package/dist/storage/node-sqlite-loader.d.ts +1 -1
  444. package/dist/storage/node-sqlite.js +51 -6
  445. package/dist/storage/outbox-keys.d.ts +77 -0
  446. package/dist/storage/outbox-keys.js +14 -0
  447. package/dist/storage/postgres.js +2 -2
  448. package/dist/storage/resolve.js +1 -1
  449. package/dist/storage/scoped-storage.js +1 -1
  450. package/dist/storage/storage-configuration.d.ts +6 -0
  451. package/dist/storage/testing.js +1 -1
  452. package/dist/storage/turso.js +2 -2
  453. package/dist/version.d.ts +1 -1
  454. package/dist/version.js +1 -1
  455. package/dist/web-extension.js +1 -1
  456. package/dist/worker/manifest/registry-contract-builder.d.ts +21 -3
  457. package/dist/worker/manifest/registry-contract-builder.js +40 -31
  458. package/dist/worker/protocol-messages.d.ts +17 -0
  459. package/dist/worker/protocol-schemas.d.ts +32 -0
  460. package/dist/worker/protocol-schemas.js +7 -3
  461. package/dist/worker/protocol-task-result.js +35 -3
  462. package/dist/worker/protocol.js +1 -1
  463. package/dist/worker/registry/types.d.ts +2 -0
  464. package/dist/worker/registry.d.ts +1 -1
  465. package/dist/worker/registry.js +3 -1
  466. package/dist/workers/workflow-runner.d.ts +9 -0
  467. package/dist/workers/workflow-runner.js +9 -2
  468. package/dist/workers/workflow-worker-entry.js +9 -4
  469. package/package.json +2 -2
@@ -0,0 +1,28 @@
1
+ export function totalWorkflowRevisionReferences(counts) {
2
+ return counts.registeredDefinitions + counts.inFlightStarts + counts.nonTerminalRuns + counts.pinnedSchedules + counts.pendingDispatches + counts.activeExecutionRealms + counts.retainedRecoveryRecords;
3
+ }
4
+ export function incrementNestedRevisionCount(counts, name, revision) {
5
+ let byRevision = counts.get(name);
6
+ if (byRevision === void 0) {
7
+ byRevision = new Map;
8
+ counts.set(name, byRevision);
9
+ }
10
+ byRevision.set(revision, (byRevision.get(revision) ?? 0) + 1);
11
+ }
12
+ export function decrementNestedRevisionCount(counts, name, revision) {
13
+ const byRevision = counts.get(name);
14
+ if (byRevision === void 0)
15
+ return;
16
+ const current = byRevision.get(revision);
17
+ if (current === void 0)
18
+ return;
19
+ if (current <= 1) {
20
+ byRevision.delete(revision);
21
+ if (byRevision.size === 0)
22
+ counts.delete(name);
23
+ } else
24
+ byRevision.set(revision, current - 1);
25
+ }
26
+ export function readNestedRevisionCount(counts, name, revision) {
27
+ return counts.get(name)?.get(revision) ?? 0;
28
+ }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * The mechanical CAS-delete primitive `WorkflowCatalog.remove()` delegates
3
+ * to, plus the tombstone primitives that make its own resolution
4
+ * (restore-or-finalize) atomic too. Split into its own module to protect
5
+ * `workflow-catalog.ts`'s size against the repository's 500-line
6
+ * implementation-file ceiling — mirrors how `storage-io.ts`/`codec.ts`
7
+ * already sit alongside `workflow-catalog.ts` rather than inline in it.
8
+ *
9
+ * Fences the delete on BOTH the exact entry bytes read AND the exact active
10
+ * pointer bytes read, in one `conditionalBatch` — not entry bytes alone.
11
+ * Between an in-memory "is this the active revision" check and the delete,
12
+ * a concurrent `activateCandidate`/`activateRegistered` call (this process
13
+ * or another sharing the same durable store) could make the target revision
14
+ * active; fencing the active-pointer key too means that race loses the CAS
15
+ * and surfaces as `'conflict'` (the caller re-decides) instead of silently
16
+ * deleting a revision that became active a moment before the delete landed.
17
+ *
18
+ * Single-shot — no retry loop, matching `activateCandidate`'s own
19
+ * no-retry-caller-decides precedent, since a removal decision (unlike
20
+ * `activateRegistered`'s unconditional retry) already depends on reference
21
+ * counts computed by the caller and should not blindly re-attempt against
22
+ * possibly-stale counts.
23
+ *
24
+ * WFT-17/18 (Codex review, PR #958): a bare delete of the entry key, with
25
+ * `removeWorkflowRevision`'s own reference re-check and possible restore as
26
+ * a SEPARATE, later commit, left a crash window between the two commits
27
+ * where the entry was durably gone but a run still referenced it, with NO
28
+ * durable record of what was deleted for ANY process — including the one
29
+ * that crashed — to recover from. {@link removeCatalogEntry} now writes a
30
+ * `catalog-tombstone:<name>:<revision>` record (the exact deleted entry
31
+ * bytes) in the SAME `conditionalBatch` as the delete, so "the entry is
32
+ * gone" and "a durable record of what it was" land atomically together.
33
+ * {@link finalizeCatalogTombstone} and {@link restoreCatalogEntryFromTombstone}
34
+ * are the two possible resolutions, each its own single-CAS atomic
35
+ * operation conditioned on the tombstone's exact bytes — so a concurrent
36
+ * second attempt at resolving the SAME tombstone (this process retrying, or
37
+ * `catalog-readiness.ts`'s boot-time sweep racing a live caller) loses its
38
+ * CAS harmlessly rather than double-processing. Orphan resolution (a
39
+ * tombstone left behind by a process that crashed between the delete and
40
+ * its own resolution) is `resolveOrphanedCatalogTombstones` in
41
+ * `core/engine/catalog-tombstone-recovery.ts` — deliberately OUTSIDE
42
+ * `core/catalog/**`, not alongside these primitives, because it needs
43
+ * `core/engine/nonterminal-revision-count.ts`'s durable non-terminal-run
44
+ * scan and `core/catalog/**` never imports from `core/engine/**` (a
45
+ * directional boundary `check-import-cycles.ts` enforces); see that
46
+ * module's doc for why it lives there.
47
+ *
48
+ * @module core/catalog/removal
49
+ */
50
+ import { type Storage } from '../../storage/interface.ts';
51
+ /** Outcome of {@link removeCatalogEntry}. */
52
+ export type WorkflowCatalogRemovalOutcome = Readonly<{
53
+ outcome: 'removed';
54
+ tombstoneBytes: Uint8Array;
55
+ }> | Readonly<{
56
+ outcome: 'not-found';
57
+ }> | Readonly<{
58
+ outcome: 'active';
59
+ activeRevision: string;
60
+ }> | Readonly<{
61
+ outcome: 'conflict';
62
+ }>;
63
+ /**
64
+ * Durably delete the installed-revision record for `(name, revision)`,
65
+ * atomically replacing it with a tombstone carrying the deleted bytes (see
66
+ * this module's doc for why).
67
+ *
68
+ * Reads the current `catalog-entry:<name>:<revision>` bytes; `null` is
69
+ * `'not-found'` (a no-op — the caller may already believe the revision is
70
+ * gone). Reads the current `catalog-active:<name>` bytes and refuses with
71
+ * `'active'` when they decode to exactly this revision — a structural
72
+ * invariant independent of reference counts, since every future or
73
+ * resuming run resolves the active pointer, not a specific installed
74
+ * entry. Otherwise deletes the entry key and puts the tombstone key (CAS'd
75
+ * on the tombstone being absent — a stale leftover from a previous,
76
+ * unresolved removal of this exact `(name, revision)` is a durable-store
77
+ * inconsistency this fails closed on rather than silently overwriting) via
78
+ * ONE `conditionalBatch`, also CAS'd on the exact entry bytes AND the exact
79
+ * active-pointer bytes read above (present as a no-op `put`-free
80
+ * precondition when active-pointer bytes are `null` — never activated). A
81
+ * CAS loss (any of the three keys changed concurrently) surfaces as
82
+ * `'conflict'`.
83
+ */
84
+ export declare function removeCatalogEntry(storage: Storage, name: string, revision: string): Promise<WorkflowCatalogRemovalOutcome>;
85
+ /**
86
+ * Resolve an outstanding tombstone by completing the removal: CAS-delete
87
+ * the `catalog-tombstone:<name>:<revision>` key, conditioned on its exact
88
+ * bytes still being `tombstoneBytes`. Returns `false` (a harmless no-op for
89
+ * the caller, not an error) when the tombstone was already resolved by
90
+ * someone else — this same process re-driving after a transient failure,
91
+ * or a concurrent boot-time sweep on another process.
92
+ */
93
+ export declare function finalizeCatalogTombstone(storage: Storage, name: string, revision: string, tombstoneBytes: Uint8Array): Promise<boolean>;
94
+ /**
95
+ * Resolve an outstanding tombstone by restoring the entry: ONE
96
+ * `conditionalBatch` that both re-`put`s the `catalog-entry:<name>:<revision>`
97
+ * key (byte-identical to what {@link removeCatalogEntry} deleted — the
98
+ * tombstone bytes ARE the entry bytes) and deletes the tombstone key,
99
+ * conditioned on the tombstone's exact bytes still being `tombstoneBytes`.
100
+ * Atomic by construction — there is no intermediate durable state where the
101
+ * entry is back but the tombstone still claims a removal is in flight, or
102
+ * vice versa. Returns `false` (harmless no-op) when the tombstone was
103
+ * already resolved by someone else.
104
+ */
105
+ export declare function restoreCatalogEntryFromTombstone(storage: Storage, name: string, revision: string, tombstoneBytes: Uint8Array): Promise<boolean>;
@@ -0,0 +1,30 @@
1
+ import { KEYS, storageConditionalBatch } from "../../storage/interface.js";
2
+ import { decodeActivePointer } from "./codec.js";
3
+ export async function removeCatalogEntry(storage, name, revision) {
4
+ const entryKey = KEYS.catalogEntry(name, revision), entryBytes = await storage.get(entryKey);
5
+ if (entryBytes === null)
6
+ return { outcome: "not-found" };
7
+ const activeKey = KEYS.catalogActive(name), activeBytes = await storage.get(activeKey), activePointer = activeBytes === null ? null : decodeActivePointer(activeBytes);
8
+ if (activePointer !== null && activePointer.revision === revision)
9
+ return { outcome: "active", activeRevision: activePointer.revision };
10
+ const tombstoneKey = KEYS.catalogTombstone(name, revision);
11
+ return await storageConditionalBatch(storage, [
12
+ { key: entryKey, expectedValue: entryBytes },
13
+ { key: activeKey, expectedValue: activeBytes },
14
+ { key: tombstoneKey, expectedValue: null }
15
+ ], [
16
+ { type: "delete", key: entryKey },
17
+ { type: "put", key: tombstoneKey, value: entryBytes }
18
+ ]) ? { outcome: "removed", tombstoneBytes: entryBytes } : { outcome: "conflict" };
19
+ }
20
+ export async function finalizeCatalogTombstone(storage, name, revision, tombstoneBytes) {
21
+ const tombstoneKey = KEYS.catalogTombstone(name, revision);
22
+ return storageConditionalBatch(storage, [{ key: tombstoneKey, expectedValue: tombstoneBytes }], [{ type: "delete", key: tombstoneKey }]);
23
+ }
24
+ export async function restoreCatalogEntryFromTombstone(storage, name, revision, tombstoneBytes) {
25
+ const entryKey = KEYS.catalogEntry(name, revision), tombstoneKey = KEYS.catalogTombstone(name, revision);
26
+ return storageConditionalBatch(storage, [{ key: tombstoneKey, expectedValue: tombstoneBytes }], [
27
+ { type: "put", key: entryKey, value: tombstoneBytes },
28
+ { type: "delete", key: tombstoneKey }
29
+ ]);
30
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Durable I/O for the workflow catalog: restoring catalog state at boot and
3
+ * writing individual installed-revision entries.
4
+ *
5
+ * `restoreWorkflowCatalog` fails closed on any corrupt or unparseable
6
+ * durable record — a corrupted catalog entry or active pointer is data
7
+ * corruption in Weft's own durable store, not hostile external input, and
8
+ * every other fail-closed precedent in this codebase (a corrupt lease epoch,
9
+ * a corrupt ownership-mode marker) treats that the same way. Silently
10
+ * dropping it could later resurrect a stale or wrong active revision.
11
+ *
12
+ * @module core/catalog/storage-io
13
+ */
14
+ import { type Storage } from '../../storage/interface.ts';
15
+ import type { WorkflowRevisionManifest } from '../contract/types.ts';
16
+ import type { WorkflowCatalogActivePointer, WorkflowCatalogEntry } from './types.ts';
17
+ /** In-memory catalog state hydrated from durable storage. */
18
+ export type RestoredWorkflowCatalogState = {
19
+ entries: Map<string, Map<string, WorkflowCatalogEntry>>;
20
+ active: Map<string, WorkflowCatalogActivePointer>;
21
+ };
22
+ /**
23
+ * Restore the durable workflow catalog: every installed `(name, revision)`
24
+ * entry and every name's active pointer. Every entry record is validated via
25
+ * {@link parseWorkflowRevisionManifest}; every active-pointer record is
26
+ * validated via {@link decodeActivePointer}. A corrupt or unparseable record
27
+ * throws rather than being silently skipped. Restoring an empty store
28
+ * returns empty maps.
29
+ */
30
+ export declare function restoreWorkflowCatalog(storage: Storage): Promise<RestoredWorkflowCatalogState>;
31
+ /**
32
+ * Decode and validate one durable catalog-entry record: parse as JSON,
33
+ * validate as a manifest via {@link parseWorkflowRevisionManifest}, and check
34
+ * that the decoded `(name, revision)` agrees with the caller-supplied
35
+ * expectation (normally read from the storage key itself). Fails closed on
36
+ * any disagreement — shared by {@link restoreWorkflowCatalog}'s per-entry
37
+ * restore, {@link readCatalogEntry}'s single-key read, and the by-name scan
38
+ * `WorkflowCatalog.listInstalledRevisions` uses, so the three consumers of
39
+ * one durable record shape can never validate it three different ways.
40
+ */
41
+ /**
42
+ * Decode and validate a raw catalog-entry-record byte string against an
43
+ * expected `(name, revision)` — exported for {@link import('./removal.ts').restoreCatalogEntryFromTombstone}'s
44
+ * caller (`WorkflowCatalog.restoreFromTombstone`) and the boot-time orphan
45
+ * sweep (`orphaned-tombstones.ts`) to re-populate the in-memory cache from
46
+ * a tombstone's bytes, which are byte-identical to the entry bytes this
47
+ * function already validates for {@link restoreWorkflowCatalog} and
48
+ * {@link readCatalogEntry}.
49
+ */
50
+ export declare function decodeCatalogEntryRecord(key: string, bytes: Uint8Array, expectedName: string, expectedRevision: string): Promise<{
51
+ manifest: WorkflowRevisionManifest;
52
+ installedAt: number;
53
+ }>;
54
+ /**
55
+ * Read one durable installed-revision record for `(name, revision)`, or
56
+ * `null` when absent. Fails closed on corruption, exactly matching
57
+ * {@link restoreWorkflowCatalog}'s per-entry validation — used by
58
+ * `WorkflowCatalog.install()` to read through the local in-memory cache to
59
+ * durable storage, which may already hold this `(name, revision)` key
60
+ * courtesy of a different `WorkflowCatalog` instance/process.
61
+ */
62
+ export declare function readCatalogEntry(storage: Storage, name: string, revision: string): Promise<{
63
+ manifest: WorkflowRevisionManifest;
64
+ installedAt: number;
65
+ } | null>;
66
+ /**
67
+ * Durably scan every installed revision of `name` — the
68
+ * `catalog-entry:<name>:` prefix `WORKFLOW_CATALOG_KEYS.catalogEntryPrefix`
69
+ * builds. Each record is validated exactly like
70
+ * {@link restoreWorkflowCatalog}'s per-entry restore (via
71
+ * {@link decodeCatalogEntryRecord}, shared rather than duplicated); a
72
+ * corrupt or unparseable entry fails closed rather than being silently
73
+ * skipped. Returns entries in no particular order — callers that need a
74
+ * deterministic order (`WorkflowCatalog.listInstalledRevisions`) sort the
75
+ * result themselves.
76
+ */
77
+ export declare function scanCatalogEntriesForName(storage: Storage, name: string): Promise<Array<{
78
+ manifest: WorkflowRevisionManifest;
79
+ installedAt: number;
80
+ }>>;
81
+ /** Read one name's durable active pointer, or `null` when absent. */
82
+ export declare function readActivePointer(storage: Storage, name: string): Promise<WorkflowCatalogActivePointer | null>;
83
+ /**
84
+ * Durably write one installed-revision entry, CAS-guarded on the key being
85
+ * absent (`expectedValue: null`). Returns `true` when this write won the
86
+ * race, `false` when another writer had already durably installed this
87
+ * exact `(name, revision)` key first — `WorkflowCatalog.install()` re-reads
88
+ * via {@link readCatalogEntry} on `false` to decide whether that concurrent
89
+ * write was byte-identical (idempotent) or a genuine conflict.
90
+ *
91
+ * CAS-protected rather than a plain `put`: "content-addressed by
92
+ * `(name, revision)`, so racing writers always agree" only holds when
93
+ * `revision` is content-derived. `buildWorkflowRevisionManifest`'s public
94
+ * `options.revision` escape hatch lets a caller supply a non-content-derived
95
+ * revision (e.g. a deploy tag), so two different `WorkflowCatalog`
96
+ * instances/processes — each with their own, independently-seeded
97
+ * in-memory cache — could otherwise race a differing-content write to the
98
+ * same key past each other's in-memory-only conflict check with a plain
99
+ * `put` (last write wins, silently).
100
+ */
101
+ export declare function writeCatalogEntry(storage: Storage, manifest: WorkflowRevisionManifest, installedAt: number): Promise<boolean>;
@@ -0,0 +1,106 @@
1
+ import { KEYS, storageConditionalBatch } from "../../storage/interface.js";
2
+ import { tryDecodeStorageKeyComponent } from "../../storage/key-encoding.js";
3
+ import { parseWorkflowRevisionManifest } from "../contract/manifest-parse.js";
4
+ import { decodeActivePointer } from "./codec.js";
5
+ function splitCatalogEntryKey(key) {
6
+ const parts = key.split(":");
7
+ if (parts.length !== 3)
8
+ return null;
9
+ const name = tryDecodeStorageKeyComponent(parts[1] ?? ""), revision = tryDecodeStorageKeyComponent(parts[2] ?? "");
10
+ if (name === null || revision === null)
11
+ return null;
12
+ return { name, revision };
13
+ }
14
+ function splitCatalogActiveKey(key) {
15
+ const parts = key.split(":");
16
+ if (parts.length !== 2)
17
+ return null;
18
+ return tryDecodeStorageKeyComponent(parts[1] ?? "");
19
+ }
20
+ function parseCatalogEntryRecord(bytes) {
21
+ let parsed;
22
+ try {
23
+ parsed = JSON.parse(new TextDecoder().decode(bytes));
24
+ } catch {
25
+ return null;
26
+ }
27
+ if (typeof parsed !== "object" || parsed === null)
28
+ return null;
29
+ const record = parsed;
30
+ if (typeof record.installedAt !== "number")
31
+ return null;
32
+ return { manifest: record.manifest, installedAt: record.installedAt };
33
+ }
34
+ function failClosed(kind, key, reason) {
35
+ throw Error(`The store's ${kind} ("${key}") exists but ${reason}. Treating it as absent would risk resurrecting a stale or wrong active workflow revision, so the workflow catalog fails closed instead. Resolve by operator repair: inspect the stored bytes and, only if certain no other engine relies on them, delete the key so it can be re-established.`);
36
+ }
37
+ export async function restoreWorkflowCatalog(storage) {
38
+ const entries = new Map, active = new Map;
39
+ for await (const [key, bytes] of storage.scan("catalog-entry:"))
40
+ await restoreOneCatalogEntry(entries, key, bytes);
41
+ for await (const [key, bytes] of storage.scan("catalog-active:"))
42
+ restoreOneActivePointer(active, key, bytes);
43
+ return { entries, active };
44
+ }
45
+ export async function decodeCatalogEntryRecord(key, bytes, expectedName, expectedRevision) {
46
+ const parsedRecord = parseCatalogEntryRecord(bytes);
47
+ if (parsedRecord === null)
48
+ failClosed("workflow catalog entry", key, "could not be parsed as JSON");
49
+ const parsed = await parseWorkflowRevisionManifest(parsedRecord.manifest);
50
+ if (!parsed.ok)
51
+ failClosed("workflow catalog entry", key, "does not decode as a valid installed-revision record");
52
+ const manifest = parsed.manifest;
53
+ if (manifest.name !== expectedName || manifest.revision !== expectedRevision)
54
+ failClosed("workflow catalog entry", key, "contains a manifest whose (name, revision) disagrees with its storage key");
55
+ return { manifest, installedAt: parsedRecord.installedAt };
56
+ }
57
+ async function restoreOneCatalogEntry(entries, key, bytes) {
58
+ const split = splitCatalogEntryKey(key);
59
+ if (split === null)
60
+ failClosed("workflow catalog entry key", key, "does not match the expected catalog-entry:<name>:<revision> shape");
61
+ const { manifest, installedAt } = await decodeCatalogEntryRecord(key, bytes, split.name, split.revision);
62
+ let byName = entries.get(split.name);
63
+ if (byName === void 0) {
64
+ byName = new Map;
65
+ entries.set(split.name, byName);
66
+ }
67
+ byName.set(split.revision, { manifest, installedAt });
68
+ }
69
+ function restoreOneActivePointer(active, key, bytes) {
70
+ const name = splitCatalogActiveKey(key);
71
+ if (name === null)
72
+ failClosed("workflow catalog active pointer key", key, "does not match the expected catalog-active:<name> shape");
73
+ const pointer = decodeActivePointer(bytes);
74
+ if (pointer === null)
75
+ failClosed("workflow catalog active pointer", key, "does not decode as a valid { revision, generation, activatedAt } record");
76
+ active.set(name, pointer);
77
+ }
78
+ export async function readCatalogEntry(storage, name, revision) {
79
+ const key = KEYS.catalogEntry(name, revision), bytes = await storage.get(key);
80
+ if (bytes === null)
81
+ return null;
82
+ return decodeCatalogEntryRecord(key, bytes, name, revision);
83
+ }
84
+ export async function scanCatalogEntriesForName(storage, name) {
85
+ const results = [];
86
+ for await (const [key, bytes] of storage.scan(KEYS.catalogEntryPrefix(name))) {
87
+ const split = splitCatalogEntryKey(key);
88
+ if (split === null)
89
+ failClosed("workflow catalog entry key", key, "does not match the expected catalog-entry:<name>:<revision> shape");
90
+ results.push(await decodeCatalogEntryRecord(key, bytes, split.name, split.revision));
91
+ }
92
+ return results;
93
+ }
94
+ export async function readActivePointer(storage, name) {
95
+ const bytes = await storage.get(KEYS.catalogActive(name));
96
+ if (bytes === null)
97
+ return null;
98
+ const pointer = decodeActivePointer(bytes);
99
+ if (pointer === null)
100
+ failClosed("workflow catalog active pointer", KEYS.catalogActive(name), "does not decode as a valid { revision, generation, activatedAt } record");
101
+ return pointer;
102
+ }
103
+ export async function writeCatalogEntry(storage, manifest, installedAt) {
104
+ const bytes = new TextEncoder().encode(JSON.stringify({ manifest, installedAt })), key = KEYS.catalogEntry(manifest.name, manifest.revision);
105
+ return storageConditionalBatch(storage, [{ key, expectedValue: null }], [{ type: "put", key, value: bytes }]);
106
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * In-memory and wire types for the durable workflow catalog (WFT-9/WFT-10).
3
+ *
4
+ * `WorkflowCatalog` keys entries with nested `Map<string, Map<string, ...>>`
5
+ * structures — never delimiter-joined strings — so a workflow `name` or
6
+ * `revision` equal to `'__proto__'`, `'toString'`, or containing a colon is
7
+ * always safe to store and look up.
8
+ *
9
+ * @module core/catalog/types
10
+ */
11
+ import type { WorkflowCompatibilityVerdict } from '../contract/compatibility.ts';
12
+ import type { WorkflowRevisionManifest } from '../contract/types.ts';
13
+ import type { RegisteredWorkflowDefinition } from '../types/workflow-registry.ts';
14
+ /**
15
+ * One installed `(name, revision)` catalog entry. `definition` is populated
16
+ * only when this process itself installed the entry (via
17
+ * `engine.register()`'s drain path) — an entry hydrated from durable storage
18
+ * at boot, for a revision this process has not (yet) registered, carries no
19
+ * live `definition` because a `RegisteredWorkflowDefinition` cannot be
20
+ * persisted (it holds function references). Consumers that only need
21
+ * identity (`resolveActive`, registry-snapshot's `activeRevisions`) never
22
+ * touch `definition`.
23
+ */
24
+ export type WorkflowCatalogEntry = Readonly<{
25
+ manifest: WorkflowRevisionManifest;
26
+ definition?: RegisteredWorkflowDefinition;
27
+ installedAt: number;
28
+ }>;
29
+ /**
30
+ * The durable `{ revision, generation, activatedAt }` pointer for one
31
+ * workflow name — what `engine.workflows.getActive()` returns.
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * import { Engine } from '@lostgradient/weft';
36
+ * import type { WorkflowCatalogActivePointer } from '@lostgradient/weft';
37
+ *
38
+ * const engine = new Engine();
39
+ * const pointer: WorkflowCatalogActivePointer | null = await engine.workflows.getActive('checkout');
40
+ * console.log(pointer?.generation ?? null);
41
+ * ```
42
+ */
43
+ export type WorkflowCatalogActivePointer = Readonly<{
44
+ revision: string;
45
+ generation: number;
46
+ activatedAt: number;
47
+ }>;
48
+ /**
49
+ * Public-facing view of one installed `(name, revision)` catalog entry —
50
+ * {@link WorkflowCatalogEntry} minus `definition`, which can carry a live
51
+ * function reference and must never reach a public return value.
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * import { Engine } from '@lostgradient/weft';
56
+ * import type { WorkflowRevisionRecord } from '@lostgradient/weft';
57
+ *
58
+ * const engine = new Engine();
59
+ * const record: WorkflowRevisionRecord | null = await engine.workflows.getRevision(
60
+ * 'checkout',
61
+ * 'some-revision',
62
+ * );
63
+ * console.log(record?.manifest.revision ?? null);
64
+ * ```
65
+ */
66
+ export type WorkflowRevisionRecord = Readonly<{
67
+ manifest: WorkflowRevisionManifest;
68
+ installedAt: number;
69
+ }>;
70
+ /**
71
+ * Result of {@link import('./workflow-catalog.ts').WorkflowCatalog.activateCandidate}.
72
+ * A candidate either applies (bumping the durable generation) or is refused
73
+ * with a specific, machine-readable reason — never silently ignored.
74
+ *
75
+ * @example
76
+ * ```ts
77
+ * import { Engine } from '@lostgradient/weft';
78
+ * import type { WorkflowCatalogActivationResult } from '@lostgradient/weft';
79
+ *
80
+ * const engine = new Engine();
81
+ * const active = await engine.workflows.getActive('checkout');
82
+ * const result: WorkflowCatalogActivationResult = await engine.workflows.activate(
83
+ * 'checkout',
84
+ * active?.revision ?? '',
85
+ * active === null ? {} : { expectedGeneration: active.generation },
86
+ * );
87
+ * console.log(result.applied);
88
+ * ```
89
+ */
90
+ export type WorkflowCatalogActivationResult = Readonly<{
91
+ applied: true;
92
+ pointer: WorkflowCatalogActivePointer;
93
+ }> | Readonly<{
94
+ applied: false;
95
+ reason: 'incompatible';
96
+ verdict: WorkflowCompatibilityVerdict;
97
+ }> | Readonly<{
98
+ applied: false;
99
+ reason: 'stale-generation';
100
+ currentGeneration: number;
101
+ }> | Readonly<{
102
+ applied: false;
103
+ reason: 'conflict';
104
+ }> | Readonly<{
105
+ applied: false;
106
+ reason: 'expected-generation-required';
107
+ currentGeneration: number;
108
+ }>;
File without changes
@@ -0,0 +1,165 @@
1
+ /**
2
+ * `WorkflowCatalog` — the immutable-entry, durable workflow catalog
3
+ * (WFT-9/WFT-10).
4
+ *
5
+ * In-memory state is `Map<string, Map<string, WorkflowCatalogEntry>>` for
6
+ * installed entries and `Map<string, WorkflowCatalogActivePointer>` for each
7
+ * name's active pointer — nested maps, never delimiter-joined keys, so a
8
+ * workflow `name` or `revision` equal to `'__proto__'`/`'toString'` or
9
+ * containing a colon is always handled correctly.
10
+ *
11
+ * Two activation entry points exist, matching WFT-9/WFT-10's distinct
12
+ * producers:
13
+ *
14
+ * - {@link WorkflowCatalog.activateRegistered} — unconditional, used
15
+ * exclusively by `engine.register()`'s drain path
16
+ * (`core/engine/catalog-readiness.ts`). Never consults
17
+ * `checkWorkflowCompatibility`; always wins via a bounded 5-attempt CAS
18
+ * retry loop. Registering a different version of an already-registered
19
+ * workflow must never hard-fail construction or registration (the
20
+ * `version-mismatch-recovery.test.ts` precedent), which is why this path
21
+ * is unconditional. "Unconditional" is about bypassing
22
+ * `checkWorkflowCompatibility`, not immunity to failure in general: under
23
+ * sustained contention the bounded 5-attempt CAS retry can still exhaust
24
+ * and throw {@link WorkflowCatalogActivationConflictError}, which
25
+ * propagates out of `ensureWorkflowCatalogReady` and fails
26
+ * `Engine.create()` itself (safely — `Engine.create()`'s `try`/`catch`
27
+ * disposes the half-booted engine before rethrowing).
28
+ * - {@link WorkflowCatalog.activateCandidate} — the guarded primitive:
29
+ * `checkWorkflowCompatibility`-gated, single-shot CAS (no retry — the
30
+ * caller decides whether to re-read and retry), refuses on incompatibility
31
+ * or a stale expected generation. As of WFT-11, reachable by external
32
+ * callers via `engine.workflows.activate()` — the first production
33
+ * caller. Because a multi-writer caller can activate a revision this
34
+ * process's `#entries` cache never observed, the compatibility check
35
+ * reads through to durable storage rather than trusting the cache.
36
+ * Reused as-is by later dynamic-loading work (WFT-13+).
37
+ *
38
+ * @module core/catalog/workflow-catalog
39
+ */
40
+ import { type Storage } from '../../storage/interface.ts';
41
+ import { type WorkflowCompatibilityPolicy } from '../contract/compatibility.ts';
42
+ import type { WorkflowRevisionManifest } from '../contract/types.ts';
43
+ import type { RegisteredWorkflowDefinition } from '../types/workflow-registry.ts';
44
+ import { type WorkflowCatalogRemovalOutcome } from './removal.ts';
45
+ import { type RestoredWorkflowCatalogState } from './storage-io.ts';
46
+ import type { WorkflowCatalogActivationResult, WorkflowCatalogActivePointer, WorkflowCatalogEntry, WorkflowRevisionRecord } from './types.ts';
47
+ /** Options accepted by {@link WorkflowCatalog.activateCandidate}. */
48
+ export type ActivateCandidateOptions = {
49
+ /** The generation this caller last observed; refused with `stale-generation` if it disagrees with the durable pointer. */
50
+ expectedGeneration?: number;
51
+ /** Compatibility policy; defaults to {@link DEFAULT_WORKFLOW_COMPATIBILITY_POLICY}. */
52
+ policy?: WorkflowCompatibilityPolicy;
53
+ };
54
+ export declare class WorkflowCatalog {
55
+ #private;
56
+ constructor(storage: Storage, seed?: RestoredWorkflowCatalogState);
57
+ /** Look up one installed `(name, revision)` entry, or `undefined` when not installed. */
58
+ getEntry(name: string, revision: string): WorkflowCatalogEntry | undefined;
59
+ /** Every installed revision of `name`, in no particular order. */
60
+ listRevisions(name: string): readonly WorkflowCatalogEntry[];
61
+ /** The current active pointer for `name`, or `undefined` when never activated. */
62
+ resolveActive(name: string): WorkflowCatalogActivePointer | undefined;
63
+ /**
64
+ * Resolve one installed `(name, revision)` entry as a public
65
+ * {@link WorkflowRevisionRecord} — cache hit first, else a durable
66
+ * read-through via {@link readCatalogEntry} (adopted into the local cache
67
+ * on hit, exactly like `install()`'s own read-through), else `undefined`
68
+ * when truly absent. No TOCTOU gap against a concurrent `install()` on
69
+ * this same instance: JS is single-threaded and neither this method nor
70
+ * `install()`'s fast (already-cached) path yields between the cache read
71
+ * and its use.
72
+ */
73
+ resolveEntry(name: string, revision: string): Promise<WorkflowRevisionRecord | undefined>;
74
+ /**
75
+ * Every durably installed revision of `name`, sorted by {@link compareCodepoint}
76
+ * on `revision` for a deterministic order — never `localeCompare`, per
77
+ * this codebase's determinism rule. Durable scan via
78
+ * {@link scanCatalogEntriesForName}, validated the same fail-closed way
79
+ * {@link restoreWorkflowCatalog} validates every entry it restores.
80
+ * Returns an empty array for an unknown name rather than throwing.
81
+ */
82
+ listInstalledRevisions(name: string): Promise<readonly WorkflowRevisionRecord[]>;
83
+ /**
84
+ * Whether `(name, revision)` is already durably installed — checked
85
+ * against this process's in-memory cache first, then, on a cache miss,
86
+ * durable storage itself (a different process may have installed it).
87
+ * Used by `catalog-events.ts`'s installed/activated/draining dispatch
88
+ * helper to decide whether an activation call is installing genuinely
89
+ * new content, since `install()`'s own cache-hit branch does not
90
+ * distinguish "already known to this process" from "just installed a
91
+ * moment ago by this same call."
92
+ */
93
+ hasInstalled(name: string, revision: string): Promise<boolean>;
94
+ /**
95
+ * Durably resolve `name`'s active pointer — always reads through to
96
+ * durable storage rather than trusting the in-memory `#active` cache.
97
+ * Deliberately NOT cache-first like {@link hasInstalled}: `hasInstalled`'s
98
+ * cache-hit short-circuit can only go stale in the SAFE direction (a
99
+ * different process's `remove()` durably deleting an entry this
100
+ * process's `#entries` still holds still lets `catalog.remove()`'s own
101
+ * durable re-read refuse with `'not-found'` rather than double-deleting),
102
+ * whereas a name's active pointer has no such one-directional guarantee —
103
+ * a second engine/process can durably move it (e.g. via
104
+ * `activateCandidate`, or a second engine holding the ADR&nbsp;0002
105
+ * workflow-lease) to a revision this process never installed at all, so a
106
+ * stale cache HIT here can misreport a durably-active revision as
107
+ * inactive. Used by removal and diagnostics
108
+ * (`core/engine/catalog-removal.ts`) so a durably-active revision is
109
+ * never misreported as inactive/removable; `resolveActive` stays the
110
+ * cheap, synchronous, best-effort accessor for in-process callers (e.g.
111
+ * `reserveInFlightStart`) that only care about this process's own view.
112
+ */
113
+ resolveActiveDurable(name: string): Promise<WorkflowCatalogActivePointer | undefined>;
114
+ /**
115
+ * Durably remove the installed `(name, revision)` entry — delegates the
116
+ * CAS mechanics to {@link removeCatalogEntry}. On success, evicts the
117
+ * entry from this process's in-memory `#entries` cache too, so a
118
+ * subsequent `getEntry`/`listRevisions` call in this same process never
119
+ * observes a removed revision. Refuses (`'active'`) when `revision` is
120
+ * currently the active pointer for `name` — a structural invariant this
121
+ * method itself enforces, independent of any reference-count decision a
122
+ * caller layers on top (see `core/engine/catalog-removal.ts`).
123
+ */
124
+ remove(name: string, revision: string): Promise<WorkflowCatalogRemovalOutcome>;
125
+ /**
126
+ * Install `manifest` (paired with `definition`, when this process holds
127
+ * one). Idempotent on a byte-identical reinstall for the same
128
+ * `(name, revision)` key; throws {@link WorkflowCatalogConflictError} when
129
+ * an existing entry for that key has different manifest content —
130
+ * checked against BOTH this process's in-memory cache and, on a cache
131
+ * miss, durable storage itself (a different `WorkflowCatalog`
132
+ * instance/process may already have installed this exact key). The
133
+ * durable write is CAS-guarded ({@link writeCatalogEntry}) rather than a
134
+ * plain `put`, so two processes racing to install genuinely different
135
+ * content under the same key — possible whenever `revision` is an
136
+ * explicit, non-content-derived caller value rather than the default
137
+ * content hash — cannot silently last-write-win each other; the loser
138
+ * re-reads and resolves through the same idempotent/conflict check.
139
+ *
140
+ * Defensively re-validates `name` against the wire-safe name grammar even
141
+ * though `engine.register()`'s existing `validateWorkflowOrActivityName`
142
+ * already guarantees this for the only current producer — `install()` is
143
+ * deliberately safe for a future producer that is not `engine.register()`.
144
+ */
145
+ install(manifest: WorkflowRevisionManifest, definition?: RegisteredWorkflowDefinition): Promise<WorkflowCatalogEntry>;
146
+ /**
147
+ * Unconditionally activate `manifest` for `name`, installing it first if
148
+ * needed. Used exclusively by `engine.register()`'s drain path — never
149
+ * consults `checkWorkflowCompatibility`. Reactivating the currently active
150
+ * revision is a no-op (generation unchanged); activating a different
151
+ * revision bumps the generation by exactly 1. Retries the CAS write up to
152
+ * {@link MAX_ACTIVATE_REGISTERED_ATTEMPTS} times under contention, throwing
153
+ * {@link WorkflowCatalogActivationConflictError} on exhaustion.
154
+ */
155
+ activateRegistered(name: string, manifest: WorkflowRevisionManifest, definition: RegisteredWorkflowDefinition): Promise<WorkflowCatalogActivePointer>;
156
+ /**
157
+ * The guarded activation primitive: reads the currently-active manifest
158
+ * for `name` (absence is treated as automatically compatible — first
159
+ * activation), checks `checkWorkflowCompatibility`, and refuses rather
160
+ * than applies when incompatible or when `expectedGeneration` disagrees
161
+ * with the durably-read generation. Single-shot CAS write — no retry; the
162
+ * caller decides whether to re-read and retry.
163
+ */
164
+ activateCandidate(name: string, candidateManifest: WorkflowRevisionManifest, options?: ActivateCandidateOptions): Promise<WorkflowCatalogActivationResult>;
165
+ }