@segmentstream/ontology 0.0.0-stage → 0.92.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (483) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +2 -0
  3. package/README.md +1655 -2
  4. package/dist/action.d.ts +193 -0
  5. package/dist/action.js +41 -0
  6. package/dist/agent-chat.d.ts +44 -0
  7. package/dist/agent-chat.js +91 -0
  8. package/dist/agent-task-contract.d.ts +64 -0
  9. package/dist/agent-task-contract.js +15 -0
  10. package/dist/agent-task.d.ts +39 -0
  11. package/dist/agent-task.js +112 -0
  12. package/dist/analyze.d.ts +215 -0
  13. package/dist/analyze.js +2438 -0
  14. package/dist/app-feedback.d.ts +167 -0
  15. package/dist/app-feedback.js +176 -0
  16. package/dist/authoring-input.d.ts +3 -0
  17. package/dist/authoring-input.js +107 -0
  18. package/dist/authoring-names.d.ts +4 -0
  19. package/dist/authoring-names.js +13 -0
  20. package/dist/authoring-records.d.ts +35 -0
  21. package/dist/authoring-records.js +264 -0
  22. package/dist/authoring.d.ts +15 -0
  23. package/dist/authoring.js +331 -0
  24. package/dist/bin/check.d.ts +30 -0
  25. package/dist/bin/check.js +99 -0
  26. package/dist/bin/load.d.ts +48 -0
  27. package/dist/bin/load.js +139 -0
  28. package/dist/bin/main.d.ts +31 -0
  29. package/dist/bin/main.js +121 -0
  30. package/dist/bin/migrate.d.ts +57 -0
  31. package/dist/bin/migrate.js +170 -0
  32. package/dist/bin/output.d.ts +31 -0
  33. package/dist/bin/output.js +84 -0
  34. package/dist/bin.d.ts +2 -0
  35. package/dist/bin.js +31 -0
  36. package/dist/blob.d.ts +52 -0
  37. package/dist/blob.js +87 -0
  38. package/dist/channel-session.d.ts +16 -0
  39. package/dist/channel-session.js +16 -0
  40. package/dist/chat.d.ts +43 -0
  41. package/dist/chat.js +34 -0
  42. package/dist/confirmation.d.ts +46 -0
  43. package/dist/confirmation.js +41 -0
  44. package/dist/connection.d.ts +213 -0
  45. package/dist/connection.js +289 -0
  46. package/dist/deployment.d.ts +35 -0
  47. package/dist/deployment.js +76 -0
  48. package/dist/diagnostics.d.ts +27 -0
  49. package/dist/diagnostics.js +106 -0
  50. package/dist/emails.d.ts +70 -0
  51. package/dist/emails.js +221 -0
  52. package/dist/entity.d.ts +331 -0
  53. package/dist/entity.js +153 -0
  54. package/dist/errors.d.ts +154 -0
  55. package/dist/errors.js +109 -0
  56. package/dist/feedback-config.d.ts +24 -0
  57. package/dist/feedback-config.js +126 -0
  58. package/dist/files.d.ts +39 -0
  59. package/dist/files.js +4 -0
  60. package/dist/groups.d.ts +95 -0
  61. package/dist/groups.js +280 -0
  62. package/dist/index.d.ts +109 -0
  63. package/dist/index.js +70 -0
  64. package/dist/internal/diagnostics.js +594 -0
  65. package/dist/legacy.d.ts +160 -0
  66. package/dist/legacy.js +270 -0
  67. package/dist/manifest-compatibility.d.ts +8 -0
  68. package/dist/manifest-compatibility.js +87 -0
  69. package/dist/manifest.d.ts +3329 -0
  70. package/dist/manifest.js +757 -0
  71. package/dist/me.d.ts +46 -0
  72. package/dist/me.js +109 -0
  73. package/dist/metric-filters.d.ts +48 -0
  74. package/dist/metric-filters.js +252 -0
  75. package/dist/metric-routes.d.ts +132 -0
  76. package/dist/metric-routes.js +498 -0
  77. package/dist/metric.d.ts +172 -0
  78. package/dist/metric.js +132 -0
  79. package/dist/migrate/apply.d.ts +94 -0
  80. package/dist/migrate/apply.js +526 -0
  81. package/dist/migrate/errors.d.ts +46 -0
  82. package/dist/migrate/errors.js +38 -0
  83. package/dist/migrate/index.d.ts +10 -0
  84. package/dist/migrate/index.js +7 -0
  85. package/dist/migrate/model.d.ts +2311 -0
  86. package/dist/migrate/model.js +148 -0
  87. package/dist/migrate/plan.d.ts +51 -0
  88. package/dist/migrate/plan.js +1856 -0
  89. package/dist/migrate/preview.d.ts +13 -0
  90. package/dist/migrate/preview.js +65 -0
  91. package/dist/migrate/storage.d.ts +226 -0
  92. package/dist/migrate/storage.js +774 -0
  93. package/dist/model-metrics.d.ts +22 -0
  94. package/dist/model-metrics.js +545 -0
  95. package/dist/names.d.ts +49 -0
  96. package/dist/names.js +110 -0
  97. package/dist/navigation-artifact.d.ts +13 -0
  98. package/dist/navigation-artifact.js +11 -0
  99. package/dist/navigation-catalog.d.ts +55 -0
  100. package/dist/navigation-catalog.js +297 -0
  101. package/dist/nitro-navigation.d.ts +3 -0
  102. package/dist/nitro-navigation.js +64 -0
  103. package/dist/nitro-workflows.d.ts +108 -0
  104. package/dist/nitro-workflows.js +286 -0
  105. package/dist/nitro.d.ts +40 -0
  106. package/dist/nitro.js +50 -0
  107. package/dist/ontology.d.ts +41 -0
  108. package/dist/ontology.js +46 -0
  109. package/dist/permissions.d.ts +198 -0
  110. package/dist/permissions.js +272 -0
  111. package/dist/property.d.ts +188 -0
  112. package/dist/property.js +301 -0
  113. package/dist/query.d.ts +245 -0
  114. package/dist/query.js +615 -0
  115. package/dist/relationship.d.ts +99 -0
  116. package/dist/relationship.js +33 -0
  117. package/dist/render.d.ts +81 -0
  118. package/dist/render.js +512 -0
  119. package/dist/replication/baseline.d.ts +51 -0
  120. package/dist/replication/baseline.js +215 -0
  121. package/dist/replication/bigquery.d.ts +37 -0
  122. package/dist/replication/bigquery.js +211 -0
  123. package/dist/replication/coverage.d.ts +14 -0
  124. package/dist/replication/coverage.js +53 -0
  125. package/dist/replication/evolution.d.ts +25 -0
  126. package/dist/replication/evolution.js +184 -0
  127. package/dist/replication/identities.d.ts +6 -0
  128. package/dist/replication/identities.js +47 -0
  129. package/dist/replication/legacy-baseline.d.ts +27 -0
  130. package/dist/replication/legacy-baseline.js +109 -0
  131. package/dist/replication/legacy-store.d.ts +19 -0
  132. package/dist/replication/legacy-store.js +63 -0
  133. package/dist/replication/lineage.d.ts +36 -0
  134. package/dist/replication/lineage.js +72 -0
  135. package/dist/replication/migrate.d.ts +21 -0
  136. package/dist/replication/migrate.js +84 -0
  137. package/dist/replication/model.d.ts +52 -0
  138. package/dist/replication/model.js +99 -0
  139. package/dist/replication/scans.d.ts +34 -0
  140. package/dist/replication/scans.js +158 -0
  141. package/dist/replication/schema.d.ts +11 -0
  142. package/dist/replication/schema.js +226 -0
  143. package/dist/replication/shadow-schema.d.ts +4 -0
  144. package/dist/replication/shadow-schema.js +61 -0
  145. package/dist/replication/shadow.d.ts +127 -0
  146. package/dist/replication/shadow.js +614 -0
  147. package/dist/replication/source-replacement.d.ts +33 -0
  148. package/dist/replication/source-replacement.js +76 -0
  149. package/dist/replication/store.d.ts +36 -0
  150. package/dist/replication/store.js +382 -0
  151. package/dist/replication/transaction.d.ts +23 -0
  152. package/dist/replication/transaction.js +88 -0
  153. package/dist/role.d.ts +61 -0
  154. package/dist/role.js +76 -0
  155. package/dist/runtime/action-deadlocks.d.ts +21 -0
  156. package/dist/runtime/action-deadlocks.js +130 -0
  157. package/dist/runtime/action-transactions.d.ts +35 -0
  158. package/dist/runtime/action-transactions.js +54 -0
  159. package/dist/runtime/agent-counters.d.ts +26 -0
  160. package/dist/runtime/agent-counters.js +98 -0
  161. package/dist/runtime/agent-tasks.d.ts +63 -0
  162. package/dist/runtime/agent-tasks.js +120 -0
  163. package/dist/runtime/agents.d.ts +15 -0
  164. package/dist/runtime/agents.js +457 -0
  165. package/dist/runtime/batch-diagnostics.d.ts +22 -0
  166. package/dist/runtime/batch-diagnostics.js +89 -0
  167. package/dist/runtime/batch.d.ts +52 -0
  168. package/dist/runtime/batch.js +534 -0
  169. package/dist/runtime/channel-session-revocation.d.ts +3 -0
  170. package/dist/runtime/channel-session-revocation.js +5 -0
  171. package/dist/runtime/channel-sessions.d.ts +4 -0
  172. package/dist/runtime/channel-sessions.js +61 -0
  173. package/dist/runtime/confirmations.d.ts +29 -0
  174. package/dist/runtime/confirmations.js +57 -0
  175. package/dist/runtime/cursor.d.ts +23 -0
  176. package/dist/runtime/cursor.js +100 -0
  177. package/dist/runtime/deployment.d.ts +7 -0
  178. package/dist/runtime/deployment.js +4 -0
  179. package/dist/runtime/errors.d.ts +118 -0
  180. package/dist/runtime/errors.js +109 -0
  181. package/dist/runtime/federation-tables.d.ts +2 -0
  182. package/dist/runtime/federation-tables.js +20 -0
  183. package/dist/runtime/federation.d.ts +42 -0
  184. package/dist/runtime/federation.js +222 -0
  185. package/dist/runtime/files.d.ts +39 -0
  186. package/dist/runtime/files.js +44 -0
  187. package/dist/runtime/generated-actions.d.ts +37 -0
  188. package/dist/runtime/generated-actions.js +307 -0
  189. package/dist/runtime/history.d.ts +91 -0
  190. package/dist/runtime/history.js +77 -0
  191. package/dist/runtime/ids.d.ts +6 -0
  192. package/dist/runtime/ids.js +39 -0
  193. package/dist/runtime/member-tables.d.ts +8 -0
  194. package/dist/runtime/member-tables.js +33 -0
  195. package/dist/runtime/member-writes.d.ts +78 -0
  196. package/dist/runtime/member-writes.js +202 -0
  197. package/dist/runtime/metric-query.d.ts +19 -0
  198. package/dist/runtime/metric-query.js +411 -0
  199. package/dist/runtime/model.d.ts +67 -0
  200. package/dist/runtime/model.js +150 -0
  201. package/dist/runtime/new-user-hook.d.ts +6 -0
  202. package/dist/runtime/new-user-hook.js +37 -0
  203. package/dist/runtime/oauth.d.ts +9 -0
  204. package/dist/runtime/oauth.js +299 -0
  205. package/dist/runtime/pipeline.d.ts +155 -0
  206. package/dist/runtime/pipeline.js +597 -0
  207. package/dist/runtime/postgres-tls.d.ts +10 -0
  208. package/dist/runtime/postgres-tls.js +14 -0
  209. package/dist/runtime/principals.d.ts +302 -0
  210. package/dist/runtime/principals.js +1307 -0
  211. package/dist/runtime/reads.d.ts +55 -0
  212. package/dist/runtime/reads.js +313 -0
  213. package/dist/runtime/records.d.ts +42 -0
  214. package/dist/runtime/records.js +245 -0
  215. package/dist/runtime/resolve-agent-identity.d.ts +6 -0
  216. package/dist/runtime/resolve-agent-identity.js +181 -0
  217. package/dist/runtime/service-binding.d.ts +18 -0
  218. package/dist/runtime/service-binding.js +266 -0
  219. package/dist/runtime/sign-in-links.d.ts +68 -0
  220. package/dist/runtime/sign-in-links.js +147 -0
  221. package/dist/runtime/sql-runtime.d.ts +131 -0
  222. package/dist/runtime/sql-runtime.js +362 -0
  223. package/dist/runtime/statement.d.ts +18 -0
  224. package/dist/runtime/statement.js +31 -0
  225. package/dist/runtime/user-identities.d.ts +66 -0
  226. package/dist/runtime/user-identities.js +106 -0
  227. package/dist/runtime/user-identity-sql.d.ts +86 -0
  228. package/dist/runtime/user-identity-sql.js +297 -0
  229. package/dist/runtime/user-roles.d.ts +16 -0
  230. package/dist/runtime/user-roles.js +32 -0
  231. package/dist/runtime/validation.d.ts +16 -0
  232. package/dist/runtime/validation.js +26 -0
  233. package/dist/runtime/where.d.ts +57 -0
  234. package/dist/runtime/where.js +404 -0
  235. package/dist/runtime/workflow-admission.d.ts +49 -0
  236. package/dist/runtime/workflow-admission.js +92 -0
  237. package/dist/runtime/workflow-argument-codec.d.ts +10 -0
  238. package/dist/runtime/workflow-argument-codec.js +120 -0
  239. package/dist/runtime/workflow-arguments.d.ts +17 -0
  240. package/dist/runtime/workflow-arguments.js +99 -0
  241. package/dist/runtime/workflow-starts.d.ts +203 -0
  242. package/dist/runtime/workflow-starts.js +493 -0
  243. package/dist/runtime/workflow-users.d.ts +9 -0
  244. package/dist/runtime/workflow-users.js +17 -0
  245. package/dist/runtime/workload.d.ts +25 -0
  246. package/dist/runtime/workload.js +24 -0
  247. package/dist/runtime/writes.d.ts +108 -0
  248. package/dist/runtime/writes.js +491 -0
  249. package/dist/runtime-source.d.ts +22 -0
  250. package/dist/runtime-source.js +28 -0
  251. package/dist/runtime.d.ts +310 -0
  252. package/dist/runtime.js +31 -0
  253. package/dist/server.d.ts +23 -0
  254. package/dist/server.js +107 -0
  255. package/dist/service/agent/attachments.d.ts +33 -0
  256. package/dist/service/agent/attachments.js +228 -0
  257. package/dist/service/agent/authentication.d.ts +22 -0
  258. package/dist/service/agent/authentication.js +58 -0
  259. package/dist/service/agent/confirmations.d.ts +5 -0
  260. package/dist/service/agent/confirmations.js +73 -0
  261. package/dist/service/agent/conversation-routes.d.ts +14 -0
  262. package/dist/service/agent/conversation-routes.js +45 -0
  263. package/dist/service/agent/conversations.d.ts +40 -0
  264. package/dist/service/agent/conversations.js +69 -0
  265. package/dist/service/agent/files.d.ts +12 -0
  266. package/dist/service/agent/files.js +69 -0
  267. package/dist/service/agent/protocol.d.ts +90 -0
  268. package/dist/service/agent/protocol.js +114 -0
  269. package/dist/service/agent/server.d.ts +28 -0
  270. package/dist/service/agent/server.js +374 -0
  271. package/dist/service/agent/task-caller.d.ts +21 -0
  272. package/dist/service/agent/task-caller.js +35 -0
  273. package/dist/service/agent/task-start.d.ts +53 -0
  274. package/dist/service/agent/task-start.js +161 -0
  275. package/dist/service/agent/tasks.d.ts +40 -0
  276. package/dist/service/agent/tasks.js +155 -0
  277. package/dist/service/api.d.ts +50 -0
  278. package/dist/service/api.js +288 -0
  279. package/dist/service/authentication.d.ts +227 -0
  280. package/dist/service/authentication.js +767 -0
  281. package/dist/service/caller-context.d.ts +24 -0
  282. package/dist/service/caller-context.js +6 -0
  283. package/dist/service/catalog.d.ts +42 -0
  284. package/dist/service/catalog.js +66 -0
  285. package/dist/service/channel/provider.d.ts +10 -0
  286. package/dist/service/channel/provider.js +1 -0
  287. package/dist/service/channel/providers/telegram.d.ts +18 -0
  288. package/dist/service/channel/providers/telegram.js +108 -0
  289. package/dist/service/channel/sessions.d.ts +5 -0
  290. package/dist/service/channel/sessions.js +53 -0
  291. package/dist/service/channel/sign-in.d.ts +7 -0
  292. package/dist/service/channel/sign-in.js +78 -0
  293. package/dist/service/chat/archive.d.ts +64 -0
  294. package/dist/service/chat/archive.js +62 -0
  295. package/dist/service/chat/attribution.d.ts +26 -0
  296. package/dist/service/chat/attribution.js +28 -0
  297. package/dist/service/chat/delivery.d.ts +18 -0
  298. package/dist/service/chat/delivery.js +251 -0
  299. package/dist/service/chat/diagnostics.d.ts +2 -0
  300. package/dist/service/chat/diagnostics.js +35 -0
  301. package/dist/service/chat/envelope.d.ts +96 -0
  302. package/dist/service/chat/envelope.js +39 -0
  303. package/dist/service/chat/idempotency.d.ts +19 -0
  304. package/dist/service/chat/idempotency.js +37 -0
  305. package/dist/service/chat/providers/telegram.d.ts +36 -0
  306. package/dist/service/chat/providers/telegram.js +133 -0
  307. package/dist/service/chat/reads.d.ts +25 -0
  308. package/dist/service/chat/reads.js +75 -0
  309. package/dist/service/chat/resolve.d.ts +34 -0
  310. package/dist/service/chat/resolve.js +150 -0
  311. package/dist/service/chat/routes.d.ts +23 -0
  312. package/dist/service/chat/routes.js +75 -0
  313. package/dist/service/chat/schemas.d.ts +43 -0
  314. package/dist/service/chat/schemas.js +31 -0
  315. package/dist/service/config.d.ts +218 -0
  316. package/dist/service/config.js +265 -0
  317. package/dist/service/connection-stream.d.ts +10 -0
  318. package/dist/service/connection-stream.js +191 -0
  319. package/dist/service/connections.d.ts +60 -0
  320. package/dist/service/connections.js +271 -0
  321. package/dist/service/decisions.d.ts +35 -0
  322. package/dist/service/decisions.js +63 -0
  323. package/dist/service/descriptions.d.ts +8 -0
  324. package/dist/service/descriptions.js +20 -0
  325. package/dist/service/discovery-schemas.d.ts +92 -0
  326. package/dist/service/discovery-schemas.js +59 -0
  327. package/dist/service/discovery-search.d.ts +30 -0
  328. package/dist/service/discovery-search.js +95 -0
  329. package/dist/service/discovery.d.ts +37 -0
  330. package/dist/service/discovery.js +436 -0
  331. package/dist/service/docs.d.ts +9 -0
  332. package/dist/service/docs.js +197 -0
  333. package/dist/service/errors.d.ts +145 -0
  334. package/dist/service/errors.js +704 -0
  335. package/dist/service/federation-provider.d.ts +36 -0
  336. package/dist/service/federation-provider.js +110 -0
  337. package/dist/service/federation-server.d.ts +20 -0
  338. package/dist/service/federation-server.js +249 -0
  339. package/dist/service/federation-status.d.ts +40 -0
  340. package/dist/service/federation-status.js +151 -0
  341. package/dist/service/handlers.d.ts +18 -0
  342. package/dist/service/handlers.js +287 -0
  343. package/dist/service/mcp/confirmation.d.ts +68 -0
  344. package/dist/service/mcp/confirmation.js +178 -0
  345. package/dist/service/mcp/find-input.d.ts +12 -0
  346. package/dist/service/mcp/find-input.js +122 -0
  347. package/dist/service/mcp/input-schema.d.ts +6 -0
  348. package/dist/service/mcp/input-schema.js +126 -0
  349. package/dist/service/mcp/listing.d.ts +26 -0
  350. package/dist/service/mcp/listing.js +92 -0
  351. package/dist/service/mcp/metric-tools.d.ts +85 -0
  352. package/dist/service/mcp/metric-tools.js +258 -0
  353. package/dist/service/mcp/request-log.d.ts +5 -0
  354. package/dist/service/mcp/request-log.js +53 -0
  355. package/dist/service/mcp/runtime-policy.d.ts +27 -0
  356. package/dist/service/mcp/runtime-policy.js +62 -0
  357. package/dist/service/mcp/server.d.ts +52 -0
  358. package/dist/service/mcp/server.js +861 -0
  359. package/dist/service/mcp/tools.d.ts +99 -0
  360. package/dist/service/mcp/tools.js +938 -0
  361. package/dist/service/member-file-routes.d.ts +22 -0
  362. package/dist/service/member-file-routes.js +52 -0
  363. package/dist/service/member-files.d.ts +245 -0
  364. package/dist/service/member-files.js +258 -0
  365. package/dist/service/metric-query-platform.d.ts +77 -0
  366. package/dist/service/metric-query-platform.js +198 -0
  367. package/dist/service/metric-query-scopes.d.ts +43 -0
  368. package/dist/service/metric-query-scopes.js +147 -0
  369. package/dist/service/metric-query.d.ts +48 -0
  370. package/dist/service/metric-query.js +200 -0
  371. package/dist/service/name-suggestions.d.ts +3 -0
  372. package/dist/service/name-suggestions.js +38 -0
  373. package/dist/service/navigation-schemas.d.ts +80 -0
  374. package/dist/service/navigation-schemas.js +49 -0
  375. package/dist/service/navigation.d.ts +20 -0
  376. package/dist/service/navigation.js +165 -0
  377. package/dist/service/oauth/protocol.d.ts +70 -0
  378. package/dist/service/oauth/protocol.js +157 -0
  379. package/dist/service/oauth/refresh-replay.d.ts +2 -0
  380. package/dist/service/oauth/refresh-replay.js +20 -0
  381. package/dist/service/oauth/server.d.ts +28 -0
  382. package/dist/service/oauth/server.js +769 -0
  383. package/dist/service/operations.d.ts +170 -0
  384. package/dist/service/operations.js +392 -0
  385. package/dist/service/plan.d.ts +57 -0
  386. package/dist/service/plan.js +96 -0
  387. package/dist/service/rate-limit.d.ts +22 -0
  388. package/dist/service/rate-limit.js +52 -0
  389. package/dist/service/record-reads.d.ts +21 -0
  390. package/dist/service/record-reads.js +155 -0
  391. package/dist/service/replication.d.ts +5 -0
  392. package/dist/service/replication.js +39 -0
  393. package/dist/service/runtime-contract.d.ts +508 -0
  394. package/dist/service/runtime-contract.js +18 -0
  395. package/dist/service/runtime-identity.d.ts +71 -0
  396. package/dist/service/runtime-identity.js +122 -0
  397. package/dist/service/schemas.d.ts +961 -0
  398. package/dist/service/schemas.js +639 -0
  399. package/dist/service/service.d.ts +21 -0
  400. package/dist/service/service.js +526 -0
  401. package/dist/service/warehouse-platform.d.ts +17 -0
  402. package/dist/service/warehouse-platform.js +122 -0
  403. package/dist/service/warehouse-query.d.ts +24 -0
  404. package/dist/service/warehouse-query.js +280 -0
  405. package/dist/service/webhooks.d.ts +106 -0
  406. package/dist/service/webhooks.js +233 -0
  407. package/dist/service/workflow-admission-provider.d.ts +31 -0
  408. package/dist/service/workflow-admission-provider.js +205 -0
  409. package/dist/service/workflow-argument-serialization.d.ts +2 -0
  410. package/dist/service/workflow-argument-serialization.js +14 -0
  411. package/dist/service/workflow-callback.d.ts +22 -0
  412. package/dist/service/workflow-callback.js +75 -0
  413. package/dist/service/workflow-deployment.d.ts +8 -0
  414. package/dist/service/workflow-deployment.js +131 -0
  415. package/dist/service/workflow-diagnostics.d.ts +23 -0
  416. package/dist/service/workflow-diagnostics.js +263 -0
  417. package/dist/service/workflow-errors.d.ts +26 -0
  418. package/dist/service/workflow-errors.js +181 -0
  419. package/dist/service/workflow-logging.d.ts +2 -0
  420. package/dist/service/workflow-logging.js +50 -0
  421. package/dist/service/workflow-queue-fence.d.ts +6 -0
  422. package/dist/service/workflow-queue-fence.js +66 -0
  423. package/dist/service/workflows.d.ts +200 -0
  424. package/dist/service/workflows.js +1549 -0
  425. package/dist/sign-in.d.ts +58 -0
  426. package/dist/sign-in.js +57 -0
  427. package/dist/source.d.ts +30 -0
  428. package/dist/source.js +107 -0
  429. package/dist/start-recovery-contract.d.ts +49 -0
  430. package/dist/start-recovery-contract.js +26 -0
  431. package/dist/start-recovery.d.ts +27 -0
  432. package/dist/start-recovery.js +81 -0
  433. package/dist/storage/api.d.ts +4 -0
  434. package/dist/storage/api.js +40 -0
  435. package/dist/storage/attachments.d.ts +146 -0
  436. package/dist/storage/attachments.js +733 -0
  437. package/dist/storage/authorization.d.ts +38 -0
  438. package/dist/storage/authorization.js +83 -0
  439. package/dist/storage/file-access.d.ts +17 -0
  440. package/dist/storage/file-access.js +8 -0
  441. package/dist/storage/limits.d.ts +12 -0
  442. package/dist/storage/limits.js +17 -0
  443. package/dist/storage/maintenance-auth.d.ts +20 -0
  444. package/dist/storage/maintenance-auth.js +24 -0
  445. package/dist/storage/maintenance.d.ts +11 -0
  446. package/dist/storage/maintenance.js +67 -0
  447. package/dist/storage/neon.d.ts +4 -0
  448. package/dist/storage/neon.js +318 -0
  449. package/dist/storage/operator.d.ts +11 -0
  450. package/dist/storage/operator.js +30 -0
  451. package/dist/storage/provider.d.ts +93 -0
  452. package/dist/storage/provider.js +36 -0
  453. package/dist/storage/sessions.d.ts +184 -0
  454. package/dist/storage/sessions.js +688 -0
  455. package/dist/storage/tables.d.ts +54 -0
  456. package/dist/storage/tables.js +205 -0
  457. package/dist/storage/transfers.d.ts +16 -0
  458. package/dist/storage/transfers.js +153 -0
  459. package/dist/storage/writes.d.ts +7 -0
  460. package/dist/storage/writes.js +88 -0
  461. package/dist/telegram.d.ts +35 -0
  462. package/dist/telegram.js +173 -0
  463. package/dist/user-identity.d.ts +28 -0
  464. package/dist/user-identity.js +106 -0
  465. package/dist/user.d.ts +75 -0
  466. package/dist/user.js +111 -0
  467. package/dist/warehouse-backed.d.ts +6 -0
  468. package/dist/warehouse-backed.js +495 -0
  469. package/dist/webhook-token.d.ts +11 -0
  470. package/dist/webhook-token.js +18 -0
  471. package/dist/webhooks.d.ts +40 -0
  472. package/dist/webhooks.js +55 -0
  473. package/dist/wire.d.ts +10 -0
  474. package/dist/wire.js +35 -0
  475. package/dist/workflow-runtime.d.ts +116 -0
  476. package/dist/workflow-runtime.js +109 -0
  477. package/dist/workflow-topics.d.ts +28 -0
  478. package/dist/workflow-topics.js +28 -0
  479. package/dist/workflow.d.ts +284 -0
  480. package/dist/workflow.js +259 -0
  481. package/dist/workflows.d.ts +24 -0
  482. package/dist/workflows.js +18 -0
  483. package/package.json +127 -4
