@sixb/core 0.1.18 → 0.1.19

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 (404) hide show
  1. package/README.md +7 -6
  2. package/dist/actions/commit-edits.d.ts +7 -19
  3. package/dist/actions/commit-edits.d.ts.map +1 -1
  4. package/dist/actions/errors.d.ts +0 -3
  5. package/dist/actions/errors.d.ts.map +1 -1
  6. package/dist/actions/execution.d.ts +10 -3
  7. package/dist/actions/execution.d.ts.map +1 -1
  8. package/dist/actions/index.d.ts +6 -9
  9. package/dist/actions/index.d.ts.map +1 -1
  10. package/dist/actions/index.js +51 -50
  11. package/dist/actions/index.js.map +1 -1
  12. package/dist/actions/request.d.ts +14 -25
  13. package/dist/actions/request.d.ts.map +1 -1
  14. package/dist/actions/run/context.d.ts +40 -0
  15. package/dist/actions/run/context.d.ts.map +1 -0
  16. package/dist/actions/run/edits-commit.d.ts +31 -0
  17. package/dist/actions/run/edits-commit.d.ts.map +1 -0
  18. package/dist/actions/run/effects.d.ts +23 -0
  19. package/dist/actions/run/effects.d.ts.map +1 -0
  20. package/dist/actions/run/execute.d.ts +30 -0
  21. package/dist/actions/run/execute.d.ts.map +1 -0
  22. package/dist/actions/run/executor.d.ts +95 -0
  23. package/dist/actions/run/executor.d.ts.map +1 -0
  24. package/dist/actions/run/normalize.d.ts +36 -0
  25. package/dist/actions/run/normalize.d.ts.map +1 -0
  26. package/dist/actions/run/phase-types.d.ts +18 -0
  27. package/dist/actions/run/phase-types.d.ts.map +1 -0
  28. package/dist/actions/run/phases.d.ts +20 -0
  29. package/dist/actions/run/phases.d.ts.map +1 -0
  30. package/dist/actions/run/run-action.d.ts +25 -0
  31. package/dist/actions/run/run-action.d.ts.map +1 -0
  32. package/dist/actions/run/signals.d.ts +67 -0
  33. package/dist/actions/run/signals.d.ts.map +1 -0
  34. package/dist/actions/run/state.d.ts +47 -0
  35. package/dist/actions/run/state.d.ts.map +1 -0
  36. package/dist/actions/run/types.d.ts +54 -0
  37. package/dist/actions/run/types.d.ts.map +1 -0
  38. package/dist/actions/run/writeback.d.ts +17 -0
  39. package/dist/actions/run/writeback.d.ts.map +1 -0
  40. package/dist/actions/run-authorization.d.ts +1 -1
  41. package/dist/actions/run-authorization.d.ts.map +1 -1
  42. package/dist/actions/run-persistence.d.ts +53 -0
  43. package/dist/actions/run-persistence.d.ts.map +1 -0
  44. package/dist/actions/types/context.d.ts +10 -0
  45. package/dist/actions/types/context.d.ts.map +1 -1
  46. package/dist/agents/index.js +52 -55
  47. package/dist/agents/index.js.map +1 -1
  48. package/dist/agents/streams/index.js +2 -2
  49. package/dist/agents/streams/internal.js +2 -2
  50. package/dist/auth/index.js +11 -11
  51. package/dist/auth/index.js.map +1 -1
  52. package/dist/authorization/event-visibility.d.ts.map +1 -1
  53. package/dist/authorization/index.js +3 -3
  54. package/dist/bootstrap/index.js +46 -49
  55. package/dist/bootstrap/index.js.map +2 -2
  56. package/dist/{chunk-d0eb5yy6.js → chunk-1f7wpmyp.js} +5 -5
  57. package/dist/{chunk-d0eb5yy6.js.map → chunk-1f7wpmyp.js.map} +1 -1
  58. package/dist/{chunk-vgk3hknm.js → chunk-1k89ecfr.js} +1 -1
  59. package/dist/{chunk-6h5m9xjx.js → chunk-3gnrh4ve.js} +6 -10
  60. package/dist/{chunk-6h5m9xjx.js.map → chunk-3gnrh4ve.js.map} +4 -4
  61. package/dist/{chunk-htr6wjmv.js → chunk-4tcx876c.js} +15 -15
  62. package/dist/{chunk-htr6wjmv.js.map → chunk-4tcx876c.js.map} +1 -1
  63. package/dist/{chunk-za5yd58j.js → chunk-52bk93x8.js} +2 -2
  64. package/dist/{chunk-22v5bpz3.js → chunk-5e43w60z.js} +1 -1
  65. package/dist/{chunk-zxawp1n2.js → chunk-7kg3k1m9.js} +1 -1
  66. package/dist/{chunk-ny3xyvcn.js → chunk-7mbzgd9m.js} +3 -3
  67. package/dist/{chunk-b9affek3.js → chunk-7n440zgt.js} +30 -30
  68. package/dist/{chunk-b9affek3.js.map → chunk-7n440zgt.js.map} +4 -4
  69. package/dist/{chunk-kkqwzy6h.js → chunk-7t429e3f.js} +1 -1
  70. package/dist/{chunk-s4jhg8a3.js → chunk-80mc0971.js} +13 -13
  71. package/dist/{chunk-s4jhg8a3.js.map → chunk-80mc0971.js.map} +1 -1
  72. package/dist/{chunk-1b1f2d5f.js → chunk-85yvv5n0.js} +8 -8
  73. package/dist/{chunk-1b1f2d5f.js.map → chunk-85yvv5n0.js.map} +1 -1
  74. package/dist/{chunk-6fsr7y93.js → chunk-8mx7qw3y.js} +14 -13
  75. package/dist/{chunk-6fsr7y93.js.map → chunk-8mx7qw3y.js.map} +4 -4
  76. package/dist/{chunk-kpx7jrmn.js → chunk-91mg3nj9.js} +59 -59
  77. package/dist/{chunk-kpx7jrmn.js.map → chunk-91mg3nj9.js.map} +5 -5
  78. package/dist/{chunk-48e3cn2d.js → chunk-9gyp8hg1.js} +2 -2
  79. package/dist/{chunk-ewqa58h9.js → chunk-9k93ah50.js} +4 -4
  80. package/dist/{chunk-ewqa58h9.js.map → chunk-9k93ah50.js.map} +1 -1
  81. package/dist/{chunk-4wn7bxxa.js → chunk-9ybhtmg4.js} +1 -1
  82. package/dist/{chunk-fc5zfxy1.js → chunk-a40ad1ft.js} +5 -13
  83. package/dist/chunk-a40ad1ft.js.map +10 -0
  84. package/dist/{chunk-vz3gc1tm.js → chunk-aff7cyea.js} +62 -64
  85. package/dist/{chunk-vz3gc1tm.js.map → chunk-aff7cyea.js.map} +4 -4
  86. package/dist/{chunk-y2aq6ag9.js → chunk-b38szspf.js} +5 -14
  87. package/dist/chunk-b38szspf.js.map +11 -0
  88. package/dist/{chunk-r2y23egw.js → chunk-bkpke1sg.js} +1 -10
  89. package/dist/chunk-bkpke1sg.js.map +10 -0
  90. package/dist/{chunk-bs7sqkre.js → chunk-bpgy64fw.js} +1 -1
  91. package/dist/{chunk-vx2yp45y.js → chunk-c85m1hyj.js} +2 -2
  92. package/dist/{chunk-v6xpd60t.js → chunk-d2ync1v3.js} +5 -5
  93. package/dist/{chunk-fe52eczz.js → chunk-ey3pyej1.js} +6345 -2125
  94. package/dist/chunk-ey3pyej1.js.map +106 -0
  95. package/dist/{chunk-vjcd8d27.js → chunk-fa02jy15.js} +1 -1
  96. package/dist/{chunk-y05543x6.js → chunk-hvsb78a1.js} +4 -4
  97. package/dist/{chunk-y05543x6.js.map → chunk-hvsb78a1.js.map} +1 -1
  98. package/dist/{chunk-qq90g05n.js → chunk-j2bp5ryw.js} +53 -244
  99. package/dist/{chunk-qq90g05n.js.map → chunk-j2bp5ryw.js.map} +3 -3
  100. package/dist/{chunk-qmnnhde6.js → chunk-kg5j6fct.js} +1 -1
  101. package/dist/{chunk-de4z2bj6.js → chunk-kyzq4wy9.js} +6 -6
  102. package/dist/{chunk-wr8qp979.js → chunk-m427qgq0.js} +9 -5
  103. package/dist/{chunk-wr8qp979.js.map → chunk-m427qgq0.js.map} +4 -3
  104. package/dist/{chunk-e1c18xjx.js → chunk-nq6gpjt7.js} +4 -45
  105. package/dist/{chunk-e1c18xjx.js.map → chunk-nq6gpjt7.js.map} +5 -5
  106. package/dist/{chunk-ex496vct.js → chunk-nre82s12.js} +1 -1
  107. package/dist/{chunk-1hqwggsn.js → chunk-nzapm6p0.js} +1 -2
  108. package/dist/chunk-nzapm6p0.js.map +12 -0
  109. package/dist/{chunk-wt4szfec.js → chunk-pbcbn83d.js} +4 -4
  110. package/dist/{chunk-wt4szfec.js.map → chunk-pbcbn83d.js.map} +1 -1
  111. package/dist/{chunk-7hpdd6r9.js → chunk-q1vkr2ys.js} +4 -4
  112. package/dist/{chunk-zb77kjth.js → chunk-rhg02hxq.js} +3 -3
  113. package/dist/{chunk-y9jmd7e5.js → chunk-s16apyk9.js} +102 -16
  114. package/dist/chunk-s16apyk9.js.map +16 -0
  115. package/dist/{chunk-yv9czyfr.js → chunk-t6vq7bff.js} +5 -5
  116. package/dist/{chunk-qkvz64nd.js → chunk-tjb1mk5t.js} +2 -2
  117. package/dist/{chunk-p844e0rn.js → chunk-tpn40322.js} +2 -2
  118. package/dist/{chunk-rt9p8m9t.js → chunk-v9xd5wzd.js} +1 -1
  119. package/dist/{chunk-2vhg6ysa.js → chunk-vyf9kfqf.js} +1 -1
  120. package/dist/{chunk-k6hrnf35.js → chunk-wwdfe68s.js} +51 -4
  121. package/dist/chunk-wwdfe68s.js.map +11 -0
  122. package/dist/{chunk-f1zxad2t.js → chunk-wz0xs816.js} +6 -6
  123. package/dist/{chunk-f1zxad2t.js.map → chunk-wz0xs816.js.map} +1 -1
  124. package/dist/chunk-y0c4e6ex.js +16 -0
  125. package/dist/chunk-y0c4e6ex.js.map +10 -0
  126. package/dist/{chunk-qkbx3tf6.js → chunk-y7rqfrgg.js} +6 -13
  127. package/dist/{chunk-qkbx3tf6.js.map → chunk-y7rqfrgg.js.map} +4 -4
  128. package/dist/{chunk-dfm066qg.js → chunk-yxd3nnqt.js} +2 -29
  129. package/dist/chunk-yxd3nnqt.js.map +11 -0
  130. package/dist/{chunk-70jmg2zx.js → chunk-yzwja0mj.js} +4 -19
  131. package/dist/{chunk-70jmg2zx.js.map → chunk-yzwja0mj.js.map} +3 -3
  132. package/dist/{chunk-p0dqwj3n.js → chunk-zrmy8zvw.js} +66 -47
  133. package/dist/{chunk-p0dqwj3n.js.map → chunk-zrmy8zvw.js.map} +4 -4
  134. package/dist/deploy/index.d.ts +1 -1
  135. package/dist/deploy/index.d.ts.map +1 -1
  136. package/dist/deploy/internal.js +10 -8
  137. package/dist/deploy/internal.js.map +4 -4
  138. package/dist/deploy/services.d.ts +1 -2
  139. package/dist/deploy/services.d.ts.map +1 -1
  140. package/dist/deploy/types.d.ts +6 -5
  141. package/dist/deploy/types.d.ts.map +1 -1
  142. package/dist/deploy/validation.d.ts.map +1 -1
  143. package/dist/edits/index.js +3 -5
  144. package/dist/edits/index.js.map +1 -1
  145. package/dist/error-reporting/internal.js +3 -3
  146. package/dist/error-reporting/types.d.ts +2 -9
  147. package/dist/error-reporting/types.d.ts.map +1 -1
  148. package/dist/errors/catalog.d.ts +12 -0
  149. package/dist/errors/catalog.d.ts.map +1 -1
  150. package/dist/errors/internal.js +1 -1
  151. package/dist/events/definitions.d.ts +0 -5
  152. package/dist/events/definitions.d.ts.map +1 -1
  153. package/dist/events/index.d.ts +3 -3
  154. package/dist/events/index.d.ts.map +1 -1
  155. package/dist/events/index.js +6 -8
  156. package/dist/events/index.js.map +1 -1
  157. package/dist/events/scope.d.ts +0 -1
  158. package/dist/events/scope.d.ts.map +1 -1
  159. package/dist/events/scope.js +1 -1
  160. package/dist/events/selectors/builder.d.ts +1 -8
  161. package/dist/events/selectors/builder.d.ts.map +1 -1
  162. package/dist/events/selectors/index.d.ts +2 -2
  163. package/dist/events/selectors/index.d.ts.map +1 -1
  164. package/dist/events/selectors/index.js +2 -2
  165. package/dist/events/selectors/predicate.d.ts.map +1 -1
  166. package/dist/events/selectors/types.d.ts +2 -33
  167. package/dist/events/selectors/types.d.ts.map +1 -1
  168. package/dist/events/types/index.d.ts +1 -12
  169. package/dist/events/types/index.d.ts.map +1 -1
  170. package/dist/execution/agent.js +41 -44
  171. package/dist/execution/agent.js.map +1 -1
  172. package/dist/execution/primitive.js +57 -88
  173. package/dist/execution/primitive.js.map +3 -4
  174. package/dist/execution/request.js +35 -38
  175. package/dist/execution/request.js.map +2 -2
  176. package/dist/index.d.ts +3 -3
  177. package/dist/index.d.ts.map +1 -1
  178. package/dist/index.js +111 -120
  179. package/dist/index.js.map +1 -1
  180. package/dist/materialization/index.js +3 -3
  181. package/dist/materialization/index.js.map +1 -1
  182. package/dist/materialization/model.d.ts +19 -6
  183. package/dist/materialization/model.d.ts.map +1 -1
  184. package/dist/materializer/index.js +31 -31
  185. package/dist/materializer/index.js.map +1 -1
  186. package/dist/materializer/shared/normalize.d.ts.map +1 -1
  187. package/dist/models/execution/index.js +18 -18
  188. package/dist/objects/action/errors.d.ts +0 -9
  189. package/dist/objects/action/errors.d.ts.map +1 -1
  190. package/dist/objects/action/index.d.ts +3 -3
  191. package/dist/objects/action/index.d.ts.map +1 -1
  192. package/dist/objects/action/request.d.ts +2 -8
  193. package/dist/objects/action/request.d.ts.map +1 -1
  194. package/dist/objects/execution.d.ts +0 -2
  195. package/dist/objects/execution.d.ts.map +1 -1
  196. package/dist/objects/index.js +44 -29
  197. package/dist/objects/index.js.map +1 -1
  198. package/dist/objects/query/index.js +29 -29
  199. package/dist/objects/query/index.js.map +1 -1
  200. package/dist/objects/sdk/object-handle.d.ts.map +1 -1
  201. package/dist/objects/sdk/object-set.d.ts.map +1 -1
  202. package/dist/objects/service/index.d.ts +0 -1
  203. package/dist/objects/service/index.d.ts.map +1 -1
  204. package/dist/ontology/index.js +5 -5
  205. package/dist/ontology/index.js.map +1 -1
  206. package/dist/ontology/internal.js +2 -2
  207. package/dist/pipelines/index.js +28 -28
  208. package/dist/projections/internal.js +22 -22
  209. package/dist/projections/internal.js.map +1 -1
  210. package/dist/query.js +2 -2
  211. package/dist/queues/in-memory.d.ts +1 -2
  212. package/dist/queues/in-memory.d.ts.map +1 -1
  213. package/dist/queues/index.d.ts +1 -1
  214. package/dist/queues/index.d.ts.map +1 -1
  215. package/dist/queues/index.js +1 -1
  216. package/dist/queues/types.d.ts +0 -7
  217. package/dist/queues/types.d.ts.map +1 -1
  218. package/dist/runtime/host.d.ts.map +1 -1
  219. package/dist/runtime/internal.js +24 -25
  220. package/dist/runtime/internal.js.map +1 -1
  221. package/dist/runtime/resolve-definitions.d.ts.map +1 -1
  222. package/dist/runtime/types.d.ts +16 -36
  223. package/dist/runtime/types.d.ts.map +1 -1
  224. package/dist/sandboxes/index.js +5 -5
  225. package/dist/schedules/builders.d.ts.map +1 -1
  226. package/dist/schedules/index.js +3 -3
  227. package/dist/schedules/runtime.d.ts.map +1 -1
  228. package/dist/schedules/validation.d.ts +0 -1
  229. package/dist/schedules/validation.d.ts.map +1 -1
  230. package/dist/security/every.d.ts +1 -1
  231. package/dist/shares/internal.js +17 -17
  232. package/dist/storage/action-runs/failure.js +2 -2
  233. package/dist/storage/action-runs/idempotency.d.ts +3 -4
  234. package/dist/storage/action-runs/idempotency.d.ts.map +1 -1
  235. package/dist/storage/action-runs/in-memory.d.ts +2 -9
  236. package/dist/storage/action-runs/in-memory.d.ts.map +1 -1
  237. package/dist/storage/action-runs/index.d.ts +1 -3
  238. package/dist/storage/action-runs/index.d.ts.map +1 -1
  239. package/dist/storage/action-runs/provider.js +1 -1
  240. package/dist/storage/action-runs/record.d.ts +20 -0
  241. package/dist/storage/action-runs/record.d.ts.map +1 -0
  242. package/dist/storage/action-runs/types.d.ts +39 -68
  243. package/dist/storage/action-runs/types.d.ts.map +1 -1
  244. package/dist/storage/ai-limits/enforcement.js +2 -2
  245. package/dist/storage/connector-connections/provider.js +1 -1
  246. package/dist/storage/file-upload-sessions/provider.js +9 -9
  247. package/dist/storage/index.d.ts +2 -2
  248. package/dist/storage/index.d.ts.map +1 -1
  249. package/dist/storage/index.js +30 -31
  250. package/dist/storage/index.js.map +1 -1
  251. package/dist/storage/internal.d.ts +1 -1
  252. package/dist/storage/internal.d.ts.map +1 -1
  253. package/dist/storage/internal.js +9 -12
  254. package/dist/storage/internal.js.map +1 -1
  255. package/dist/storage/objects/execution-limits.d.ts +1 -1
  256. package/dist/storage/objects/execution-limits.d.ts.map +1 -1
  257. package/dist/storage/ontology/provider.js +2 -2
  258. package/dist/storage/projection-runs/provider.js +2 -2
  259. package/dist/storage/types.d.ts +1 -1
  260. package/dist/storage/types.d.ts.map +1 -1
  261. package/dist/storage/workflow-runs/provider.js +9 -9
  262. package/dist/syncs/index.js +29 -29
  263. package/dist/testing/action-execution.d.ts +34 -4
  264. package/dist/testing/action-execution.d.ts.map +1 -1
  265. package/dist/testing/action-run-storage-contract.d.ts +11 -0
  266. package/dist/testing/action-run-storage-contract.d.ts.map +1 -0
  267. package/dist/testing/index.d.ts +2 -1
  268. package/dist/testing/index.d.ts.map +1 -1
  269. package/dist/testing/index.js +3202 -2860
  270. package/dist/testing/index.js.map +7 -6
  271. package/dist/testing/materializer-storage-contract.d.ts.map +1 -1
  272. package/dist/testing/queues-contract.d.ts.map +1 -1
  273. package/dist/webhooks/internal.js +23 -23
  274. package/dist/webhooks/internal.js.map +1 -1
  275. package/dist/workers/index.js +1 -1
  276. package/dist/workflows/index.js +48 -51
  277. package/dist/workflows/index.js.map +1 -1
  278. package/package.json +1 -7
  279. package/src/actions/commit-edits.ts +9 -37
  280. package/src/actions/errors.ts +0 -4
  281. package/src/actions/execution.ts +10 -10
  282. package/src/actions/index.ts +5 -24
  283. package/src/actions/request.ts +157 -295
  284. package/src/actions/run/context.ts +193 -0
  285. package/src/actions/run/edits-commit.ts +102 -0
  286. package/src/actions/run/effects.ts +105 -0
  287. package/src/actions/run/execute.ts +113 -0
  288. package/src/actions/run/executor.ts +314 -0
  289. package/src/actions/run/normalize.ts +142 -0
  290. package/src/actions/run/phase-types.ts +20 -0
  291. package/src/actions/run/phases.ts +150 -0
  292. package/src/actions/run/run-action.ts +170 -0
  293. package/src/actions/run/signals.ts +118 -0
  294. package/src/actions/run/state.ts +95 -0
  295. package/src/actions/run/types.ts +61 -0
  296. package/src/actions/run/writeback.ts +79 -0
  297. package/src/actions/run-authorization.ts +1 -1
  298. package/src/actions/run-persistence.ts +105 -0
  299. package/src/actions/types/context.ts +10 -0
  300. package/src/authorization/event-visibility.ts +0 -12
  301. package/src/deploy/index.ts +0 -1
  302. package/src/deploy/services.ts +2 -11
  303. package/src/deploy/types.ts +6 -6
  304. package/src/deploy/validation.ts +12 -6
  305. package/src/error-reporting/types.ts +2 -10
  306. package/src/errors/catalog.ts +13 -0
  307. package/src/events/definitions.ts +0 -17
  308. package/src/events/index.ts +0 -14
  309. package/src/events/scope.ts +0 -12
  310. package/src/events/selectors/builder.ts +0 -51
  311. package/src/events/selectors/index.ts +0 -6
  312. package/src/events/selectors/predicate.ts +0 -2
  313. package/src/events/selectors/types.ts +7 -65
  314. package/src/events/types/index.ts +0 -11
  315. package/src/index.ts +1 -19
  316. package/src/materialization/model.ts +26 -9
  317. package/src/materializer/README.md +6 -1
  318. package/src/materializer/edits/commit.ts +42 -32
  319. package/src/materializer/shared/normalize.ts +25 -14
  320. package/src/objects/action/errors.ts +0 -12
  321. package/src/objects/action/index.ts +3 -3
  322. package/src/objects/action/request.ts +2 -29
  323. package/src/objects/execution.ts +2 -2
  324. package/src/objects/sdk/object-handle.ts +3 -29
  325. package/src/objects/sdk/object-set.ts +5 -30
  326. package/src/objects/service/index.ts +0 -1
  327. package/src/queues/in-memory.ts +0 -6
  328. package/src/queues/index.ts +0 -2
  329. package/src/queues/types.ts +0 -11
  330. package/src/runtime/host.ts +5 -0
  331. package/src/runtime/resolve-definitions.ts +0 -1
  332. package/src/runtime/types.ts +16 -45
  333. package/src/schedules/builders.ts +1 -4
  334. package/src/schedules/runtime.ts +0 -41
  335. package/src/schedules/validation.ts +2 -16
  336. package/src/security/every.ts +1 -1
  337. package/src/storage/action-runs/idempotency.ts +13 -41
  338. package/src/storage/action-runs/in-memory.ts +20 -311
  339. package/src/storage/action-runs/index.ts +1 -16
  340. package/src/storage/action-runs/record.ts +130 -0
  341. package/src/storage/action-runs/types.ts +37 -92
  342. package/src/storage/index.ts +1 -9
  343. package/src/storage/internal.ts +1 -7
  344. package/src/storage/objects/execution-limits.ts +3 -2
  345. package/src/storage/types.ts +2 -4
  346. package/src/testing/action-execution.ts +73 -13
  347. package/src/testing/action-run-storage-contract.ts +335 -0
  348. package/src/testing/index.ts +8 -1
  349. package/src/testing/materializer-storage-contract.ts +174 -18
  350. package/src/testing/queues-contract.ts +0 -18
  351. package/dist/actions/run-dispatch.d.ts +0 -25
  352. package/dist/actions/run-dispatch.d.ts.map +0 -1
  353. package/dist/actions/worker.d.ts +0 -3
  354. package/dist/actions/worker.d.ts.map +0 -1
  355. package/dist/actions/worker.js +0 -325
  356. package/dist/actions/worker.js.map +0 -11
  357. package/dist/chunk-1hqwggsn.js.map +0 -12
  358. package/dist/chunk-85cd7e8c.js +0 -51
  359. package/dist/chunk-85cd7e8c.js.map +0 -10
  360. package/dist/chunk-dfm066qg.js.map +0 -11
  361. package/dist/chunk-fc5zfxy1.js.map +0 -11
  362. package/dist/chunk-fe52eczz.js.map +0 -49
  363. package/dist/chunk-fx93hd2n.js +0 -1614
  364. package/dist/chunk-fx93hd2n.js.map +0 -20
  365. package/dist/chunk-k6hrnf35.js.map +0 -10
  366. package/dist/chunk-qxvp5epv.js +0 -1329
  367. package/dist/chunk-qxvp5epv.js.map +0 -36
  368. package/dist/chunk-r2y23egw.js.map +0 -10
  369. package/dist/chunk-tffvfy6s.js +0 -593
  370. package/dist/chunk-tffvfy6s.js.map +0 -15
  371. package/dist/chunk-y2aq6ag9.js.map +0 -11
  372. package/dist/chunk-y9jmd7e5.js.map +0 -15
  373. package/dist/chunk-zvx7h87b.js +0 -12
  374. package/dist/chunk-zvx7h87b.js.map +0 -10
  375. package/dist/events/types/actions.d.ts +0 -39
  376. package/dist/events/types/actions.d.ts.map +0 -1
  377. package/dist/objects/service/action-service.d.ts +0 -13
  378. package/dist/objects/service/action-service.d.ts.map +0 -1
  379. package/src/actions/run-dispatch.ts +0 -282
  380. package/src/actions/worker.ts +0 -2
  381. package/src/events/types/actions.ts +0 -42
  382. package/src/objects/service/action-service.ts +0 -43
  383. /package/dist/{chunk-vgk3hknm.js.map → chunk-1k89ecfr.js.map} +0 -0
  384. /package/dist/{chunk-za5yd58j.js.map → chunk-52bk93x8.js.map} +0 -0
  385. /package/dist/{chunk-22v5bpz3.js.map → chunk-5e43w60z.js.map} +0 -0
  386. /package/dist/{chunk-zxawp1n2.js.map → chunk-7kg3k1m9.js.map} +0 -0
  387. /package/dist/{chunk-ny3xyvcn.js.map → chunk-7mbzgd9m.js.map} +0 -0
  388. /package/dist/{chunk-kkqwzy6h.js.map → chunk-7t429e3f.js.map} +0 -0
  389. /package/dist/{chunk-48e3cn2d.js.map → chunk-9gyp8hg1.js.map} +0 -0
  390. /package/dist/{chunk-4wn7bxxa.js.map → chunk-9ybhtmg4.js.map} +0 -0
  391. /package/dist/{chunk-bs7sqkre.js.map → chunk-bpgy64fw.js.map} +0 -0
  392. /package/dist/{chunk-vx2yp45y.js.map → chunk-c85m1hyj.js.map} +0 -0
  393. /package/dist/{chunk-v6xpd60t.js.map → chunk-d2ync1v3.js.map} +0 -0
  394. /package/dist/{chunk-vjcd8d27.js.map → chunk-fa02jy15.js.map} +0 -0
  395. /package/dist/{chunk-qmnnhde6.js.map → chunk-kg5j6fct.js.map} +0 -0
  396. /package/dist/{chunk-de4z2bj6.js.map → chunk-kyzq4wy9.js.map} +0 -0
  397. /package/dist/{chunk-ex496vct.js.map → chunk-nre82s12.js.map} +0 -0
  398. /package/dist/{chunk-7hpdd6r9.js.map → chunk-q1vkr2ys.js.map} +0 -0
  399. /package/dist/{chunk-zb77kjth.js.map → chunk-rhg02hxq.js.map} +0 -0
  400. /package/dist/{chunk-yv9czyfr.js.map → chunk-t6vq7bff.js.map} +0 -0
  401. /package/dist/{chunk-qkvz64nd.js.map → chunk-tjb1mk5t.js.map} +0 -0
  402. /package/dist/{chunk-p844e0rn.js.map → chunk-tpn40322.js.map} +0 -0
  403. /package/dist/{chunk-rt9p8m9t.js.map → chunk-v9xd5wzd.js.map} +0 -0
  404. /package/dist/{chunk-2vhg6ysa.js.map → chunk-vyf9kfqf.js.map} +0 -0
