@shware/workflow 3.3.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. package/README.md +230 -40
  2. package/dist/apns.cjs +113 -26
  3. package/dist/apns.cjs.map +1 -1
  4. package/dist/apns.d.cts +52 -6
  5. package/dist/apns.d.cts.map +1 -1
  6. package/dist/apns.d.mts +52 -6
  7. package/dist/apns.d.mts.map +1 -1
  8. package/dist/apns.mjs +111 -27
  9. package/dist/apns.mjs.map +1 -1
  10. package/dist/aws/engine-step.cjs +5 -5
  11. package/dist/aws/engine-step.cjs.map +1 -1
  12. package/dist/aws/engine-step.d.cts +8 -2
  13. package/dist/aws/engine-step.d.cts.map +1 -1
  14. package/dist/aws/engine-step.d.mts +8 -2
  15. package/dist/aws/engine-step.d.mts.map +1 -1
  16. package/dist/aws/engine-step.mjs +5 -5
  17. package/dist/aws/engine-step.mjs.map +1 -1
  18. package/dist/aws/index.cjs +50 -6
  19. package/dist/aws/index.cjs.map +1 -1
  20. package/dist/aws/index.d.cts +4 -3
  21. package/dist/aws/index.d.cts.map +1 -1
  22. package/dist/aws/index.d.mts +4 -3
  23. package/dist/aws/index.d.mts.map +1 -1
  24. package/dist/aws/index.mjs +49 -7
  25. package/dist/aws/index.mjs.map +1 -1
  26. package/dist/aws/instances.cjs +29 -6
  27. package/dist/aws/instances.cjs.map +1 -1
  28. package/dist/aws/instances.d.cts +11 -1
  29. package/dist/aws/instances.d.cts.map +1 -1
  30. package/dist/aws/instances.d.mts +11 -1
  31. package/dist/aws/instances.d.mts.map +1 -1
  32. package/dist/aws/instances.mjs +27 -4
  33. package/dist/aws/instances.mjs.map +1 -1
  34. package/dist/client.cjs +4 -1
  35. package/dist/client.cjs.map +1 -1
  36. package/dist/client.d.cts +2 -2
  37. package/dist/client.d.cts.map +1 -1
  38. package/dist/client.d.mts +2 -2
  39. package/dist/client.d.mts.map +1 -1
  40. package/dist/client.mjs +4 -1
  41. package/dist/client.mjs.map +1 -1
  42. package/dist/cloudflare/bindings.cjs +28 -25
  43. package/dist/cloudflare/bindings.cjs.map +1 -1
  44. package/dist/cloudflare/bindings.d.cts +15 -129
  45. package/dist/cloudflare/bindings.d.cts.map +1 -1
  46. package/dist/cloudflare/bindings.d.mts +15 -129
  47. package/dist/cloudflare/bindings.d.mts.map +1 -1
  48. package/dist/cloudflare/bindings.mjs +28 -23
  49. package/dist/cloudflare/bindings.mjs.map +1 -1
  50. package/dist/cloudflare/d1-env.cjs +6 -6
  51. package/dist/cloudflare/d1-env.cjs.map +1 -1
  52. package/dist/cloudflare/d1-env.d.cts +5 -5
  53. package/dist/cloudflare/d1-env.d.mts +5 -5
  54. package/dist/cloudflare/d1-env.mjs +6 -6
  55. package/dist/cloudflare/d1-env.mjs.map +1 -1
  56. package/dist/cloudflare/index.cjs +29 -18
  57. package/dist/cloudflare/index.d.cts +8 -4
  58. package/dist/cloudflare/index.d.mts +8 -4
  59. package/dist/cloudflare/index.mjs +9 -5
  60. package/dist/cloudflare/runner.cjs +29 -14
  61. package/dist/cloudflare/runner.cjs.map +1 -1
  62. package/dist/cloudflare/runner.d.cts +9 -2
  63. package/dist/cloudflare/runner.d.cts.map +1 -1
  64. package/dist/cloudflare/runner.d.mts +9 -2
  65. package/dist/cloudflare/runner.d.mts.map +1 -1
  66. package/dist/cloudflare/runner.mjs +28 -13
  67. package/dist/cloudflare/runner.mjs.map +1 -1
  68. package/dist/cloudflare/schema.sql +27 -6
  69. package/dist/cloudflare/worker.cjs +19 -9
  70. package/dist/cloudflare/worker.cjs.map +1 -1
  71. package/dist/cloudflare/worker.d.cts +2 -2
  72. package/dist/cloudflare/worker.d.cts.map +1 -1
  73. package/dist/cloudflare/worker.d.mts +2 -2
  74. package/dist/cloudflare/worker.d.mts.map +1 -1
  75. package/dist/cloudflare/worker.mjs +19 -9
  76. package/dist/cloudflare/worker.mjs.map +1 -1
  77. package/dist/config.cjs +21 -5
  78. package/dist/config.cjs.map +1 -1
  79. package/dist/config.mjs +21 -5
  80. package/dist/config.mjs.map +1 -1
  81. package/dist/dsl/action.cjs +3 -6
  82. package/dist/dsl/action.cjs.map +1 -1
  83. package/dist/dsl/action.d.cts +12 -11
  84. package/dist/dsl/action.d.cts.map +1 -1
  85. package/dist/dsl/action.d.mts +12 -11
  86. package/dist/dsl/action.d.mts.map +1 -1
  87. package/dist/dsl/action.mjs +3 -6
  88. package/dist/dsl/action.mjs.map +1 -1
  89. package/dist/dsl/bundle.cjs +35 -31
  90. package/dist/dsl/bundle.cjs.map +1 -1
  91. package/dist/dsl/bundle.d.cts.map +1 -1
  92. package/dist/dsl/bundle.d.mts.map +1 -1
  93. package/dist/dsl/bundle.mjs +35 -31
  94. package/dist/dsl/bundle.mjs.map +1 -1
  95. package/dist/dsl/condition.cjs +13 -1
  96. package/dist/dsl/condition.cjs.map +1 -1
  97. package/dist/dsl/condition.d.cts +9 -1
  98. package/dist/dsl/condition.d.cts.map +1 -1
  99. package/dist/dsl/condition.d.mts +9 -1
  100. package/dist/dsl/condition.d.mts.map +1 -1
  101. package/dist/dsl/condition.mjs +13 -2
  102. package/dist/dsl/condition.mjs.map +1 -1
  103. package/dist/dsl/flow.cjs +33 -11
  104. package/dist/dsl/flow.cjs.map +1 -1
  105. package/dist/dsl/flow.d.cts +9 -1
  106. package/dist/dsl/flow.d.cts.map +1 -1
  107. package/dist/dsl/flow.d.mts +9 -1
  108. package/dist/dsl/flow.d.mts.map +1 -1
  109. package/dist/dsl/flow.mjs +34 -13
  110. package/dist/dsl/flow.mjs.map +1 -1
  111. package/dist/dsl/workflow.cjs +2 -3
  112. package/dist/dsl/workflow.cjs.map +1 -1
  113. package/dist/dsl/workflow.d.cts.map +1 -1
  114. package/dist/dsl/workflow.d.mts.map +1 -1
  115. package/dist/dsl/workflow.mjs +3 -4
  116. package/dist/dsl/workflow.mjs.map +1 -1
  117. package/dist/engine/actions.cjs +1 -8
  118. package/dist/engine/actions.cjs.map +1 -1
  119. package/dist/engine/actions.d.cts +1 -12
  120. package/dist/engine/actions.d.cts.map +1 -1
  121. package/dist/engine/actions.d.mts +1 -12
  122. package/dist/engine/actions.d.mts.map +1 -1
  123. package/dist/engine/actions.mjs +1 -8
  124. package/dist/engine/actions.mjs.map +1 -1
  125. package/dist/engine/condition.cjs +25 -5
  126. package/dist/engine/condition.cjs.map +1 -1
  127. package/dist/engine/condition.d.cts +12 -3
  128. package/dist/engine/condition.d.cts.map +1 -1
  129. package/dist/engine/condition.d.mts +12 -3
  130. package/dist/engine/condition.d.mts.map +1 -1
  131. package/dist/engine/condition.mjs +25 -6
  132. package/dist/engine/condition.mjs.map +1 -1
  133. package/dist/engine/index.cjs +3 -0
  134. package/dist/engine/index.d.cts +5 -5
  135. package/dist/engine/index.d.mts +5 -5
  136. package/dist/engine/index.mjs +4 -4
  137. package/dist/engine/interpreter.cjs +27 -20
  138. package/dist/engine/interpreter.cjs.map +1 -1
  139. package/dist/engine/interpreter.d.cts.map +1 -1
  140. package/dist/engine/interpreter.d.mts.map +1 -1
  141. package/dist/engine/interpreter.mjs +27 -20
  142. package/dist/engine/interpreter.mjs.map +1 -1
  143. package/dist/engine/ports.d.cts +15 -6
  144. package/dist/engine/ports.d.cts.map +1 -1
  145. package/dist/engine/ports.d.mts +15 -6
  146. package/dist/engine/ports.d.mts.map +1 -1
  147. package/dist/engine/retry.cjs +13 -0
  148. package/dist/engine/retry.cjs.map +1 -1
  149. package/dist/engine/retry.d.cts +12 -0
  150. package/dist/engine/retry.d.cts.map +1 -1
  151. package/dist/engine/retry.d.mts +12 -0
  152. package/dist/engine/retry.d.mts.map +1 -1
  153. package/dist/engine/retry.mjs +13 -1
  154. package/dist/engine/retry.mjs.map +1 -1
  155. package/dist/engine/time-scale.cjs +90 -15
  156. package/dist/engine/time-scale.cjs.map +1 -1
  157. package/dist/engine/time-scale.d.cts +9 -6
  158. package/dist/engine/time-scale.d.cts.map +1 -1
  159. package/dist/engine/time-scale.d.mts +9 -6
  160. package/dist/engine/time-scale.d.mts.map +1 -1
  161. package/dist/engine/time-scale.mjs +90 -16
  162. package/dist/engine/time-scale.mjs.map +1 -1
  163. package/dist/fcm.cjs +111 -38
  164. package/dist/fcm.cjs.map +1 -1
  165. package/dist/fcm.d.cts +45 -5
  166. package/dist/fcm.d.cts.map +1 -1
  167. package/dist/fcm.d.mts +45 -5
  168. package/dist/fcm.d.mts.map +1 -1
  169. package/dist/fcm.mjs +110 -39
  170. package/dist/fcm.mjs.map +1 -1
  171. package/dist/hash.cjs +6 -6
  172. package/dist/hash.cjs.map +1 -1
  173. package/dist/hash.mjs +6 -6
  174. package/dist/hash.mjs.map +1 -1
  175. package/dist/host/auth.cjs +49 -0
  176. package/dist/host/auth.cjs.map +1 -0
  177. package/dist/host/auth.d.cts +35 -0
  178. package/dist/host/auth.d.cts.map +1 -0
  179. package/dist/host/auth.d.mts +35 -0
  180. package/dist/host/auth.d.mts.map +1 -0
  181. package/dist/host/auth.mjs +46 -0
  182. package/dist/host/auth.mjs.map +1 -0
  183. package/dist/host/channels.cjs +118 -40
  184. package/dist/host/channels.cjs.map +1 -1
  185. package/dist/host/channels.d.cts +27 -1
  186. package/dist/host/channels.d.cts.map +1 -1
  187. package/dist/host/channels.d.mts +27 -1
  188. package/dist/host/channels.d.mts.map +1 -1
  189. package/dist/host/channels.mjs +121 -44
  190. package/dist/host/channels.mjs.map +1 -1
  191. package/dist/host/execute.cjs +14 -2
  192. package/dist/host/execute.cjs.map +1 -1
  193. package/dist/host/execute.d.cts +4 -4
  194. package/dist/host/execute.d.cts.map +1 -1
  195. package/dist/host/execute.d.mts +4 -4
  196. package/dist/host/execute.d.mts.map +1 -1
  197. package/dist/host/execute.mjs +13 -1
  198. package/dist/host/execute.mjs.map +1 -1
  199. package/dist/host/fresh-read.cjs +30 -0
  200. package/dist/host/fresh-read.cjs.map +1 -0
  201. package/dist/host/fresh-read.d.cts +18 -0
  202. package/dist/host/fresh-read.d.cts.map +1 -0
  203. package/dist/host/fresh-read.d.mts +18 -0
  204. package/dist/host/fresh-read.d.mts.map +1 -0
  205. package/dist/host/fresh-read.mjs +29 -0
  206. package/dist/host/fresh-read.mjs.map +1 -0
  207. package/dist/host/http.cjs +340 -0
  208. package/dist/host/http.cjs.map +1 -0
  209. package/dist/host/http.d.cts +58 -0
  210. package/dist/host/http.d.cts.map +1 -0
  211. package/dist/host/http.d.mts +58 -0
  212. package/dist/host/http.d.mts.map +1 -0
  213. package/dist/host/http.mjs +336 -0
  214. package/dist/host/http.mjs.map +1 -0
  215. package/dist/host/{router.cjs → ingest.cjs} +147 -175
  216. package/dist/host/ingest.cjs.map +1 -0
  217. package/dist/host/{router.d.cts → ingest.d.cts} +48 -30
  218. package/dist/host/ingest.d.cts.map +1 -0
  219. package/dist/host/{router.d.mts → ingest.d.mts} +48 -30
  220. package/dist/host/ingest.d.mts.map +1 -0
  221. package/dist/host/{router.mjs → ingest.mjs} +144 -173
  222. package/dist/host/ingest.mjs.map +1 -0
  223. package/dist/host/journey-host.cjs +66 -93
  224. package/dist/host/journey-host.cjs.map +1 -1
  225. package/dist/host/journey-host.d.cts +31 -26
  226. package/dist/host/journey-host.d.cts.map +1 -1
  227. package/dist/host/journey-host.d.mts +31 -26
  228. package/dist/host/journey-host.d.mts.map +1 -1
  229. package/dist/host/journey-host.mjs +66 -93
  230. package/dist/host/journey-host.mjs.map +1 -1
  231. package/dist/host/platform.cjs +21 -0
  232. package/dist/host/platform.cjs.map +1 -0
  233. package/dist/host/platform.d.cts +126 -0
  234. package/dist/host/platform.d.cts.map +1 -0
  235. package/dist/host/platform.d.mts +126 -0
  236. package/dist/host/platform.d.mts.map +1 -0
  237. package/dist/host/platform.mjs +19 -0
  238. package/dist/host/platform.mjs.map +1 -0
  239. package/dist/host/senders.cjs +18 -9
  240. package/dist/host/senders.cjs.map +1 -1
  241. package/dist/host/senders.d.cts +9 -4
  242. package/dist/host/senders.d.cts.map +1 -1
  243. package/dist/host/senders.d.mts +9 -4
  244. package/dist/host/senders.d.mts.map +1 -1
  245. package/dist/host/senders.mjs +18 -9
  246. package/dist/host/senders.mjs.map +1 -1
  247. package/dist/index.cjs +1 -1
  248. package/dist/index.mjs +1 -1
  249. package/dist/ir.cjs +5 -7
  250. package/dist/ir.cjs.map +1 -1
  251. package/dist/ir.d.cts +3 -12
  252. package/dist/ir.d.cts.map +1 -1
  253. package/dist/ir.d.mts +3 -12
  254. package/dist/ir.d.mts.map +1 -1
  255. package/dist/ir.mjs +5 -7
  256. package/dist/ir.mjs.map +1 -1
  257. package/dist/plan.cjs +27 -33
  258. package/dist/plan.cjs.map +1 -1
  259. package/dist/plan.d.cts +1 -1
  260. package/dist/plan.d.cts.map +1 -1
  261. package/dist/plan.d.mts +1 -1
  262. package/dist/plan.d.mts.map +1 -1
  263. package/dist/plan.mjs +27 -33
  264. package/dist/plan.mjs.map +1 -1
  265. package/dist/reporting/tracking.cjs +14 -4
  266. package/dist/reporting/tracking.cjs.map +1 -1
  267. package/dist/reporting/tracking.d.cts +10 -10
  268. package/dist/reporting/tracking.d.cts.map +1 -1
  269. package/dist/reporting/tracking.d.mts +10 -10
  270. package/dist/reporting/tracking.d.mts.map +1 -1
  271. package/dist/reporting/tracking.mjs +14 -4
  272. package/dist/reporting/tracking.mjs.map +1 -1
  273. package/dist/store/d1.cjs +88 -29
  274. package/dist/store/d1.cjs.map +1 -1
  275. package/dist/store/d1.d.cts +29 -11
  276. package/dist/store/d1.d.cts.map +1 -1
  277. package/dist/store/d1.d.mts +29 -11
  278. package/dist/store/d1.d.mts.map +1 -1
  279. package/dist/store/d1.mjs +88 -29
  280. package/dist/store/d1.mjs.map +1 -1
  281. package/dist/store/erasure.cjs +22 -0
  282. package/dist/store/erasure.cjs.map +1 -0
  283. package/dist/store/erasure.d.cts +18 -0
  284. package/dist/store/erasure.d.cts.map +1 -0
  285. package/dist/store/erasure.d.mts +18 -0
  286. package/dist/store/erasure.d.mts.map +1 -0
  287. package/dist/store/erasure.mjs +21 -0
  288. package/dist/store/erasure.mjs.map +1 -0
  289. package/dist/store/index.cjs +2 -0
  290. package/dist/store/index.cjs.map +1 -1
  291. package/dist/store/index.d.cts +67 -13
  292. package/dist/store/index.d.cts.map +1 -1
  293. package/dist/store/index.d.mts +67 -13
  294. package/dist/store/index.d.mts.map +1 -1
  295. package/dist/store/index.mjs +2 -1
  296. package/dist/store/index.mjs.map +1 -1
  297. package/dist/store/postgres-reporting.cjs +125 -62
  298. package/dist/store/postgres-reporting.cjs.map +1 -1
  299. package/dist/store/postgres-reporting.d.cts +30 -2
  300. package/dist/store/postgres-reporting.d.cts.map +1 -1
  301. package/dist/store/postgres-reporting.d.mts +30 -2
  302. package/dist/store/postgres-reporting.d.mts.map +1 -1
  303. package/dist/store/postgres-reporting.mjs +125 -62
  304. package/dist/store/postgres-reporting.mjs.map +1 -1
  305. package/dist/store/postgres.cjs +179 -60
  306. package/dist/store/postgres.cjs.map +1 -1
  307. package/dist/store/postgres.d.cts +44 -7
  308. package/dist/store/postgres.d.cts.map +1 -1
  309. package/dist/store/postgres.d.mts +44 -7
  310. package/dist/store/postgres.d.mts.map +1 -1
  311. package/dist/store/postgres.mjs +179 -60
  312. package/dist/store/postgres.mjs.map +1 -1
  313. package/dist/store/tables.cjs +7 -5
  314. package/dist/store/tables.cjs.map +1 -1
  315. package/dist/store/tables.d.cts.map +1 -1
  316. package/dist/store/tables.d.mts.map +1 -1
  317. package/dist/store/tables.mjs +7 -5
  318. package/dist/store/tables.mjs.map +1 -1
  319. package/dist/store/user-lock.cjs +43 -0
  320. package/dist/store/user-lock.cjs.map +1 -0
  321. package/dist/store/user-lock.d.cts +32 -0
  322. package/dist/store/user-lock.d.cts.map +1 -0
  323. package/dist/store/user-lock.d.mts +32 -0
  324. package/dist/store/user-lock.d.mts.map +1 -0
  325. package/dist/store/user-lock.mjs +40 -0
  326. package/dist/store/user-lock.mjs.map +1 -0
  327. package/dist/transports/cloudflare-email.cjs +1 -1
  328. package/dist/transports/cloudflare-email.mjs +1 -1
  329. package/dist/transports/ses.cjs +47 -6
  330. package/dist/transports/ses.cjs.map +1 -1
  331. package/dist/transports/ses.d.cts +9 -1
  332. package/dist/transports/ses.d.cts.map +1 -1
  333. package/dist/transports/ses.d.mts +9 -1
  334. package/dist/transports/ses.d.mts.map +1 -1
  335. package/dist/transports/ses.mjs +47 -7
  336. package/dist/transports/ses.mjs.map +1 -1
  337. package/dist/webpush.cjs +133 -47
  338. package/dist/webpush.cjs.map +1 -1
  339. package/dist/webpush.d.cts +49 -8
  340. package/dist/webpush.d.cts.map +1 -1
  341. package/dist/webpush.d.mts +49 -8
  342. package/dist/webpush.d.mts.map +1 -1
  343. package/dist/webpush.mjs +130 -47
  344. package/dist/webpush.mjs.map +1 -1
  345. package/package.json +1 -1
  346. package/src/cloudflare/schema.sql +27 -6
  347. package/dist/host/router.cjs.map +0 -1
  348. package/dist/host/router.d.cts.map +0 -1
  349. package/dist/host/router.d.mts.map +0 -1
  350. package/dist/host/router.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"action.cjs","names":["sha256Hex"],"sources":["../../src/dsl/action.ts"],"sourcesContent":["import { sha256Hex } from '../hash';\n\n/**\n * Custom actions — the escape hatch for side effects the built-in nodes do not\n * cover (CRM sync, coupon issuing, outbound webhooks with custom auth, …).\n *\n * Dual-plane model: the *code* solidifies into the Worker bundle at deploy\n * time (it ships with the app, like any other module), while the *identity*\n * solidifies into IR as `{ type: 'action', action: name, args, codeHash }`.\n * The runtime resolves the name against a registry (see engine/actions.ts) and\n * compares codeHash to detect version skew between a pinned IR and the\n * currently deployed implementation.\n *\n * Deliberately NOT supported: custom *conditions*. Branching stays declarative\n * so the canvas can always explain why a user took a path — an action that\n * needs to influence routing writes a property or emits an event, and the\n * decision is expressed as a normal predicate on that data.\n */\n\n/** Runtime context handed to the handler; deliberately small — facts and messaging have their own nodes. */\nexport interface ActionContext {\n userId: string;\n /**\n * `${instanceId}:${nodeId}` — stable across replays and retries of this one\n * action node. A handler with an external side effect (charging, issuing,\n * POSTing) must pass it along so the far side collapses the duplicate: the\n * step body can re-run after the handler already resolved.\n */\n idempotencyKey: string;\n}\n\n/**\n * The handler: plain code, executed inside a durable step (`step.do`), so a\n * throw is retried by the runtime and a success is checkpointed — it should be\n * idempotent under retry, which is what `ctx.idempotencyKey` is for. Args\n * arrive with user_property references already resolved to values.\n */\nexport type ActionHandler<A extends object> = (args: A, ctx: ActionContext) => Promise<void> | void;\n\n/**\n * A registered custom action: the named asset `.run()` refers to. Carries the\n * implementation for the runtime registry and the codeHash for the IR pin.\n */\nexport interface ActionRef<A extends object = object> {\n readonly name: string;\n /** Hash of the handler's source text at definition time — the code-identity pin recorded in IR. */\n readonly codeHash: string;\n /** Method syntax on purpose: bivariant args let refs with different arg shapes share one list (compileBundle, registries). */\n handler(args: A, ctx: ActionContext): Promise<void> | void;\n}\n\n/**\n * Define a custom action. The generic `A` types the args `.run()` accepts —\n * the only type injection point, mirroring `template.sms<P>()`:\n *\n * const syncCrm = action<{ plan: string }>('sync_crm', async ({ plan }, { userId }) => {\n * await fetch('https://crm.example.com/sync', { method: 'POST', body: JSON.stringify({ userId, plan }) });\n * });\n *\n * workflow(...).run(syncCrm, { plan: u.subscription_plan })\n *\n * codeHash comes from `handler.toString()`, so it changes when the code\n * changes and stays put when it does not. Different build pipelines can print\n * the same function differently (minification), which is why the default\n * runtime policy on mismatch is warn, not fail.\n */\nexport function action<A extends object = Record<string, never>>(\n name: string,\n handler: ActionHandler<A>\n): ActionRef<A> {\n return { name, codeHash: sha256Hex(handler.toString()).slice(0, 32), handler };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkEA,SAAgB,OACd,MACA,SACc;CACd,OAAO;EAAE;EAAM,UAAUA,aAAAA,UAAU,QAAQ,SAAS,CAAC,CAAC,CAAC,MAAM,GAAG,EAAE;EAAG;CAAQ;AAC/E"}
