okengine 0.18.4 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (512) hide show
  1. package/AGENTS.md +18 -10
  2. package/README.md +2 -2
  3. package/package.json +51 -50
  4. package/site/content/docs/ai/index.mdx +25 -6
  5. package/site/content/docs/ai/llms-txt.mdx +1 -1
  6. package/site/content/docs/ai/meta.json +1 -1
  7. package/site/content/docs/ai/skills.mdx +4 -1
  8. package/site/content/docs/ai/try-it.mdx +58 -0
  9. package/site/content/docs/client/auth.mdx +208 -0
  10. package/site/content/docs/client/calling.mdx +451 -0
  11. package/site/content/docs/client/index.mdx +209 -0
  12. package/site/content/docs/client/live.mdx +234 -0
  13. package/site/content/docs/client/meta.json +5 -0
  14. package/site/content/docs/client/react.mdx +249 -0
  15. package/site/content/docs/elements/ai/agents.mdx +232 -0
  16. package/site/content/docs/elements/ai/index.mdx +341 -0
  17. package/site/content/docs/elements/ai/mcp.mdx +275 -0
  18. package/site/content/docs/elements/ai/meta.json +5 -0
  19. package/site/content/docs/elements/ai/models.mdx +283 -0
  20. package/site/content/docs/elements/ai/prompts.mdx +256 -0
  21. package/site/content/docs/elements/channel/email.mdx +266 -0
  22. package/site/content/docs/elements/channel/index.mdx +344 -0
  23. package/site/content/docs/elements/channel/meta.json +5 -0
  24. package/site/content/docs/elements/channel/push.mdx +218 -0
  25. package/site/content/docs/elements/channel/receipts.mdx +206 -0
  26. package/site/content/docs/elements/channel/sms.mdx +264 -0
  27. package/site/content/docs/elements/channel/whatsapp.mdx +220 -0
  28. package/site/content/docs/elements/clock/index.mdx +300 -0
  29. package/site/content/docs/elements/clock/meta.json +5 -0
  30. package/site/content/docs/elements/clock/schedules.mdx +508 -0
  31. package/site/content/docs/elements/clock/sleep.mdx +445 -0
  32. package/site/content/docs/elements/flow/consumers.mdx +707 -0
  33. package/site/content/docs/elements/flow/http.mdx +1080 -0
  34. package/site/content/docs/elements/flow/index.mdx +618 -0
  35. package/site/content/docs/elements/flow/meta.json +5 -0
  36. package/site/content/docs/elements/flow/routing.mdx +559 -0
  37. package/site/content/docs/elements/flow/workflows.mdx +640 -0
  38. package/site/content/docs/elements/gate/auth.mdx +371 -0
  39. package/site/content/docs/elements/gate/authorization.mdx +298 -0
  40. package/site/content/docs/elements/gate/index.mdx +418 -0
  41. package/site/content/docs/elements/gate/meta.json +5 -0
  42. package/site/content/docs/elements/gate/rate-limits.mdx +365 -0
  43. package/site/content/docs/elements/gate/rls.mdx +303 -0
  44. package/site/content/docs/elements/gate/tenancy.mdx +372 -0
  45. package/site/content/docs/elements/index.mdx +52 -11
  46. package/site/content/docs/elements/signal/broadcast.mdx +517 -0
  47. package/site/content/docs/elements/signal/index.mdx +352 -0
  48. package/site/content/docs/elements/signal/live.mdx +590 -0
  49. package/site/content/docs/elements/signal/meta.json +5 -0
  50. package/site/content/docs/elements/signal/once.mdx +596 -0
  51. package/site/content/docs/elements/store/files.mdx +659 -0
  52. package/site/content/docs/elements/store/index.mdx +310 -0
  53. package/site/content/docs/elements/store/kv.mdx +629 -0
  54. package/site/content/docs/elements/store/meta.json +5 -0
  55. package/site/content/docs/elements/store/search.mdx +946 -0
  56. package/site/content/docs/elements/store/sql.mdx +937 -0
  57. package/site/content/docs/elements/vault/config.mdx +256 -0
  58. package/site/content/docs/elements/vault/index.mdx +356 -0
  59. package/site/content/docs/elements/vault/meta.json +5 -0
  60. package/site/content/docs/elements/vault/rotation.mdx +273 -0
  61. package/site/content/docs/elements/vault/secrets.mdx +285 -0
  62. package/site/content/docs/index.mdx +78 -30
  63. package/site/content/docs/meta.json +11 -8
  64. package/site/content/docs/plugins/cors.mdx +2 -1
  65. package/site/content/docs/plugins/csrf.mdx +4 -1
  66. package/site/content/docs/plugins/headers.mdx +2 -1
  67. package/site/content/docs/plugins/ip-allowlist.mdx +2 -1
  68. package/site/content/docs/plugins/magic-link.mdx +6 -0
  69. package/site/content/docs/plugins/maintenance-mode.mdx +2 -1
  70. package/site/content/docs/plugins/oauth.mdx +1 -1
  71. package/site/content/docs/plugins/otp.mdx +12 -1
  72. package/site/content/docs/plugins/passkey.mdx +39 -15
  73. package/site/content/docs/plugins/two-factor.mdx +91 -34
  74. package/site/content/docs/plugins/username.mdx +1 -1
  75. package/site/content/docs/recipes/caddy.mdx +3 -3
  76. package/site/content/docs/recipes/index.mdx +18 -28
  77. package/site/content/docs/recipes/mailpit.mdx +1 -1
  78. package/site/content/docs/recipes/meilisearch.mdx +3 -3
  79. package/site/content/docs/recipes/meta.json +1 -4
  80. package/site/content/docs/recipes/nginx.mdx +2 -2
  81. package/site/content/docs/recipes/openrouter.mdx +283 -0
  82. package/site/content/docs/recipes/pgdog.mdx +3 -3
  83. package/site/content/docs/recipes/postgres.mdx +1 -2
  84. package/site/content/docs/recipes/rustfs.mdx +2 -2
  85. package/site/content/docs/recipes/traefik.mdx +3 -3
  86. package/site/content/docs/reference/cli.mdx +231 -0
  87. package/site/content/docs/reference/configuration.mdx +30 -10
  88. package/site/content/docs/reference/environment-variables.mdx +93 -20
  89. package/site/content/docs/reference/errors.mdx +132 -29
  90. package/site/content/docs/reference/fx.mdx +137 -73
  91. package/site/content/docs/reference/index.mdx +13 -21
  92. package/site/content/docs/reference/meta.json +4 -5
  93. package/site/content/docs/reference/okid.mdx +42 -17
  94. package/site/content/docs/reference/plugins.mdx +4 -3
  95. package/site/content/docs/reference/security.mdx +197 -0
  96. package/site/content/docs/understand/meta.json +5 -0
  97. package/site/content/docs/understand/the-anatomy.mdx +132 -0
  98. package/site/content/docs/understand/the-model.mdx +32 -0
  99. package/site/content/docs/understand/the-problem.mdx +74 -0
  100. package/site/content/docs/understand/the-vocabulary.mdx +26 -0
  101. package/src/auth/api-keys.ts +2 -1
  102. package/src/auth/bindings.ts +64 -16
  103. package/src/auth/gate-auth.test.ts +6 -1
  104. package/src/auth/identity.ts +152 -5
  105. package/src/auth/index.ts +29 -0
  106. package/src/auth/invites.ts +2 -1
  107. package/src/auth/method-context.ts +41 -0
  108. package/src/auth/oauth-as/crypto.ts +2 -1
  109. package/src/auth/oauth-as/stores.ts +2 -1
  110. package/src/auth/operator.ts +2 -1
  111. package/src/auth/sessions-jwt.test.ts +77 -0
  112. package/src/auth/sessions.ts +20 -4
  113. package/src/auth/tenants.ts +3 -2
  114. package/src/auth/two-factor-challenge.test.ts +61 -0
  115. package/src/auth/two-factor-challenge.ts +284 -0
  116. package/src/auth/verification.ts +5 -0
  117. package/src/bench/REPORT.md +49 -0
  118. package/src/bench/g03-signal-once.bench.ts +1 -1
  119. package/src/bench/g10-observability-contention.bench.ts +1 -1
  120. package/src/bench/g17-hybrid-search.bench.ts +572 -0
  121. package/src/bench/load-app.ts +2 -10
  122. package/src/cli/ai-setup/ai-setup.test.ts +331 -42
  123. package/src/cli/ai-setup/apply.ts +294 -52
  124. package/src/cli/ai-setup/catalog.ts +238 -1342
  125. package/src/cli/ai-setup/index.ts +14 -99
  126. package/src/cli/ai-setup/prompts.ts +60 -443
  127. package/src/cli/ask-seed.test.ts +96 -0
  128. package/src/cli/ask-seed.ts +82 -0
  129. package/src/cli/ask-vault-gaps.test.ts +84 -0
  130. package/src/cli/ask-vault-gaps.ts +146 -0
  131. package/src/cli/build.ts +2 -2
  132. package/src/cli/client-add.test.ts +26 -1
  133. package/src/cli/client-add.ts +106 -27
  134. package/src/cli/competitor-mention-removal.test.ts +1 -1
  135. package/src/cli/db-seed.ts +2 -0
  136. package/src/cli/db.ts +150 -11
  137. package/src/cli/dev.ts +119 -211
  138. package/src/cli/docker-clean.ts +2 -2
  139. package/src/cli/docker-cli.test.ts +1 -1
  140. package/src/cli/doctor-diff.ts +4 -2
  141. package/src/cli/doctor-pii.test.ts +1 -1
  142. package/src/cli/doctor.ts +14 -1
  143. package/src/cli/hero-meta.test.ts +4 -2
  144. package/src/cli/load-config.images.test.ts +8 -10
  145. package/src/cli/load-config.ts +1 -1
  146. package/src/cli/registry.ts +22 -10
  147. package/src/cli/replay.ts +3 -1
  148. package/src/cli/start.ts +1 -1
  149. package/src/cli/tui/keys.ts +2 -3
  150. package/src/client/auth/cookies.ts +74 -0
  151. package/src/client/auth/create-auth-client.ts +636 -0
  152. package/src/client/auth/denials.ts +99 -0
  153. package/src/client/auth/session.ts +283 -0
  154. package/src/client/auth.test.ts +251 -0
  155. package/src/client/auth.ts +39 -114
  156. package/src/client/create-with-session.ts +245 -0
  157. package/src/client/create.ts +51 -5
  158. package/src/client/index.ts +11 -1
  159. package/src/client/live.ts +11 -79
  160. package/src/client/notes-contract.test.ts +5 -10
  161. package/src/client/sse.ts +137 -0
  162. package/src/client/stream.ts +147 -0
  163. package/src/client/transport.test.ts +32 -0
  164. package/src/client/transport.ts +123 -26
  165. package/src/client/types.ts +118 -14
  166. package/src/client-react/index.ts +185 -24
  167. package/src/client-react/use-live-query.ts +13 -3
  168. package/src/compiler/aot.test.ts +7 -4
  169. package/src/compiler/effects-embed.test.ts +56 -0
  170. package/src/compiler/effects-fetch.test.ts +43 -0
  171. package/src/compiler/effects-infer.ts +58 -2
  172. package/src/compiler/extract.test.ts +258 -44
  173. package/src/compiler/extract.ts +566 -48
  174. package/src/compiler/fixtures/skyport/src/flows/bookings/index.ts +7 -4
  175. package/src/compiler/fixtures/skyport/src/flows/bookings/signals.ts +2 -8
  176. package/src/compiler/fixtures/skyport.expected.json +4 -4
  177. package/src/compiler/fixtures/triggers/five-triggers.ts +14 -10
  178. package/src/compiler/response.ts +2 -2
  179. package/src/compiler/search-writer-isolation.test.ts +40 -0
  180. package/src/config/index.ts +11 -0
  181. package/src/console/server/app.ts +17 -23
  182. package/src/console/server/bind.ts +4 -0
  183. package/src/console/server/console.test.ts +2 -2
  184. package/src/console/server/flows-invoke.test.ts +50 -34
  185. package/src/console/server/flows.ts +436 -255
  186. package/src/console/server/runs-ingest.test.ts +6 -3
  187. package/src/console/server/serve.ts +1 -1
  188. package/src/console/server/signals.test.ts +1 -5
  189. package/src/console/server/signals.ts +8 -6
  190. package/src/console/server/state.ts +6 -1
  191. package/src/console/server/store.ts +1 -1
  192. package/src/console/ui-next/dist/assets/FileExportIcon-Ck-5od4R.js +1 -0
  193. package/src/console/ui-next/dist/assets/MoreHorizontalCircle01Icon-gMNGsE37.js +1 -0
  194. package/src/console/ui-next/dist/assets/PlusSignIcon-CwG3nxfu.js +1 -0
  195. package/src/console/ui-next/dist/assets/UnavailableIcon-D9cvHVPr.js +1 -0
  196. package/src/console/ui-next/dist/assets/UserIcon-DaE2PB5_.js +1 -0
  197. package/src/console/ui-next/dist/assets/access-page-DFeymU07.js +4 -0
  198. package/src/console/ui-next/dist/assets/agent-disclosure-U1rdfblp.js +1 -0
  199. package/src/console/ui-next/dist/assets/cache-glyph-BeFJeqBG.js +1 -0
  200. package/src/console/ui-next/dist/assets/call-pii-button-CVAONPii.js +1 -0
  201. package/src/console/ui-next/dist/assets/collapsible-D2A6NJ-3.js +1 -0
  202. package/src/console/ui-next/dist/assets/copy-inline-button-CAYD18cr.js +1 -0
  203. package/src/console/ui-next/dist/assets/dagre.esm-B1_XeuLP.js +1 -0
  204. package/src/console/ui-next/dist/assets/detail-header-DVWNjWjg.js +1 -0
  205. package/src/console/ui-next/dist/assets/dropdown-menu-4h2LOVXM.js +1 -0
  206. package/src/console/ui-next/dist/assets/duration-tone-Cgk_h5ja.js +9 -0
  207. package/src/console/ui-next/dist/assets/element-icons-BI8cJgdh.js +1 -0
  208. package/src/console/ui-next/dist/assets/explorer-empty-CJs5A-wm.js +1 -0
  209. package/src/console/ui-next/dist/assets/flows-page-Dss7941e.js +1 -0
  210. package/src/console/ui-next/dist/assets/highlighted-json-MYZQtRnw.js +154 -0
  211. package/src/console/ui-next/dist/assets/http-method-Jrh39p7A.js +1 -0
  212. package/src/console/ui-next/dist/assets/index-D0zS5rKO.css +2 -0
  213. package/src/console/ui-next/dist/assets/index-DXP2dBIF.js +63 -0
  214. package/src/console/ui-next/dist/assets/observability-page-HvolXxTI.js +4 -0
  215. package/src/console/ui-next/dist/assets/react-dom-Ddte4I-Q.js +9 -0
  216. package/src/console/ui-next/dist/assets/replica-lag-CSh2dzrb.js +18 -0
  217. package/src/console/ui-next/dist/assets/request-meta-BatF8KrK.js +1 -0
  218. package/src/console/ui-next/dist/assets/shortcut-keys-3ILd8oGn.js +1 -0
  219. package/src/console/ui-next/dist/assets/{sql-BskegiFM.js → sql-BsFa4tDR.js} +1 -1
  220. package/src/console/ui-next/dist/assets/store-page-eiKiHnNe.js +41 -0
  221. package/src/console/ui-next/dist/assets/trace-detail-sheet-CZkMeKS-.js +2 -0
  222. package/src/console/ui-next/dist/assets/tree-expand-toggle-CW8y5A2h.js +55 -0
  223. package/src/console/ui-next/dist/assets/units-page-B_RWJrEO.js +1 -0
  224. package/src/console/ui-next/dist/assets/{use-vault-list-uk4WVboC.js → use-vault-list-CT4-gajj.js} +1 -1
  225. package/src/console/ui-next/dist/assets/vault-page-CWrg-A68.js +2 -0
  226. package/src/console/ui-next/dist/assets/xyflow-CSyC6ryz.css +1 -0
  227. package/src/console/ui-next/dist/assets/xyflow-yApv7D4e.js +7 -0
  228. package/src/console/ui-next/dist/index.html +6 -13
  229. package/src/console/ui-next/seed-invoke-host.ts +21 -18
  230. package/src/console/ui-next/src/client.ts +7 -2
  231. package/src/console/ui-next/src/features/flows/graph/element-map.ts +2 -0
  232. package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +12 -2
  233. package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +4 -0
  234. package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +8 -0
  235. package/src/console/ui-next/src/features/flows/traces/waterfall-bars.ts +4 -0
  236. package/src/console/ui-next/src/features/flows/traces/waterfall-tooltip.ts +9 -3
  237. package/src/console/ui-next/src/features/store/detail/reveal-cell.tsx +7 -11
  238. package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +2 -2
  239. package/src/console/ui-next/src/features/store/lib/fields-from-table.ts +2 -0
  240. package/src/console/ui-next/src/features/store/lib/grid-model.test.ts +25 -1
  241. package/src/console/ui-next/src/features/store/lib/grid-model.ts +36 -1
  242. package/src/console/ui-next/src/features/store/query/query-results.tsx +3 -3
  243. package/src/console/ui-next/src/features/units/detail/effects-summary.tsx +2 -0
  244. package/src/console/ui-next/src/features/units/lib/call-read-safe.ts +3 -1
  245. package/src/console/ui-next/src/features/units/lib/fields-from-schema.ts +6 -0
  246. package/src/docker/ai-model-status.test.ts +1 -12
  247. package/src/docker/ai-model-status.ts +10 -38
  248. package/src/docker/compose-up.test.ts +100 -0
  249. package/src/docker/compose-up.ts +292 -0
  250. package/src/docker/compose.ts +4 -14
  251. package/src/docker/derive.ts +9 -34
  252. package/src/docker/docker.test.ts +16 -261
  253. package/src/docker/images-config.test.ts +13 -25
  254. package/src/docker/index.ts +1 -24
  255. package/src/docker/recipes/index.ts +0 -23
  256. package/src/docker/recipes/pgdog.ts +6 -4
  257. package/src/docker/stack-id.test.ts +2 -2
  258. package/src/docker/stack-id.ts +0 -1
  259. package/src/docker/types.ts +2 -2
  260. package/src/drivers/ai-anthropic.ts +10 -0
  261. package/src/drivers/ai-openai-compatible.ts +34 -7
  262. package/src/drivers/ai-providers.test.ts +0 -107
  263. package/src/drivers/ai-stream.test.ts +0 -34
  264. package/src/drivers/ai-types.ts +17 -8
  265. package/src/drivers/channel-fcm.ts +6 -1
  266. package/src/drivers/channel-msegat.ts +6 -1
  267. package/src/drivers/channel-resend.ts +5 -1
  268. package/src/drivers/channel-smtp.ts +5 -1
  269. package/src/drivers/channel-sndr.ts +10 -1
  270. package/src/drivers/channel-taqnyat-mail.ts +5 -1
  271. package/src/drivers/channel-taqnyat-whatsapp.ts +6 -1
  272. package/src/drivers/channel-taqnyat.ts +6 -1
  273. package/src/drivers/channel-types.ts +10 -0
  274. package/src/drivers/channel-unifonic.ts +6 -1
  275. package/src/drivers/channel-wa-cloud.ts +6 -1
  276. package/src/drivers/channel-webpush.ts +6 -1
  277. package/src/drivers/external.ts +33 -0
  278. package/src/drivers/index.ts +0 -10
  279. package/src/drivers/meilisearch.ts +6 -0
  280. package/src/drivers/oauth-types.ts +4 -0
  281. package/src/drivers/postgres.test.ts +74 -3
  282. package/src/drivers/postgres.ts +175 -6
  283. package/src/drivers/signal-types.ts +5 -5
  284. package/src/drivers/types.ts +7 -0
  285. package/src/drivers/vault-types.ts +5 -0
  286. package/src/elements/ai/declare.ts +17 -4
  287. package/src/elements/ai/eval.ts +1 -1
  288. package/src/elements/ai/mcp-http.ts +1 -1
  289. package/src/elements/ai/pii.ts +1 -1
  290. package/src/elements/ai/providers.test.ts +289 -0
  291. package/src/elements/ai/providers.ts +169 -0
  292. package/src/elements/ai/runtime.ts +68 -5
  293. package/src/elements/ai/schema.ts +2 -2
  294. package/src/elements/ai.test.ts +7 -7
  295. package/src/elements/ai.ts +17 -1
  296. package/src/elements/channel/runtime.ts +105 -12
  297. package/src/elements/clock/cron-fields.test.ts +144 -0
  298. package/src/elements/clock/cron-fields.ts +185 -0
  299. package/src/elements/clock/declare.ts +247 -3
  300. package/src/elements/clock.ts +21 -1
  301. package/src/elements/index.ts +15 -0
  302. package/src/elements/signal/chaos-child.ts +1 -2
  303. package/src/elements/signal/declare.ts +65 -37
  304. package/src/elements/signal/delivery-modes.test.ts +11 -31
  305. package/src/elements/signal/dry-run-replay.test.ts +1 -7
  306. package/src/elements/signal/dry-run-write-isolation.test.ts +1 -7
  307. package/src/elements/signal/key-ordering.test.ts +6 -26
  308. package/src/elements/signal/lease-reclaim.test.ts +2 -6
  309. package/src/elements/signal/optional-emit.test.ts +6 -9
  310. package/src/elements/signal/order-lifecycle.test.ts +4 -17
  311. package/src/elements/signal/orphan-messages.test.ts +4 -15
  312. package/src/elements/signal/reconcile.test.ts +2 -2
  313. package/src/elements/signal/runtime.ts +1 -1
  314. package/src/elements/signal/schema-emit.test.ts +4 -8
  315. package/src/elements/signal.test.ts +24 -49
  316. package/src/elements/signal.ts +9 -2
  317. package/src/elements/store/domain-ddl.test.ts +1 -1
  318. package/src/elements/store/live-http.test.ts +9 -4
  319. package/src/elements/store/prepare-row.test.ts +123 -0
  320. package/src/elements/store/resource.ts +43 -12
  321. package/src/elements/store/schema-decl.ts +117 -0
  322. package/src/elements/store/search-backfill.ts +205 -0
  323. package/src/elements/store/search-bind.ts +66 -0
  324. package/src/elements/store/search-bm25.ts +78 -0
  325. package/src/elements/store/search-ddl.ts +156 -0
  326. package/src/elements/store/search-embed-flow.ts +141 -0
  327. package/src/elements/store/search-errors.ts +34 -0
  328. package/src/elements/store/search-fusion.ts +112 -0
  329. package/src/elements/store/search-lsh.ts +179 -0
  330. package/src/elements/store/search-runtime.ts +300 -0
  331. package/src/elements/store/search.test.ts +144 -0
  332. package/src/elements/store/sql-session.test.ts +1 -0
  333. package/src/elements/store/sql-session.ts +83 -3
  334. package/src/elements/store/table.ts +23 -2
  335. package/src/elements/store/upsert-app.test.ts +1 -3
  336. package/src/elements/store.ts +50 -0
  337. package/src/full.ts +4 -2
  338. package/src/http.ts +4 -1
  339. package/src/i18n/catalogs/ar.ts +14 -6
  340. package/src/i18n/catalogs/en.ts +14 -6
  341. package/src/index.ts +6 -1
  342. package/src/kernel/adopt-barrel-fresh.test.ts +7 -7
  343. package/src/kernel/adopt-routes.ts +9 -1
  344. package/src/kernel/app-auth.ts +5 -0
  345. package/src/kernel/app.ts +102 -9
  346. package/src/kernel/auto-cache.test.ts +6 -12
  347. package/src/kernel/auto-registry.test.ts +3 -3
  348. package/src/kernel/boot-bind/ai.test.ts +22 -19
  349. package/src/kernel/boot-bind/ai.ts +5 -43
  350. package/src/kernel/boot-bind/clock.ts +23 -5
  351. package/src/kernel/boot-bind/honor-config.test.ts +4 -4
  352. package/src/kernel/boot.test.ts +11 -6
  353. package/src/kernel/boot.ts +66 -16
  354. package/src/kernel/boundary-contract.ts +93 -0
  355. package/src/kernel/budget.test.ts +1 -1
  356. package/src/kernel/call.ts +89 -0
  357. package/src/kernel/capability.ts +6 -0
  358. package/src/kernel/client-descriptor.ts +123 -0
  359. package/src/kernel/clock-timezone.test.ts +80 -0
  360. package/src/kernel/correlation.test.ts +1 -1
  361. package/src/kernel/dry-run.ts +11 -8
  362. package/src/kernel/effects-stamping.test.ts +42 -9
  363. package/src/kernel/effects.test.ts +4 -2
  364. package/src/kernel/effects.ts +51 -9
  365. package/src/kernel/errors-channel.ts +17 -0
  366. package/src/kernel/errors-live-resume.ts +3 -2
  367. package/src/kernel/errors-tenant.ts +7 -4
  368. package/src/kernel/errors.registry-helpers.ts +96 -0
  369. package/src/kernel/errors.registry.test.ts +139 -25
  370. package/src/kernel/errors.ts +102 -23
  371. package/src/kernel/external-effects.test.ts +196 -0
  372. package/src/kernel/flow.test.ts +13 -4
  373. package/src/kernel/flow.ts +69 -71
  374. package/src/kernel/fx-ask-telemetry.test.ts +2 -2
  375. package/src/kernel/fx-dead-letters.test.ts +3 -3
  376. package/src/kernel/fx-fetch.ts +55 -0
  377. package/src/kernel/fx-live-stream.ts +2 -2
  378. package/src/kernel/fx-live.test.ts +8 -8
  379. package/src/kernel/fx.test.ts +17 -11
  380. package/src/kernel/fx.ts +180 -57
  381. package/src/kernel/horizontal-child.ts +1 -1
  382. package/src/kernel/http-query.test.ts +3 -6
  383. package/src/kernel/index.ts +13 -3
  384. package/src/kernel/instance-id.ts +1 -1
  385. package/src/kernel/instances.test.ts +40 -0
  386. package/src/kernel/instances.ts +47 -9
  387. package/src/kernel/live-http.test.ts +6 -7
  388. package/src/kernel/live-http.ts +6 -2
  389. package/src/kernel/live-resume.test.ts +1 -1
  390. package/src/kernel/mcp-tool.test.ts +3 -3
  391. package/src/kernel/on.ts +98 -4
  392. package/src/kernel/pipeline.test.ts +12 -15
  393. package/src/kernel/plugin-elements.test.ts +1 -1
  394. package/src/kernel/stamp-http.test.ts +3 -3
  395. package/src/kernel/triggers.ts +210 -95
  396. package/src/kernel-entry.ts +0 -1
  397. package/src/manifest/diff.ts +11 -1
  398. package/src/manifest/fixtures/skyport.excerpt.json +1 -1
  399. package/src/manifest/types.ts +46 -2
  400. package/src/mcp/docs-index.ts +3 -3
  401. package/src/mcp/docs-mcp.test.ts +2 -2
  402. package/src/mcp/docs-tools.ts +1 -1
  403. package/src/okid.test.ts +56 -0
  404. package/src/okid.ts +55 -11
  405. package/src/plugins/anonymous.ts +8 -4
  406. package/src/plugins/auth/shared.ts +27 -3
  407. package/src/plugins/auth-delivery.mailpit.integration.test.ts +1 -1
  408. package/src/plugins/auth-methods.security.test.ts +561 -23
  409. package/src/plugins/config-source.ts +1 -1
  410. package/src/plugins/csrf.test.ts +65 -0
  411. package/src/plugins/index.ts +2 -0
  412. package/src/plugins/magic-link.ts +36 -16
  413. package/src/plugins/oauth/shared.ts +3 -1
  414. package/src/plugins/oauth.ts +22 -14
  415. package/src/plugins/otp.ts +37 -21
  416. package/src/plugins/passkey-webauthn.ts +9 -6
  417. package/src/plugins/passkey.ts +98 -26
  418. package/src/plugins/pre-account-hijack.test.ts +223 -0
  419. package/src/plugins/two-factor.ts +466 -37
  420. package/src/plugins/username.ts +19 -7
  421. package/src/release/build-lib.ts +1 -0
  422. package/src/release/limits.ts +2 -2
  423. package/src/release/measure.ts +1 -1
  424. package/src/runs/collect.test.ts +0 -2
  425. package/src/runtime/json-code-block.test.ts +250 -20
  426. package/src/runtime/json-code-block.ts +1261 -41
  427. package/src/term.test.ts +1 -1
  428. package/src/test/create-test-app.test.ts +12 -12
  429. package/src/test/live-signals.test.ts +6 -6
  430. package/src/test/provisions.integration.test.ts +10 -14
  431. package/src/test/reset-element-registries.ts +1 -1
  432. package/src/test/tenant-isolation.test.ts +12 -6
  433. package/site/content/docs/deployment/docker-swarm.mdx +0 -164
  434. package/site/content/docs/deployment/docker.mdx +0 -227
  435. package/site/content/docs/deployment/index.mdx +0 -83
  436. package/site/content/docs/deployment/kubernetes.mdx +0 -176
  437. package/site/content/docs/deployment/meta.json +0 -5
  438. package/site/content/docs/deployment/reverse-proxy.mdx +0 -234
  439. package/site/content/docs/elements/ai.mdx +0 -385
  440. package/site/content/docs/elements/channel.mdx +0 -346
  441. package/site/content/docs/elements/clock.mdx +0 -244
  442. package/site/content/docs/elements/flow.mdx +0 -420
  443. package/site/content/docs/elements/gate.mdx +0 -436
  444. package/site/content/docs/elements/signal.mdx +0 -380
  445. package/site/content/docs/elements/store.mdx +0 -1099
  446. package/site/content/docs/elements/vault.mdx +0 -405
  447. package/site/content/docs/get-started/basic-usage.mdx +0 -173
  448. package/site/content/docs/get-started/index.mdx +0 -43
  449. package/site/content/docs/get-started/installation.mdx +0 -220
  450. package/site/content/docs/get-started/introduction.mdx +0 -132
  451. package/site/content/docs/get-started/meta.json +0 -13
  452. package/site/content/docs/get-started/project-structure.mdx +0 -925
  453. package/site/content/docs/get-started/testing.mdx +0 -328
  454. package/site/content/docs/get-started/why.mdx +0 -162
  455. package/site/content/docs/recipes/llama-cpp.mdx +0 -151
  456. package/site/content/docs/recipes/ollama.mdx +0 -142
  457. package/site/content/docs/recipes/sglang.mdx +0 -105
  458. package/site/content/docs/recipes/vllm.mdx +0 -106
  459. package/site/content/docs/reference/cli.md +0 -232
  460. package/site/content/docs/reference/client.mdx +0 -469
  461. package/site/content/docs/reference/security.md +0 -74
  462. package/src/cli/ai-setup/detect-ollama.ts +0 -213
  463. package/src/cli/ai-setup/recommend.test.ts +0 -225
  464. package/src/cli/ai-setup/recommend.ts +0 -225
  465. package/src/cli/dev-controls.test.ts +0 -122
  466. package/src/cli/dev-controls.ts +0 -164
  467. package/src/cli/tui/DevLive.tsx +0 -126
  468. package/src/console/ui-next/dist/assets/access-page-De7Lc2JC.js +0 -4
  469. package/src/console/ui-next/dist/assets/agent-disclosure-BP0Y0Sux.js +0 -1
  470. package/src/console/ui-next/dist/assets/cache-glyph-B5X-NM0-.js +0 -1
  471. package/src/console/ui-next/dist/assets/call-pii-button-ChAUiLo9.js +0 -1
  472. package/src/console/ui-next/dist/assets/collapsible-DGnOM2ph.js +0 -1
  473. package/src/console/ui-next/dist/assets/copy-inline-button-DBHhgHKP.js +0 -1
  474. package/src/console/ui-next/dist/assets/dagre.esm-ZwcdTuZZ.js +0 -1
  475. package/src/console/ui-next/dist/assets/detail-header-DhHM1iaZ.js +0 -1
  476. package/src/console/ui-next/dist/assets/dropdown-menu-_NjTEo5_.js +0 -1
  477. package/src/console/ui-next/dist/assets/duration-tone-sC3lGABz.js +0 -9
  478. package/src/console/ui-next/dist/assets/element-icons-BVXtRyd3.js +0 -1
  479. package/src/console/ui-next/dist/assets/explorer-empty-2uIhBu0_.js +0 -1
  480. package/src/console/ui-next/dist/assets/flows-page-RGy7VEA_.js +0 -1
  481. package/src/console/ui-next/dist/assets/highlighted-json-Awq7gYdu.js +0 -154
  482. package/src/console/ui-next/dist/assets/http-method-_UHM2ODJ.js +0 -1
  483. package/src/console/ui-next/dist/assets/index-Ck88Jmv8.css +0 -2
  484. package/src/console/ui-next/dist/assets/index-_rgpdVzo.js +0 -66
  485. package/src/console/ui-next/dist/assets/link-BX6Vqztd.js +0 -1
  486. package/src/console/ui-next/dist/assets/observability-page-Ds6pcnh-.js +0 -4
  487. package/src/console/ui-next/dist/assets/preload-helper-oH4irX4C.js +0 -1
  488. package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +0 -1
  489. package/src/console/ui-next/dist/assets/react-dom-Bph1y7z7.js +0 -9
  490. package/src/console/ui-next/dist/assets/replica-lag-DKRrbvdo.js +0 -18
  491. package/src/console/ui-next/dist/assets/request-meta-DV0ywz7t.js +0 -1
  492. package/src/console/ui-next/dist/assets/shortcut-JQIZlWfm.js +0 -1
  493. package/src/console/ui-next/dist/assets/shortcut-keys-DKxNTe_m.js +0 -1
  494. package/src/console/ui-next/dist/assets/skeleton-D-czQJT6.js +0 -1
  495. package/src/console/ui-next/dist/assets/store-page-02xOiqIK.js +0 -41
  496. package/src/console/ui-next/dist/assets/trace-detail-sheet-B09O8rA6.js +0 -2
  497. package/src/console/ui-next/dist/assets/tree-expand-toggle-BtyhmWb4.js +0 -55
  498. package/src/console/ui-next/dist/assets/units-page-4rHOePuE.js +0 -1
  499. package/src/console/ui-next/dist/assets/useMutation-B8EO02Ej.js +0 -1
  500. package/src/console/ui-next/dist/assets/useRender-BE2A9BWC.js +0 -1
  501. package/src/console/ui-next/dist/assets/vault-page-DISPgxLM.js +0 -2
  502. package/src/console/ui-next/dist/assets/xyflow-D7n4g6go.js +0 -7
  503. package/src/console/ui-next/dist/assets/xyflow-DZ0Ws1xk.css +0 -1
  504. package/src/docker/ollama-pull.ts +0 -232
  505. package/src/docker/recipes/llama-cpp.ts +0 -298
  506. package/src/docker/recipes/ollama.ts +0 -44
  507. package/src/docker/recipes/sglang.ts +0 -55
  508. package/src/docker/recipes/vllm.ts +0 -44
  509. package/src/drivers/ai-ollama-tools.integration.test.ts +0 -109
  510. package/src/drivers/ai-ollama.integration.test.ts +0 -184
  511. package/src/drivers/ai-ollama.ts +0 -389
  512. package/src/drivers/ollama.ts +0 -14