@@ -0,0 +1,335 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import type {
3
+ ActionRunFailure,
4
+ ActionRunPhase,
5
+ ActionRunRecord,
6
+ RecordActionRunInput,
7
+ Storage,
8
+ } from "../storage"
9
+ import { ActionRunError } from "../storage"
10
+ import {
11
+ createTestActionExecution,
12
+ createTestActionRunRecord,
13
+ recordTestActionRun,
14
+ type TestActionRunInput,
15
+ } from "./action-execution"
16
+
17
+ export type ActionRunStorageContractStorage = Storage & {
18
+ readonly actionRuns: NonNullable<Storage["actionRuns"]>
19
+ }
20
+
21
+ export interface ActionRunStorageContractSuiteOptions<
22
+ TStorage extends ActionRunStorageContractStorage = ActionRunStorageContractStorage,
23
+ > {
24
+ readonly createStorage: () => TStorage | Promise<TStorage>
25
+ readonly cleanup?: (storage: TStorage) => void | Promise<void>
26
+ }
27
+
28
+ const projectId = "action-run-contract"
29
+ const startedAt = new Date("2026-04-29T10:00:00.000Z")
30
+ const finishedAt = new Date("2026-04-29T10:00:03.842Z")
31
+
32
+ function failure<TPhase extends ActionRunPhase>(
33
+ runId: string,
34
+ phase: TPhase,
35
+ message: string
36
+ ): ActionRunFailure<TPhase> {
37
+ return {
38
+ code: "action.phase_failed",
39
+ message,
40
+ retryable: false,
41
+ at: finishedAt.toISOString(),
42
+ details: { actionId: "createInvoice", runId, phase },
43
+ }
44
+ }
45
+
46
+ type RunOverrides = Partial<
47
+ Pick<
48
+ TestActionRunInput,
49
+ "actionId" | "subject" | "phase" | "startedAt" | "finishedAt" | "writeback"
50
+ >
51
+ > &
52
+ (
53
+ | { readonly status?: "succeeded"; readonly error?: never }
54
+ | { readonly status: "failed"; readonly error: ActionRunFailure }
55
+ )
56
+
57
+ function run(id: string, overrides: RunOverrides = {}): TestActionRunInput {
58
+ return {
59
+ id,
60
+ projectId,
61
+ actionId: "createInvoice",
62
+ subject: { kind: "none" },
63
+ params: { amount: 42 },
64
+ idempotencyKey: `action:${projectId}:${id}`,
65
+ startedAt,
66
+ finishedAt,
67
+ ...overrides,
68
+ }
69
+ }
70
+
71
+ /** Runs the record-once Action run contract against one complete storage provider. */
72
+ export function runActionRunStorageContractSuite<TStorage extends ActionRunStorageContractStorage>(
73
+ label: string,
74
+ options: ActionRunStorageContractSuiteOptions<TStorage>
75
+ ): void {
76
+ const withStorage = async (body: (storage: TStorage) => Promise<void>): Promise<void> => {
77
+ const storage = await options.createStorage()
78
+ try {
79
+ await body(storage)
80
+ } finally {
81
+ await options.cleanup?.(storage)
82
+ }
83
+ }
84
+
85
+ describe(label, () => {
86
+ test("records a terminal run once, and reads it back detached", async () => {
87
+ await withStorage(async (storage) => {
88
+ const input = await createTestActionRunRecord(
89
+ storage.executions,
90
+ run("act_1", {
91
+ subject: { kind: "object", objectTypeId: "Opportunity", primaryId: "opp-1" },
92
+ writeback: {
93
+ status: "succeeded",
94
+ completedAt: new Date("2026-04-29T10:00:01.000Z"),
95
+ result: { externalInvoiceId: "ext_1" },
96
+ },
97
+ })
98
+ )
99
+
100
+ const recorded = await storage.actionRuns.record(input)
101
+ const expected: ActionRunRecord = {
102
+ id: "act_1",
103
+ projectId,
104
+ executionId: "test_action_execution:act_1",
105
+ actionId: "createInvoice",
106
+ subject: { kind: "object", objectTypeId: "Opportunity", primaryId: "opp-1" },
107
+ status: "succeeded",
108
+ phase: "commit",
109
+ startedAt,
110
+ finishedAt,
111
+ params: { amount: 42 },
112
+ idempotencyKey: `action:${projectId}:act_1`,
113
+ writeback: {
114
+ status: "succeeded",
115
+ completedAt: new Date("2026-04-29T10:00:01.000Z"),
116
+ result: { externalInvoiceId: "ext_1" },
117
+ },
118
+ }
119
+ expect(recorded).toEqual(expected)
120
+ ;(recorded.params as { amount: number }).amount = 1
121
+ await expect(storage.actionRuns.getById({ projectId, id: "act_1" })).resolves.toEqual(
122
+ expected
123
+ )
124
+
125
+ await expect(storage.actionRuns.record(input)).rejects.toBeInstanceOf(ActionRunError)
126
+ await expect(
127
+ storage.actionRuns.record({ ...input, id: "act_2", idempotencyKey: "act_2" })
128
+ ).rejects.toBeInstanceOf(ActionRunError)
129
+ await expect(storage.actionRuns.getById({ projectId, id: "act_2" })).resolves.toBeNull()
130
+ })
131
+ })
132
+
133
+ test("requires the trusted execution created for the run", async () => {
134
+ await withStorage(async (storage) => {
135
+ const input = await createTestActionRunRecord(storage.executions, run("act_owned"))
136
+ await expect(
137
+ storage.actionRuns.record({ ...input, executionId: "missing" })
138
+ ).rejects.toThrow("does not authorize Action run")
139
+
140
+ const otherAction = await createTestActionExecution(storage.executions, {
141
+ projectId,
142
+ actionId: "otherAction",
143
+ runId: "act_other",
144
+ })
145
+ await expect(
146
+ storage.actionRuns.record({ ...input, executionId: otherAction })
147
+ ).rejects.toThrow("does not authorize Action run")
148
+ await expect(storage.actionRuns.getById({ projectId, id: "act_owned" })).resolves.toBeNull()
149
+ })
150
+ })
151
+
152
+ test("refuses a record a run cannot end with", async () => {
153
+ await withStorage(async (storage) => {
154
+ const succeeded = await createTestActionRunRecord(storage.executions, run("act_invalid"))
155
+ const writebackFailure = failure("act_invalid", "writeback", "Upstream rejected it")
156
+ const invalid: readonly RecordActionRunInput[] = [
157
+ { ...succeeded, phase: "effects" },
158
+ { ...succeeded, status: "failed", phase: "commit", error: writebackFailure },
159
+ {
160
+ ...succeeded,
161
+ status: "failed",
162
+ phase: "writeback",
163
+ error: failure("another-run", "writeback", "Upstream rejected it"),
164
+ },
165
+ { ...succeeded, finishedAt: new Date(startedAt.getTime() - 1) },
166
+ ]
167
+ for (const input of invalid) {
168
+ await expect(storage.actionRuns.record(input)).rejects.toThrow()
169
+ }
170
+ await expect(
171
+ storage.actionRuns.getById({ projectId, id: "act_invalid" })
172
+ ).resolves.toBeNull()
173
+ })
174
+ })
175
+
176
+ test("records a failure with the phase it ended in", async () => {
177
+ await withStorage(async (storage) => {
178
+ const error = failure("act_failed", "writeback", "TeamLeader API returned 503")
179
+ const recorded = await recordTestActionRun(
180
+ storage,
181
+ run("act_failed", {
182
+ status: "failed",
183
+ error,
184
+ writeback: { status: "failed", completedAt: finishedAt, error },
185
+ })
186
+ )
187
+
188
+ expect(recorded).toMatchObject({ status: "failed", phase: "writeback", error })
189
+ await expect(
190
+ storage.actionRuns.getById({ projectId, id: "act_failed" })
191
+ ).resolves.toMatchObject({
192
+ status: "failed",
193
+ phase: "writeback",
194
+ error,
195
+ writeback: { status: "failed", error },
196
+ })
197
+ })
198
+ })
199
+
200
+ test("records the effects of a run that committed, once", async () => {
201
+ await withStorage(async (storage) => {
202
+ await recordTestActionRun(storage, run("act_effects"))
203
+ const error = failure("act_effects", "effects", "Slack timed out")
204
+ const completedAt = new Date("2026-04-29T10:00:05.000Z")
205
+
206
+ const recorded = await storage.actionRuns.recordEffects({
207
+ id: "act_effects",
208
+ projectId,
209
+ status: "failed",
210
+ completedAt,
211
+ error,
212
+ })
213
+ expect(recorded).toMatchObject({
214
+ status: "succeeded",
215
+ phase: "effects",
216
+ effects: { status: "failed", completedAt, error },
217
+ })
218
+ expect(recorded.error).toBeUndefined()
219
+
220
+ // The same outcome again changes nothing, its completion time included.
221
+ await expect(
222
+ storage.actionRuns.recordEffects({
223
+ id: "act_effects",
224
+ projectId,
225
+ status: "failed",
226
+ completedAt: new Date("2026-04-29T10:00:06.000Z"),
227
+ error,
228
+ })
229
+ ).resolves.toEqual(recorded)
230
+ await expect(
231
+ storage.actionRuns.recordEffects({ id: "act_effects", projectId, status: "succeeded" })
232
+ ).rejects.toBeInstanceOf(ActionRunError)
233
+ await expect(storage.actionRuns.getById({ projectId, id: "act_effects" })).resolves.toEqual(
234
+ recorded
235
+ )
236
+ })
237
+ })
238
+
239
+ test("records effects only on a succeeded run in its commit phase", async () => {
240
+ await withStorage(async (storage) => {
241
+ await recordTestActionRun(storage, run("act_no_edits", { phase: "writeback" }))
242
+ await recordTestActionRun(
243
+ storage,
244
+ run("act_rejected", {
245
+ status: "failed",
246
+ error: failure("act_rejected", "commit", "Commit failed"),
247
+ })
248
+ )
249
+
250
+ for (const id of ["act_no_edits", "act_rejected", "act_missing"]) {
251
+ await expect(
252
+ storage.actionRuns.recordEffects({ id, projectId, status: "succeeded" })
253
+ ).rejects.toBeInstanceOf(ActionRunError)
254
+ }
255
+ await expect(
256
+ storage.actionRuns.getById({ projectId, id: "act_no_edits" })
257
+ ).resolves.toMatchObject({ phase: "writeback" })
258
+ })
259
+ })
260
+
261
+ test("lists runs with filters, in start order, by page", async () => {
262
+ await withStorage(async (storage) => {
263
+ const at = (minute: number) => new Date(Date.UTC(2026, 3, 29, 10, minute))
264
+ await recordTestActionRun(
265
+ storage,
266
+ run("act_1", {
267
+ subject: { kind: "object", objectTypeId: "Opportunity", primaryId: "opp-1" },
268
+ startedAt: at(0),
269
+ finishedAt: at(1),
270
+ status: "failed",
271
+ error: failure("act_1", "validation", "Invalid amount"),
272
+ })
273
+ )
274
+ await recordTestActionRun(
275
+ storage,
276
+ run("act_2", {
277
+ subject: { kind: "object", objectTypeId: "Opportunity", primaryId: "opp-2" },
278
+ startedAt: at(10),
279
+ finishedAt: at(11),
280
+ })
281
+ )
282
+ await recordTestActionRun(
283
+ storage,
284
+ run("act_3", { actionId: "closeOpportunity", startedAt: at(20), finishedAt: at(21) })
285
+ )
286
+
287
+ const succeeded = await storage.actionRuns.list({
288
+ projectId,
289
+ actionId: "createInvoice",
290
+ statuses: ["succeeded"],
291
+ startedAfter: at(5),
292
+ limit: 1,
293
+ })
294
+ expect(succeeded).toMatchObject({ total: 1, hasMore: false })
295
+ expect(succeeded.runs.map((recorded) => recorded.id)).toEqual(["act_2"])
296
+
297
+ const all = await storage.actionRuns.list({ projectId, order: "asc", limit: 2 })
298
+ expect(all).toMatchObject({ total: 3, hasMore: true })
299
+ expect(all.runs.map((recorded) => recorded.id)).toEqual(["act_1", "act_2"])
300
+
301
+ const latest = await storage.actionRuns.list({ projectId, startedBefore: at(15) })
302
+ expect(latest.runs.map((recorded) => recorded.id)).toEqual(["act_2", "act_1"])
303
+
304
+ const forObject = await storage.actionRuns.list({
305
+ projectId,
306
+ objectTypeId: "Opportunity",
307
+ primaryId: "opp-1",
308
+ })
309
+ expect(forObject.runs.map((recorded) => recorded.id)).toEqual(["act_1"])
310
+ })
311
+ })
312
+
313
+ test("drops a record written in a transaction that rolls back", async () => {
314
+ await withStorage(async (storage) => {
315
+ const input = await createTestActionRunRecord(storage.executions, run("act_rolled_back"))
316
+ await expect(
317
+ storage.transaction(async (tx) => {
318
+ await tx.actionRuns?.record(input)
319
+ throw new Error("rollback")
320
+ })
321
+ ).rejects.toThrow("rollback")
322
+ await expect(
323
+ storage.actionRuns.getById({ projectId, id: "act_rolled_back" })
324
+ ).resolves.toBeNull()
325
+
326
+ await storage.transaction(async (tx) => {
327
+ await tx.actionRuns?.record(input)
328
+ })
329
+ await expect(
330
+ storage.actionRuns.getById({ projectId, id: "act_rolled_back" })
331
+ ).resolves.toMatchObject({ status: "succeeded" })
332
+ })
333
+ })
334
+ })
335
+ }
@@ -1,7 +1,14 @@
1
1
  export {
2
2
  createTestActionExecution,
3
- queueTestActionRun,
3
+ createTestActionRunRecord,
4
+ recordTestActionRun,
5
+ type TestActionRunInput,
4
6
  } from "./action-execution"