1
+ {"version":3,"file":"action.cjs","names":[],"sources":["../../src/dsl/action.ts"],"sourcesContent":["/**\n * Custom actions — the escape hatch for side effects the built-in nodes do not\n * cover (CRM sync, coupon issuing, outbound webhooks with custom auth, …).\n *\n * Dual-plane model: the *code* solidifies into the Worker bundle at deploy\n * time (it ships with the app, like any other module), while the *identity*\n * solidifies into IR as `{ type: 'action', action: name, args }`. The runtime\n * resolves the name against a registry (see engine/actions.ts).\n *\n * The IR carries no hash of `handler.toString()`: the printed source depends\n * on the bundler (minification, helper injection, source maps), so every\n * toolchain would produce a different \"version\" of unchanged code and `plan`\n * would report changes nobody made. Code identity is the deployed bundle's,\n * not the IR's.\n *\n * Deliberately NOT supported: custom *conditions*. Branching stays declarative\n * so the canvas can always explain why a user took a path — an action that\n * needs to influence routing writes a property or emits an event, and the\n * decision is expressed as a normal predicate on that data.\n */\n\n/** Runtime context handed to the handler; deliberately small — facts and messaging have their own nodes. */\nexport interface ActionContext {\n userId: string;\n /**\n * `${instanceId}:${nodeId}` — stable across replays and retries of this one\n * action node. A handler with an external side effect (charging, issuing,\n * POSTing) must pass it along so the far side collapses the duplicate: the\n * step body can re-run after the handler already resolved.\n */\n idempotencyKey: string;\n}\n\n/**\n * The handler: plain code, executed inside a durable step (`step.do`), so a\n * throw is retried by the runtime and a success is checkpointed — it should be\n * idempotent under retry, which is what `ctx.idempotencyKey` is for. Args\n * arrive with user_property references already resolved to values.\n */\nexport type ActionHandler<A extends object> = (args: A, ctx: ActionContext) => Promise<void> | void;\n\n/**\n * A registered custom action: the named asset `.run()` refers to. Carries the\n * name the IR records and the implementation for the runtime registry.\n */\nexport interface ActionRef<A extends object = object> {\n readonly name: string;\n /** Method syntax on purpose: bivariant args let refs with different arg shapes share one list (compileBundle, registries). */\n handler(args: A, ctx: ActionContext): Promise<void> | void;\n}\n\n/**\n * Define a custom action. The generic `A` types the args `.run()` accepts —\n * the only type injection point, mirroring `template.sms<P>()`:\n *\n * const syncCrm = action<{ plan: string }>('sync_crm', async ({ plan }, { userId }) => {\n * await fetch('https://crm.example.com/sync', { method: 'POST', body: JSON.stringify({ userId, plan }) });\n * });\n *\n * workflow(...).run(syncCrm, { plan: u.subscription_plan })\n *\n * The returned object is the action's identity at compile time: compileBundle\n * checks that the one a workflow runs is the very one listed in `actions`, so\n * two definitions cannot share a name by accident.\n */\nexport function action<A extends object = Record<string, never>>(\n name: string,\n handler: ActionHandler<A>\n): ActionRef<A> {\n return { name, handler };\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAiEA,SAAgB,OACd,MACA,SACc;CACd,OAAO;EAAE;EAAM;CAAQ;AACzB"}
@@ -5,10 +5,14 @@
5
5
  *
6
6
  * Dual-plane model: the *code* solidifies into the Worker bundle at deploy
7
7
  * time (it ships with the app, like any other module), while the *identity*
8
- * solidifies into IR as `{ type: 'action', action: name, args, codeHash }`.
9
- * The runtime resolves the name against a registry (see engine/actions.ts) and
10
- * compares codeHash to detect version skew between a pinned IR and the
11
- * currently deployed implementation.
8
+ * solidifies into IR as `{ type: 'action', action: name, args }`. The runtime
9
+ * resolves the name against a registry (see engine/actions.ts).
10
+ *
11
+ * The IR carries no hash of `handler.toString()`: the printed source depends
12
+ * on the bundler (minification, helper injection, source maps), so every
13
+ * toolchain would produce a different "version" of unchanged code and `plan`
14
+ * would report changes nobody made. Code identity is the deployed bundle's,
15
+ * not the IR's.
12
16
  *
13
17
  * Deliberately NOT supported: custom *conditions*. Branching stays declarative
14
18
  * so the canvas can always explain why a user took a path — an action that
@@ -35,12 +39,10 @@ export interface ActionContext {
35
39
  export type ActionHandler<A extends object> = (args: A, ctx: ActionContext) => Promise<void> | void;
36
40
  /**
37
41
  * A registered custom action: the named asset `.run()` refers to. Carries the
38
- * implementation for the runtime registry and the codeHash for the IR pin.
42
+ * name the IR records and the implementation for the runtime registry.
39
43
  */
40
44
  export interface ActionRef<A extends object = object> {
41
45
  readonly name: string;
42
- /** Hash of the handler's source text at definition time — the code-identity pin recorded in IR. */
43
- readonly codeHash: string;
44
46
  /** Method syntax on purpose: bivariant args let refs with different arg shapes share one list (compileBundle, registries). */
45
47
  handler(args: A, ctx: ActionContext): Promise<void> | void;
46
48
  }
@@ -54,10 +56,9 @@ export interface ActionRef<A extends object = object> {
54
56
  *
55
57
  * workflow(...).run(syncCrm, { plan: u.subscription_plan })
56
58
  *
57
- * codeHash comes from `handler.toString()`, so it changes when the code
58
- * changes and stays put when it does not. Different build pipelines can print
59
- * the same function differently (minification), which is why the default
60
- * runtime policy on mismatch is warn, not fail.
59
+ * The returned object is the action's identity at compile time: compileBundle
60
+ * checks that the one a workflow runs is the very one listed in `actions`, so
61
+ * two definitions cannot share a name by accident.
61
62
  */
62
63
  export declare function action<A extends object = Record<string, never>>(name: string, handler: ActionHandler<A>): ActionRef<A>;
63
64
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"action.d.cts","names":[],"sources":["../../src/dsl/action.ts"],"mappings":";;;;;;;;;;;;;;;;;;iBAoBiB;EACf;;;;;;;EAOA;;;;;;;;YASU,cAAc,qBAAqB,MAAM,GAAG,KAAK,kBAAkB;;;;;iBAM9D,UAAU;WAChB;;WAEA;;EAET,QAAQ,MAAM,GAAG,KAAK,gBAAgB;;;;;;;;;;;;;;;;;wBAkBxB,OAAO,mBAAmB,uBACxC,cACA,SAAS,cAAc,KACtB,UAAU"}
1
+ {"version":3,"file":"action.d.cts","names":[],"sources":["../../src/dsl/action.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;iBAsBiB;EACf;;;;;;;EAOA;;;;;;;;YASU,cAAc,qBAAqB,MAAM,GAAG,KAAK,kBAAkB;;;;;iBAM9D,UAAU;WAChB;;EAET,QAAQ,MAAM,GAAG,KAAK,gBAAgB;;;;;;;;;;;;;;;;wBAiBxB,OAAO,mBAAmB,uBACxC,cACA,SAAS,cAAc,KACtB,UAAU"}
@@ -5,10 +5,14 @@
5
5
  *
6
6
  * Dual-plane model: the *code* solidifies into the Worker bundle at deploy
7
7
  * time (it ships with the app, like any other module), while the *identity*
8
- * solidifies into IR as `{ type: 'action', action: name, args, codeHash }`.
9
- * The runtime resolves the name against a registry (see engine/actions.ts) and
10
- * compares codeHash to detect version skew between a pinned IR and the
11
- * currently deployed implementation.
8
+ * solidifies into IR as `{ type: 'action', action: name, args }`. The runtime
9
+ * resolves the name against a registry (see engine/actions.ts).
10
+ *
11
+ * The IR carries no hash of `handler.toString()`: the printed source depends
12
+ * on the bundler (minification, helper injection, source maps), so every
13
+ * toolchain would produce a different "version" of unchanged code and `plan`
14
+ * would report changes nobody made. Code identity is the deployed bundle's,
15
+ * not the IR's.
12
16
  *
13
17
  * Deliberately NOT supported: custom *conditions*. Branching stays declarative
14
18
  * so the canvas can always explain why a user took a path — an action that
@@ -35,12 +39,10 @@ export interface ActionContext {
35
39
  export type ActionHandler<A extends object> = (args: A, ctx: ActionContext) => Promise<void> | void;
36
40
  /**
37
41
  * A registered custom action: the named asset `.run()` refers to. Carries the
38
- * implementation for the runtime registry and the codeHash for the IR pin.
42
+ * name the IR records and the implementation for the runtime registry.
39
43
  */
40
44
  export interface ActionRef<A extends object = object> {
41
45
  readonly name: string;
42
- /** Hash of the handler's source text at definition time — the code-identity pin recorded in IR. */
43
- readonly codeHash: string;
44
46
  /** Method syntax on purpose: bivariant args let refs with different arg shapes share one list (compileBundle, registries). */
45
47
  handler(args: A, ctx: ActionContext): Promise<void> | void;
46
48
  }
@@ -54,10 +56,9 @@ export interface ActionRef<A extends object = object> {
54
56
  *
55
57
  * workflow(...).run(syncCrm, { plan: u.subscription_plan })
56
58
  *
57
- * codeHash comes from `handler.toString()`, so it changes when the code
58
- * changes and stays put when it does not. Different build pipelines can print
59
- * the same function differently (minification), which is why the default
60
- * runtime policy on mismatch is warn, not fail.
59
+ * The returned object is the action's identity at compile time: compileBundle
60
+ * checks that the one a workflow runs is the very one listed in `actions`, so
61
+ * two definitions cannot share a name by accident.
61
62
  */
62
63
  export declare function action<A extends object = Record<string, never>>(name: string, handler: ActionHandler<A>): ActionRef<A>;
63
64
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"action.d.mts","names":[],"sources":["../../src/dsl/action.ts"],"mappings":";;;;;;;;;;;;;;;;;;iBAoBiB;EACf;;;;;;;EAOA;;;;;;;;YASU,cAAc,qBAAqB,MAAM,GAAG,KAAK,kBAAkB;;;;;iBAM9D,UAAU;WAChB;;WAEA;;EAET,QAAQ,MAAM,GAAG,KAAK,gBAAgB;;;;;;;;;;;;;;;;;wBAkBxB,OAAO,mBAAmB,uBACxC,cACA,SAAS,cAAc,KACtB,UAAU"}
1
+ {"version":3,"file":"action.d.mts","names":[],"sources":["../../src/dsl/action.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;iBAsBiB;EACf;;;;;;;EAOA;;;;;;;;YASU,cAAc,qBAAqB,MAAM,GAAG,KAAK,kBAAkB;;;;;iBAM9D,UAAU;WAChB;;EAET,QAAQ,MAAM,GAAG,KAAK,gBAAgB;;;;;;;;;;;;;;;;wBAiBxB,OAAO,mBAAmB,uBACxC,cACA,SAAS,cAAc,KACtB,UAAU"}
@@ -1,4 +1,3 @@
1
- import { sha256Hex } from "../hash.mjs";
2
1
  //#region src/dsl/action.ts
3
2
  /**
4
3
  * Define a custom action. The generic `A` types the args `.run()` accepts —
@@ -10,15 +9,13 @@ import { sha256Hex } from "../hash.mjs";
10
9
  *
11
10
  * workflow(...).run(syncCrm, { plan: u.subscription_plan })
12
11
  *
13
- * codeHash comes from `handler.toString()`, so it changes when the code
14
- * changes and stays put when it does not. Different build pipelines can print
15
- * the same function differently (minification), which is why the default
16
- * runtime policy on mismatch is warn, not fail.
12
+ * The returned object is the action's identity at compile time: compileBundle
13
+ * checks that the one a workflow runs is the very one listed in `actions`, so
14
+ * two definitions cannot share a name by accident.
17
15
  */
18
16
  function action(name, handler) {
19
17
  return {
20
18
  name,
21
- codeHash: sha256Hex(handler.toString()).slice(0, 32),
22
19
  handler
23
20
  };
24
21
  }
@@ -1 +1 @@
1
- {"version":3,"file":"action.mjs","names":[],"sources":["../../src/dsl/action.ts"],"sourcesContent":["import { sha256Hex } from '../hash';\n\n/**\n * Custom actions — the escape hatch for side effects the built-in nodes do not\n * cover (CRM sync, coupon issuing, outbound webhooks with custom auth, …).\n *\n * Dual-plane model: the *code* solidifies into the Worker bundle at deploy\n * time (it ships with the app, like any other module), while the *identity*\n * solidifies into IR as `{ type: 'action', action: name, args, codeHash }`.\n * The runtime resolves the name against a registry (see engine/actions.ts) and\n * compares codeHash to detect version skew between a pinned IR and the\n * currently deployed implementation.\n *\n * Deliberately NOT supported: custom *conditions*. Branching stays declarative\n * so the canvas can always explain why a user took a path — an action that\n * needs to influence routing writes a property or emits an event, and the\n * decision is expressed as a normal predicate on that data.\n */\n\n/** Runtime context handed to the handler; deliberately small — facts and messaging have their own nodes. */\nexport interface ActionContext {\n userId: string;\n /**\n * `${instanceId}:${nodeId}` — stable across replays and retries of this one\n * action node. A handler with an external side effect (charging, issuing,\n * POSTing) must pass it along so the far side collapses the duplicate: the\n * step body can re-run after the handler already resolved.\n */\n idempotencyKey: string;\n}\n\n/**\n * The handler: plain code, executed inside a durable step (`step.do`), so a\n * throw is retried by the runtime and a success is checkpointed — it should be\n * idempotent under retry, which is what `ctx.idempotencyKey` is for. Args\n * arrive with user_property references already resolved to values.\n */\nexport type ActionHandler<A extends object> = (args: A, ctx: ActionContext) => Promise<void> | void;\n\n/**\n * A registered custom action: the named asset `.run()` refers to. Carries the\n * implementation for the runtime registry and the codeHash for the IR pin.\n */\nexport interface ActionRef<A extends object = object> {\n readonly name: string;\n /** Hash of the handler's source text at definition time — the code-identity pin recorded in IR. */\n readonly codeHash: string;\n /** Method syntax on purpose: bivariant args let refs with different arg shapes share one list (compileBundle, registries). */\n handler(args: A, ctx: ActionContext): Promise<void> | void;\n}\n\n/**\n * Define a custom action. The generic `A` types the args `.run()` accepts —\n * the only type injection point, mirroring `template.sms<P>()`:\n *\n * const syncCrm = action<{ plan: string }>('sync_crm', async ({ plan }, { userId }) => {\n * await fetch('https://crm.example.com/sync', { method: 'POST', body: JSON.stringify({ userId, plan }) });\n * });\n *\n * workflow(...).run(syncCrm, { plan: u.subscription_plan })\n *\n * codeHash comes from `handler.toString()`, so it changes when the code\n * changes and stays put when it does not. Different build pipelines can print\n * the same function differently (minification), which is why the default\n * runtime policy on mismatch is warn, not fail.\n */\nexport function action<A extends object = Record<string, never>>(\n name: string,\n handler: ActionHandler<A>\n): ActionRef<A> {\n return { name, codeHash: sha256Hex(handler.toString()).slice(0, 32), handler };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAkEA,SAAgB,OACd,MACA,SACc;CACd,OAAO;EAAE;EAAM,UAAU,UAAU,QAAQ,SAAS,CAAC,CAAC,CAAC,MAAM,GAAG,EAAE;EAAG;CAAQ;AAC/E"}
1
+ {"version":3,"file":"action.mjs","names":[],"sources":["../../src/dsl/action.ts"],"sourcesContent":["/**\n * Custom actions — the escape hatch for side effects the built-in nodes do not\n * cover (CRM sync, coupon issuing, outbound webhooks with custom auth, …).\n *\n * Dual-plane model: the *code* solidifies into the Worker bundle at deploy\n * time (it ships with the app, like any other module), while the *identity*\n * solidifies into IR as `{ type: 'action', action: name, args }`. The runtime\n * resolves the name against a registry (see engine/actions.ts).\n *\n * The IR carries no hash of `handler.toString()`: the printed source depends\n * on the bundler (minification, helper injection, source maps), so every\n * toolchain would produce a different \"version\" of unchanged code and `plan`\n * would report changes nobody made. Code identity is the deployed bundle's,\n * not the IR's.\n *\n * Deliberately NOT supported: custom *conditions*. Branching stays declarative\n * so the canvas can always explain why a user took a path — an action that\n * needs to influence routing writes a property or emits an event, and the\n * decision is expressed as a normal predicate on that data.\n */\n\n/** Runtime context handed to the handler; deliberately small — facts and messaging have their own nodes. */\nexport interface ActionContext {\n userId: string;\n /**\n * `${instanceId}:${nodeId}` — stable across replays and retries of this one\n * action node. A handler with an external side effect (charging, issuing,\n * POSTing) must pass it along so the far side collapses the duplicate: the\n * step body can re-run after the handler already resolved.\n */\n idempotencyKey: string;\n}\n\n/**\n * The handler: plain code, executed inside a durable step (`step.do`), so a\n * throw is retried by the runtime and a success is checkpointed — it should be\n * idempotent under retry, which is what `ctx.idempotencyKey` is for. Args\n * arrive with user_property references already resolved to values.\n */\nexport type ActionHandler<A extends object> = (args: A, ctx: ActionContext) => Promise<void> | void;\n\n/**\n * A registered custom action: the named asset `.run()` refers to. Carries the\n * name the IR records and the implementation for the runtime registry.\n */\nexport interface ActionRef<A extends object = object> {\n readonly name: string;\n /** Method syntax on purpose: bivariant args let refs with different arg shapes share one list (compileBundle, registries). */\n handler(args: A, ctx: ActionContext): Promise<void> | void;\n}\n\n/**\n * Define a custom action. The generic `A` types the args `.run()` accepts —\n * the only type injection point, mirroring `template.sms<P>()`:\n *\n * const syncCrm = action<{ plan: string }>('sync_crm', async ({ plan }, { userId }) => {\n * await fetch('https://crm.example.com/sync', { method: 'POST', body: JSON.stringify({ userId, plan }) });\n * });\n *\n * workflow(...).run(syncCrm, { plan: u.subscription_plan })\n *\n * The returned object is the action's identity at compile time: compileBundle\n * checks that the one a workflow runs is the very one listed in `actions`, so\n * two definitions cannot share a name by accident.\n */\nexport function action<A extends object = Record<string, never>>(\n name: string,\n handler: ActionHandler<A>\n): ActionRef<A> {\n return { name, handler };\n}\n"],"mappings":";;;;;;;;;;;;;;;AAiEA,SAAgB,OACd,MACA,SACc;CACd,OAAO;EAAE;EAAM;CAAQ;AACzB"}
@@ -1,6 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_hash = require("../hash.cjs");
3
2
  const require_templates = require("../templates.cjs");
3
+ const require_dsl_flow = require("./flow.cjs");
4
+ const require_hash = require("../hash.cjs");
4
5
  const require_ir = require("../ir.cjs");
5
6
  //#region src/dsl/bundle.ts
6
7
  /**
@@ -32,8 +33,7 @@ function compileBundle(input) {
32
33
  }));
33
34
  const actions = (input.actions ?? []).map((a) => ({
34
35
  irVersion: 2,
35
- name: a.name,
36
- codeHash: a.codeHash
36
+ name: a.name
37
37
  }));
38
38
  const workflows = input.workflows.map((builder) => builder.toIR());
39
39
  assertUniqueNames(workflows.map((w) => w.id), "workflow");
@@ -41,8 +41,9 @@ function compileBundle(input) {
41
41
  assertUniqueNames(templates.map((t) => `${t.channel}:${t.key}`), "template");
42
42
  assertUniqueNames(actions.map((a) => a.name), "action");
43
43
  assertSegmentRefsResolve(workflows, segments);
44
+ assertNoSegmentCycles(segments);
44
45
  assertConditionPlacement(workflows, segments);
45
- assertActionRefsResolve(workflows, actions);
46
+ assertActionRefsResolve(workflows, input.workflows, input.actions ?? []);
46
47
  return require_ir.BundleIR.parse({
47
48
  irVersion: 2,
48
49
  workflows,
@@ -110,36 +111,39 @@ function collectNodeRefs(nodes, into) {
110
111
  case "cohort": for (const arm of node.arms) collectNodeRefs(arm.flow, into);
111
112
  }
112
113
  }
113
- function assertActionRefsResolve(workflows, actions) {
114
- const defined = new Map(actions.map((a) => [a.name, a.codeHash]));
115
- for (const workflow of workflows) {
116
- const used = /* @__PURE__ */ new Map();
117
- collectActionRefs(workflow.flow, used);
118
- for (const [name, codeHashes] of used) {
119
- const manifestHash = defined.get(name);
120
- if (manifestHash === void 0) throw new Error(`compileBundle: workflow '${workflow.id}' runs action '${name}' that is not in the bundle — pass it in 'actions'`);
121
- for (const codeHash of codeHashes) if (codeHash !== void 0 && codeHash !== manifestHash) throw new Error(`compileBundle: workflow '${workflow.id}' runs action '${name}' whose code differs from the one in 'actions' — two definitions are sharing that name`);
114
+ function assertActionRefsResolve(workflows, builders, actions) {
115
+ const listed = new Map(actions.map((a) => [a.name, a]));
116
+ workflows.forEach((workflow, i) => {
117
+ for (const used of require_dsl_flow.actionUses(builders[i])) {
118
+ const manifest = listed.get(used.name);
119
+ if (manifest === void 0) throw new Error(`compileBundle: workflow '${workflow.id}' runs action '${used.name}' that is not in the bundle — pass it in 'actions'`);
120
+ if (manifest !== used && manifest.handler !== used.handler) throw new Error(`compileBundle: workflow '${workflow.id}' runs action '${used.name}' that is not the definition listed in 'actions' — two definitions are sharing that name`);
122
121
  }
123
- }
122
+ });
124
123
  }
125
- /** name → every codeHash seen under it; a name with two is exactly the case worth failing on. */
126
- function collectActionRefs(nodes, into) {
127
- for (const node of nodes) switch (node.type) {
128
- case "action": {
129
- const seen = into.get(node.action) ?? /* @__PURE__ */ new Set();
130
- seen.add(node.codeHash);
131
- into.set(node.action, seen);
132
- break;
133
- }
134
- case "wait_until":
135
- if (Array.isArray(node.onTimeout)) collectActionRefs(node.onTimeout, into);
136
- break;
137
- case "branch":
138
- for (const branchCase of node.cases) collectActionRefs(branchCase.flow, into);
139
- if (node.otherwise) collectActionRefs(node.otherwise, into);
140
- break;
141
- case "cohort": for (const arm of node.arms) collectActionRefs(arm.flow, into);
124
+ /** Walk segment → segment references depth-first; the first back edge is reported as the cycle path. */
125
+ function assertNoSegmentCycles(segments) {
126
+ const refsOf = /* @__PURE__ */ new Map();
127
+ for (const segment of segments) {
128
+ const refs = /* @__PURE__ */ new Set();
129
+ collectConditionRefs(segment.condition, refs);
130
+ refsOf.set(segment.id, refs);
142
131
  }
132
+ const done = /* @__PURE__ */ new Set();
133
+ const path = [];
134
+ const visit = (id) => {
135
+ const at = path.indexOf(id);
136
+ if (at !== -1) {
137
+ const cycle = [...path.slice(at), id].join(" → ");
138
+ throw new Error(`compileBundle: segments reference each other in a cycle: ${cycle}`);
139
+ }
140
+ if (done.has(id)) return;
141
+ path.push(id);
142
+ for (const ref of refsOf.get(id) ?? []) visit(ref);
143
+ path.pop();
144
+ done.add(id);
145
+ };
146
+ for (const id of refsOf.keys()) visit(id);
143
147
  }
144
148
  function assertConditionPlacement(workflows, segments) {
145
149
  for (const workflow of workflows) {
@@ -1 +1 @@
1
- {"version":3,"file":"bundle.cjs","names":["semanticHash","CHANNEL_OF","BundleIRSchema"],"sources":["../../src/dsl/bundle.ts"],"sourcesContent":["import { semanticHash } from '../hash';\nimport {\n type BundleIR,\n BundleIR as BundleIRSchema,\n type ConditionIR,\n IR_VERSION,\n type NodeIR,\n type SegmentIR,\n type WorkflowIR,\n} from '../ir';\nimport { type AnyTemplate, CHANNEL_OF } from '../templates';\nimport type { ActionRef } from './action';\nimport type { SegmentInternal, SegmentRef } from './condition';\nimport type { WorkflowBuilder } from './workflow';\n\n/**\n * The deployment unit — compileBundle gathers workflows, segments and the\n * template manifest into one BundleIR snapshot (terraform-apply mental model);\n * the server diffs each definition by contentHash (see plan.ts).\n */\nexport function compileBundle(input: {\n workflows: readonly WorkflowBuilder[];\n segments?: readonly SegmentRef[];\n /** The template manifest: these definitions, by channel and id. */\n templates?: readonly AnyTemplate[];\n actions?: readonly ActionRef[];\n}): BundleIR {\n const segments = (input.segments ?? []).map((segment) => {\n const { id, definition, loc, meta } = segment as SegmentInternal;\n const compiled: SegmentIR = {\n irVersion: IR_VERSION,\n id,\n // Labels and provenance both live in meta, which stripMeta drops before hashing\n contentHash: semanticHash(definition),\n condition: definition,\n };\n const merged = {\n ...(meta?.name !== undefined ? { name: meta.name } : {}),\n ...(meta?.description !== undefined ? { description: meta.description } : {}),\n ...(loc !== undefined ? { loc } : {}),\n };\n if (Object.keys(merged).length > 0) compiled.meta = merged;\n return compiled;\n });\n\n const templates = (input.templates ?? []).map((template) => ({\n irVersion: IR_VERSION,\n key: template.id,\n channel: CHANNEL_OF[template.kind],\n }));\n\n // Manifest only — name and code-identity hash; the implementation ships in the Worker bundle, never in IR\n const actions = (input.actions ?? []).map((a) => ({\n irVersion: IR_VERSION,\n name: a.name,\n codeHash: a.codeHash,\n }));\n\n const workflows = input.workflows.map((builder) => builder.toIR());\n\n // Names are wire identity (routing tables, entry ledger, by-name refs):\n // duplicates would silently last-write-win on deploy, so fail the compile.\n assertUniqueNames(\n workflows.map((w) => w.id),\n 'workflow'\n );\n assertUniqueNames(\n segments.map((s) => s.id),\n 'segment'\n );\n // A message node names its template by channel and key, so the key is unique per channel:\n // an email and a push of one nudge may share it\n assertUniqueNames(\n templates.map((t) => `${t.channel}:${t.key}`),\n 'template'\n );\n assertUniqueNames(\n actions.map((a) => a.name),\n 'action'\n );\n\n // Deploy replaces the whole segment table, so every by-name reference must\n // resolve inside this bundle — catching it here turns a runtime \"Unknown\n // segment\" in some user's journey into a compile error.\n assertSegmentRefsResolve(workflows, segments);\n\n // Payload predicates and profile predicates share the Condition type, so\n // placement is checked here: payload only inside where-trees, and where-trees\n // contain nothing but payload predicates and combinators.\n assertConditionPlacement(workflows, segments);\n\n // Same rationale as segments: the runtime registry is looked up by name, so\n // an action a workflow runs must be in the bundle — and its codeHash must\n // agree, or two same-named definitions are competing for one identity.\n assertActionRefsResolve(workflows, actions);\n\n return BundleIRSchema.parse({ irVersion: IR_VERSION, workflows, segments, templates, actions });\n}\n\nfunction assertUniqueNames(names: readonly string[], kind: string): void {\n const seen = new Set<string>();\n for (const name of names) {\n if (seen.has(name)) throw new Error(`compileBundle: duplicate ${kind} name '${name}'`);\n seen.add(name);\n }\n}\n\nfunction assertSegmentRefsResolve(\n workflows: readonly WorkflowIR[],\n segments: readonly SegmentIR[]\n): void {\n const defined = new Set(segments.map((s) => s.id));\n const check = (refs: ReadonlySet<string>, owner: string): void => {\n for (const ref of refs) {\n if (!defined.has(ref)) {\n throw new Error(\n `compileBundle: ${owner} references segment '${ref}' that is not in the bundle — pass it in 'segments'`\n );\n }\n }\n };\n for (const workflow of workflows) {\n check(referencedSegments(workflow), `workflow '${workflow.id}'`);\n }\n // Segments may reference other segments; those must resolve too\n for (const segment of segments) {\n const refs = new Set<string>();\n collectConditionRefs(segment.condition, refs);\n check(refs, `segment '${segment.id}'`);\n }\n}\n\nfunction referencedSegments(workflow: WorkflowIR): Set<string> {\n const refs = new Set<string>();\n if (workflow.trigger.type === 'segment') refs.add(workflow.trigger.segment);\n if (workflow.trigger.type === 'event' && workflow.trigger.filter !== undefined) {\n collectConditionRefs(workflow.trigger.filter, refs);\n }\n if (workflow.goal !== undefined) collectConditionRefs(workflow.goal.condition, refs);\n if (workflow.exitWhen !== undefined) collectConditionRefs(workflow.exitWhen, refs);\n collectNodeRefs(workflow.flow, refs);\n return refs;\n}\n\nfunction collectConditionRefs(condition: ConditionIR, into: Set<string>): void {\n switch (condition.type) {\n case 'and':\n case 'or':\n for (const child of condition.conditions) collectConditionRefs(child, into);\n break;\n case 'not':\n collectConditionRefs(condition.condition, into);\n break;\n case 'segment':\n into.add(condition.segment);\n break;\n default:\n break;\n }\n}\n\nfunction collectNodeRefs(nodes: readonly NodeIR[], into: Set<string>): void {\n for (const node of nodes) {\n switch (node.type) {\n case 'wait_until':\n collectConditionRefs(node.condition, into);\n if (Array.isArray(node.onTimeout)) collectNodeRefs(node.onTimeout, into);\n break;\n case 'branch':\n for (const branchCase of node.cases) {\n collectConditionRefs(branchCase.condition, into);\n collectNodeRefs(branchCase.flow, into);\n }\n if (node.otherwise) collectNodeRefs(node.otherwise, into);\n break;\n case 'filter':\n collectConditionRefs(node.condition, into);\n break;\n case 'cohort':\n for (const arm of node.arms) collectNodeRefs(arm.flow, into);\n break;\n default:\n break;\n }\n }\n}\n\nfunction assertActionRefsResolve(\n workflows: readonly WorkflowIR[],\n actions: readonly { name: string; codeHash: string }[]\n): void {\n const defined = new Map(actions.map((a) => [a.name, a.codeHash]));\n for (const workflow of workflows) {\n const used = new Map<string, Set<string | undefined>>();\n collectActionRefs(workflow.flow, used);\n for (const [name, codeHashes] of used) {\n const manifestHash = defined.get(name);\n if (manifestHash === undefined) {\n throw new Error(\n `compileBundle: workflow '${workflow.id}' runs action '${name}' that is not in the bundle — pass it in 'actions'`\n );\n }\n // Every node's hash is checked, not just one per name: with two\n // definitions under one name, whichever the manifest holds, the *other*\n // node is the one that would meet a mismatched implementation at run time.\n for (const codeHash of codeHashes) {\n if (codeHash !== undefined && codeHash !== manifestHash) {\n throw new Error(\n `compileBundle: workflow '${workflow.id}' runs action '${name}' whose code differs from the one in 'actions' — two definitions are sharing that name`\n );\n }\n }\n }\n }\n}\n\n/** name → every codeHash seen under it; a name with two is exactly the case worth failing on. */\nfunction collectActionRefs(\n nodes: readonly NodeIR[],\n into: Map<string, Set<string | undefined>>\n): void {\n for (const node of nodes) {\n switch (node.type) {\n case 'action': {\n const seen = into.get(node.action) ?? new Set<string | undefined>();\n seen.add(node.codeHash);\n into.set(node.action, seen);\n break;\n }\n case 'wait_until':\n if (Array.isArray(node.onTimeout)) collectActionRefs(node.onTimeout, into);\n break;\n case 'branch':\n for (const branchCase of node.cases) collectActionRefs(branchCase.flow, into);\n if (node.otherwise) collectActionRefs(node.otherwise, into);\n break;\n case 'cohort':\n for (const arm of node.arms) collectActionRefs(arm.flow, into);\n break;\n default:\n break;\n }\n }\n}\n\n/* --------------------- payload / profile placement checks --------------------- */\n\nfunction assertConditionPlacement(\n workflows: readonly WorkflowIR[],\n segments: readonly SegmentIR[]\n): void {\n for (const workflow of workflows) {\n if (workflow.trigger.type === 'event') {\n if (workflow.trigger.where !== undefined) {\n assertWhereTree(workflow.trigger.where, `workflow '${workflow.id}' trigger where`);\n }\n if (workflow.trigger.filter !== undefined) {\n assertProfileTree(workflow.trigger.filter, `workflow '${workflow.id}' trigger filter`);\n }\n }\n if (workflow.goal !== undefined) {\n assertProfileTree(workflow.goal.condition, `workflow '${workflow.id}' goal`);\n }\n if (workflow.exitWhen !== undefined) {\n assertProfileTree(workflow.exitWhen, `workflow '${workflow.id}' exitWhen`);\n }\n assertNodeTrees(workflow.flow, `workflow '${workflow.id}'`);\n }\n for (const segment of segments) {\n assertProfileTree(segment.condition, `segment '${segment.id}'`);\n }\n}\n\n/** A profile-context tree: payload predicates may appear only under a performed.where. */\nfunction assertProfileTree(condition: ConditionIR, owner: string): void {\n switch (condition.type) {\n case 'and':\n case 'or':\n for (const child of condition.conditions) assertProfileTree(child, owner);\n break;\n case 'not':\n assertProfileTree(condition.condition, owner);\n break;\n case 'performed':\n if (condition.where !== undefined) assertWhereTree(condition.where, owner);\n break;\n case 'payload':\n throw new Error(\n `compileBundle: ${owner} uses a payload predicate outside a where clause — payload refs are only valid inside performed({ where }) or trigger.event({ where })`\n );\n default:\n break; // property / segment\n }\n}\n\n/** A where tree: payload predicates and combinators only — no profile facts in event scope. */\nfunction assertWhereTree(condition: ConditionIR, owner: string): void {\n switch (condition.type) {\n case 'and':\n case 'or':\n for (const child of condition.conditions) assertWhereTree(child, owner);\n break;\n case 'not':\n assertWhereTree(condition.condition, owner);\n break;\n case 'payload':\n break;\n default:\n throw new Error(\n `compileBundle: ${owner} where clause may only contain payload predicates and and/or/not, found '${condition.type}'`\n );\n }\n}\n\nfunction assertNodeTrees(nodes: readonly NodeIR[], owner: string): void {\n for (const node of nodes) {\n switch (node.type) {\n case 'wait_until':\n assertProfileTree(node.condition, owner);\n if (Array.isArray(node.onTimeout)) assertNodeTrees(node.onTimeout, owner);\n break;\n case 'branch':\n for (const branchCase of node.cases) {\n assertProfileTree(branchCase.condition, owner);\n assertNodeTrees(branchCase.flow, owner);\n }\n if (node.otherwise) assertNodeTrees(node.otherwise, owner);\n break;\n case 'filter':\n assertProfileTree(node.condition, owner);\n break;\n case 'cohort':\n for (const arm of node.arms) assertNodeTrees(arm.flow, owner);\n break;\n default:\n break;\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"bundle.cjs","names":["semanticHash","CHANNEL_OF","BundleIRSchema","actionUses"],"sources":["../../src/dsl/bundle.ts"],"sourcesContent":["import { semanticHash } from '../hash';\nimport {\n type BundleIR,\n BundleIR as BundleIRSchema,\n type ConditionIR,\n IR_VERSION,\n type NodeIR,\n type SegmentIR,\n type WorkflowIR,\n} from '../ir';\nimport { type AnyTemplate, CHANNEL_OF } from '../templates';\nimport type { ActionRef } from './action';\nimport type { SegmentInternal, SegmentRef } from './condition';\nimport { actionUses } from './flow';\nimport type { WorkflowBuilder } from './workflow';\n\n/**\n * The deployment unit — compileBundle gathers workflows, segments and the\n * template manifest into one BundleIR snapshot (terraform-apply mental model);\n * the server diffs each definition by contentHash (see plan.ts).\n */\nexport function compileBundle(input: {\n workflows: readonly WorkflowBuilder[];\n segments?: readonly SegmentRef[];\n /** The template manifest: these definitions, by channel and id. */\n templates?: readonly AnyTemplate[];\n actions?: readonly ActionRef[];\n}): BundleIR {\n const segments = (input.segments ?? []).map((segment) => {\n const { id, definition, loc, meta } = segment as SegmentInternal;\n const compiled: SegmentIR = {\n irVersion: IR_VERSION,\n id,\n // Labels and provenance both live in meta, which stripMeta drops before hashing\n contentHash: semanticHash(definition),\n condition: definition,\n };\n const merged = {\n ...(meta?.name !== undefined ? { name: meta.name } : {}),\n ...(meta?.description !== undefined ? { description: meta.description } : {}),\n ...(loc !== undefined ? { loc } : {}),\n };\n if (Object.keys(merged).length > 0) compiled.meta = merged;\n return compiled;\n });\n\n const templates = (input.templates ?? []).map((template) => ({\n irVersion: IR_VERSION,\n key: template.id,\n channel: CHANNEL_OF[template.kind],\n }));\n\n // Manifest only — names; the implementation ships in the Worker bundle, never in IR\n const actions = (input.actions ?? []).map((a) => ({ irVersion: IR_VERSION, name: a.name }));\n\n const workflows = input.workflows.map((builder) => builder.toIR());\n\n // Names are wire identity (routing tables, entry ledger, by-name refs):\n // duplicates would silently last-write-win on deploy, so fail the compile.\n assertUniqueNames(\n workflows.map((w) => w.id),\n 'workflow'\n );\n assertUniqueNames(\n segments.map((s) => s.id),\n 'segment'\n );\n // A message node names its template by channel and key, so the key is unique per channel:\n // an email and a push of one nudge may share it\n assertUniqueNames(\n templates.map((t) => `${t.channel}:${t.key}`),\n 'template'\n );\n assertUniqueNames(\n actions.map((a) => a.name),\n 'action'\n );\n\n // Deploy replaces the whole segment table, so every by-name reference must\n // resolve inside this bundle — catching it here turns a runtime \"Unknown\n // segment\" in some user's journey into a compile error.\n assertSegmentRefsResolve(workflows, segments);\n\n // A segment is evaluated by evaluating the segments it names; a cycle among\n // them has no evaluation order and would recurse until the stack ran out —\n // at run time, in some user's journey. Caught here, with the cycle spelled out.\n assertNoSegmentCycles(segments);\n\n // Payload predicates and profile predicates share the Condition type, so\n // placement is checked here: payload only inside where-trees, and where-trees\n // contain nothing but payload predicates and combinators.\n assertConditionPlacement(workflows, segments);\n\n // Same rationale as segments: the runtime registry is looked up by name, so\n // an action a workflow runs must be in the bundle — and it must be the very\n // definition listed there, or two same-named definitions are competing for\n // one identity (the IR names it; only the compile can tell them apart).\n assertActionRefsResolve(workflows, input.workflows, input.actions ?? []);\n\n return BundleIRSchema.parse({ irVersion: IR_VERSION, workflows, segments, templates, actions });\n}\n\nfunction assertUniqueNames(names: readonly string[], kind: string): void {\n const seen = new Set<string>();\n for (const name of names) {\n if (seen.has(name)) throw new Error(`compileBundle: duplicate ${kind} name '${name}'`);\n seen.add(name);\n }\n}\n\nfunction assertSegmentRefsResolve(\n workflows: readonly WorkflowIR[],\n segments: readonly SegmentIR[]\n): void {\n const defined = new Set(segments.map((s) => s.id));\n const check = (refs: ReadonlySet<string>, owner: string): void => {\n for (const ref of refs) {\n if (!defined.has(ref)) {\n throw new Error(\n `compileBundle: ${owner} references segment '${ref}' that is not in the bundle — pass it in 'segments'`\n );\n }\n }\n };\n for (const workflow of workflows) {\n check(referencedSegments(workflow), `workflow '${workflow.id}'`);\n }\n // Segments may reference other segments; those must resolve too\n for (const segment of segments) {\n const refs = new Set<string>();\n collectConditionRefs(segment.condition, refs);\n check(refs, `segment '${segment.id}'`);\n }\n}\n\nfunction referencedSegments(workflow: WorkflowIR): Set<string> {\n const refs = new Set<string>();\n if (workflow.trigger.type === 'segment') refs.add(workflow.trigger.segment);\n if (workflow.trigger.type === 'event' && workflow.trigger.filter !== undefined) {\n collectConditionRefs(workflow.trigger.filter, refs);\n }\n if (workflow.goal !== undefined) collectConditionRefs(workflow.goal.condition, refs);\n if (workflow.exitWhen !== undefined) collectConditionRefs(workflow.exitWhen, refs);\n collectNodeRefs(workflow.flow, refs);\n return refs;\n}\n\nfunction collectConditionRefs(condition: ConditionIR, into: Set<string>): void {\n switch (condition.type) {\n case 'and':\n case 'or':\n for (const child of condition.conditions) collectConditionRefs(child, into);\n break;\n case 'not':\n collectConditionRefs(condition.condition, into);\n break;\n case 'segment':\n into.add(condition.segment);\n break;\n default:\n break;\n }\n}\n\nfunction collectNodeRefs(nodes: readonly NodeIR[], into: Set<string>): void {\n for (const node of nodes) {\n switch (node.type) {\n case 'wait_until':\n collectConditionRefs(node.condition, into);\n if (Array.isArray(node.onTimeout)) collectNodeRefs(node.onTimeout, into);\n break;\n case 'branch':\n for (const branchCase of node.cases) {\n collectConditionRefs(branchCase.condition, into);\n collectNodeRefs(branchCase.flow, into);\n }\n if (node.otherwise) collectNodeRefs(node.otherwise, into);\n break;\n case 'filter':\n collectConditionRefs(node.condition, into);\n break;\n case 'cohort':\n for (const arm of node.arms) collectNodeRefs(arm.flow, into);\n break;\n default:\n break;\n }\n }\n}\n\nfunction assertActionRefsResolve(\n workflows: readonly WorkflowIR[],\n builders: readonly WorkflowBuilder[],\n actions: readonly ActionRef[]\n): void {\n const listed = new Map(actions.map((a) => [a.name, a]));\n workflows.forEach((workflow, i) => {\n for (const used of actionUses(builders[i])) {\n const manifest = listed.get(used.name);\n if (manifest === undefined) {\n throw new Error(\n `compileBundle: workflow '${workflow.id}' runs action '${used.name}' that is not in the bundle — pass it in 'actions'`\n );\n }\n // Identity, not source text: the printed handler depends on the bundler.\n // Every use is checked, not one per name — with two definitions under one\n // name, whichever is listed, the *other* node would run the wrong code.\n if (manifest !== used && manifest.handler !== used.handler) {\n throw new Error(\n `compileBundle: workflow '${workflow.id}' runs action '${used.name}' that is not the definition listed in 'actions' — two definitions are sharing that name`\n );\n }\n }\n });\n}\n\n/** Walk segment → segment references depth-first; the first back edge is reported as the cycle path. */\nfunction assertNoSegmentCycles(segments: readonly SegmentIR[]): void {\n const refsOf = new Map<string, Set<string>>();\n for (const segment of segments) {\n const refs = new Set<string>();\n collectConditionRefs(segment.condition, refs);\n refsOf.set(segment.id, refs);\n }\n const done = new Set<string>();\n const path: string[] = [];\n const visit = (id: string): void => {\n const at = path.indexOf(id);\n if (at !== -1) {\n const cycle = [...path.slice(at), id].join(' → ');\n throw new Error(`compileBundle: segments reference each other in a cycle: ${cycle}`);\n }\n if (done.has(id)) return;\n path.push(id);\n for (const ref of refsOf.get(id) ?? []) visit(ref);\n path.pop();\n done.add(id);\n };\n for (const id of refsOf.keys()) visit(id);\n}\n\n/* --------------------- payload / profile placement checks --------------------- */\n\nfunction assertConditionPlacement(\n workflows: readonly WorkflowIR[],\n segments: readonly SegmentIR[]\n): void {\n for (const workflow of workflows) {\n if (workflow.trigger.type === 'event') {\n if (workflow.trigger.where !== undefined) {\n assertWhereTree(workflow.trigger.where, `workflow '${workflow.id}' trigger where`);\n }\n if (workflow.trigger.filter !== undefined) {\n assertProfileTree(workflow.trigger.filter, `workflow '${workflow.id}' trigger filter`);\n }\n }\n if (workflow.goal !== undefined) {\n assertProfileTree(workflow.goal.condition, `workflow '${workflow.id}' goal`);\n }\n if (workflow.exitWhen !== undefined) {\n assertProfileTree(workflow.exitWhen, `workflow '${workflow.id}' exitWhen`);\n }\n assertNodeTrees(workflow.flow, `workflow '${workflow.id}'`);\n }\n for (const segment of segments) {\n assertProfileTree(segment.condition, `segment '${segment.id}'`);\n }\n}\n\n/** A profile-context tree: payload predicates may appear only under a performed.where. */\nfunction assertProfileTree(condition: ConditionIR, owner: string): void {\n switch (condition.type) {\n case 'and':\n case 'or':\n for (const child of condition.conditions) assertProfileTree(child, owner);\n break;\n case 'not':\n assertProfileTree(condition.condition, owner);\n break;\n case 'performed':\n if (condition.where !== undefined) assertWhereTree(condition.where, owner);\n break;\n case 'payload':\n throw new Error(\n `compileBundle: ${owner} uses a payload predicate outside a where clause — payload refs are only valid inside performed({ where }) or trigger.event({ where })`\n );\n default:\n break; // property / segment\n }\n}\n\n/** A where tree: payload predicates and combinators only — no profile facts in event scope. */\nfunction assertWhereTree(condition: ConditionIR, owner: string): void {\n switch (condition.type) {\n case 'and':\n case 'or':\n for (const child of condition.conditions) assertWhereTree(child, owner);\n break;\n case 'not':\n assertWhereTree(condition.condition, owner);\n break;\n case 'payload':\n break;\n default:\n throw new Error(\n `compileBundle: ${owner} where clause may only contain payload predicates and and/or/not, found '${condition.type}'`\n );\n }\n}\n\nfunction assertNodeTrees(nodes: readonly NodeIR[], owner: string): void {\n for (const node of nodes) {\n switch (node.type) {\n case 'wait_until':\n assertProfileTree(node.condition, owner);\n if (Array.isArray(node.onTimeout)) assertNodeTrees(node.onTimeout, owner);\n break;\n case 'branch':\n for (const branchCase of node.cases) {\n assertProfileTree(branchCase.condition, owner);\n assertNodeTrees(branchCase.flow, owner);\n }\n if (node.otherwise) assertNodeTrees(node.otherwise, owner);\n break;\n case 'filter':\n assertProfileTree(node.condition, owner);\n break;\n case 'cohort':\n for (const arm of node.arms) assertNodeTrees(arm.flow, owner);\n break;\n default:\n break;\n }\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"bundle.d.cts","names":[],"sources":["../../src/dsl/bundle.ts"],"mappings":";;;;;;;;;;;wBAoBgB,cAAc;EAC5B,oBAAoB;EACpB,oBAAoB;;EAEpB,qBAAqB;EACrB,mBAAmB;IACjB"}
1
+ {"version":3,"file":"bundle.d.cts","names":[],"sources":["../../src/dsl/bundle.ts"],"mappings":";;;;;;;;;;;wBAqBgB,cAAc;EAC5B,oBAAoB;EACpB,oBAAoB;;EAEpB,qBAAqB;EACrB,mBAAmB;IACjB"}
@@ -1 +1 @@
1
- {"version":3,"file":"bundle.d.mts","names":[],"sources":["../../src/dsl/bundle.ts"],"mappings":";;;;;;;;;;;wBAoBgB,cAAc;EAC5B,oBAAoB;EACpB,oBAAoB;;EAEpB,qBAAqB;EACrB,mBAAmB;IACjB"}
1
+ {"version":3,"file":"bundle.d.mts","names":[],"sources":["../../src/dsl/bundle.ts"],"mappings":";;;;;;;;;;;wBAqBgB,cAAc;EAC5B,oBAAoB;EACpB,oBAAoB;;EAEpB,qBAAqB;EACrB,mBAAmB;IACjB"}
@@ -1,5 +1,6 @@
1
- import { semanticHash } from "../hash.mjs";
2
1
  import { CHANNEL_OF } from "../templates.mjs";
2
+ import { actionUses } from "./flow.mjs";
3
+ import { semanticHash } from "../hash.mjs";
3
4
  import { BundleIR } from "../ir.mjs";
4
5
  //#region src/dsl/bundle.ts
5
6
  /**
@@ -31,8 +32,7 @@ function compileBundle(input) {
31
32
  }));
32
33
  const actions = (input.actions ?? []).map((a) => ({
33
34
  irVersion: 2,
34
- name: a.name,
35
- codeHash: a.codeHash
35
+ name: a.name
36
36
  }));
37
37
  const workflows = input.workflows.map((builder) => builder.toIR());
38
38
  assertUniqueNames(workflows.map((w) => w.id), "workflow");
@@ -40,8 +40,9 @@ function compileBundle(input) {
40
40
  assertUniqueNames(templates.map((t) => `${t.channel}:${t.key}`), "template");
41
41
  assertUniqueNames(actions.map((a) => a.name), "action");
42
42
  assertSegmentRefsResolve(workflows, segments);
43
+ assertNoSegmentCycles(segments);
43
44
  assertConditionPlacement(workflows, segments);
44
- assertActionRefsResolve(workflows, actions);
45
+ assertActionRefsResolve(workflows, input.workflows, input.actions ?? []);
45
46
  return BundleIR.parse({
46
47
  irVersion: 2,
47
48
  workflows,
@@ -109,36 +110,39 @@ function collectNodeRefs(nodes, into) {
109
110
  case "cohort": for (const arm of node.arms) collectNodeRefs(arm.flow, into);
110
111
  }
111
112
  }
112
- function assertActionRefsResolve(workflows, actions) {
113
- const defined = new Map(actions.map((a) => [a.name, a.codeHash]));
114
- for (const workflow of workflows) {
115
- const used = /* @__PURE__ */ new Map();
116
- collectActionRefs(workflow.flow, used);
117
- for (const [name, codeHashes] of used) {
118
- const manifestHash = defined.get(name);
119
- if (manifestHash === void 0) throw new Error(`compileBundle: workflow '${workflow.id}' runs action '${name}' that is not in the bundle — pass it in 'actions'`);
120
- for (const codeHash of codeHashes) if (codeHash !== void 0 && codeHash !== manifestHash) throw new Error(`compileBundle: workflow '${workflow.id}' runs action '${name}' whose code differs from the one in 'actions' — two definitions are sharing that name`);
113
+ function assertActionRefsResolve(workflows, builders, actions) {
114
+ const listed = new Map(actions.map((a) => [a.name, a]));
115
+ workflows.forEach((workflow, i) => {
116
+ for (const used of actionUses(builders[i])) {
117
+ const manifest = listed.get(used.name);
118
+ if (manifest === void 0) throw new Error(`compileBundle: workflow '${workflow.id}' runs action '${used.name}' that is not in the bundle — pass it in 'actions'`);
119
+ if (manifest !== used && manifest.handler !== used.handler) throw new Error(`compileBundle: workflow '${workflow.id}' runs action '${used.name}' that is not the definition listed in 'actions' — two definitions are sharing that name`);
121
120
  }
122
- }
121
+ });
123
122
  }
124
- /** name → every codeHash seen under it; a name with two is exactly the case worth failing on. */
125
- function collectActionRefs(nodes, into) {
126
- for (const node of nodes) switch (node.type) {
127
- case "action": {
128
- const seen = into.get(node.action) ?? /* @__PURE__ */ new Set();
129
- seen.add(node.codeHash);
130
- into.set(node.action, seen);
131
- break;
132
- }
133
- case "wait_until":
134
- if (Array.isArray(node.onTimeout)) collectActionRefs(node.onTimeout, into);
135
- break;
136
- case "branch":
137
- for (const branchCase of node.cases) collectActionRefs(branchCase.flow, into);
138
- if (node.otherwise) collectActionRefs(node.otherwise, into);
139
- break;
140
- case "cohort": for (const arm of node.arms) collectActionRefs(arm.flow, into);
123
+ /** Walk segment → segment references depth-first; the first back edge is reported as the cycle path. */
124
+ function assertNoSegmentCycles(segments) {
125
+ const refsOf = /* @__PURE__ */ new Map();
126
+ for (const segment of segments) {
127
+ const refs = /* @__PURE__ */ new Set();
128
+ collectConditionRefs(segment.condition, refs);
129
+ refsOf.set(segment.id, refs);
141
130
  }
131
+ const done = /* @__PURE__ */ new Set();
132
+ const path = [];
133
+ const visit = (id) => {
134
+ const at = path.indexOf(id);
135
+ if (at !== -1) {
136
+ const cycle = [...path.slice(at), id].join(" → ");
137
+ throw new Error(`compileBundle: segments reference each other in a cycle: ${cycle}`);
138
+ }
139
+ if (done.has(id)) return;
140
+ path.push(id);
141
+ for (const ref of refsOf.get(id) ?? []) visit(ref);
142
+ path.pop();
143
+ done.add(id);
144
+ };
145
+ for (const id of refsOf.keys()) visit(id);
142
146
  }
143
147
  function assertConditionPlacement(workflows, segments) {
144
148
  for (const workflow of workflows) {
@@ -1 +1 @@
1
- {"version":3,"file":"bundle.mjs","names":["BundleIRSchema"],"sources":["../../src/dsl/bundle.ts"],"sourcesContent":["import { semanticHash } from '../hash';\nimport {\n type BundleIR,\n BundleIR as BundleIRSchema,\n type ConditionIR,\n IR_VERSION,\n type NodeIR,\n type SegmentIR,\n type WorkflowIR,\n} from '../ir';\nimport { type AnyTemplate, CHANNEL_OF } from '../templates';\nimport type { ActionRef } from './action';\nimport type { SegmentInternal, SegmentRef } from './condition';\nimport type { WorkflowBuilder } from './workflow';\n\n/**\n * The deployment unit — compileBundle gathers workflows, segments and the\n * template manifest into one BundleIR snapshot (terraform-apply mental model);\n * the server diffs each definition by contentHash (see plan.ts).\n */\nexport function compileBundle(input: {\n workflows: readonly WorkflowBuilder[];\n segments?: readonly SegmentRef[];\n /** The template manifest: these definitions, by channel and id. */\n templates?: readonly AnyTemplate[];\n actions?: readonly ActionRef[];\n}): BundleIR {\n const segments = (input.segments ?? []).map((segment) => {\n const { id, definition, loc, meta } = segment as SegmentInternal;\n const compiled: SegmentIR = {\n irVersion: IR_VERSION,\n id,\n // Labels and provenance both live in meta, which stripMeta drops before hashing\n contentHash: semanticHash(definition),\n condition: definition,\n };\n const merged = {\n ...(meta?.name !== undefined ? { name: meta.name } : {}),\n ...(meta?.description !== undefined ? { description: meta.description } : {}),\n ...(loc !== undefined ? { loc } : {}),\n };\n if (Object.keys(merged).length > 0) compiled.meta = merged;\n return compiled;\n });\n\n const templates = (input.templates ?? []).map((template) => ({\n irVersion: IR_VERSION,\n key: template.id,\n channel: CHANNEL_OF[template.kind],\n }));\n\n // Manifest only — name and code-identity hash; the implementation ships in the Worker bundle, never in IR\n const actions = (input.actions ?? []).map((a) => ({\n irVersion: IR_VERSION,\n name: a.name,\n codeHash: a.codeHash,\n }));\n\n const workflows = input.workflows.map((builder) => builder.toIR());\n\n // Names are wire identity (routing tables, entry ledger, by-name refs):\n // duplicates would silently last-write-win on deploy, so fail the compile.\n assertUniqueNames(\n workflows.map((w) => w.id),\n 'workflow'\n );\n assertUniqueNames(\n segments.map((s) => s.id),\n 'segment'\n );\n // A message node names its template by channel and key, so the key is unique per channel:\n // an email and a push of one nudge may share it\n assertUniqueNames(\n templates.map((t) => `${t.channel}:${t.key}`),\n 'template'\n );\n assertUniqueNames(\n actions.map((a) => a.name),\n 'action'\n );\n\n // Deploy replaces the whole segment table, so every by-name reference must\n // resolve inside this bundle — catching it here turns a runtime \"Unknown\n // segment\" in some user's journey into a compile error.\n assertSegmentRefsResolve(workflows, segments);\n\n // Payload predicates and profile predicates share the Condition type, so\n // placement is checked here: payload only inside where-trees, and where-trees\n // contain nothing but payload predicates and combinators.\n assertConditionPlacement(workflows, segments);\n\n // Same rationale as segments: the runtime registry is looked up by name, so\n // an action a workflow runs must be in the bundle — and its codeHash must\n // agree, or two same-named definitions are competing for one identity.\n assertActionRefsResolve(workflows, actions);\n\n return BundleIRSchema.parse({ irVersion: IR_VERSION, workflows, segments, templates, actions });\n}\n\nfunction assertUniqueNames(names: readonly string[], kind: string): void {\n const seen = new Set<string>();\n for (const name of names) {\n if (seen.has(name)) throw new Error(`compileBundle: duplicate ${kind} name '${name}'`);\n seen.add(name);\n }\n}\n\nfunction assertSegmentRefsResolve(\n workflows: readonly WorkflowIR[],\n segments: readonly SegmentIR[]\n): void {\n const defined = new Set(segments.map((s) => s.id));\n const check = (refs: ReadonlySet<string>, owner: string): void => {\n for (const ref of refs) {\n if (!defined.has(ref)) {\n throw new Error(\n `compileBundle: ${owner} references segment '${ref}' that is not in the bundle — pass it in 'segments'`\n );\n }\n }\n };\n for (const workflow of workflows) {\n check(referencedSegments(workflow), `workflow '${workflow.id}'`);\n }\n // Segments may reference other segments; those must resolve too\n for (const segment of segments) {\n const refs = new Set<string>();\n collectConditionRefs(segment.condition, refs);\n check(refs, `segment '${segment.id}'`);\n }\n}\n\nfunction referencedSegments(workflow: WorkflowIR): Set<string> {\n const refs = new Set<string>();\n if (workflow.trigger.type === 'segment') refs.add(workflow.trigger.segment);\n if (workflow.trigger.type === 'event' && workflow.trigger.filter !== undefined) {\n collectConditionRefs(workflow.trigger.filter, refs);\n }\n if (workflow.goal !== undefined) collectConditionRefs(workflow.goal.condition, refs);\n if (workflow.exitWhen !== undefined) collectConditionRefs(workflow.exitWhen, refs);\n collectNodeRefs(workflow.flow, refs);\n return refs;\n}\n\nfunction collectConditionRefs(condition: ConditionIR, into: Set<string>): void {\n switch (condition.type) {\n case 'and':\n case 'or':\n for (const child of condition.conditions) collectConditionRefs(child, into);\n break;\n case 'not':\n collectConditionRefs(condition.condition, into);\n break;\n case 'segment':\n into.add(condition.segment);\n break;\n default:\n break;\n }\n}\n\nfunction collectNodeRefs(nodes: readonly NodeIR[], into: Set<string>): void {\n for (const node of nodes) {\n switch (node.type) {\n case 'wait_until':\n collectConditionRefs(node.condition, into);\n if (Array.isArray(node.onTimeout)) collectNodeRefs(node.onTimeout, into);\n break;\n case 'branch':\n for (const branchCase of node.cases) {\n collectConditionRefs(branchCase.condition, into);\n collectNodeRefs(branchCase.flow, into);\n }\n if (node.otherwise) collectNodeRefs(node.otherwise, into);\n break;\n case 'filter':\n collectConditionRefs(node.condition, into);\n break;\n case 'cohort':\n for (const arm of node.arms) collectNodeRefs(arm.flow, into);\n break;\n default:\n break;\n }\n }\n}\n\nfunction assertActionRefsResolve(\n workflows: readonly WorkflowIR[],\n actions: readonly { name: string; codeHash: string }[]\n): void {\n const defined = new Map(actions.map((a) => [a.name, a.codeHash]));\n for (const workflow of workflows) {\n const used = new Map<string, Set<string | undefined>>();\n collectActionRefs(workflow.flow, used);\n for (const [name, codeHashes] of used) {\n const manifestHash = defined.get(name);\n if (manifestHash === undefined) {\n throw new Error(\n `compileBundle: workflow '${workflow.id}' runs action '${name}' that is not in the bundle — pass it in 'actions'`\n );\n }\n // Every node's hash is checked, not just one per name: with two\n // definitions under one name, whichever the manifest holds, the *other*\n // node is the one that would meet a mismatched implementation at run time.\n for (const codeHash of codeHashes) {\n if (codeHash !== undefined && codeHash !== manifestHash) {\n throw new Error(\n `compileBundle: workflow '${workflow.id}' runs action '${name}' whose code differs from the one in 'actions' — two definitions are sharing that name`\n );\n }\n }\n }\n }\n}\n\n/** name → every codeHash seen under it; a name with two is exactly the case worth failing on. */\nfunction collectActionRefs(\n nodes: readonly NodeIR[],\n into: Map<string, Set<string | undefined>>\n): void {\n for (const node of nodes) {\n switch (node.type) {\n case 'action': {\n const seen = into.get(node.action) ?? new Set<string | undefined>();\n seen.add(node.codeHash);\n into.set(node.action, seen);\n break;\n }\n case 'wait_until':\n if (Array.isArray(node.onTimeout)) collectActionRefs(node.onTimeout, into);\n break;\n case 'branch':\n for (const branchCase of node.cases) collectActionRefs(branchCase.flow, into);\n if (node.otherwise) collectActionRefs(node.otherwise, into);\n break;\n case 'cohort':\n for (const arm of node.arms) collectActionRefs(arm.flow, into);\n break;\n default:\n break;\n }\n }\n}\n\n/* --------------------- payload / profile placement checks --------------------- */\n\nfunction assertConditionPlacement(\n workflows: readonly WorkflowIR[],\n segments: readonly SegmentIR[]\n): void {\n for (const workflow of workflows) {\n if (workflow.trigger.type === 'event') {\n if (workflow.trigger.where !== undefined) {\n assertWhereTree(workflow.trigger.where, `workflow '${workflow.id}' trigger where`);\n }\n if (workflow.trigger.filter !== undefined) {\n assertProfileTree(workflow.trigger.filter, `workflow '${workflow.id}' trigger filter`);\n }\n }\n if (workflow.goal !== undefined) {\n assertProfileTree(workflow.goal.condition, `workflow '${workflow.id}' goal`);\n }\n if (workflow.exitWhen !== undefined) {\n assertProfileTree(workflow.exitWhen, `workflow '${workflow.id}' exitWhen`);\n }\n assertNodeTrees(workflow.flow, `workflow '${workflow.id}'`);\n }\n for (const segment of segments) {\n assertProfileTree(segment.condition, `segment '${segment.id}'`);\n }\n}\n\n/** A profile-context tree: payload predicates may appear only under a performed.where. */\nfunction assertProfileTree(condition: ConditionIR, owner: string): void {\n switch (condition.type) {\n case 'and':\n case 'or':\n for (const child of condition.conditions) assertProfileTree(child, owner);\n break;\n case 'not':\n assertProfileTree(condition.condition, owner);\n break;\n case 'performed':\n if (condition.where !== undefined) assertWhereTree(condition.where, owner);\n break;\n case 'payload':\n throw new Error(\n `compileBundle: ${owner} uses a payload predicate outside a where clause — payload refs are only valid inside performed({ where }) or trigger.event({ where })`\n );\n default:\n break; // property / segment\n }\n}\n\n/** A where tree: payload predicates and combinators only — no profile facts in event scope. */\nfunction assertWhereTree(condition: ConditionIR, owner: string): void {\n switch (condition.type) {\n case 'and':\n case 'or':\n for (const child of condition.conditions) assertWhereTree(child, owner);\n break;\n case 'not':\n assertWhereTree(condition.condition, owner);\n break;\n case 'payload':\n break;\n default:\n throw new Error(\n `compileBundle: ${owner} where clause may only contain payload predicates and and/or/not, found '${condition.type}'`\n );\n }\n}\n\nfunction assertNodeTrees(nodes: readonly NodeIR[], owner: string): void {\n for (const node of nodes) {\n switch (node.type) {\n case 'wait_until':\n assertProfileTree(node.condition, owner);\n if (Array.isArray(node.onTimeout)) assertNodeTrees(node.onTimeout, owner);\n break;\n case 'branch':\n for (const branchCase of node.cases) {\n assertProfileTree(branchCase.condition, owner);\n assertNodeTrees(branchCase.flow, owner);\n }\n if (node.otherwise) assertNodeTrees(node.otherwise, owner);\n break;\n case 'filter':\n assertProfileTree(node.condition, owner);\n break;\n case 'cohort':\n for (const arm of node.arms) assertNodeTrees(arm.flow, owner);\n break;\n default:\n break;\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"bundle.mjs","names":["BundleIRSchema"],"sources":["../../src/dsl/bundle.ts"],"sourcesContent":["import { semanticHash } from '../hash';\nimport {\n type BundleIR,\n BundleIR as BundleIRSchema,\n type ConditionIR,\n IR_VERSION,\n type NodeIR,\n type SegmentIR,\n type WorkflowIR,\n} from '../ir';\nimport { type AnyTemplate, CHANNEL_OF } from '../templates';\nimport type { ActionRef } from './action';\nimport type { SegmentInternal, SegmentRef } from './condition';\nimport { actionUses } from './flow';\nimport type { WorkflowBuilder } from './workflow';\n\n/**\n * The deployment unit — compileBundle gathers workflows, segments and the\n * template manifest into one BundleIR snapshot (terraform-apply mental model);\n * the server diffs each definition by contentHash (see plan.ts).\n */\nexport function compileBundle(input: {\n workflows: readonly WorkflowBuilder[];\n segments?: readonly SegmentRef[];\n /** The template manifest: these definitions, by channel and id. */\n templates?: readonly AnyTemplate[];\n actions?: readonly ActionRef[];\n}): BundleIR {\n const segments = (input.segments ?? []).map((segment) => {\n const { id, definition, loc, meta } = segment as SegmentInternal;\n const compiled: SegmentIR = {\n irVersion: IR_VERSION,\n id,\n // Labels and provenance both live in meta, which stripMeta drops before hashing\n contentHash: semanticHash(definition),\n condition: definition,\n };\n const merged = {\n ...(meta?.name !== undefined ? { name: meta.name } : {}),\n ...(meta?.description !== undefined ? { description: meta.description } : {}),\n ...(loc !== undefined ? { loc } : {}),\n };\n if (Object.keys(merged).length > 0) compiled.meta = merged;\n return compiled;\n });\n\n const templates = (input.templates ?? []).map((template) => ({\n irVersion: IR_VERSION,\n key: template.id,\n channel: CHANNEL_OF[template.kind],\n }));\n\n // Manifest only — names; the implementation ships in the Worker bundle, never in IR\n const actions = (input.actions ?? []).map((a) => ({ irVersion: IR_VERSION, name: a.name }));\n\n const workflows = input.workflows.map((builder) => builder.toIR());\n\n // Names are wire identity (routing tables, entry ledger, by-name refs):\n // duplicates would silently last-write-win on deploy, so fail the compile.\n assertUniqueNames(\n workflows.map((w) => w.id),\n 'workflow'\n );\n assertUniqueNames(\n segments.map((s) => s.id),\n 'segment'\n );\n // A message node names its template by channel and key, so the key is unique per channel:\n // an email and a push of one nudge may share it\n assertUniqueNames(\n templates.map((t) => `${t.channel}:${t.key}`),\n 'template'\n );\n assertUniqueNames(\n actions.map((a) => a.name),\n 'action'\n );\n\n // Deploy replaces the whole segment table, so every by-name reference must\n // resolve inside this bundle — catching it here turns a runtime \"Unknown\n // segment\" in some user's journey into a compile error.\n assertSegmentRefsResolve(workflows, segments);\n\n // A segment is evaluated by evaluating the segments it names; a cycle among\n // them has no evaluation order and would recurse until the stack ran out —\n // at run time, in some user's journey. Caught here, with the cycle spelled out.\n assertNoSegmentCycles(segments);\n\n // Payload predicates and profile predicates share the Condition type, so\n // placement is checked here: payload only inside where-trees, and where-trees\n // contain nothing but payload predicates and combinators.\n assertConditionPlacement(workflows, segments);\n\n // Same rationale as segments: the runtime registry is looked up by name, so\n // an action a workflow runs must be in the bundle — and it must be the very\n // definition listed there, or two same-named definitions are competing for\n // one identity (the IR names it; only the compile can tell them apart).\n assertActionRefsResolve(workflows, input.workflows, input.actions ?? []);\n\n return BundleIRSchema.parse({ irVersion: IR_VERSION, workflows, segments, templates, actions });\n}\n\nfunction assertUniqueNames(names: readonly string[], kind: string): void {\n const seen = new Set<string>();\n for (const name of names) {\n if (seen.has(name)) throw new Error(`compileBundle: duplicate ${kind} name '${name}'`);\n seen.add(name);\n }\n}\n\nfunction assertSegmentRefsResolve(\n workflows: readonly WorkflowIR[],\n segments: readonly SegmentIR[]\n): void {\n const defined = new Set(segments.map((s) => s.id));\n const check = (refs: ReadonlySet<string>, owner: string): void => {\n for (const ref of refs) {\n if (!defined.has(ref)) {\n throw new Error(\n `compileBundle: ${owner} references segment '${ref}' that is not in the bundle — pass it in 'segments'`\n );\n }\n }\n };\n for (const workflow of workflows) {\n check(referencedSegments(workflow), `workflow '${workflow.id}'`);\n }\n // Segments may reference other segments; those must resolve too\n for (const segment of segments) {\n const refs = new Set<string>();\n collectConditionRefs(segment.condition, refs);\n check(refs, `segment '${segment.id}'`);\n }\n}\n\nfunction referencedSegments(workflow: WorkflowIR): Set<string> {\n const refs = new Set<string>();\n if (workflow.trigger.type === 'segment') refs.add(workflow.trigger.segment);\n if (workflow.trigger.type === 'event' && workflow.trigger.filter !== undefined) {\n collectConditionRefs(workflow.trigger.filter, refs);\n }\n if (workflow.goal !== undefined) collectConditionRefs(workflow.goal.condition, refs);\n if (workflow.exitWhen !== undefined) collectConditionRefs(workflow.exitWhen, refs);\n collectNodeRefs(workflow.flow, refs);\n return refs;\n}\n\nfunction collectConditionRefs(condition: ConditionIR, into: Set<string>): void {\n switch (condition.type) {\n case 'and':\n case 'or':\n for (const child of condition.conditions) collectConditionRefs(child, into);\n break;\n case 'not':\n collectConditionRefs(condition.condition, into);\n break;\n case 'segment':\n into.add(condition.segment);\n break;\n default:\n break;\n }\n}\n\nfunction collectNodeRefs(nodes: readonly NodeIR[], into: Set<string>): void {\n for (const node of nodes) {\n switch (node.type) {\n case 'wait_until':\n collectConditionRefs(node.condition, into);\n if (Array.isArray(node.onTimeout)) collectNodeRefs(node.onTimeout, into);\n break;\n case 'branch':\n for (const branchCase of node.cases) {\n collectConditionRefs(branchCase.condition, into);\n collectNodeRefs(branchCase.flow, into);\n }\n if (node.otherwise) collectNodeRefs(node.otherwise, into);\n break;\n case 'filter':\n collectConditionRefs(node.condition, into);\n break;\n case 'cohort':\n for (const arm of node.arms) collectNodeRefs(arm.flow, into);\n break;\n default:\n break;\n }\n }\n}\n\nfunction assertActionRefsResolve(\n workflows: readonly WorkflowIR[],\n builders: readonly WorkflowBuilder[],\n actions: readonly ActionRef[]\n): void {\n const listed = new Map(actions.map((a) => [a.name, a]));\n workflows.forEach((workflow, i) => {\n for (const used of actionUses(builders[i])) {\n const manifest = listed.get(used.name);\n if (manifest === undefined) {\n throw new Error(\n `compileBundle: workflow '${workflow.id}' runs action '${used.name}' that is not in the bundle — pass it in 'actions'`\n );\n }\n // Identity, not source text: the printed handler depends on the bundler.\n // Every use is checked, not one per name — with two definitions under one\n // name, whichever is listed, the *other* node would run the wrong code.\n if (manifest !== used && manifest.handler !== used.handler) {\n throw new Error(\n `compileBundle: workflow '${workflow.id}' runs action '${used.name}' that is not the definition listed in 'actions' — two definitions are sharing that name`\n );\n }\n }\n });\n}\n\n/** Walk segment → segment references depth-first; the first back edge is reported as the cycle path. */\nfunction assertNoSegmentCycles(segments: readonly SegmentIR[]): void {\n const refsOf = new Map<string, Set<string>>();\n for (const segment of segments) {\n const refs = new Set<string>();\n collectConditionRefs(segment.condition, refs);\n refsOf.set(segment.id, refs);\n }\n const done = new Set<string>();\n const path: string[] = [];\n const visit = (id: string): void => {\n const at = path.indexOf(id);\n if (at !== -1) {\n const cycle = [...path.slice(at), id].join(' → ');\n throw new Error(`compileBundle: segments reference each other in a cycle: ${cycle}`);\n }\n if (done.has(id)) return;\n path.push(id);\n for (const ref of refsOf.get(id) ?? []) visit(ref);\n path.pop();\n done.add(id);\n };\n for (const id of refsOf.keys()) visit(id);\n}\n\n/* --------------------- payload / profile placement checks --------------------- */\n\nfunction assertConditionPlacement(\n workflows: readonly WorkflowIR[],\n segments: readonly SegmentIR[]\n): void {\n for (const workflow of workflows) {\n if (workflow.trigger.type === 'event') {\n if (workflow.trigger.where !== undefined) {\n assertWhereTree(workflow.trigger.where, `workflow '${workflow.id}' trigger where`);\n }\n if (workflow.trigger.filter !== undefined) {\n assertProfileTree(workflow.trigger.filter, `workflow '${workflow.id}' trigger filter`);\n }\n }\n if (workflow.goal !== undefined) {\n assertProfileTree(workflow.goal.condition, `workflow '${workflow.id}' goal`);\n }\n if (workflow.exitWhen !== undefined) {\n assertProfileTree(workflow.exitWhen, `workflow '${workflow.id}' exitWhen`);\n }\n assertNodeTrees(workflow.flow, `workflow '${workflow.id}'`);\n }\n for (const segment of segments) {\n assertProfileTree(segment.condition, `segment '${segment.id}'`);\n }\n}\n\n/** A profile-context tree: payload predicates may appear only under a performed.where. */\nfunction assertProfileTree(condition: ConditionIR, owner: string): void {\n switch (condition.type) {\n case 'and':\n case 'or':\n for (const child of condition.conditions) assertProfileTree(child, owner);\n break;\n case 'not':\n assertProfileTree(condition.condition, owner);\n break;\n case 'performed':\n if (condition.where !== undefined) assertWhereTree(condition.where, owner);\n break;\n case 'payload':\n throw new Error(\n `compileBundle: ${owner} uses a payload predicate outside a where clause — payload refs are only valid inside performed({ where }) or trigger.event({ where })`\n );\n default:\n break; // property / segment\n }\n}\n\n/** A where tree: payload predicates and combinators only — no profile facts in event scope. */\nfunction assertWhereTree(condition: ConditionIR, owner: string): void {\n switch (condition.type) {\n case 'and':\n case 'or':\n for (const child of condition.conditions) assertWhereTree(child, owner);\n break;\n case 'not':\n assertWhereTree(condition.condition, owner);\n break;\n case 'payload':\n break;\n default:\n throw new Error(\n `compileBundle: ${owner} where clause may only contain payload predicates and and/or/not, found '${condition.type}'`\n );\n }\n}\n\nfunction assertNodeTrees(nodes: readonly NodeIR[], owner: string): void {\n for (const node of nodes) {\n switch (node.type) {\n case 'wait_until':\n assertProfileTree(node.condition, owner);\n if (Array.isArray(node.onTimeout)) assertNodeTrees(node.onTimeout, owner);\n break;\n case 'branch':\n for (const branchCase of node.cases) {\n assertProfileTree(branchCase.condition, owner);\n assertNodeTrees(branchCase.flow, owner);\n }\n if (node.otherwise) assertNodeTrees(node.otherwise, owner);\n break;\n case 'filter':\n assertProfileTree(node.condition, owner);\n break;\n case 'cohort':\n for (const arm of node.arms) assertNodeTrees(arm.flow, owner);\n break;\n default:\n break;\n }\n }\n}\n"],"mappings":"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"}
@@ -77,14 +77,25 @@ function contains(ref, value) {
77
77
  function notContains(ref, value) {
78
78
  return prop(ref, "not_contains", value, void 0, require_provenance.captureLoc(notContains));
79
79
  }
80
+ /**
81
+ * @internal A duration that has to elapse — a wait's timeout, a goal's window, a performed
82
+ * window. Zero parses as a valid duration but means "never" or "always" depending on the node,
83
+ * and either way it is not what the author meant.
84
+ */
85
+ function positiveDurationIR(value, what) {
86
+ const ir = require_dsl_base.durationIR(value);
87
+ if (ir.ms <= 0) throw new Error(`${what} must be positive, got '${value}'`);
88
+ return ir;
89
+ }
80
90
  function performed(event, whereOrOpts, maybeOpts) {
81
91
  const where = typeof whereOrOpts === "function" ? whereOrOpts : void 0;
82
92
  const opts = typeof whereOrOpts === "function" ? maybeOpts : whereOrOpts;
93
+ if (opts?.count !== void 0 && (!Number.isInteger(opts.count) || opts.count < 1)) throw new Error(`performed(): count must be a positive integer, got ${String(opts.count)}`);
83
94
  return cond({
84
95
  type: "performed",
85
96
  event: event.name,
86
97
  ...where !== void 0 ? { where: condIR(where(require_dsl_refs.payloadRefs())) } : {},
87
- ...opts?.within !== void 0 ? { within: require_dsl_base.durationIR(opts.within) } : {},
98
+ ...opts?.within !== void 0 ? { within: positiveDurationIR(opts.within, "performed(): within") } : {},
88
99
  ...opts?.count !== void 0 ? { count: opts.count } : {}
89
100
  }, require_provenance.captureLoc(performed));
90
101
  }
@@ -173,6 +184,7 @@ exports.notExists = notExists;
173
184
  exports.notInArray = notInArray;
174
185
  exports.or = or;
175
186
  exports.performed = performed;
187
+ exports.positiveDurationIR = positiveDurationIR;
176
188
  exports.reachable = reachable;
177
189
  exports.segment = segment;
178
190