@@ -0,0 +1,1549 @@
1
+ import { createHash, timingSafeEqual } from "node:crypto";
2
+ import { Cause, Context, Effect, Exit, Layer, Redacted, Schema } from "effect";
3
+ import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
4
+ import { HttpApiBuilder, HttpApiSecurity } from "effect/unstable/httpapi";
5
+ import { decodeProtectedHeader } from "jose";
6
+ import { deploymentFromEnv, requireDeploymentCapability, } from "../deployment.js";
7
+ import { NO_INPUT } from "../analyze.js";
8
+ import { ONTOLOGY_QUEUE_NAMESPACE } from "../workflow-topics.js";
9
+ import { actionInputToWire } from "../authoring-input.js";
10
+ import { nameToWire } from "../authoring-names.js";
11
+ import { connectionContext } from "../connection.js";
12
+ import { Files } from "../files.js";
13
+ import { canonicalJson } from "../manifest.js";
14
+ import { unavailableFiles } from "../storage/file-access.js";
15
+ import { ActionRejected, PermissionDenied, } from "../errors.js";
16
+ import { isActionTrigger, isCronTrigger, isWebhookTrigger, } from "../manifest.js";
17
+ import { webhookTokenMatches } from "../webhook-token.js";
18
+ import { MAX_WEBHOOK_BODY_BYTES, MAX_META_WEBHOOK_BODY_BYTES, MAX_WEBHOOK_VERIFICATION_CHARS, WEBHOOK_BASE_PATH, webhookPath, } from "../webhooks.js";
19
+ import { readWebhookBody, SIGNATURE_SCHEMES, signatureRefusals, utf8Text, } from "./webhooks.js";
20
+ import { clientAddress } from "./rate-limit.js";
21
+ import { isAllowed, workflowPermission } from "../permissions.js";
22
+ import { OntologyActions, OntologyReads, OntologyWrites, } from "../runtime.js";
23
+ import { writesOnlyThroughActions } from "../runtime/generated-actions.js";
24
+ import { validate } from "../runtime/validation.js";
25
+ import { triggerContext, WorkflowStartFailed, } from "../runtime/workflow-starts.js";
26
+ import { RECOVERY_CONFIRM_MS, RECOVERY_COVERAGE_MARGIN_MS, RECOVERY_FIRST_SWEEP_MS, } from "../start-recovery-contract.js";
27
+ import { nextCronTime, } from "../workflow.js";
28
+ import { platformWorkflows, setWorkflowBinding, WORKFLOW_BASE_PATH, workflowSdk, } from "../workflow-runtime.js";
29
+ import { callbackIdentityToken } from "./workflow-callback.js";
30
+ import { expireAgentTask, startAgentTask, } from "./agent/task-start.js";
31
+ import { CurrentPrincipal, WORKLOAD_TOKEN_TYPE, withPrincipal, workloadKind, } from "./authentication.js";
32
+ import { CurrentCaller } from "./caller-context.js";
33
+ import { InvalidActionInput, InvalidRequestBody, RequestId, failureResponse, forbidden, internalError, notFound, toServerResponse, unauthorized, unavailable, } from "./errors.js";
34
+ import { identityToken as requestIdentityToken, makeOperations, } from "./operations.js";
35
+ import { OntologyBackend } from "./runtime-contract.js";
36
+ import { stepFailure, WorkflowStepFailed, workflowErrorView } from "./workflow-errors.js";
37
+ export { WorkflowStepFailed } from "./workflow-errors.js";
38
+ import { runtimeCredential } from "./runtime-identity.js";
39
+ import { deploymentWorkflowEngine } from "./workflow-deployment.js";
40
+ import { decodeWorkflowArguments, encodeWorkflowArguments } from "../runtime/workflow-arguments.js";
41
+ import { WorkflowRecoveryUnavailable } from "../runtime/errors.js";
42
+ import { hasWorkflowError, hydrateWorkflowStepPage, readWorkflowDiagnostics, WORLD_PAGE_SIZE, } from "./workflow-diagnostics.js";
43
+ export { MAX_LISTED_WORKFLOW_EVENTS, WORLD_PAGE_SIZE } from "./workflow-diagnostics.js";
44
+ /**
45
+ * The world has no such run: the Workflow SDK's `WorkflowRunNotFoundError`, told apart by its name
46
+ * as its own `is` guard does (this module imports none of the SDK), or a world's bare 404.
47
+ */
48
+ function isRunNotFound(error) {
49
+ const candidate = error;
50
+ return candidate?.name === "WorkflowRunNotFoundError" || candidate?.status === 404;
51
+ }
52
+ /**
53
+ * No run waits on the hook any longer: the SDK's `HookNotFoundError`, or `RunExpiredError` for a
54
+ * run that ended, told apart by name as their own `is` guards do.
55
+ */
56
+ function isHookGone(error) {
57
+ const name = error?.name;
58
+ return name === "HookNotFoundError" || name === "RunExpiredError";
59
+ }
60
+ /**
61
+ * The Workflow SDK's engine over a world: `start` from `workflow/api` on the ontology's queue, and
62
+ * the world's `runs.list`, `runs.get` and `steps.list`, paged within what the Vercel world accepts.
63
+ */
64
+ export function worldWorkflowEngine(world, begin = (workflow, args, options) => installedWorkflowSdk().start(workflow, args, options), hydrate = (value, run, kind) => installedWorkflowSdk().hydrateError?.(value, run, kind) ?? Promise.resolve(value), resume = (token, payload) => installedWorkflowSdk().resumeHook(token, payload)) {
65
+ const engine = {
66
+ allocateRun: () => {
67
+ const allocate = installedWorkflowSdk().allocateRun;
68
+ if (!allocate) {
69
+ throw new Error("The Workflow runtime cannot reserve durable provider run identities.");
70
+ }
71
+ return allocate();
72
+ },
73
+ getHookRunId: async (token) => {
74
+ try {
75
+ const current = await world();
76
+ if (!current.hooks) {
77
+ throw new Error("WorkflowHookLookupUnavailable");
78
+ }
79
+ return (await current.hooks.getByToken(token)).runId;
80
+ }
81
+ catch (error) {
82
+ if (isHookGone(error) || isRunNotFound(error)) {
83
+ return undefined;
84
+ }
85
+ throw error;
86
+ }
87
+ },
88
+ resumeHook: async (token, payload) => {
89
+ requireDeploymentCapability(deploymentFromEnv(), "workflows");
90
+ try {
91
+ await resume(token, payload);
92
+ return "resumed";
93
+ }
94
+ catch (error) {
95
+ if (isHookGone(error)) {
96
+ return "not_found";
97
+ }
98
+ throw error;
99
+ }
100
+ },
101
+ // The ontology's queue, which only the ontology service's flow function consumes.
102
+ start: async (workflow, args, options) => {
103
+ requireDeploymentCapability(deploymentFromEnv(), "workflows");
104
+ return begin(workflow, [...args], { ...options, namespace: ONTOLOGY_QUEUE_NAMESPACE });
105
+ },
106
+ listRuns: async ({ workflowName, limit, cursor }) => (await world()).runs.list({
107
+ workflowName,
108
+ pagination: {
109
+ limit: Math.min(limit, WORLD_PAGE_SIZE),
110
+ ...(cursor === undefined ? {} : { cursor }),
111
+ sortOrder: "desc",
112
+ },
113
+ resolveData: "none",
114
+ }),
115
+ getRun: async (runId) => {
116
+ try {
117
+ return await (await world()).runs.get(runId, { resolveData: "none" });
118
+ }
119
+ catch (error) {
120
+ if (isRunNotFound(error)) {
121
+ return undefined;
122
+ }
123
+ throw error;
124
+ }
125
+ },
126
+ listSteps: async (runId, options = {}) => {
127
+ const current = await world();
128
+ const page = await current.steps.list({
129
+ runId,
130
+ pagination: {
131
+ limit: Math.min(Math.max(options.limit ?? WORLD_PAGE_SIZE, 1), WORLD_PAGE_SIZE),
132
+ sortOrder: "desc",
133
+ ...(options.cursor === undefined ? {} : { cursor: options.cursor }),
134
+ },
135
+ resolveData: "none",
136
+ });
137
+ return hydrateWorkflowStepPage(current, runId, page, hydrate);
138
+ },
139
+ diagnostics: async (run, steps) => readWorkflowDiagnostics(await world(), run, steps, hydrate),
140
+ };
141
+ return deploymentWorkflowEngine(engine, deploymentFromEnv);
142
+ }
143
+ /**
144
+ * The Vercel Workflow SDK as `installWorkflowRuntime()` registered it. The service imports none of
145
+ * the SDK itself: a process that bundles the service without serving workflows, such as the
146
+ * platform's backend on another SDK major, carries none of it.
147
+ */
148
+ export const vercelWorkflowEngine = worldWorkflowEngine(async () => (await installedWorkflowSdk().world()));
149
+ /** A workflow route was served in a process that never called `installWorkflowRuntime()`. */
150
+ export class WorkflowRuntimeNotInstalled extends Error {
151
+ constructor() {
152
+ super("The ontology's workflow routes need installWorkflowRuntime() from " +
153
+ '"@segmentstream/ontology/server", which the starter\'s server/plugin.ts calls ' +
154
+ "at startup, before they serve a run.");
155
+ this.name = "WorkflowRuntimeNotInstalled";
156
+ }
157
+ }
158
+ function installedWorkflowSdk() {
159
+ const sdk = workflowSdk();
160
+ if (sdk === undefined) {
161
+ throw new WorkflowRuntimeNotInstalled();
162
+ }
163
+ return sdk;
164
+ }
165
+ /** The Workflow SDK's id of a compiled `"use workflow"` function; `undefined` before compilation. */
166
+ export function compiledWorkflowId(run) {
167
+ const id = run?.workflowId;
168
+ return typeof id === "string" && id !== "" ? id : undefined;
169
+ }
170
+ function declaredWorkflows(ontology) {
171
+ const declarations = ontology.config.workflows ?? [];
172
+ return ontology.workflows.flatMap((manifest) => {
173
+ const declaration = declarations.find((candidate) => nameToWire(candidate.name) === manifest.name);
174
+ return declaration === undefined
175
+ ? []
176
+ : [
177
+ {
178
+ manifest,
179
+ declaration,
180
+ input: declaration.input === undefined ? NO_INPUT : actionInputToWire(declaration.input),
181
+ },
182
+ ];
183
+ });
184
+ }
185
+ /** How long the recovery run's start may take; the arming request waits for it. */
186
+ const RECOVERY_START_TIMEOUT_MS = 15_000;
187
+ /** The platform's recovery workflow, compiled, or why it cannot be started. */
188
+ function recoveryWorkflow() {
189
+ const run = platformWorkflows()?.recoverStarts;
190
+ if (run === undefined) {
191
+ return new WorkflowStartFailed({
192
+ reason: "no_recovery_workflow",
193
+ message: "The platform's start recovery workflow is not registered: build the ontology service " +
194
+ "with ontologyWorkflows() in nitro.config.ts, which compiles and registers it.",
195
+ });
196
+ }
197
+ if (compiledWorkflowId(run) === undefined) {
198
+ return new WorkflowStartFailed({
199
+ reason: "not_compiled",
200
+ message: "The platform's start recovery workflow was not compiled by the Workflow SDK.",
201
+ });
202
+ }
203
+ return run;
204
+ }
205
+ /**
206
+ * Starts the runs action triggers decide, through the engine on the ontology's queue: the row's
207
+ * wire input decoded with the workflow's input, and `startedBy` naming the trigger, the action and,
208
+ * when it was evaluated per edited record, the record. The ontology service hands it to the runtime.
209
+ *
210
+ * `ensureRecovery` arms the platform's recovery run, one per process at a time: a run started at
211
+ * `A` first sweeps at `A + RECOVERY_FIRST_SWEEP_MS` and looks once more RECOVERY_CONFIRM_MS later,
212
+ * so it covers every request whose transaction deadline falls before that horizon, less a margin.
213
+ * Requests that arrive while a start is in flight wait for it instead of starting their own.
214
+ */
215
+ export function workflowStarter(ontology, engine, clock = Date.now) {
216
+ const workflows = declaredWorkflows(ontology);
217
+ const capture = (pending) => {
218
+ if (pending.arguments && pending.workflowId) {
219
+ return {
220
+ ...pending,
221
+ workflowId: pending.workflowId,
222
+ arguments: decodeWorkflowArguments(encodeWorkflowArguments(pending.arguments)),
223
+ attributes: pending.attributes ?? {},
224
+ };
225
+ }
226
+ const workflow = workflows.find((candidate) => candidate.manifest.name === pending.workflow);
227
+ const id = pending.workflowId ?? (workflow && compiledWorkflowId(workflow.declaration.run));
228
+ if (!workflow || !id) {
229
+ throw new WorkflowStartFailed({
230
+ reason: "not_compiled",
231
+ message: "Workflow input capture requires a compiled declaration.",
232
+ });
233
+ }
234
+ const decoded = validate(workflow.input, pending.input ?? {});
235
+ if (!decoded.ok) {
236
+ throw new WorkflowStartFailed({
237
+ reason: "invalid_input",
238
+ message: "The workflow admission input is invalid.",
239
+ });
240
+ }
241
+ return {
242
+ ...pending,
243
+ workflowId: id,
244
+ arguments: decodeWorkflowArguments(encodeWorkflowArguments([decoded.value, triggerContext(pending)])),
245
+ attributes: pending.attributes ?? startAttributes(triggerContext(pending).startedBy),
246
+ };
247
+ };
248
+ // The horizon of the latest armed recovery run, as this process may rely on it.
249
+ let coveredUntil = 0;
250
+ let arming;
251
+ const arm = (submit) => (arming ??= (async () => {
252
+ const run = recoveryWorkflow();
253
+ if (run instanceof WorkflowStartFailed) {
254
+ throw run;
255
+ }
256
+ const armedAt = clock();
257
+ let timer;
258
+ try {
259
+ await Promise.race([
260
+ submit
261
+ ? submit({
262
+ workflowId: compiledWorkflowId(run),
263
+ arguments: [],
264
+ attributes: { "startedBy.kind": "platform" },
265
+ })
266
+ : engine.start(run, [], { attributes: { "startedBy.kind": "platform" } }),
267
+ new Promise((_, reject) => {
268
+ timer = setTimeout(() => reject(new WorkflowStartFailed({
269
+ reason: "timeout",
270
+ message: "The Workflow SDK did not accept the recovery run in time.",
271
+ })), RECOVERY_START_TIMEOUT_MS);
272
+ }),
273
+ ]);
274
+ }
275
+ catch (cause) {
276
+ throw cause instanceof WorkflowStartFailed
277
+ ? cause
278
+ : new WorkflowStartFailed({
279
+ reason: "engine",
280
+ message: "The Workflow SDK did not start the recovery run.",
281
+ cause,
282
+ });
283
+ }
284
+ finally {
285
+ clearTimeout(timer);
286
+ }
287
+ coveredUntil =
288
+ armedAt + RECOVERY_FIRST_SWEEP_MS + RECOVERY_CONFIRM_MS - RECOVERY_COVERAGE_MARGIN_MS;
289
+ })().finally(() => {
290
+ arming = undefined;
291
+ }));
292
+ return {
293
+ ...(engine.allocateRun ? { allocateRun: engine.allocateRun } : {}),
294
+ recoveryHorizon: () => coveredUntil,
295
+ capture,
296
+ prepare: (pending) => Effect.gen(function* () {
297
+ if (pending.reservation && pending.arguments && pending.workflowId) {
298
+ return {
299
+ ...pending,
300
+ reservation: pending.reservation,
301
+ arguments: pending.arguments,
302
+ workflowId: pending.workflowId,
303
+ attributes: pending.attributes ?? {},
304
+ };
305
+ }
306
+ if (!engine.allocateRun) {
307
+ return yield* new WorkflowStartFailed({
308
+ reason: "engine",
309
+ message: "Workflow reservation is unavailable.",
310
+ });
311
+ }
312
+ const captured = yield* Effect.try({
313
+ try: () => capture(pending),
314
+ catch: (cause) => cause instanceof WorkflowStartFailed
315
+ ? cause
316
+ : new WorkflowStartFailed({
317
+ reason: "invalid_input",
318
+ message: "Workflow input capture failed.",
319
+ cause,
320
+ }),
321
+ });
322
+ const reservation = yield* Effect.tryPromise({
323
+ try: engine.allocateRun,
324
+ catch: (cause) => new WorkflowStartFailed({
325
+ reason: "engine",
326
+ message: "Workflow run identity allocation failed.",
327
+ cause,
328
+ }),
329
+ });
330
+ return {
331
+ ...captured,
332
+ reservation: {
333
+ ...reservation,
334
+ ...(pending.deploymentId ? { deploymentId: pending.deploymentId } : {}),
335
+ },
336
+ };
337
+ }),
338
+ ensureRecovery: ({ commitBy, submit }) => Effect.gen(function* () {
339
+ // At most twice: the first start may be one in flight for an earlier request; the second
340
+ // is this request's own, and covers it unless its deadline is beyond one run's horizon.
341
+ for (let attempt = 0; commitBy >= coveredUntil; attempt += 1) {
342
+ if (attempt === 2) {
343
+ return yield* new WorkflowStartFailed({
344
+ reason: "engine",
345
+ message: "The action's transaction deadline is beyond what one recovery run covers " +
346
+ `(${RECOVERY_FIRST_SWEEP_MS + RECOVERY_CONFIRM_MS - RECOVERY_COVERAGE_MARGIN_MS} ms).`,
347
+ });
348
+ }
349
+ yield* Effect.tryPromise({
350
+ try: () => arm(submit),
351
+ catch: (cause) => cause instanceof WorkflowStartFailed
352
+ ? cause
353
+ : new WorkflowStartFailed({
354
+ reason: "engine",
355
+ message: "The Workflow SDK did not start the recovery run.",
356
+ cause,
357
+ }),
358
+ });
359
+ }
360
+ }),
361
+ start: (start) => Effect.gen(function* () {
362
+ if (start.reservation && start.arguments && start.workflowId) {
363
+ const begun = yield* Effect.tryPromise({
364
+ try: () => engine.start({ workflowId: start.workflowId }, start.arguments, {
365
+ reservation: start.reservation,
366
+ attributes: start.attributes ?? {},
367
+ }),
368
+ catch: (cause) => new WorkflowStartFailed({
369
+ reason: "engine",
370
+ message: "The reserved workflow submission failed.",
371
+ cause,
372
+ }),
373
+ });
374
+ return begun.runId;
375
+ }
376
+ const workflow = workflows.find((candidate) => candidate.manifest.name === start.workflow);
377
+ if (workflow === undefined) {
378
+ return yield* new WorkflowStartFailed({
379
+ reason: "unknown_workflow",
380
+ message: `The serving ontology declares no workflow ${start.workflow}.`,
381
+ });
382
+ }
383
+ if (compiledWorkflowId(workflow.declaration.run) === undefined) {
384
+ return yield* new WorkflowStartFailed({
385
+ reason: "not_compiled",
386
+ message: `The run function of ${start.workflow} was not compiled by the Workflow SDK.`,
387
+ });
388
+ }
389
+ const decoded = validate(workflow.input, start.input ?? {});
390
+ if (!decoded.ok) {
391
+ return yield* new WorkflowStartFailed({
392
+ reason: "invalid_input",
393
+ message: `The stored input of ${start.trigger} does not decode: ${decoded.issues
394
+ .map((issue) => `${issue.path.join(".") || "input"}: ${issue.message}`)
395
+ .join("; ")}`,
396
+ });
397
+ }
398
+ const context = triggerContext(start);
399
+ const started = yield* Effect.tryPromise({
400
+ try: () => engine.start(workflow.declaration.run, [decoded.value, context], { attributes: startAttributes(context.startedBy) }),
401
+ catch: (cause) => new WorkflowStartFailed({
402
+ reason: "engine",
403
+ message: "The Workflow SDK did not start the run.",
404
+ cause,
405
+ }),
406
+ });
407
+ return started.runId;
408
+ }),
409
+ };
410
+ }
411
+ const json = (body, status = 200) => HttpServerResponse.jsonUnsafe(body, {
412
+ status,
413
+ headers: { "cache-control": "private, no-store" },
414
+ });
415
+ function iso(value) {
416
+ if (value === undefined) {
417
+ return null;
418
+ }
419
+ return value instanceof Date ? value.toISOString() : value;
420
+ }
421
+ /** Workflow SDK 5 attributes are flat strings; `$` keys belong to the SDK. */
422
+ function startAttributes(startedBy) {
423
+ return Object.fromEntries(Object.entries(startedBy)
424
+ .filter((entry) => typeof entry[1] === "string")
425
+ .map(([key, value]) => [`startedBy.${key}`, value]));
426
+ }
427
+ /** Older runs and attributes from other uses carry no ontology start context. */
428
+ function runStartedBy(attributes) {
429
+ const kind = attributes?.["startedBy.kind"];
430
+ const userId = attributes?.["startedBy.userId"];
431
+ if (kind === "member" && userId !== undefined) {
432
+ return { kind, userId };
433
+ }
434
+ if (kind === "platform") {
435
+ return { kind, userId: userId ?? null };
436
+ }
437
+ const trigger = attributes?.["startedBy.trigger"];
438
+ if (kind === "trigger" && trigger !== undefined) {
439
+ const actionId = attributes?.["startedBy.actionId"];
440
+ const recordId = attributes?.["startedBy.recordId"];
441
+ const triggerKind = attributes?.["startedBy.triggerKind"];
442
+ return {
443
+ kind,
444
+ ...(triggerKind === "cron" || triggerKind === "action" || triggerKind === "webhook"
445
+ ? { triggerKind }
446
+ : {}),
447
+ trigger,
448
+ ...(actionId === undefined ? {} : { actionId }),
449
+ ...(recordId === undefined ? {} : { recordId }),
450
+ };
451
+ }
452
+ return undefined;
453
+ }
454
+ function runView(workflow, record) {
455
+ const startedBy = runStartedBy(record.attributes);
456
+ return {
457
+ runId: record.runId,
458
+ workflow,
459
+ status: record.status,
460
+ ...(record.deploymentId === undefined ? {} : { deploymentId: record.deploymentId }),
461
+ createdAt: iso(record.createdAt),
462
+ startedAt: iso(record.startedAt),
463
+ completedAt: iso(record.completedAt),
464
+ ...(startedBy === undefined ? {} : { startedBy }),
465
+ error: workflowErrorView(record.error),
466
+ };
467
+ }
468
+ function stepView(record) {
469
+ return {
470
+ stepId: record.stepId,
471
+ stepName: record.stepName,
472
+ status: record.status,
473
+ attempt: record.attempt ?? null,
474
+ startedAt: iso(record.startedAt),
475
+ completedAt: iso(record.completedAt),
476
+ error: workflowErrorView(record.error),
477
+ ...(record.attempts === undefined
478
+ ? {}
479
+ : {
480
+ attempts: record.attempts.map((attempt) => ({
481
+ attempt: attempt.attempt,
482
+ status: attempt.status,
483
+ startedAt: iso(attempt.startedAt),
484
+ completedAt: iso(attempt.completedAt),
485
+ error: workflowErrorView(attempt.error),
486
+ })),
487
+ }),
488
+ };
489
+ }
490
+ /** The route Vercel calls for a cron trigger: `/api/ontology/workflows/<workflow>/triggers/<trigger>`. */
491
+ export function triggerPath(workflow, trigger) {
492
+ return `${WORKFLOW_BASE_PATH}/workflows/${workflow}/triggers/${trigger}`;
493
+ }
494
+ /** The route Vercel calls every five minutes when the manifest declares an action trigger. */
495
+ export const SWEEP_PATH = `${WORKFLOW_BASE_PATH}/workflows/sweep`;
496
+ function workflowView(workflow, now) {
497
+ return {
498
+ ...workflow,
499
+ // Triggers of kinds this SDK does not run are not presented.
500
+ triggers: workflow.triggers.flatMap((trigger) => isCronTrigger(trigger)
501
+ ? [
502
+ {
503
+ ...trigger,
504
+ path: triggerPath(workflow.name, trigger.name),
505
+ nextAt: nextCronTime(trigger.cron, now)?.toISOString() ?? null,
506
+ },
507
+ ]
508
+ : isActionTrigger(trigger)
509
+ ? [
510
+ {
511
+ kind: trigger.kind,
512
+ name: trigger.name,
513
+ description: trigger.description,
514
+ entity: trigger.entity,
515
+ // `null` for a trigger on any action of the entity.
516
+ action: trigger.action ?? null,
517
+ },
518
+ ]
519
+ : isWebhookTrigger(trigger)
520
+ ? [
521
+ {
522
+ kind: trigger.kind,
523
+ name: trigger.name,
524
+ description: trigger.description,
525
+ // Never the token: whoever reads the declarations does not get the URL.
526
+ path: webhookPath(trigger.name),
527
+ signature: trigger.signature ?? null,
528
+ headers: trigger.headers ?? [],
529
+ },
530
+ ]
531
+ : []),
532
+ };
533
+ }
534
+ function bearerEquals(header, secret) {
535
+ if (Redacted.value(secret) === "") {
536
+ return false;
537
+ }
538
+ const expected = Buffer.from(`Bearer ${Redacted.value(secret)}`);
539
+ const received = Buffer.from(header ?? "");
540
+ return received.length === expected.length && timingSafeEqual(received, expected);
541
+ }
542
+ function isWorkloadToken(token) {
543
+ try {
544
+ return decodeProtectedHeader(token).typ === WORKLOAD_TOKEN_TYPE;
545
+ }
546
+ catch {
547
+ return false;
548
+ }
549
+ }
550
+ /** Refusals that are typed on the entity handles pass through; the others end the step. */
551
+ function expected(effect, tags) {
552
+ return effect.pipe(Effect.catchCause((cause) => {
553
+ const failure = Cause.squash(cause);
554
+ return typeof failure?._tag === "string" && tags.includes(failure._tag)
555
+ ? Effect.fail(failure)
556
+ : Effect.failCause(cause);
557
+ }));
558
+ }
559
+ /**
560
+ * The workflow routes and the in-process binding of workflow runs:
561
+ *
562
+ * - `GET /api/ontology/workflows/:workflow/triggers/:trigger`, Vercel's cron call, verified with the
563
+ * project's cron secret, starts the workflow with the trigger's input;
564
+ * - `POST /api/ontology/workflows/:workflow/start` starts it for a member holding
565
+ * `workflow:<workflow>:start`, or for the platform;
566
+ * - `GET /api/ontology/workflows`, `…/:workflow/runs` and `…/runs/:runId` read the declarations
567
+ * and the SDK's run records for callers holding `workflow:<workflow>:read` and the platform;
568
+ * - `GET /api/ontology/workflows/:workflow/starts` lists the starts its action triggers decided,
569
+ * pending and started, for the same callers;
570
+ * - `GET` and `POST /api/ontology/workflows/sweep`, verified with the cron secret, start the
571
+ * starts that stayed pending.
572
+ *
573
+ * Building it installs the binding `run(effect)` uses inside steps; the flow callback, which
574
+ * `ontologyWorkflows()` mounts outside these routes, runs them.
575
+ */
576
+ export function workflowsLayer(options) {
577
+ return Layer.effectDiscard(Effect.gen(function* () {
578
+ const router = yield* HttpRouter.HttpRouter;
579
+ const backend = yield* OntologyBackend;
580
+ const engine = options.engine ?? deploymentWorkflowEngine(vercelWorkflowEngine, options.deployment);
581
+ const { plan } = options;
582
+ const operations = makeOperations(backend, plan, options.hooks, options.connections, options.replicate, options.chatDelivery);
583
+ const workflows = declaredWorkflows(plan.ontology);
584
+ // Either spelling names a workflow: `remindStaleLeads` or its wire name `remind_stale_leads`.
585
+ const byName = (name) => name === undefined
586
+ ? undefined
587
+ : workflows.find((candidate) => candidate.manifest.name === nameToWire(name));
588
+ const planned = (entity) => {
589
+ const found = plan.entities.find((candidate) => candidate.name === entity);
590
+ if (found === undefined) {
591
+ throw new ActionRejected({ message: `The ontology has no entity ${entity}.` });
592
+ }
593
+ return found;
594
+ };
595
+ const plannedAction = (entity, action) => {
596
+ const found = entity.actions.find((candidate) => candidate.name === action);
597
+ if (found === undefined) {
598
+ throw new ActionRejected({
599
+ message: writesOnlyThroughActions(plan.ontology, entity.name, action) ??
600
+ `${entity.name} has no action ${action}.`,
601
+ });
602
+ }
603
+ return found;
604
+ };
605
+ // The services the layer was built with, so every step runs as the routes do.
606
+ const layerServices = yield* Effect.context();
607
+ for (const workflow of workflows) {
608
+ if (compiledWorkflowId(workflow.declaration.run) === undefined) {
609
+ yield* Effect.logError("ontology.workflow.not_compiled").pipe(Effect.annotateLogs({
610
+ workflow: workflow.manifest.name,
611
+ detail: "Its run function was not compiled by the Workflow SDK: keep the workflow's file " +
612
+ "in a directory ontologyWorkflows({ dirs }) compiles.",
613
+ }));
614
+ }
615
+ }
616
+ /** The workflow each run records, by run id, read from the world once per found run. */
617
+ const runWorkflows = new Map();
618
+ const recordedWorkflow = async (runId) => {
619
+ requireDeploymentCapability(options.deployment, "workflows");
620
+ const cached = runWorkflows.get(runId);
621
+ if (cached !== undefined) {
622
+ return cached;
623
+ }
624
+ const run = await engine.getRun(runId);
625
+ if (run === undefined) {
626
+ return undefined;
627
+ }
628
+ if (runWorkflows.size >= 1_000) {
629
+ runWorkflows.delete(runWorkflows.keys().next().value);
630
+ }
631
+ runWorkflows.set(runId, run.workflowName);
632
+ return run.workflowName;
633
+ };
634
+ /** The declared workflow a step's run runs, after checking the run's own record agrees. */
635
+ const boundWorkflow = async (step) => {
636
+ const workflow = workflows.find((candidate) => compiledWorkflowId(candidate.declaration.run) === step.workflowName);
637
+ if (workflow === undefined) {
638
+ throw new WorkflowStepFailed("WorkflowNotDeclared", `The run's workflow ${step.workflowName} is not one the serving ontology declares ` +
639
+ "in defineOntology({ workflows }).", true);
640
+ }
641
+ // The step's queue message names the workflow; the run's own record must agree.
642
+ const recorded = await recordedWorkflow(step.workflowRunId);
643
+ if (recorded === undefined) {
644
+ // The world may not show a fresh run yet: the step is retried.
645
+ throw new WorkflowStepFailed("WorkflowRunUnknown", `The world has no record of run ${step.workflowRunId} yet.`);
646
+ }
647
+ if (recorded !== step.workflowName) {
648
+ throw new WorkflowStepFailed("WorkflowMismatch", `The step names workflow ${step.workflowName}, which is not its run's.`, true);
649
+ }
650
+ return workflow;
651
+ };
652
+ /** The platform's recovery workflow's compiled id, when the generated plugin registered it. */
653
+ const recoveryWorkflowId = () => {
654
+ const run = platformWorkflows()?.recoverStarts;
655
+ return run === undefined ? undefined : compiledWorkflowId(run);
656
+ };
657
+ setWorkflowBinding({
658
+ admitWorkflow: (workflowName) => {
659
+ if (!workflows.some((candidate) => compiledWorkflowId(candidate.declaration.run) === workflowName) &&
660
+ workflowName !== recoveryWorkflowId()) {
661
+ throw new WorkflowStepFailed("WorkflowNotDeclared", "The deployment declares no such workflow.", true);
662
+ }
663
+ },
664
+ run: async (step, effect) => runStep(await boundWorkflow(step), step, effect),
665
+ recoverStarts: async (step, input) => {
666
+ const expected = recoveryWorkflowId();
667
+ if (expected === undefined || step.workflowName !== expected) {
668
+ throw new WorkflowStepFailed("WorkflowNotDeclared", `The run's workflow ${step.workflowName} is not the platform's start recovery workflow.`, true);
669
+ }
670
+ const recorded = await recordedWorkflow(step.workflowRunId);
671
+ if (recorded === undefined) {
672
+ throw new WorkflowStepFailed("WorkflowRunUnknown", `The world has no record of run ${step.workflowRunId} yet.`);
673
+ }
674
+ if (recorded !== expected) {
675
+ throw new WorkflowStepFailed("WorkflowMismatch", `The step names workflow ${step.workflowName}, which is not its run's.`, true);
676
+ }
677
+ const requestId = crypto.randomUUID();
678
+ const result = backend.workflowStarts === undefined
679
+ ? { started: 0, failed: 0, abandoned: 0, waiting: 0, at: new Date().toISOString() }
680
+ : await Effect.runPromise(backend.workflowStarts.sweep({
681
+ retryAbandoned: false,
682
+ requestId,
683
+ ...(input.before === undefined ? {} : { before: input.before }),
684
+ }));
685
+ console.log(JSON.stringify({
686
+ event: "ontology.workflow.recovery_sweep",
687
+ requestId,
688
+ workflowRunId: step.workflowRunId,
689
+ stepId: step.stepId,
690
+ attempt: step.attempt,
691
+ ...result,
692
+ }));
693
+ return result;
694
+ },
695
+ startAgentTask: async (step, input) => {
696
+ const workflow = await boundWorkflow(step);
697
+ if (options.deployment.target === "preview") {
698
+ return { status: "decided", result: { status: "failed", reason: "agent_unavailable" } };
699
+ }
700
+ return runAgentTaskStep(workflow, step, true, (identityToken) => backend.agentTasks === undefined
701
+ ? Effect.succeed({
702
+ status: "decided",
703
+ result: { status: "failed", reason: "agent_unavailable" },
704
+ })
705
+ : startAgentTask({
706
+ store: backend.agentTasks,
707
+ origin: options.agentOrigin,
708
+ identityToken,
709
+ runtimeIdentity: options.verifyRuntime !== undefined,
710
+ transport: options.agentTransport ?? fetch,
711
+ workflow: workflow.manifest.name,
712
+ runId: step.workflowRunId,
713
+ }, input));
714
+ },
715
+ expireAgentTask: async (step, taskId) => {
716
+ const workflow = await boundWorkflow(step);
717
+ if (options.deployment.target === "preview") {
718
+ return { status: "timed_out" };
719
+ }
720
+ return runAgentTaskStep(workflow, step, false, () => backend.agentTasks === undefined
721
+ ? Effect.succeed({ status: "timed_out" })
722
+ : expireAgentTask(backend.agentTasks, taskId));
723
+ },
724
+ });
725
+ /**
726
+ * Runs one of `agentTask`'s steps for the run's workflow, with the invocation's Vercel OIDC
727
+ * token. The start refuses a workflow user that is missing or inactive, as every step does; the
728
+ * expiry only records the timeout. Failures are logged and thrown as the SDK retries them.
729
+ */
730
+ function runAgentTaskStep(workflow, step, requireUser, program) {
731
+ const requestId = crypto.randomUUID();
732
+ const identityToken = callbackIdentityToken() ?? process.env.VERCEL_OIDC_TOKEN;
733
+ const checked = Effect.gen(function* () {
734
+ if (requireUser) {
735
+ const principal = yield* backend.resolveWorkflowUser?.({
736
+ workspaceId: options.workspaceId,
737
+ workflow: workflow.manifest.name,
738
+ }) ?? Effect.succeed(undefined);
739
+ if (principal === undefined) {
740
+ return yield* new PermissionDenied({
741
+ message: `The workflow user of ${workflow.manifest.name} is missing or inactive.`,
742
+ });
743
+ }
744
+ }
745
+ return yield* program(identityToken);
746
+ }).pipe(Effect.annotateLogs({ requestId }));
747
+ return Effect.runPromiseExitWith(layerServices)(checked).then((exit) => {
748
+ if (Exit.isSuccess(exit)) {
749
+ return exit.value;
750
+ }
751
+ const failure = stepFailure(exit.cause, requestId, step, workflow.manifest.name, [
752
+ identityToken,
753
+ ]);
754
+ console.error(JSON.stringify({
755
+ event: "ontology.workflow.step.failed",
756
+ workflow: workflow.manifest.name,
757
+ workflowRunId: step.workflowRunId,
758
+ stepId: step.stepId,
759
+ attempt: step.attempt,
760
+ error: workflowErrorView(failure),
761
+ }));
762
+ throw failure;
763
+ });
764
+ }
765
+ /** Runs one step's effect as the workflow's user. */
766
+ function runStep(workflow, step, effect) {
767
+ const requestId = crypto.randomUUID();
768
+ // Capture this invocation's identity before Effect resumes from a shared database socket,
769
+ // whose async context may not carry the callback token. Each invocation reads it anew.
770
+ const identityToken = callbackIdentityToken() ?? process.env.VERCEL_OIDC_TOKEN;
771
+ const program = Effect.gen(function* () {
772
+ const principal = yield* backend.resolveWorkflowUser?.({
773
+ workspaceId: options.workspaceId,
774
+ workflow: workflow.manifest.name,
775
+ }) ?? Effect.succeed(undefined);
776
+ if (principal === undefined) {
777
+ return yield* new PermissionDenied({
778
+ message: `The workflow user of ${workflow.manifest.name} is missing or inactive.`,
779
+ });
780
+ }
781
+ const caller = Context.make(CurrentPrincipal, {
782
+ ...principal,
783
+ authenticatedVia: "workflow",
784
+ }).pipe(Context.add(CurrentCaller, { kind: "service", subjectId: principal.id }), Context.add(RequestId, requestId));
785
+ const as = (operation) => operation.pipe(Effect.provideContext(caller));
786
+ const answer = (entity, action, input, supplied) => Effect.suspend(() => {
787
+ const target = planned(entity);
788
+ const definition = plannedAction(target, action);
789
+ const hasUpload = (value) => value !== null &&
790
+ typeof value === "object" &&
791
+ ("uploadId" in value ||
792
+ "upload_id" in value ||
793
+ Object.values(value).some(hasUpload));
794
+ const fileAction = (definition.action.files?.length ?? 0) > 0 || hasUpload(input);
795
+ const actionOptions = fileAction
796
+ ? {
797
+ ...supplied,
798
+ idempotencyKey: supplied?.idempotencyKey ??
799
+ createHash("sha256")
800
+ .update(canonicalJson({
801
+ run: step.workflowRunId,
802
+ step: step.stepId,
803
+ entity,
804
+ action,
805
+ input,
806
+ createIntent: supplied?.createIntent ?? null,
807
+ }))
808
+ .digest("hex"),
809
+ }
810
+ : supplied;
811
+ return as(operations.runAction(target, definition, {
812
+ input,
813
+ validateOnly: false,
814
+ identityToken,
815
+ ...(actionOptions ? { options: actionOptions } : {}),
816
+ }));
817
+ });
818
+ const result = (outcome) => Effect.map(outcome, (answered) => answered.result);
819
+ const reads = OntologyReads.of({
820
+ get: ({ entity, id }) => expected(Effect.suspend(() => as(operations.get(planned(entity), id))), ["NotFound"]),
821
+ find: ({ entity, options: query }) => expected(Effect.suspend(() => {
822
+ // Outside a transaction there is nothing to lock: the lock is dropped.
823
+ const rest = { ...query };
824
+ delete rest.forUpdate;
825
+ return as(operations.find(planned(entity), rest));
826
+ }), []).pipe(Effect.map((page) => page.items)),
827
+ // A step reads as its user, through the API, where metric queries are the warehouse's.
828
+ queryMetrics: () => Effect.die(new Error("Fresh metric queries run in action handlers and prepare; a workflow step runs " +
829
+ "an action that queries metrics.")),
830
+ });
831
+ const refusedBatch = () => Effect.fail(new ActionRejected({
832
+ message: "Batch writes are not available in workflow steps; run an action that writes " +
833
+ "the batch.",
834
+ }));
835
+ const writes = OntologyWrites.of({
836
+ createMany: refusedBatch,
837
+ updateMany: refusedBatch,
838
+ upsertMany: refusedBatch,
839
+ create: ({ entity, values }) => expected(result(answer(entity, "create", values)), [
840
+ "Conflict",
841
+ "NotFound",
842
+ "ScopeViolated",
843
+ ]),
844
+ update: ({ entity, id, values }) => expected(result(answer(entity, "update", { ...values, id })), [
845
+ "Conflict",
846
+ "NotFound",
847
+ "ScopeViolated",
848
+ ]),
849
+ upsertBy: ({ entity, property, values }) => expected(result(answer(entity, `upsert_by_${property}`, values)), [
850
+ "Conflict",
851
+ "NotFound",
852
+ "ScopeViolated",
853
+ ]),
854
+ delete: ({ entity, id }) => expected(Effect.asVoid(answer(entity, "delete", { id })), [
855
+ "Conflict",
856
+ "NotFound",
857
+ "ScopeViolated",
858
+ ]),
859
+ link: ({ entity, relationship, id, targetId }) => expected(Effect.asVoid(answer(entity, "link", { relationship, id, target: targetId })), ["NotFound", "ScopeViolated"]),
860
+ unlink: ({ entity, relationship, id, targetId }) => expected(Effect.asVoid(answer(entity, "unlink", { relationship, id, target: targetId })), ["NotFound", "ScopeViolated"]),
861
+ // A step's member writes are the generated `user` actions, with their permissions.
862
+ grantUserRoles: ({ id, roles }) => expected(result(answer("user", "grant_roles", { id, roles })), [
863
+ "ActionRejected",
864
+ "NotFound",
865
+ ]),
866
+ revokeUserRoles: ({ id, roles }) => expected(result(answer("user", "revoke_roles", roles === "all" ? { id, all: true } : { id, roles })), ["ActionRejected", "NotFound"]),
867
+ inviteUser: ({ email, name, roles }) => expected(result(answer("user", "invite", {
868
+ email,
869
+ ...(name === undefined ? {} : { name }),
870
+ ...(roles === undefined ? {} : { roles }),
871
+ })), ["ActionRejected", "Conflict"]),
872
+ setUserActive: ({ id, active }) => expected(result(answer("user", active ? "reactivate" : "deactivate", { id })), ["NotFound"]),
873
+ sendUserSignInLink: () => Effect.fail(new ActionRejected({
874
+ message: "Sign-in links are sent from action handlers; a workflow step runs an action " +
875
+ "that sends one.",
876
+ })),
877
+ });
878
+ const actions = OntologyActions.of({
879
+ run: ({ entity, action, input, options: actionOptions }) => Effect.gen(function* () {
880
+ const target = planned(entity);
881
+ const definition = plannedAction(target, action);
882
+ // The handle takes the input in TypeScript names; the route takes its wire form.
883
+ const wire = yield* Schema.encodeUnknownEffect(definition.action.input)(input).pipe(Effect.mapError((issue) => new ActionRejected({
884
+ cause: issue,
885
+ message: `The input of ${entity}.${action} does not encode: ${issue.message}`,
886
+ })));
887
+ return yield* result(answer(entity, action, wire, actionOptions));
888
+ }).pipe(Effect.catchIf((error) => error._tag === "InvalidActionInput", (error) => Effect.fail(new ActionRejected({
889
+ message: `The input of ${entity}.${action} is invalid: ` +
890
+ error.issues
891
+ .map((issue) => `${issue.path.join(".") || "input"}: ${issue.message}`)
892
+ .join("; "),
893
+ }))), (operation) => expected(operation, [
894
+ "ActionRejected",
895
+ "PermissionDenied",
896
+ "ScopeViolated",
897
+ "Conflict",
898
+ "NotFound",
899
+ "ConnectionError",
900
+ ])),
901
+ });
902
+ const services = Context.make(OntologyReads, reads).pipe(Context.add(Files, backend.blobs?.files?.({
903
+ principal,
904
+ idempotencyKey: createHash("sha256")
905
+ .update(`${step.workflowRunId}:${step.stepId}`)
906
+ .digest("hex"),
907
+ }) ?? unavailableFiles), Context.add(OntologyWrites, writes), Context.add(OntologyActions, actions), Context.merge(connectionContext(workflow.declaration.connections, options.connections({
908
+ identityToken,
909
+ audit: {
910
+ action: `workflow.${workflow.manifest.name}`,
911
+ principalId: principal.id,
912
+ requestId,
913
+ },
914
+ }))));
915
+ console.info(JSON.stringify({
916
+ event: "ontology.workflow.step.started",
917
+ workflow: workflow.manifest.name,
918
+ workflowRunId: step.workflowRunId,
919
+ stepId: step.stepId,
920
+ attempt: step.attempt,
921
+ }));
922
+ return yield* Effect.provideContext(effect, services);
923
+ });
924
+ return Effect.runPromiseExitWith(layerServices)(program).then(async (exit) => {
925
+ if (Exit.isSuccess(exit)) {
926
+ return exit.value;
927
+ }
928
+ const failure = stepFailure(exit.cause, requestId, step, workflow.manifest.name, [
929
+ identityToken,
930
+ options.cronSecret === undefined ? undefined : Redacted.value(options.cronSecret),
931
+ options.webhookSecret === undefined ? undefined : Redacted.value(options.webhookSecret),
932
+ ]);
933
+ console.error(JSON.stringify({
934
+ event: "ontology.workflow.step.failed",
935
+ workflow: workflow.manifest.name,
936
+ workflowRunId: step.workflowRunId,
937
+ stepId: step.stepId,
938
+ attempt: step.attempt,
939
+ error: workflowErrorView(failure),
940
+ }));
941
+ throw failure;
942
+ });
943
+ }
944
+ /**
945
+ * Starts a run with input in its wire form, as the cron, start and webhook routes receive
946
+ * it: the run id, or the response that refuses it.
947
+ */
948
+ const beginRun = (workflow, input, context) => Effect.gen(function* () {
949
+ const requestId = yield* RequestId;
950
+ const refused = (response) => ({ ok: false, response });
951
+ // A workflow's input is an object of its fields, whatever its schema accepts.
952
+ const shape = validate(Schema.Record(Schema.String, Schema.Unknown), input ?? {});
953
+ const decoded = shape.ok ? validate(workflow.input, input ?? {}) : shape;
954
+ if (!decoded.ok) {
955
+ return refused(yield* failureResponse(Cause.fail(new InvalidActionInput({ issues: decoded.issues })), requestId));
956
+ }
957
+ if (compiledWorkflowId(workflow.declaration.run) === undefined) {
958
+ return refused(toServerResponse({
959
+ status: 503,
960
+ body: {
961
+ error: "workflow_not_compiled",
962
+ detail: "The workflow's run function was not compiled by the Workflow SDK; its file " +
963
+ "must be in a directory the build compiles.",
964
+ requestId,
965
+ },
966
+ }));
967
+ }
968
+ let admitted;
969
+ if (backend.workflowAdmissions && !engine.allocateRun) {
970
+ return refused(toServerResponse(unavailable(requestId)));
971
+ }
972
+ if (backend.workflowAdmissions) {
973
+ const argumentsCheck = yield* Effect.try({
974
+ try: () => encodeWorkflowArguments([decoded.value, context]),
975
+ catch: () => new InvalidActionInput({
976
+ issues: [{ path: [], message: "workflow_arguments_unsupported" }],
977
+ }),
978
+ }).pipe(Effect.result);
979
+ if (argumentsCheck._tag === "Failure") {
980
+ return refused(yield* failureResponse(Cause.fail(argumentsCheck.failure), requestId));
981
+ }
982
+ }
983
+ if (backend.workflowAdmissions && engine.allocateRun) {
984
+ const requestedCommitBy = Date.now() + 5_000;
985
+ const recovery = yield* backend.workflowAdmissions
986
+ .ensureRecovery(requestedCommitBy)
987
+ .pipe(Effect.result);
988
+ if (recovery._tag === "Failure") {
989
+ return refused(yield* failureResponse(Cause.fail(recovery.failure), requestId));
990
+ }
991
+ const commitBy = Math.min(requestedCommitBy, recovery.success);
992
+ if (!Number.isFinite(commitBy) || Date.now() >= commitBy) {
993
+ return refused(yield* failureResponse(Cause.fail(new WorkflowRecoveryUnavailable({
994
+ message: "The workflow recovery coverage expired before reservation could commit.",
995
+ })), requestId));
996
+ }
997
+ const allocated = yield* Effect.tryPromise(() => engine.allocateRun()).pipe(Effect.result);
998
+ if (allocated._tag === "Failure") {
999
+ return refused(toServerResponse(unavailable(requestId)));
1000
+ }
1001
+ const identity = context.startedBy.kind === "trigger" && context.startedBy.triggerKind === "cron"
1002
+ ? `${workflow.manifest.name}:${context.startedBy.trigger}:${Math.floor(Date.now() / 60_000)}`
1003
+ : `${requestId}:${canonicalJson(context.startedBy)}`;
1004
+ const hash = createHash("sha256").update(identity).digest("hex");
1005
+ const actionId = [
1006
+ hash.slice(0, 8),
1007
+ hash.slice(8, 12),
1008
+ hash.slice(12, 16),
1009
+ hash.slice(16, 20),
1010
+ hash.slice(20, 32),
1011
+ ].join("-");
1012
+ const admission = yield* backend.workflowAdmissions
1013
+ .reserve({
1014
+ trigger: context.startedBy.kind === "trigger"
1015
+ ? context.startedBy.trigger
1016
+ : `_manual.${workflow.manifest.name}`,
1017
+ workflow: workflow.manifest.name,
1018
+ workflowId: compiledWorkflowId(workflow.declaration.run),
1019
+ actionId,
1020
+ recordId: null,
1021
+ input: input ?? {},
1022
+ arguments: [decoded.value, context],
1023
+ reservation: allocated.success,
1024
+ attributes: startAttributes(context.startedBy),
1025
+ commitBy,
1026
+ })
1027
+ .pipe(Effect.result);
1028
+ if (admission._tag === "Failure") {
1029
+ return refused(yield* failureResponse(Cause.fail(admission.failure), requestId));
1030
+ }
1031
+ admitted = admission.success;
1032
+ }
1033
+ const started = yield* Effect.tryPromise(() => engine.start(admitted
1034
+ ? { workflowId: admitted.workflowId }
1035
+ : workflow.declaration.run, admitted?.arguments ?? [decoded.value, context], {
1036
+ attributes: admitted?.attributes ?? startAttributes(context.startedBy),
1037
+ ...(admitted ? { reservation: admitted.reservation } : {}),
1038
+ })).pipe(Effect.timeoutOrElse({
1039
+ duration: "15 seconds",
1040
+ orElse: () => Effect.fail(new Error("Workflow submission acknowledgement timed out.")),
1041
+ }), Effect.result);
1042
+ if (started._tag === "Failure") {
1043
+ yield* Effect.logError("ontology.workflow.start.failed", started.failure).pipe(Effect.annotateLogs({ requestId, workflow: workflow.manifest.name }));
1044
+ if (admitted) {
1045
+ // Recovery was durably armed before this journal commit. A failed acknowledgement
1046
+ // is accepted pending, never both a retriable ingress failure and an independent run.
1047
+ return { ok: true, runId: admitted.reservation.runId };
1048
+ }
1049
+ return refused(toServerResponse({
1050
+ status: 503,
1051
+ body: {
1052
+ error: "workflow_unavailable",
1053
+ detail: "The workflow could not be started. Quote the request id when you report it.",
1054
+ requestId,
1055
+ },
1056
+ }));
1057
+ }
1058
+ if (admitted) {
1059
+ if (started.success.runId !== admitted.reservation.runId) {
1060
+ return refused(toServerResponse(unavailable(requestId)));
1061
+ }
1062
+ yield* backend
1063
+ .workflowAdmissions.submitted(admitted)
1064
+ .pipe(Effect.catchCause((cause) => Effect.logError("ontology.workflow.submission_journal_ack_failed", cause)));
1065
+ }
1066
+ yield* Effect.logInfo("ontology.workflow.started").pipe(Effect.annotateLogs({
1067
+ requestId,
1068
+ workflow: workflow.manifest.name,
1069
+ runId: started.success.runId,
1070
+ startedBy: context.startedBy,
1071
+ }));
1072
+ return { ok: true, runId: started.success.runId };
1073
+ });
1074
+ /** Starts a run and answers 202 with its id, as the cron and start routes do. */
1075
+ const startRun = (workflow, input, context) => Effect.map(beginRun(workflow, input, context), (begun) => begun.ok
1076
+ ? json({ runId: begun.runId, workflow: workflow.manifest.name }, 202)
1077
+ : begun.response);
1078
+ /** Authenticates a workflow route's caller: the platform's workload token, or a principal. */
1079
+ const authorized = (handle) => (request) => Effect.gen(function* () {
1080
+ const requestId = yield* RequestId;
1081
+ const bearer = yield* HttpApiBuilder.securityDecode(HttpApiSecurity.bearer).pipe(Effect.provideService(HttpServerRequest.HttpServerRequest, request));
1082
+ const token = Redacted.value(bearer);
1083
+ if (token !== "" && isWorkloadToken(token)) {
1084
+ const verification = yield* Effect.promise(() => options.verifyToken(token));
1085
+ if (verification.status === "unavailable") {
1086
+ return toServerResponse(unavailable(requestId));
1087
+ }
1088
+ if (verification.status !== "workload") {
1089
+ return toServerResponse(unauthorized(requestId, "The token is not valid."));
1090
+ }
1091
+ // Only the platform's subject reaches the workflow routes with a workload token.
1092
+ if (workloadKind({ sub: verification.execution.subject }, verification.execution.workspaceId) !== "platform" ||
1093
+ (options.deployment.target === "preview" &&
1094
+ verification.execution.ontologyOrigin !==
1095
+ new URL(request.source instanceof Request ? request.source.url : request.url, `https://${request.headers.host ?? "ontology.invalid"}`).origin)) {
1096
+ return toServerResponse(forbidden(requestId, "This workload may not use the workflow routes."));
1097
+ }
1098
+ return yield* handle({ kind: "platform", execution: verification.execution }, request);
1099
+ }
1100
+ const runtime = runtimeCredential(options.verifyRuntime, request);
1101
+ return yield* withPrincipal(bearer, options.verifyToken, backend, Effect.gen(function* () {
1102
+ const principal = yield* CurrentPrincipal;
1103
+ const caller = yield* CurrentCaller;
1104
+ // A pipeline worker never uses the workflow routes.
1105
+ if (caller.kind === "workload") {
1106
+ return toServerResponse(forbidden(requestId, "This workload may not use the workflow routes."));
1107
+ }
1108
+ return yield* handle({ kind: "principal", principal, caller }, request);
1109
+ }), {
1110
+ session: { request, exchange: options.exchangeSession },
1111
+ ...(runtime === undefined ? {} : { runtime }),
1112
+ });
1113
+ });
1114
+ const mayRead = (caller, workflow) => caller.kind === "platform" ||
1115
+ isAllowed(workflowPermission(workflow, "read"), caller.principal);
1116
+ const unknownWorkflow = (requestId) => toServerResponse(notFound(requestId, "The ontology declares no such workflow."));
1117
+ const readable = (caller, name) => Effect.gen(function* () {
1118
+ const requestId = yield* RequestId;
1119
+ const workflow = byName(name);
1120
+ if (workflow === undefined) {
1121
+ return { ok: false, refused: unknownWorkflow(requestId) };
1122
+ }
1123
+ if (!mayRead(caller, workflow.manifest.name)) {
1124
+ return {
1125
+ ok: false,
1126
+ refused: toServerResponse(forbidden(requestId, `Reading this workflow's runs requires ${workflowPermission(workflow.manifest.name, "read")}.`)),
1127
+ };
1128
+ }
1129
+ return { ok: true, workflow };
1130
+ });
1131
+ const recorded = (read) => Effect.gen(function* () {
1132
+ const requestId = yield* RequestId;
1133
+ const outcome = yield* Effect.tryPromise(read).pipe(Effect.result);
1134
+ if (outcome._tag === "Failure") {
1135
+ yield* Effect.logError("ontology.workflow.runs.failed", outcome.failure).pipe(Effect.annotateLogs({ requestId }));
1136
+ return { ok: false, refused: toServerResponse(internalError(requestId)) };
1137
+ }
1138
+ return { ok: true, value: outcome.success };
1139
+ });
1140
+ yield* router.add("GET", `${WORKFLOW_BASE_PATH}/workflows/:workflow/triggers/:trigger`, (request) => Effect.gen(function* () {
1141
+ const requestId = yield* RequestId;
1142
+ if (options.deployment.target === "preview") {
1143
+ return json({ error: "preview_capability_unavailable", capability: "workflows" }, 403);
1144
+ }
1145
+ if (options.cronSecret === undefined ||
1146
+ !bearerEquals(request.headers.authorization, options.cronSecret)) {
1147
+ return toServerResponse(unauthorized(requestId, "The cron secret is required."));
1148
+ }
1149
+ const params = yield* HttpRouter.params;
1150
+ const workflow = byName(params.workflow);
1151
+ const trigger = workflow?.manifest.triggers
1152
+ .filter(isCronTrigger)
1153
+ .find((candidate) => params.trigger !== undefined && candidate.name === nameToWire(params.trigger));
1154
+ if (workflow === undefined || trigger === undefined) {
1155
+ return toServerResponse(notFound(requestId, "The ontology declares no such workflow trigger."));
1156
+ }
1157
+ return yield* startRun(workflow, trigger.input, {
1158
+ startedBy: {
1159
+ kind: "trigger",
1160
+ triggerKind: "cron",
1161
+ trigger: `${workflow.manifest.name}.${trigger.name}`,
1162
+ },
1163
+ });
1164
+ }));
1165
+ // Webhooks, by the wire name that is their URL: one delivery starts one run.
1166
+ const webhooks = new Map();
1167
+ for (const workflow of workflows) {
1168
+ for (const trigger of workflow.manifest.triggers.filter(isWebhookTrigger)) {
1169
+ webhooks.set(trigger.name, { workflow, trigger });
1170
+ }
1171
+ }
1172
+ const refusals = signatureRefusals();
1173
+ const webhook = (request) => Effect.gen(function* () {
1174
+ const requestId = yield* RequestId;
1175
+ const params = yield* HttpRouter.params;
1176
+ const began = Date.now();
1177
+ const declared = params.name === undefined ? undefined : webhooks.get(params.name);
1178
+ // Only declared names enter the logs: an arbitrary path segment may be private.
1179
+ const name = declared?.trigger.name;
1180
+ const refuse = (reason, status, headers = {}) => Effect.as(Effect.logWarning("ontology.webhook.refused").pipe(Effect.annotateLogs({
1181
+ requestId,
1182
+ reason,
1183
+ status,
1184
+ ...(name === undefined ? {} : { webhook: name }),
1185
+ })), HttpServerResponse.empty({
1186
+ status,
1187
+ headers: { "cache-control": "no-store", ...headers },
1188
+ }));
1189
+ if (request.method !== "POST" && request.method !== "GET") {
1190
+ return yield* refuse("method", 405, {
1191
+ allow: declared?.trigger.verification === undefined ? "POST" : "GET, POST",
1192
+ });
1193
+ }
1194
+ if (request.method === "GET" && declared?.trigger.verification === undefined) {
1195
+ return yield* refuse("method", 405, { allow: "POST" });
1196
+ }
1197
+ const secret = options.webhookSecret === undefined ? "" : Redacted.value(options.webhookSecret);
1198
+ if (secret === "") {
1199
+ return yield* refuse("webhook_secret_missing", 404);
1200
+ }
1201
+ if (declared === undefined) {
1202
+ return yield* refuse("unknown_webhook", 404);
1203
+ }
1204
+ const { trigger, workflow } = declared;
1205
+ const signature = trigger.signature;
1206
+ if (signature === undefined) {
1207
+ if (params.token === undefined ||
1208
+ !webhookTokenMatches(secret, trigger.name, params.token)) {
1209
+ return yield* refuse("token", 404);
1210
+ }
1211
+ }
1212
+ else if (params.token !== undefined) {
1213
+ return yield* refuse("unexpected_token", 404);
1214
+ }
1215
+ if (request.method === "GET") {
1216
+ const verification = trigger.verification;
1217
+ const verifier = options.webhookTokenVerifier;
1218
+ if (verification?.scheme !== "meta" ||
1219
+ signature?.scheme !== "meta" ||
1220
+ verifier === undefined) {
1221
+ return yield* refuse("verification_unavailable", 503);
1222
+ }
1223
+ const client = `${trigger.name}:${clientAddress(request)}`;
1224
+ const wait = refusals.blocked(client);
1225
+ if (wait !== undefined) {
1226
+ return yield* refuse("verification_throttled", 429, { "retry-after": String(wait) });
1227
+ }
1228
+ const query = new URL(request.url, "https://ontology.invalid").searchParams;
1229
+ const token = query.get("hub.verify_token");
1230
+ const challenge = query.get("hub.challenge");
1231
+ if (query.getAll("hub.mode").length !== 1 ||
1232
+ query.get("hub.mode") !== "subscribe" ||
1233
+ query.getAll("hub.verify_token").length !== 1 ||
1234
+ token === null ||
1235
+ token.length === 0 ||
1236
+ token.length > MAX_WEBHOOK_VERIFICATION_CHARS ||
1237
+ query.getAll("hub.challenge").length !== 1 ||
1238
+ challenge === null ||
1239
+ challenge.length === 0 ||
1240
+ challenge.length > MAX_WEBHOOK_VERIFICATION_CHARS) {
1241
+ refusals.refused(client);
1242
+ return yield* refuse("verification_query", 403);
1243
+ }
1244
+ const verified = yield* Effect.promise(() => verifier({
1245
+ secret: verification.verifyToken,
1246
+ token,
1247
+ identityToken: requestIdentityToken(request),
1248
+ requestId,
1249
+ }));
1250
+ if (verified.outcome === "unavailable") {
1251
+ yield* Effect.logError("ontology.webhook.verification_unavailable", verified.cause).pipe(Effect.annotateLogs({ requestId, webhook: trigger.name, status: verified.status }));
1252
+ return yield* refuse("verification_unavailable", 503);
1253
+ }
1254
+ if (verified.outcome !== "valid") {
1255
+ refusals.refused(client);
1256
+ return yield* refuse("verification_token", 403);
1257
+ }
1258
+ return HttpServerResponse.text(challenge, {
1259
+ status: 200,
1260
+ headers: { "cache-control": "no-store", "content-type": "text/plain; charset=utf-8" },
1261
+ });
1262
+ }
1263
+ const receivedAt = new Date();
1264
+ const body = yield* readWebhookBody(request, signature?.scheme === "meta" ? MAX_META_WEBHOOK_BODY_BYTES : MAX_WEBHOOK_BODY_BYTES);
1265
+ if (body === "too_large") {
1266
+ return yield* refuse("too_large", 413);
1267
+ }
1268
+ if (body === "unreadable") {
1269
+ return yield* refuse("unreadable", 400);
1270
+ }
1271
+ if (signature !== undefined) {
1272
+ const client = `${trigger.name}:${clientAddress(request)}`;
1273
+ const wait = refusals.blocked(client);
1274
+ if (wait !== undefined) {
1275
+ return yield* refuse("signature_throttled", 429, { "retry-after": String(wait) });
1276
+ }
1277
+ const scheme = SIGNATURE_SCHEMES[signature.scheme];
1278
+ if (scheme === undefined || options.webhookVerifier === undefined) {
1279
+ return yield* refuse("scheme_unavailable", 503);
1280
+ }
1281
+ const content = scheme(request.headers, body, receivedAt);
1282
+ if (!content.ok) {
1283
+ refusals.refused(client);
1284
+ return yield* refuse(content.reason, 401);
1285
+ }
1286
+ const verifier = options.webhookVerifier;
1287
+ const verified = yield* Effect.promise(() => verifier({
1288
+ secret: signature.secret,
1289
+ algorithm: "sha256",
1290
+ encoding: "hex",
1291
+ message: content.message,
1292
+ signatures: content.signatures,
1293
+ identityToken: requestIdentityToken(request),
1294
+ requestId,
1295
+ }));
1296
+ if (verified.outcome === "unavailable") {
1297
+ yield* Effect.logError("ontology.webhook.verification_unavailable", verified.cause).pipe(Effect.annotateLogs({ requestId, webhook: trigger.name, status: verified.status }));
1298
+ return yield* refuse("verification_unavailable", 503);
1299
+ }
1300
+ if (verified.outcome !== "valid") {
1301
+ refusals.refused(client);
1302
+ return yield* refuse(verified.outcome === "secret_missing"
1303
+ ? "signature_secret_missing"
1304
+ : "signature_invalid", 401);
1305
+ }
1306
+ }
1307
+ const text = utf8Text(body);
1308
+ if (text === undefined) {
1309
+ return yield* refuse("encoding", 415);
1310
+ }
1311
+ const headers = {};
1312
+ for (const header of trigger.headers ?? []) {
1313
+ const value = request.headers[header];
1314
+ if (typeof value === "string") {
1315
+ headers[header] = value;
1316
+ }
1317
+ }
1318
+ const begun = yield* beginRun(workflow, {
1319
+ body: text,
1320
+ content_type: request.headers["content-type"] ?? "",
1321
+ query: new URL(request.url, "https://ontology.invalid").search.replace(/^\?/, ""),
1322
+ headers,
1323
+ received_at: receivedAt.toISOString(),
1324
+ }, {
1325
+ startedBy: {
1326
+ kind: "trigger",
1327
+ triggerKind: "webhook",
1328
+ trigger: `${workflow.manifest.name}.${trigger.name}`,
1329
+ },
1330
+ });
1331
+ if (!begun.ok) {
1332
+ return yield* refuse("start_failed", 503);
1333
+ }
1334
+ yield* Effect.logInfo("ontology.webhook.received").pipe(Effect.annotateLogs({
1335
+ requestId,
1336
+ webhook: trigger.name,
1337
+ workflow: workflow.manifest.name,
1338
+ runId: begun.runId,
1339
+ bytes: body.byteLength,
1340
+ durationMs: Date.now() - began,
1341
+ }));
1342
+ return HttpServerResponse.empty({
1343
+ status: 200,
1344
+ headers: { "cache-control": "no-store" },
1345
+ });
1346
+ });
1347
+ yield* router.add("*", `${WEBHOOK_BASE_PATH}/:name`, webhook);
1348
+ yield* router.add("*", `${WEBHOOK_BASE_PATH}/:name/:token`, webhook);
1349
+ // The sweep: Vercel's cron calls it with GET; an operator may POST, with
1350
+ // `?retryAbandoned=true` to retry the starts the automatic sweep gave up on.
1351
+ const sweep = (request) => Effect.gen(function* () {
1352
+ const requestId = yield* RequestId;
1353
+ if (options.deployment.target === "preview") {
1354
+ if (request.method !== "POST") {
1355
+ return json({ error: "preview_capability_unavailable", capability: "workflows" }, 403);
1356
+ }
1357
+ return yield* authorized((caller) => caller.kind === "platform"
1358
+ ? performSweep(request)
1359
+ : Effect.succeed(toServerResponse(forbidden(requestId, "The platform token is required."))))(request);
1360
+ }
1361
+ if (options.cronSecret === undefined ||
1362
+ !bearerEquals(request.headers.authorization, options.cronSecret)) {
1363
+ return toServerResponse(unauthorized(requestId, "The cron secret is required."));
1364
+ }
1365
+ return yield* performSweep(request);
1366
+ });
1367
+ const performSweep = (request) => Effect.gen(function* () {
1368
+ const requestId = yield* RequestId;
1369
+ if (backend.workflowStarts === undefined) {
1370
+ return json({ started: 0, failed: 0, abandoned: 0 });
1371
+ }
1372
+ const query = new URL(request.url, "https://ontology.invalid").searchParams;
1373
+ const { started, failed, abandoned, waiting } = yield* backend.workflowStarts.sweep({
1374
+ retryAbandoned: query.get("retryAbandoned") === "true",
1375
+ requestId,
1376
+ });
1377
+ yield* Effect.logInfo("ontology.workflow.sweep").pipe(Effect.annotateLogs({ requestId, started, failed, abandoned, waiting }));
1378
+ return json({ started, failed, abandoned });
1379
+ });
1380
+ yield* router.add("GET", SWEEP_PATH, sweep);
1381
+ yield* router.add("POST", SWEEP_PATH, sweep);
1382
+ yield* router.add("GET", `${WORKFLOW_BASE_PATH}/workflows/:workflow/starts`, authorized((caller, request) => Effect.gen(function* () {
1383
+ const requestId = yield* RequestId;
1384
+ const params = yield* HttpRouter.params;
1385
+ const found = yield* readable(caller, params.workflow);
1386
+ if (!found.ok) {
1387
+ return found.refused;
1388
+ }
1389
+ const query = new URL(request.url, "https://ontology.invalid").searchParams;
1390
+ const pending = query.get("pending");
1391
+ if (pending !== null && pending !== "true" && pending !== "false") {
1392
+ return toServerResponse({
1393
+ status: 400,
1394
+ body: {
1395
+ error: "invalid_request",
1396
+ detail: "The query parameters are invalid.",
1397
+ requestId,
1398
+ issues: [{ path: ["pending"], message: "Expected true or false" }],
1399
+ },
1400
+ });
1401
+ }
1402
+ const limit = Math.min(Math.max(Number(query.get("limit") ?? 20) || 20, 1), 100);
1403
+ const after = query.get("after") ?? undefined;
1404
+ if (backend.workflowStarts === undefined) {
1405
+ return json({ items: [], nextCursor: null });
1406
+ }
1407
+ const page = yield* backend.workflowStarts
1408
+ .list({
1409
+ workflow: found.workflow.manifest.name,
1410
+ pending: pending === null ? undefined : pending === "true",
1411
+ limit,
1412
+ after,
1413
+ })
1414
+ .pipe(Effect.result);
1415
+ if (page._tag === "Failure") {
1416
+ return toServerResponse({
1417
+ status: 400,
1418
+ body: {
1419
+ error: "invalid_request",
1420
+ detail: "The query parameters are invalid.",
1421
+ requestId,
1422
+ issues: [{ path: ["after"], message: page.failure.message }],
1423
+ },
1424
+ });
1425
+ }
1426
+ return json(page.success);
1427
+ })));
1428
+ yield* router.add("POST", `${WORKFLOW_BASE_PATH}/workflows/:workflow/start`, authorized((caller, request) => Effect.gen(function* () {
1429
+ const requestId = yield* RequestId;
1430
+ const params = yield* HttpRouter.params;
1431
+ const workflow = byName(params.workflow);
1432
+ if (workflow === undefined) {
1433
+ return unknownWorkflow(requestId);
1434
+ }
1435
+ const required = workflowPermission(workflow.manifest.name, "start");
1436
+ if (caller.kind === "principal" &&
1437
+ (caller.caller.kind === "runtime" || !isAllowed(required, caller.principal))) {
1438
+ return toServerResponse(forbidden(requestId, `Starting this workflow requires ${required}.`));
1439
+ }
1440
+ const text = yield* request.text.pipe(Effect.orElseSucceed(() => ""));
1441
+ let body;
1442
+ try {
1443
+ body = text.trim() === "" ? {} : JSON.parse(text);
1444
+ }
1445
+ catch {
1446
+ return yield* failureResponse(Cause.fail(new InvalidRequestBody({ reason: "invalid_json", issues: [] })), requestId);
1447
+ }
1448
+ const fields = typeof body === "object" && body !== null && !Array.isArray(body)
1449
+ ? body
1450
+ : {};
1451
+ const startedBy = caller.kind === "platform"
1452
+ ? {
1453
+ kind: "platform",
1454
+ userId: typeof fields.startedBy === "string" ? fields.startedBy : null,
1455
+ }
1456
+ : { kind: "member", userId: caller.principal.id };
1457
+ return yield* startRun(workflow, fields.input, { startedBy });
1458
+ })));
1459
+ yield* router.add("GET", `${WORKFLOW_BASE_PATH}/workflows`, authorized((caller) => Effect.sync(() => {
1460
+ const now = new Date();
1461
+ return json({
1462
+ items: plan.ontology.workflows
1463
+ .filter((workflow) => mayRead(caller, workflow.name))
1464
+ .map((workflow) => workflowView(workflow, now)),
1465
+ });
1466
+ })));
1467
+ yield* router.add("GET", `${WORKFLOW_BASE_PATH}/workflows/:workflow/runs`, authorized((caller, request) => Effect.gen(function* () {
1468
+ const params = yield* HttpRouter.params;
1469
+ const found = yield* readable(caller, params.workflow);
1470
+ if (!found.ok) {
1471
+ return found.refused;
1472
+ }
1473
+ const workflowName = compiledWorkflowId(found.workflow.declaration.run);
1474
+ if (workflowName === undefined) {
1475
+ return json({ items: [], nextCursor: null });
1476
+ }
1477
+ const query = new URL(request.url, "https://ontology.invalid").searchParams;
1478
+ const limit = Math.min(Math.max(Number(query.get("limit") ?? 20) || 20, 1), 100);
1479
+ const cursor = query.get("after") ?? undefined;
1480
+ const page = yield* recorded(() => engine.listRuns({ workflowName, limit, ...(cursor ? { cursor } : {}) }));
1481
+ if (!page.ok) {
1482
+ return page.refused;
1483
+ }
1484
+ return json({
1485
+ items: page.value.data.map((record) => runView(found.workflow.manifest.name, record)),
1486
+ nextCursor: page.value.hasMore ? page.value.cursor : null,
1487
+ });
1488
+ })));
1489
+ yield* router.add("GET", `${WORKFLOW_BASE_PATH}/workflows/:workflow/runs/:runId`, authorized((caller, request) => Effect.gen(function* () {
1490
+ const requestId = yield* RequestId;
1491
+ const params = yield* HttpRouter.params;
1492
+ const found = yield* readable(caller, params.workflow);
1493
+ if (!found.ok) {
1494
+ return found.refused;
1495
+ }
1496
+ const workflowName = compiledWorkflowId(found.workflow.declaration.run);
1497
+ const runId = params.runId ?? "";
1498
+ const run = yield* recorded(() => engine.getRun(runId));
1499
+ if (!run.ok) {
1500
+ return run.refused;
1501
+ }
1502
+ // A run of another workflow, or of the agent, is not this workflow's.
1503
+ if (run.value === undefined ||
1504
+ workflowName === undefined ||
1505
+ run.value.workflowName !== workflowName) {
1506
+ return toServerResponse(notFound(requestId, "This workflow has no such run."));
1507
+ }
1508
+ // The run is answered without its steps when the world cannot list them.
1509
+ const query = new URL(request.url, "https://ontology.invalid").searchParams;
1510
+ const limit = Math.min(Math.max(Math.trunc(Number(query.get("limit")) || WORLD_PAGE_SIZE), 1), WORLD_PAGE_SIZE);
1511
+ const cursor = query.get("after") ?? undefined;
1512
+ const steps = yield* Effect.tryPromise(() => engine.listSteps(runId, { limit, ...(cursor ? { cursor } : {}) })).pipe(Effect.result);
1513
+ if (steps._tag === "Failure") {
1514
+ yield* Effect.logError("ontology.workflow.steps.failed", steps.failure).pipe(Effect.annotateLogs({ requestId, workflow: found.workflow.manifest.name, runId }));
1515
+ }
1516
+ const diagnostics = engine.diagnostics === undefined
1517
+ ? undefined
1518
+ : yield* Effect.tryPromise(() => engine.diagnostics(run.value, steps._tag === "Success" ? steps.success.data : [])).pipe(Effect.result);
1519
+ const observed = diagnostics?._tag === "Success" ? diagnostics.success : undefined;
1520
+ if (diagnostics?._tag === "Failure") {
1521
+ yield* Effect.logError("ontology.workflow.diagnostics.failed", diagnostics.failure).pipe(Effect.annotateLogs({ requestId, runId }));
1522
+ }
1523
+ return json({
1524
+ ...runView(found.workflow.manifest.name, {
1525
+ ...run.value,
1526
+ error: observed?.error ?? run.value.error,
1527
+ }),
1528
+ steps: steps._tag === "Success"
1529
+ ? steps.success.data.map((step) => {
1530
+ const attempts = observed?.attempts.get(step.stepId);
1531
+ const failure = attempts?.at(-1)?.error;
1532
+ return stepView({
1533
+ ...step,
1534
+ error: hasWorkflowError(step.error) ? step.error : (failure ?? step.error),
1535
+ attempts,
1536
+ });
1537
+ })
1538
+ : null,
1539
+ stepsTruncated: steps._tag === "Success" ? steps.success.hasMore : true,
1540
+ stepsNextCursor: steps._tag === "Success" && steps.success.hasMore ? steps.success.cursor : null,
1541
+ ...(diagnostics === undefined
1542
+ ? {}
1543
+ : {
1544
+ attemptsTruncated: steps._tag === "Failure" || (observed?.truncated ?? true),
1545
+ }),
1546
+ });
1547
+ })));
1548
+ }));
1549
+ }