7
+ export {
8
+ type ActionRunStorageContractStorage,
9
+ type ActionRunStorageContractSuiteOptions,
10
+ runActionRunStorageContractSuite,
11
+ } from "./action-run-storage-contract"
5
12
  export { createTestAgentExecution } from "./agent-execution"
6
13
  export {
7
14
  type AgentStorageContractSuiteOptions,
@@ -13,6 +13,7 @@ import {
13
13
  import type { TrustedPrimitiveRef } from "../execution"
14
14
  import { restoreTrustedPrimitiveExecutionScope } from "../execution/durable"
15
15
  import { createTestingScope } from "../execution/scopes"
16
+ import { MaterializationConflictError } from "../materialization/errors"
16
17
  import type {
17
18
  ExpectedLinkRevision,
18
19
  ExpectedObjectRevision,
@@ -35,8 +36,13 @@ import type {
35
36
  PlannedReplacementIdentity,
36
37
  ProjectionExecution,
37
38
  ProjectionRunStorage,
39
+ RecordActionRunInput,
38
40
  } from "../storage"
39
- import { createTestActionExecution, queueTestActionRun } from "./action-execution"
41
+ import {
42
+ createTestActionExecution,
43
+ createTestActionRunRecord,
44
+ recordTestActionRun,
45
+ } from "./action-execution"
40
46
  import { createTestProjectionExecution, startTestProjectionRun } from "./projection-execution"
41
47
 
42
48
  export interface MaterializerStorageContractProvider<TStorage extends Storage> {
@@ -406,23 +412,29 @@ export function runMaterializerStorageContractSuite<TStorage extends Storage>(
406
412
  })
407
413
  ).toBeNull()
408
414
 
409
- await createTestActionExecution(storage.executions, {
415
+ // A run id is recorded once, so a commit for a run that is already recorded commits nothing.
416
+ const recordedRun = {
417
+ id: "recorded-action-run",
410
418
  projectId: "materializer-storage-contract",
411
419
  actionId: "renameDevice",
412
- runId: "missing-action-run",
413
- })
414
- const missingActionMaterializer = await primitiveMaterializer(materializer, storage, {
420
+ subject: { kind: "object", objectTypeId: Device.id, primaryId: "one" },
421
+ params: {},
422
+ idempotencyKey: "recorded-action-run",
423
+ } as const
424
+ await recordTestActionRun(storage, { ...recordedRun, phase: "validation" })
425
+ const recordedActionMaterializer = await primitiveMaterializer(materializer, storage, {
415
426
  kind: "action",
416
427
  id: "renameDevice",
417
- runId: "missing-action-run",
428
+ runId: recordedRun.id,
418
429
  })
419
430
  await expect(
420
- missingActionMaterializer.edits.commit({
431
+ recordedActionMaterializer.edits.commit({
421
432
  mode: "atomic",
422
- source: { kind: "action", actionId: "renameDevice", runId: "missing-action-run" },
433
+ source: { kind: "action", actionId: "renameDevice", runId: recordedRun.id },
434
+ run: await createTestActionRunRecord(storage.executions, recordedRun),
423
435
  operations: [
424
436
  {
425
- id: "missing-run-rename",
437
+ id: "recorded-run-rename",
426
438
  kind: "object.patch",
427
439
  ref: { objectTypeId: Device.id, primaryId: "one" },
428
440
  set: { name: "Must not persist" },
@@ -434,9 +446,15 @@ export function runMaterializerStorageContractSuite<TStorage extends Storage>(
434
446
  expectedLinks: [],
435
447
  expectedLinkScopes: [],
436
448
  })
437
- ).rejects.toThrow("missing-action-run")
449
+ ).rejects.toThrow("recorded-action-run")
450
+ expect(
451
+ await storage.actionRuns.getById({
452
+ projectId: "materializer-storage-contract",
453
+ id: recordedRun.id,
454
+ })
455
+ ).toMatchObject({ phase: "validation" })
438
456
 
439
- await queueTestActionRun(storage, {
457
+ const actionRun = await createTestActionRunRecord(storage.executions, {
440
458
  id: "action-run",
441
459
  projectId: "materializer-storage-contract",
442
460
  actionId: "renameDevice",
@@ -444,10 +462,6 @@ export function runMaterializerStorageContractSuite<TStorage extends Storage>(
444
462
  params: {},
445
463
  idempotencyKey: "action-run",
446
464
  })
447
- await storage.actionRuns.start({
448
- id: "action-run",
449
- projectId: "materializer-storage-contract",
450
- })
451
465
  const actionMaterializer = await primitiveMaterializer(materializer, storage, {
452
466
  kind: "action",
453
467
  id: "renameDevice",
@@ -456,6 +470,7 @@ export function runMaterializerStorageContractSuite<TStorage extends Storage>(
456
470
  const actionCommit = await actionMaterializer.edits.commit({
457
471
  mode: "atomic",
458
472
  source: { kind: "action", actionId: "renameDevice", runId: "action-run" },
473
+ run: actionRun,
459
474
  operations: [
460
475
  {
461
476
  id: "rename",
@@ -470,6 +485,13 @@ export function runMaterializerStorageContractSuite<TStorage extends Storage>(
470
485
  expectedLinks: [],
471
486
  expectedLinkScopes: [],
472
487
  })
488
+ // The commit recorded the run it carried, with its edits.
489
+ expect(
490
+ await storage.actionRuns.getById({
491
+ projectId: "materializer-storage-contract",
492
+ id: "action-run",
493
+ })
494
+ ).toMatchObject({ status: "succeeded", phase: "commit", executionId: actionRun.executionId })
473
495
  expect(
474
496
  await storage.ontology.commits.getByOrigin({
475
497
  projectId: "materializer-storage-contract",
@@ -941,7 +963,7 @@ export function runMaterializerStorageContractSuite<TStorage extends Storage>(
941
963
  })
942
964
  )
943
965
  const commitObservation = async (runId: string, name: string) => {
944
- await queueTestActionRun(storage, {
966
+ const run = await createTestActionRunRecord(storage.executions, {
945
967
  id: runId,
946
968
  projectId: "materializer-storage-contract",
947
969
  actionId: "observePeers",
@@ -949,7 +971,6 @@ export function runMaterializerStorageContractSuite<TStorage extends Storage>(
949
971
  params: {},
950
972
  idempotencyKey: runId,
951
973
  })
952
- await storage.actionRuns.start({ id: runId, projectId: "materializer-storage-contract" })
953
974
  const actionMaterializer = await primitiveMaterializer(materializer, storage, {
954
975
  kind: "action",
955
976
  id: "observePeers",
@@ -958,6 +979,7 @@ export function runMaterializerStorageContractSuite<TStorage extends Storage>(
958
979
  return actionMaterializer.edits.commit({
959
980
  mode: "atomic",
960
981
  source: { kind: "action", actionId: "observePeers", runId },
982
+ run,
961
983
  operations: [
962
984
  {
963
985
  id: `record-${runId}`,
@@ -1000,6 +1022,13 @@ export function runMaterializerStorageContractSuite<TStorage extends Storage>(
1000
1022
  primaryId: source.primaryId,
1001
1023
  })
1002
1024
  ).toMatchObject({ properties: { name: "observed peers" } })
1025
+ // The conflict fails the commit after it inserted the run's record, which rolls back with it.
1026
+ expect(
1027
+ await storage.actionRuns.getById({
1028
+ projectId: "materializer-storage-contract",
1029
+ id: "stale-many-scope-action",
1030
+ })
1031
+ ).toBeNull()
1003
1032
  } finally {
1004
1033
  await provider.cleanup?.(createdStorage)
1005
1034
  }
@@ -1354,6 +1383,97 @@ export function runMaterializerStorageContractSuite<TStorage extends Storage>(
1354
1383
  }
1355
1384
  })
1356
1385
 
1386
+ // Two processes can execute one run id, each under an execution of its own, and commit its edits
1387
+ // at the same time: the second commit waits for the first, then replays it and is refused.
1388
+ // Removal proof: drop `recordActionRun` from `executeEditTransaction`
1389
+ // (`materializer/edits/commit.ts`), and no record holds the run that committed.
1390
+ if (provider.concurrentTransactions) {
1391
+ test(`${name} commits a run id once when two of its executions commit together`, async () => {
1392
+ const createdStorage = await provider.createStorage()
1393
+ const storage = requireContractStorage(createdStorage)
1394
+ const primitive = { kind: "action", id: "createDevice", runId: "shared-run" } as const
1395
+ const scopeOf = async (executionId: string) => {
1396
+ const execution = await storage.executions.getById({
1397
+ projectId: CONTRACT_PROJECT,
1398
+ id: executionId,
1399
+ })
1400
+ if (!execution) throw new Error(`Test execution '${executionId}' is missing.`)
1401
+ return restoreTrustedPrimitiveExecutionScope({ execution, primitive })
1402
+ }
1403
+ const commitOf = (run: RecordActionRunInput, primaryId: string) => ({
1404
+ mode: "atomic" as const,
1405
+ source: { kind: "action" as const, actionId: "createDevice", runId: "shared-run" },
1406
+ run,
1407
+ operations: [
1408
+ {
1409
+ id: "create",
1410
+ kind: "object.create" as const,
1411
+ ref: deviceRef(primaryId),
1412
+ properties: { id: primaryId, name: primaryId },
1413
+ },
1414
+ ],
1415
+ expectedObjects: [],
1416
+ expectedLinks: [],
1417
+ expectedLinkScopes: [],
1418
+ })
1419
+ try {
1420
+ const firstRun = await createTestActionRunRecord(storage.executions, {
1421
+ id: "shared-run",
1422
+ projectId: CONTRACT_PROJECT,
1423
+ actionId: "createDevice",
1424
+ subject: { kind: "none" },
1425
+ params: {},
1426
+ idempotencyKey: "shared-run",
1427
+ })
1428
+ const secondRun = {
1429
+ ...firstRun,
1430
+ executionId: await createTestActionExecution(storage.executions, {
1431
+ projectId: CONTRACT_PROJECT,
1432
+ actionId: "createDevice",
1433
+ runId: "shared-run",
1434
+ executionId: "shared-run-second",
1435
+ }),
1436
+ }
1437
+ const paused = pauseBeforeFinalize(storage)
1438
+ const first = contractMaterializer(paused.storage)
1439
+ .withScope(await scopeOf(firstRun.executionId))
1440
+ .edits.commit(commitOf(firstRun, "first"))
1441
+ // It has recorded the run and holds its transaction open.
1442
+ await paused.reached
1443
+
1444
+ let retries = 0
1445
+ const recording = observeActionRunRecords(storage)
1446
+ const second = contractMaterializer(recording.storage, {}, () => {
1447
+ retries += 1
1448
+ })
1449
+ .withScope(await scopeOf(secondRun.executionId))
1450
+ .edits.commit(commitOf(secondRun, "second"))
1451
+ .then(
1452
+ () => null,
1453
+ (error: unknown) => error
1454
+ )
1455
+ // Its transaction reads before the first one commits, and then records the run too.
1456
+ await recording.reached
1457
+ paused.release()
1458
+
1459
+ await expect(first).resolves.toMatchObject({ created: true })
1460
+ const refused = await second
1461
+ expect(refused).toBeInstanceOf(MaterializationConflictError)
1462
+ expect(refused instanceof MaterializationConflictError ? refused.kind : refused).toBe(
1463
+ "idempotency"
1464
+ )
1465
+ expect(retries).toBeGreaterThan(0)
1466
+ expect(
1467
+ await storage.actionRuns.getById({ projectId: CONTRACT_PROJECT, id: "shared-run" })
1468
+ ).toMatchObject({ executionId: firstRun.executionId })
1469
+ expect(await device(storage, "first")).not.toBeNull()
1470
+ expect(await device(storage, "second")).toBeNull()
1471
+ } finally {
1472
+ await provider.cleanup?.(createdStorage)
1473
+ }
1474
+ })
1475
+ }
1476
+
1357
1477
  // Removal proof: in PostgreSQL's `committedSince`, count only commits at or past the
1358
1478
  // watermark's xmax; the edit, in flight when the plan opened, is then taken as seen.
1359
1479
  if (provider.concurrentTransactions) {
@@ -1907,7 +2027,8 @@ const CONTRACT_PROJECT = "materializer-storage-contract"
1907
2027
 
1908
2028
  function contractMaterializer(
1909
2029
  storage: Storage,
1910
- batching: { readonly transactionReplanRows?: number } = {}
2030
+ batching: { readonly transactionReplanRows?: number } = {},
2031
+ onSerializationRetry?: () => void
1911
2032
  ): OntologyMaterializer {
1912
2033
  return createOntologyMaterializer({
1913
2034
  projectId: CONTRACT_PROJECT,
@@ -1916,6 +2037,7 @@ function contractMaterializer(
1916
2037
  storage,
1917
2038
  dependencies: {
1918
2039
  batching: { sourceStageRows: 1, statePageRows: 1, planChunkRows: 1, ...batching },
2040
+ ...(onSerializationRetry === undefined ? {} : { onSerializationRetry }),
1919
2041
  },
1920
2042
  })
1921
2043
  }
@@ -1986,6 +2108,40 @@ function observePlanning(
1986
2108
  }
1987
2109
 
1988
2110
  /** The same storage, whose transactions wait before they finalize until `release` is called. */
2111
+ /** Resolves `reached` when a transaction of `storage` first records an Action run. */
2112
+ function observeActionRunRecords(storage: ContractStorage) {
2113
+ const reached = Promise.withResolvers<void>()
2114
+ const observed = new Proxy(storage, {
2115
+ get(target, property) {
2116
+ if (property !== "transaction") {
2117
+ const value = Reflect.get(target, property, target)
2118
+ return typeof value === "function" ? value.bind(target) : value
2119
+ }
2120
+ return (run: (tx: Storage) => unknown, options?: Parameters<Storage["transaction"]>[1]) =>
2121
+ target.transaction((tx) => {
2122
+ const actionRuns = tx.actionRuns
2123
+ if (!actionRuns) return run(tx)
2124
+ const recording: ActionRunStorage = {
2125
+ record: (input) => {
2126
+ reached.resolve()
2127
+ return actionRuns.record(input)
2128
+ },
2129
+ recordEffects: (input) => actionRuns.recordEffects(input),
2130
+ getById: (input) => actionRuns.getById(input),
2131
+ list: (input) => actionRuns.list(input),
2132
+ }
2133
+ return run(
2134
+ new Proxy(tx, {
2135
+ get: (store, key) =>
2136
+ key === "actionRuns" ? recording : Reflect.get(store, key, store),
2137
+ })
2138
+ )
2139
+ }, options)
2140
+ },
2141
+ })
2142
+ return { storage: observed, reached: reached.promise }
2143
+ }
2144
+
1989
2145
  function pauseBeforeFinalize(storage: ContractStorage) {
1990
2146
  let reach!: () => void
1991
2147
  const reached = new Promise<void>((resolve) => {
@@ -388,17 +388,6 @@ export function runQueueContractSuite(label: string, options: QueueContractSuite
388
388
  },
389
389
  ],
390
390
  })
391
- await queues.actions.enqueue({
392
- projectId: "project-a",
393
- jobs: [
394
- {
395
- type: "action.run.requested",
396
- payload: {
397
- runId: "act_1",
398
- },
399
- },
400
- ],
401
- })
402
391
  await queues.agents.enqueue({
403
392
  projectId: "project-a",
404
393
  jobs: [
@@ -433,11 +422,6 @@ export function runQueueContractSuite(label: string, options: QueueContractSuite
433
422
  workerId: "workflow-worker-1",
434
423
  limit: 10,
435
424
  })
436
- const actionLane = await queues.actions.claim({
437
- projectId: "project-a",
438
- workerId: "action-worker-1",
439
- limit: 10,
440
- })
441
425
  const agentLane = await queues.agents.claim({
442
426
  projectId: "project-a",
443
427
  workerId: "agent-worker-1",
@@ -460,8 +444,6 @@ export function runQueueContractSuite(label: string, options: QueueContractSuite
460
444
  expect(projectionLane[0]?.job.payload.runId).toBe("projection-run-1")
461
445
  expect(workflowLane).toHaveLength(1)
462
446
  expect(workflowLane[0]?.job.payload.runId).toBe("workflow-run-1")
463
- expect(actionLane).toHaveLength(1)
464
- expect(actionLane[0]?.job.payload.runId).toBe("act_1")
465
447
  expect(agentLane).toHaveLength(1)
466
448
  expect(agentLane[0]?.job).toMatchObject({
467
449
  type: "agent.run.requested",