@@ -0,0 +1,640 @@
1
+ ---
2
+ title: "Workflows"
3
+ description: "Journaled multi-step Flows — fx.step replay, LIFO undo, durable sleep, and crash resume."
4
+ icon: "GitFork"
5
+ source: "docs/spec/unified-theory.md"
6
+ ---
7
+
8
+ A durable Flow is an ordinary Flow with `durable: true`. Named `fx.step` calls journal
9
+ their results so a crash resumes without re-running completed work — checkout, onboarding,
10
+ anything that must not double-charge.
11
+
12
+ For developers writing multi-step work on okengine — set `durable: true`, wrap side
13
+ effects in `fx.step`, keep time on `fx.clock`.
14
+
15
+ <Callout title="The one rule">
16
+ Wrap every side effect in a uniquely named `fx.step`. Replay returns the journaled value and never
17
+ re-runs the body. Register `{undo}` on steps that must reverse; sleep only with
18
+ `fx.clock.sleep(label, duration)` on a durable Flow.
19
+ </Callout>
20
+
21
+ <FlowDurable />
22
+
23
+ ## Smallest Example
24
+
25
+ <Steps>
26
+
27
+ <Step>
28
+ ### Define a durable Flow
29
+
30
+ ```typescript title="src/flows/orders/checkout.ts"
31
+ import { on, flow, http } from "okengine";
32
+ import { z } from "zod";
33
+ import { db, charges, orders } from "@/schema";
34
+
35
+ export const checkout = on(
36
+ http.post({
37
+ in: z.object({ userId: z.string(), sku: z.string() }),
38
+ out: z.object({ orderId: z.string() }),
39
+ }),
40
+ flow({
41
+ durable: true,
42
+ do: async ({ userId, sku }, fx) => {
43
+ const charge = await fx.step("charge", async () => {
44
+ const id = fx.id();
45
+ await fx.store(db).insert(charges).values({ id, userId, amount: 50 });
46
+ return { id };
47
+ });
48
+
49
+ const orderId = await fx.step("create-order", async () => {
50
+ const id = fx.id();
51
+ await fx.store(db).insert(orders).values({
52
+ id,
53
+ userId,
54
+ sku,
55
+ chargeId: charge.id,
56
+ });
57
+ return id;
58
+ });
59
+
60
+ return { orderId };
61
+ },
62
+ }),
63
+ );
64
+ ```
65
+
66
+ </Step>
67
+
68
+ <Step>
69
+ ### Call the endpoint
70
+
71
+ ```bash
72
+ curl -X POST http://localhost:6530/orders/checkout \
73
+ -H "content-type: application/json" \
74
+ -d '{"userId":"usr_1","sku":"sku_42"}'
75
+ ```
76
+
77
+ Response:
78
+
79
+ ```json
80
+ {
81
+ "data": { "orderId": "ord_1" },
82
+ "error": null
83
+ }
84
+ ```
85
+
86
+ Kill the process after `charge` persists and before `create-order` finishes.
87
+ Boot again — `charge` replays from the journal; the card is not charged twice.
88
+
89
+ </Step>
90
+
91
+ </Steps>
92
+
93
+ <Callout title="Not a separate species">
94
+ There is no workflow engine API. `durable: true` is a Flow option — HTTP, Signal, Clock, CDC, and
95
+ call-only Flows all journal the same way. See [Consumers](/docs/elements/flow/consumers).
96
+ </Callout>
97
+
98
+ ## Progressive Patterns
99
+
100
+ Explore durable Flows from a two-step journal to undo, sleep, and flow-level compensation:
101
+
102
+ <Tabs items={["Minimal", "Undo", "Sleep", "Compensate"]}>
103
+
104
+ <Tab value="Minimal">
105
+
106
+ Two named steps. `fx.id()` lives **inside** the step so resume reuses the journaled id:
107
+
108
+ ```typescript title="src/flows/billing/charge.ts"
109
+ import { on, flow, http } from "okengine";
110
+ import { z } from "zod";
111
+ import { db, payments } from "@/schema";
112
+
113
+ export const charge = on(
114
+ http.post({
115
+ in: z.object({ userId: z.string(), amount: z.number() }),
116
+ out: z.object({ paymentId: z.string() }),
117
+ }),
118
+ flow({
119
+ durable: true,
120
+ do: async ({ userId, amount }, fx) => {
121
+ const paymentId = await fx.step("create-intent", async () => {
122
+ const id = fx.id();
123
+ await fx.store(db).insert(payments).values({ id, userId, amount });
124
+ return id;
125
+ });
126
+ await fx.step("confirm", async () => {
127
+ await fx.call(capturePayment, { paymentId });
128
+ });
129
+ return { paymentId };
130
+ },
131
+ }),
132
+ );
133
+ ```
134
+
135
+ </Tab>
136
+
137
+ <Tab value="Undo">
138
+
139
+ `{ undo }` receives the journaled return value. On terminal failure, completed
140
+ undos run last-in first-out; the failed step does **not** undo:
141
+
142
+ ```typescript title="src/flows/orders/checkout.ts"
143
+ import { on, flow, http } from "okengine";
144
+ import { z } from "zod";
145
+ import { eq } from "drizzle-orm";
146
+ import { db, charges } from "@/schema";
147
+
148
+ export const checkout = on(
149
+ http.post({ in: z.object({ userId: z.string() }) }),
150
+ flow({
151
+ durable: true,
152
+ do: async ({ userId }, fx) => {
153
+ const charge = await fx.step(
154
+ "charge",
155
+ async () => {
156
+ const id = fx.id();
157
+ await fx.store(db).insert(charges).values({ id, userId, amount: 50 });
158
+ return { id };
159
+ },
160
+ {
161
+ undo: async (res) => {
162
+ await fx.store(db).delete(charges).where(eq(charges.id, res.id));
163
+ },
164
+ },
165
+ );
166
+ await fx.step("fulfill", async () => {
167
+ await fx.call(fulfillOrder, { chargeId: charge.id });
168
+ });
169
+ return { chargeId: charge.id };
170
+ },
171
+ }),
172
+ );
173
+ ```
174
+
175
+ </Tab>
176
+
177
+ <Tab value="Sleep">
178
+
179
+ `fx.clock.sleep(label, duration)` parks the run and releases the worker. HTTP
180
+ returns **`204 No Content`** immediately — the caller is not waiting at wake:
181
+
182
+ ```typescript title="src/flows/trials/reminder.ts"
183
+ import { on, flow, http } from "okengine";
184
+ import { z } from "zod";
185
+ import { trialExpiringEmail } from "@/channels/trial";
186
+
187
+ export const reminder = on(
188
+ http.post({ in: z.object({ email: z.string().email() }) }),
189
+ flow({
190
+ durable: true,
191
+ do: async ({ email }, fx) => {
192
+ await fx.step("mark-trial", async () => {
193
+ await fx.call(startTrial, { email });
194
+ });
195
+ await fx.clock.sleep("expiry-window", "3d");
196
+ await fx.step("notify", async () => {
197
+ await fx.send(trialExpiringEmail, { to: email });
198
+ });
199
+ },
200
+ }),
201
+ );
202
+ ```
203
+
204
+ Without `durable: true`, `sleep` resolves immediately and does not park.
205
+
206
+ </Tab>
207
+
208
+ <Tab value="Compensate">
209
+
210
+ Per-step `{ undo }` runs first (LIFO), then optional `compensate` for
211
+ cross-cutting cleanup. Manual undo work uses distinct `undo:…` step names:
212
+
213
+ ```typescript title="src/flows/orders/checkout.ts"
214
+ import { on, flow, http } from "okengine";
215
+ import { z } from "zod";
216
+
217
+ export const checkout = on(
218
+ http.post({ in: z.object({ userId: z.string(), sku: z.string() }) }),
219
+ flow({
220
+ durable: true,
221
+ do: async ({ userId, sku }, fx) => {
222
+ await fx.step("reserve", () => fx.call(reserveStock, { sku }), {
223
+ undo: () => fx.call(releaseStock, { sku }),
224
+ });
225
+ await fx.step("charge", () => fx.call(chargeCard, { userId }));
226
+ return { ok: true as const };
227
+ },
228
+ compensate: async (ctx, fx) => {
229
+ await fx.step("undo:alert", async () => {
230
+ await fx.send(opsAlert, {
231
+ to: "oncall@example.com",
232
+ data: {
233
+ steps: ctx.completedSteps.join(", "),
234
+ error: String(ctx.error),
235
+ },
236
+ });
237
+ });
238
+ },
239
+ }),
240
+ );
241
+ ```
242
+
243
+ `compensate` does **not** run on success, between `retry` attempts, or on sleep park.
244
+
245
+ </Tab>
246
+
247
+ </Tabs>
248
+
249
+ ## Options Reference
250
+
251
+ | Option / call | Type | Default | Meaning |
252
+ | --------------------------------- | ---------------------- | --------- | --------------------------------------------------------- |
253
+ | `durable` | `boolean` | `false` | Journal `fx.step`, `fx.clock.sleep`, and gated `fx` calls |
254
+ | `compensate` | `(ctx, fx) => unknown` | omitted | After LIFO undos, before the run commits `failed` |
255
+ | `retry` | `FxRetryOptions` | omitted | Whole-`do` retry on the **same** journal session |
256
+ | `fx.step(name, fn, opts?)` | step | — | Named checkpoint; `{ undo }` is optional |
257
+ | `fx.clock.sleep(label, duration)` | park | — | Durable pause; two arguments (label then duration) |
258
+ | `fx.retry(fn, opts?)` | inner retry | see Retry | Put **inside** a step so a completed charge never re-runs |
259
+
260
+ `compensate` context: `{ input, error, completedSteps }` — `completedSteps` are
261
+ forward names only (`undo:…` entries are excluded).
262
+
263
+ **Consequence:** `durable: true` disables automatic read-cache for that Flow.
264
+
265
+ ## Steps
266
+
267
+ <Callout title="Detailed section">
268
+ If you only need a named checkpoint, jump to Replay below. Step names must be unique per run. The
269
+ prefix `undo:` is reserved for compensation — a forward step with that prefix throws `journal:
270
+ step name "…" uses reserved prefix "undo:"`.
271
+ </Callout>
272
+
273
+ Every `fx.step` persists `{ name, value }` before the next line runs. On resume the
274
+ engine matches by name, returns the stored value, and skips the function.
275
+
276
+ <Tabs items={["Replay", "At-least-once", "Inner retry"]}>
277
+
278
+ <Tab value="Replay">
279
+
280
+ Completed steps are skipped. Generate ids and call providers **inside** the step:
281
+
282
+ ```typescript
283
+ const intent = await fx.step("create-intent", async () => {
284
+ const id = fx.id();
285
+ await fx.call(createPaymentIntent, { id, amount: input.total });
286
+ return { id };
287
+ });
288
+ ```
289
+
290
+ `fx.id()` outside a step mints a new id on every resume. Gated `fx` calls
291
+ (`store`, `emit`, `send`, `ask`, `call`, `vault`) are also journaled in call
292
+ order — named steps are the stable checkpoint when the sequence might branch.
293
+
294
+ </Tab>
295
+
296
+ <Tab value="At-least-once">
297
+
298
+ A crash **during** a step (before persist) re-runs that function. Completed
299
+ neighbors never re-run. Make the in-flight body safe to repeat, or persist at
300
+ the provider first and journal only the id (the create-intent pattern).
301
+
302
+ **Consequence:** two replicas will not double-run a **journaled** step; they
303
+ may double-run the step that was in flight when the holder died.
304
+
305
+ </Tab>
306
+
307
+ <Tab value="Inner retry">
308
+
309
+ `fx.retry` inside `fx.step` retries the provider without committing a step
310
+ until success. Flow-level `retry` re-enters `do` on the same journal — completed
311
+ steps still replay:
312
+
313
+ ```typescript
314
+ const charge = await fx.step("charge", () =>
315
+ fx.retry(() => fx.call(stripeCharge, { amount: input.total }), {
316
+ retries: 3,
317
+ delay: "100ms",
318
+ backoff: 2,
319
+ jitter: true,
320
+ }),
321
+ );
322
+ ```
323
+
324
+ Do **not** put `fx.retry` around the whole `do` by hand — use `flow({ retry })`.
325
+
326
+ </Tab>
327
+
328
+ </Tabs>
329
+
330
+ <Accordions>
331
+
332
+ <Accordion title="Step Options">
333
+ Third argument to `fx.step(name, fn, options)`.
334
+
335
+ | Option | Type | Default | Meaning |
336
+ | ------ | -------------------- | ------- | -------------------------------------------------------- |
337
+ | `undo` | `(value) => unknown` | omitted | Runs on terminal failure with the journaled value (LIFO) |
338
+
339
+ `undo` closures are re-bound on resume by re-entering `do` without new forward
340
+ work. Nested `{ undo }` on an `undo:…` step throws
341
+ `journal: undo steps cannot register nested undo`.
342
+
343
+ </Accordion>
344
+
345
+ <Accordion title="Duplicate names">
346
+ A second forward step with the same name throws
347
+ `journal: duplicate step name "charge"`. Pick a new name (`charge-tax`) or
348
+ fold the work into the first step.
349
+
350
+ Sleep matches by **label** from the cursor — use a distinct label per pause.
351
+
352
+ </Accordion>
353
+
354
+ <Accordion title="Manifest steps">
355
+ The compiler records `fx.step("…")` string names on the Flow as `steps`.
356
+ Removing a name is a Manifest contract change (`oke doctor --diff`). Adding
357
+ a name is recorded the same way. Names only — bodies are not in the Manifest.
358
+
359
+ </Accordion>
360
+
361
+ <Accordion title="fx.using is not journaled">
362
+ `fx.using(acquire, release, use)` is same-attempt cleanup. Do not hold a
363
+ connection or file handle across `fx.clock.sleep` — acquire again after wake.
364
+
365
+ </Accordion>
366
+
367
+ </Accordions>
368
+
369
+ ## Compensation
370
+
371
+ <Callout title="Detailed section">
372
+ If you only need per-step refunds, jump to the table below. Compensation runs on throw **and**
373
+ `fx.fail` — after retries are exhausted, never on sleep park.
374
+ </Callout>
375
+
376
+ When a durable run fails terminally, status becomes `compensating`, then `failed`.
377
+
378
+ | Step | State | Action when `fulfill` throws |
379
+ | --------------------------- | ---------------------- | ------------------------------- |
380
+ | 1. `charge` with `{ undo }` | Succeeded | `undo(journaledValue)` — refund |
381
+ | 2. `fulfill` | Failed (not persisted) | No undo for this step |
382
+ | 3. later work | Not started | Never executed |
383
+
384
+ Order: reverse `{ undo }` frames, then `flow.compensate`, then commit `failed`.
385
+ If an undo or `compensate` throws, the journal error is `compensate:{code}`.
386
+
387
+ <Accordions>
388
+
389
+ <Accordion title="compensate context">
390
+
391
+ | Field | Meaning |
392
+ | ---------------- | --------------------------------------- |
393
+ | `input` | Original validated Flow input |
394
+ | `error` | Thrown value or `fx.fail` result |
395
+ | `completedSteps` | Forward step names (no `undo:` entries) |
396
+
397
+ Use `compensate` for alerts and cross-cutting cleanup. Prefer `{ undo }` for
398
+ the reverse of one step. Manual bodies must call `fx.step("undo:…", …)` — never
399
+ reuse a forward name.
400
+
401
+ </Accordion>
402
+
403
+ <Accordion title="Orphan mid-undo">
404
+ A crash during compensation resumes in `compensating`. Already-journaled
405
+ `undo:charge` is skipped; remaining undos continue — forward `do` does not
406
+ re-enter. Failed/completed runs refuse resume (`journal: run is already failed`).
407
+
408
+ </Accordion>
409
+
410
+ <Accordion title="Retry vs undo">
411
+ `flow({ retry })` does **not** undo between attempts. Undos run once, after
412
+ the last extra attempt still throws or `fx.fail`s.
413
+
414
+ </Accordion>
415
+
416
+ </Accordions>
417
+
418
+ ## Durable Sleep
419
+
420
+ <Callout title="Detailed section">
421
+ If you only need a pause, jump to the example below. Signature is `fx.clock.sleep(label,
422
+ duration)` — a single duration string is the **label**, not the wait. Durations: `"200ms"` ·
423
+ `"30s"` · `"2m"` · `"1h"` · `"7d"`.
424
+ </Callout>
425
+
426
+ Sleep writes a wake time, sets status `sleeping`, and **releases the run lease**
427
+ so a parked flow does not hold a 30s lock for days. Any instance may claim the
428
+ row when `wakeAt` is due.
429
+
430
+ ```typescript title="src/flows/onboarding/welcome.ts"
431
+ import { on, flow } from "okengine";
432
+ import { userSignedUp } from "@/signals";
433
+ import { welcomeEmail } from "@/channels/welcome";
434
+
435
+ export const sendWelcome = on(
436
+ userSignedUp,
437
+ flow("onboarding.welcome", {
438
+ durable: true,
439
+ do: async ({ email }, fx) => {
440
+ await fx.step("provision", async () => {
441
+ await fx.call(createWorkspace, { email });
442
+ });
443
+ await fx.clock.sleep("morning-window", "8h");
444
+ await fx.step("notify", async () => {
445
+ await fx.send(welcomeEmail, { to: email });
446
+ });
447
+ },
448
+ }),
449
+ );
450
+ ```
451
+
452
+ HTTP + sleep: the request returns `204` with an empty body. Resume is a
453
+ scheduler job, not a second response to that client.
454
+
455
+ <Accordions>
456
+
457
+ <Accordion title="Duration strings">
458
+ Integer + unit only — no weeks. `"d"` is 86_400_000 ms, not a calendar day.
459
+ Unknown strings parse as `0` (wake immediately). Same grammar as
460
+ `fx.clock.ago` / `fromNow`.
461
+
462
+ </Accordion>
463
+
464
+ <Accordion title="Do not fx.call a sleeper">
465
+ `fx.call` waits for the callee to return. If the callee parks, the caller
466
+ receives `undefined` and continues; the child wakes later as its own run.
467
+ Sleep on the **root** durable Flow, or split with `fx.emit` to a consumer.
468
+
469
+ </Accordion>
470
+
471
+ <Accordion title="Non-durable sleep">
472
+ Without a journal, `fx.clock.sleep` resolves immediately (tests and sync
473
+ Flows). There is no thread sleep and no `setTimeout`.
474
+
475
+ </Accordion>
476
+
477
+ </Accordions>
478
+
479
+ ## Retry
480
+
481
+ Two layers — do not mix them up.
482
+
483
+ | Layer | Where | Journal | Undo |
484
+ | --------------------------- | ----------------- | -------------------------------- | ---------------------------- |
485
+ | `fx.retry` inside `fx.step` | One provider call | Step commits once, after success | No |
486
+ | `flow({ retry })` | Whole `do` | Same session; rewind + replay | After the last attempt fails |
487
+
488
+ | `retry` option | Default | Meaning |
489
+ | -------------- | ---------------------- | --------------------------------------------- |
490
+ | `retries` | `0` | Extra attempts after the first |
491
+ | `delay` | `50` (ms) or `"100ms"` | Initial backoff |
492
+ | `backoff` | `2` | Multiplier after each retry |
493
+ | `jitter` | `true` | Full jitter (thundering-herd) |
494
+ | `when` | thrown errors | Skips abort and sleep park (`JournalSuspend`) |
495
+
496
+ **Consequence:** put provider retries inside the step; use Flow `retry` for
497
+ transient failures **after** a step (network to your own `fx.call`).
498
+
499
+ ## Journal
500
+
501
+ <Callout title="Detailed section">
502
+ If you only need defaults, jump to the table. `drivers.journal` is `postgres` in `dev`/`prod` and
503
+ `memory` in `test`. Pin `file` for a single host without Postgres.
504
+ </Callout>
505
+
506
+ The journal is a driver, not an element. Runs are rows: `running` · `sleeping` ·
507
+ `compensating` · `completed` · `failed`.
508
+
509
+ | Driver | Default env | Best for |
510
+ | ---------- | -------------- | ---------------------------------------------------- |
511
+ | `postgres` | `dev`, `prod` | Shared durable runs across replicas (`DATABASE_URL`) |
512
+ | `memory` | `test` | Process-local; lost on exit |
513
+ | `file` | pin explicitly | One machine — `.oke/journal.json` |
514
+
515
+ Unknown ids throw `oke boot: unknown journal driver "…" (expected memory · file · postgres)`.
516
+ Postgres without a URL throws `oke boot: journal driver "postgres" needs DATABASE_URL`.
517
+
518
+ <Accordions>
519
+
520
+ <Accordion title="Leases">
521
+ Default lease is **30s** (same as Signal claims). A live holder renews on
522
+ every journal write. Sleep and terminal commit **release** the lease.
523
+
524
+ Resume that loses the race throws `journal: run "{id}" is leased by another instance`
525
+ (`JournalLeaseBusy`). The other holder continues; this instance skips.
526
+
527
+ </Accordion>
528
+
529
+ <Accordion title="Orphans & ready">
530
+ Boot resumes `running` / `sleeping` / `compensating` rows with no live lease;
531
+ future sleeps stay scheduled until `wakeAt`. `GET /_/ready` stays
532
+ `503 { ready: false, reason: "orphan_scan" }` until that scan finishes.
533
+
534
+ </Accordion>
535
+
536
+ <Accordion title="Statuses">
537
+
538
+ | Status | Meaning |
539
+ | -------------- | ------------------------------------- |
540
+ | `running` | In-flight attempt; lease held |
541
+ | `sleeping` | Parked; lease released; `wakeAt` set |
542
+ | `compensating` | LIFO undos / `compensate` in progress |
543
+ | `completed` | Terminal success — resume refused |
544
+ | `failed` | Terminal failure — resume refused |
545
+
546
+ </Accordion>
547
+
548
+ </Accordions>
549
+
550
+ ## Troubleshooting
551
+
552
+ <Accordions>
553
+
554
+ <Accordion title='journal: duplicate step name "charge"'>
555
+ Two forward `fx.step("charge", …)` calls in one run. Rename one, or combine the work. Compensation
556
+ uses `undo:charge` automatically — do not declare a second forward step with that name.
557
+ </Accordion>
558
+
559
+ <Accordion title='journal: step name "undo:x" uses reserved prefix "undo:"'>
560
+ `undo:` is for the compensation phase. Forward work needs a plain name (`refund`). Inside
561
+ `compensate`, `fx.step("undo:alert", …)` is the intended form.
562
+ </Accordion>
563
+
564
+ <Accordion title="HTTP 204 with empty body after POST">
565
+ The Flow parked on `fx.clock.sleep` — success, not a missing handler. The original client is done;
566
+ wake continues on a worker. Return a body **before** sleep if the caller must see an id, or emit
567
+ to a Signal consumer for the rest.
568
+ </Accordion>
569
+
570
+ <Accordion title="Sleep returns immediately / work runs twice after wait">
571
+ Missing `durable: true`, or `fx.clock.sleep("8h")` with one argument — `"8h"` is the label,
572
+ duration is missing. Use `fx.clock.sleep("label", "8h")`. Non-durable sleep is a no-op.
573
+ </Accordion>
574
+
575
+ <Accordion title="Card charged twice after a crash">
576
+ The provider call was outside `fx.step`, or the crash was mid-step (at-least-once). Move
577
+ create-intent into a step and make confirm idempotent. `Date.now()` / `fetch` bypass the journal —
578
+ use `fx.clock.now()` and `fx.step`.
579
+ </Accordion>
580
+
581
+ <Accordion title="journal: run is leased by another instance">
582
+ Two instances claimed the same run. This is skip-not-fail: the holder continues. Shared `postgres`
583
+ (or `file` on one host) is required — `memory` does not coordinate across processes.
584
+ </Accordion>
585
+
586
+ <Accordion title="oke boot: journal driver postgres needs DATABASE_URL">
587
+ Dev/prod default is `postgres`. Set `DATABASE_URL`, or pin `drivers.journal.test` / a non-Postgres
588
+ map in `oke.config.ts` for local experiments without SQL.
589
+ </Accordion>
590
+
591
+ <Accordion title="compensate:{code} on the failed run">
592
+ An `{undo}` or `compensate` body threw. Fix the reverse path; the forward error is already
593
+ recorded. Forward `do` will not re-run on that run id.
594
+ </Accordion>
595
+
596
+ <Accordion title="fx.call of a durable sleeper returned undefined">
597
+ The callee parked. Sleep on the root Flow, or emit to a durable consumer instead of calling a
598
+ sleeper inline.
599
+ </Accordion>
600
+
601
+ <Accordion title="GET /_/ready is 503 reason orphan_scan">
602
+ Boot is resuming durable orphans. Wait — do not point a liveness probe at `/_/ready`. Use a
603
+ separate liveness check; readiness may stay 503 until the orphan scan finishes.
604
+ </Accordion>
605
+
606
+ </Accordions>
607
+
608
+ ## Learn more
609
+
610
+ - [Flow](/docs/elements/flow) — `durable`, `retry`, `compensate` on the Flow options table
611
+ - [Consumers](/docs/elements/flow/consumers) — Signal / Clock / CDC as the same species
612
+ - [HTTP](/docs/elements/flow/http) — request envelope; `204` from `undefined`
613
+ - [Clock · Durable Sleep](/docs/elements/clock/sleep) — pause physics
614
+ - [fx](/docs/reference/fx) — `fx.step`, `fx.retry`, `fx.clock.sleep`, `fx.using`
615
+ - [Configuration](/docs/reference/configuration) — `drivers.journal`
616
+
617
+ ## Next
618
+
619
+ <Cards>
620
+ <Card
621
+ title="Durable Sleep"
622
+ description="Process-safe pauses that resume across reboots."
623
+ href="/docs/elements/clock/sleep"
624
+ />
625
+ <Card
626
+ title="Consumers"
627
+ description="Signal workers, named Clock jobs, and SQL CDC — one Flow species."
628
+ href="/docs/elements/flow/consumers"
629
+ />
630
+ <Card
631
+ title="HTTP"
632
+ description="Synchronous REST, QUERY, resources, and live SSE."
633
+ href="/docs/elements/flow/http"
634
+ />
635
+ <Card
636
+ title="Flow Overview"
637
+ description="One shape for every kind of backend behavior."
638
+ href="/docs/elements/flow"
639
+ />
640
+ </Cards>