okengine 0.18.5 → 0.19.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 (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/llms-txt.mdx +1 -1
  5. package/site/content/docs/ai/skills.mdx +4 -1
  6. package/site/content/docs/client/auth.mdx +208 -0
  7. package/site/content/docs/client/calling.mdx +451 -0
  8. package/site/content/docs/client/index.mdx +208 -0
  9. package/site/content/docs/client/live.mdx +234 -0
  10. package/site/content/docs/client/meta.json +5 -0
  11. package/site/content/docs/client/react.mdx +249 -0
  12. package/site/content/docs/elements/ai/agents.mdx +235 -0
  13. package/site/content/docs/elements/ai/index.mdx +342 -0
  14. package/site/content/docs/elements/ai/mcp.mdx +275 -0
  15. package/site/content/docs/elements/ai/meta.json +5 -0
  16. package/site/content/docs/elements/ai/models.mdx +283 -0
  17. package/site/content/docs/elements/ai/prompts.mdx +257 -0
  18. package/site/content/docs/elements/channel/email.mdx +269 -0
  19. package/site/content/docs/elements/channel/index.mdx +344 -0
  20. package/site/content/docs/elements/channel/meta.json +5 -0
  21. package/site/content/docs/elements/channel/push.mdx +218 -0
  22. package/site/content/docs/elements/channel/receipts.mdx +206 -0
  23. package/site/content/docs/elements/channel/sms.mdx +264 -0
  24. package/site/content/docs/elements/channel/whatsapp.mdx +220 -0
  25. package/site/content/docs/elements/clock/index.mdx +301 -0
  26. package/site/content/docs/elements/clock/meta.json +5 -0
  27. package/site/content/docs/elements/clock/schedules.mdx +508 -0
  28. package/site/content/docs/elements/clock/sleep.mdx +448 -0
  29. package/site/content/docs/elements/flow/consumers.mdx +707 -0
  30. package/site/content/docs/elements/flow/http.mdx +1080 -0
  31. package/site/content/docs/elements/flow/index.mdx +618 -0
  32. package/site/content/docs/elements/flow/meta.json +5 -0
  33. package/site/content/docs/elements/flow/routing.mdx +559 -0
  34. package/site/content/docs/elements/flow/workflows.mdx +640 -0
  35. package/site/content/docs/elements/gate/auth.mdx +371 -0
  36. package/site/content/docs/elements/gate/authorization.mdx +298 -0
  37. package/site/content/docs/elements/gate/index.mdx +418 -0
  38. package/site/content/docs/elements/gate/meta.json +5 -0
  39. package/site/content/docs/elements/gate/rate-limits.mdx +365 -0
  40. package/site/content/docs/elements/gate/rls.mdx +303 -0
  41. package/site/content/docs/elements/gate/tenancy.mdx +375 -0
  42. package/site/content/docs/elements/index.mdx +20 -10
  43. package/site/content/docs/elements/signal/broadcast.mdx +519 -0
  44. package/site/content/docs/elements/signal/index.mdx +352 -0
  45. package/site/content/docs/elements/signal/live.mdx +591 -0
  46. package/site/content/docs/elements/signal/meta.json +5 -0
  47. package/site/content/docs/elements/signal/once.mdx +597 -0
  48. package/site/content/docs/elements/store/files.mdx +667 -0
  49. package/site/content/docs/elements/store/index.mdx +313 -0
  50. package/site/content/docs/elements/store/kv.mdx +637 -0
  51. package/site/content/docs/elements/store/meta.json +5 -0
  52. package/site/content/docs/elements/store/search.mdx +950 -0
  53. package/site/content/docs/elements/store/sql.mdx +941 -0
  54. package/site/content/docs/elements/vault/config.mdx +259 -0
  55. package/site/content/docs/elements/vault/index.mdx +356 -0
  56. package/site/content/docs/elements/vault/meta.json +5 -0
  57. package/site/content/docs/elements/vault/rotation.mdx +276 -0
  58. package/site/content/docs/elements/vault/secrets.mdx +288 -0
  59. package/site/content/docs/index.mdx +52 -40
  60. package/site/content/docs/meta.json +8 -7
  61. package/site/content/docs/plugins/cors.mdx +2 -1
  62. package/site/content/docs/plugins/csrf.mdx +4 -1
  63. package/site/content/docs/plugins/headers.mdx +2 -1
  64. package/site/content/docs/plugins/ip-allowlist.mdx +2 -1
  65. package/site/content/docs/plugins/magic-link.mdx +6 -0
  66. package/site/content/docs/plugins/maintenance-mode.mdx +2 -1
  67. package/site/content/docs/plugins/oauth.mdx +1 -1
  68. package/site/content/docs/plugins/otp.mdx +12 -1
  69. package/site/content/docs/plugins/passkey.mdx +39 -15
  70. package/site/content/docs/plugins/two-factor.mdx +91 -34
  71. package/site/content/docs/plugins/username.mdx +1 -1
  72. package/site/content/docs/recipes/caddy.mdx +3 -3
  73. package/site/content/docs/recipes/index.mdx +18 -28
  74. package/site/content/docs/recipes/mailpit.mdx +1 -1
  75. package/site/content/docs/recipes/meilisearch.mdx +3 -3
  76. package/site/content/docs/recipes/meta.json +1 -4
  77. package/site/content/docs/recipes/nginx.mdx +2 -2
  78. package/site/content/docs/recipes/openrouter.mdx +283 -0
  79. package/site/content/docs/recipes/pgdog.mdx +3 -3
  80. package/site/content/docs/recipes/postgres.mdx +1 -2
  81. package/site/content/docs/recipes/rustfs.mdx +2 -2
  82. package/site/content/docs/recipes/traefik.mdx +3 -3
  83. package/site/content/docs/reference/cli.mdx +231 -0
  84. package/site/content/docs/reference/configuration.mdx +30 -10
  85. package/site/content/docs/reference/environment-variables.mdx +93 -20
  86. package/site/content/docs/reference/errors.mdx +132 -29
  87. package/site/content/docs/reference/fx.mdx +138 -73
  88. package/site/content/docs/reference/index.mdx +13 -21
  89. package/site/content/docs/reference/meta.json +4 -5
  90. package/site/content/docs/reference/okid.mdx +42 -17
  91. package/site/content/docs/reference/plugins.mdx +4 -3
  92. package/site/content/docs/reference/security.mdx +197 -0
  93. package/site/content/docs/understand/meta.json +5 -0
  94. package/site/content/docs/understand/the-architecture.mdx +264 -0
  95. package/site/content/docs/understand/try-it.mdx +58 -0
  96. package/src/auth/api-keys.ts +2 -1
  97. package/src/auth/bindings.ts +64 -16
  98. package/src/auth/gate-auth.test.ts +6 -1
  99. package/src/auth/identity.ts +152 -5
  100. package/src/auth/index.ts +29 -0
  101. package/src/auth/invites.ts +2 -1
  102. package/src/auth/method-context.ts +41 -0
  103. package/src/auth/oauth-as/crypto.ts +2 -1
  104. package/src/auth/oauth-as/stores.ts +2 -1
  105. package/src/auth/operator.ts +2 -1
  106. package/src/auth/sessions-jwt.test.ts +77 -0
  107. package/src/auth/sessions.ts +20 -4
  108. package/src/auth/tenants.ts +3 -2
  109. package/src/auth/two-factor-challenge.test.ts +61 -0
  110. package/src/auth/two-factor-challenge.ts +284 -0
  111. package/src/auth/verification.ts +5 -0
  112. package/src/bench/REPORT.md +80 -0
  113. package/src/bench/g03-signal-once.bench.ts +1 -1
  114. package/src/bench/g10-observability-contention.bench.ts +1 -1
  115. package/src/bench/g17-hybrid-search.bench.ts +572 -0
  116. package/src/bench/load-app.ts +2 -10
  117. package/src/cli/ai-setup/ai-setup.test.ts +331 -42
  118. package/src/cli/ai-setup/apply.ts +294 -52
  119. package/src/cli/ai-setup/catalog.ts +238 -1342
  120. package/src/cli/ai-setup/index.ts +14 -99
  121. package/src/cli/ai-setup/prompts.ts +60 -443
  122. package/src/cli/ask-seed.test.ts +96 -0
  123. package/src/cli/ask-seed.ts +82 -0
  124. package/src/cli/ask-vault-gaps.test.ts +84 -0
  125. package/src/cli/ask-vault-gaps.ts +146 -0
  126. package/src/cli/build.ts +2 -2
  127. package/src/cli/client-add.test.ts +26 -1
  128. package/src/cli/client-add.ts +106 -27
  129. package/src/cli/db-seed.ts +2 -0
  130. package/src/cli/db.ts +150 -11
  131. package/src/cli/dev.ts +119 -211
  132. package/src/cli/docker-clean.ts +2 -2
  133. package/src/cli/docker-cli.test.ts +1 -1
  134. package/src/cli/doctor-diff.ts +4 -2
  135. package/src/cli/doctor-pii.test.ts +1 -1
  136. package/src/cli/doctor.ts +14 -1
  137. package/src/cli/hero-meta.test.ts +4 -2
  138. package/src/cli/load-config.images.test.ts +8 -10
  139. package/src/cli/load-config.ts +1 -1
  140. package/src/cli/registry.ts +22 -10
  141. package/src/cli/replay.ts +3 -1
  142. package/src/cli/start.ts +1 -1
  143. package/src/cli/tui/keys.ts +2 -3
  144. package/src/client/auth/cookies.ts +74 -0
  145. package/src/client/auth/create-auth-client.ts +636 -0
  146. package/src/client/auth/denials.ts +99 -0
  147. package/src/client/auth/session.ts +283 -0
  148. package/src/client/auth.test.ts +251 -0
  149. package/src/client/auth.ts +39 -114
  150. package/src/client/create-with-session.ts +245 -0
  151. package/src/client/create.ts +51 -5
  152. package/src/client/index.ts +11 -1
  153. package/src/client/live.ts +11 -79
  154. package/src/client/notes-contract.test.ts +5 -10
  155. package/src/client/sse.ts +137 -0
  156. package/src/client/stream.ts +147 -0
  157. package/src/client/transport.test.ts +32 -0
  158. package/src/client/transport.ts +123 -26
  159. package/src/client/types.ts +118 -14
  160. package/src/client-react/index.ts +185 -24
  161. package/src/client-react/use-live-query.ts +13 -3
  162. package/src/compiler/aot.test.ts +7 -4
  163. package/src/compiler/effects-embed.test.ts +56 -0
  164. package/src/compiler/effects-fetch.test.ts +43 -0
  165. package/src/compiler/effects-infer.ts +58 -2
  166. package/src/compiler/extract.test.ts +257 -43
  167. package/src/compiler/extract.ts +566 -48
  168. package/src/compiler/fixtures/skyport/src/flows/bookings/index.ts +7 -4
  169. package/src/compiler/fixtures/skyport/src/flows/bookings/signals.ts +2 -8
  170. package/src/compiler/fixtures/skyport.expected.json +4 -4
  171. package/src/compiler/fixtures/triggers/five-triggers.ts +14 -10
  172. package/src/compiler/response.ts +2 -2
  173. package/src/compiler/search-writer-isolation.test.ts +40 -0
  174. package/src/config/index.ts +11 -0
  175. package/src/console/server/app.ts +17 -23
  176. package/src/console/server/bind.ts +4 -0
  177. package/src/console/server/console.test.ts +2 -2
  178. package/src/console/server/flows-invoke.test.ts +50 -34
  179. package/src/console/server/flows.ts +436 -255
  180. package/src/console/server/runs-ingest.test.ts +6 -3
  181. package/src/console/server/serve.ts +1 -1
  182. package/src/console/server/signals.test.ts +1 -5
  183. package/src/console/server/signals.ts +8 -6
  184. package/src/console/server/state.ts +6 -1
  185. package/src/console/server/store.ts +1 -1
  186. package/src/console/ui-next/dist/assets/FileExportIcon-Ck-5od4R.js +1 -0
  187. package/src/console/ui-next/dist/assets/MoreHorizontalCircle01Icon-gMNGsE37.js +1 -0
  188. package/src/console/ui-next/dist/assets/PlusSignIcon-CwG3nxfu.js +1 -0
  189. package/src/console/ui-next/dist/assets/UnavailableIcon-D9cvHVPr.js +1 -0
  190. package/src/console/ui-next/dist/assets/UserIcon-DaE2PB5_.js +1 -0
  191. package/src/console/ui-next/dist/assets/access-page-C_qLDhTq.js +4 -0
  192. package/src/console/ui-next/dist/assets/agent-disclosure-BHVqr3TN.js +1 -0
  193. package/src/console/ui-next/dist/assets/cache-glyph-CKe92lRQ.js +1 -0
  194. package/src/console/ui-next/dist/assets/call-pii-button-DEwTl8ZX.js +1 -0
  195. package/src/console/ui-next/dist/assets/collapsible-BCBtDrCt.js +1 -0
  196. package/src/console/ui-next/dist/assets/copy-inline-button-CAYD18cr.js +1 -0
  197. package/src/console/ui-next/dist/assets/dagre.esm-B1_XeuLP.js +1 -0
  198. package/src/console/ui-next/dist/assets/detail-header-DVWNjWjg.js +1 -0
  199. package/src/console/ui-next/dist/assets/dropdown-menu-4h2LOVXM.js +1 -0
  200. package/src/console/ui-next/dist/assets/duration-tone-JroqeuCp.js +9 -0
  201. package/src/console/ui-next/dist/assets/element-icons-BI8cJgdh.js +1 -0
  202. package/src/console/ui-next/dist/assets/explorer-empty-CJs5A-wm.js +1 -0
  203. package/src/console/ui-next/dist/assets/flows-page-CVHa0RTt.js +1 -0
  204. package/src/console/ui-next/dist/assets/highlighted-json-DjJW6hqe.js +154 -0
  205. package/src/console/ui-next/dist/assets/http-method-DC5HBdLU.js +1 -0
  206. package/src/console/ui-next/dist/assets/index-CYjiZ3WO.js +63 -0
  207. package/src/console/ui-next/dist/assets/index-D0zS5rKO.css +2 -0
  208. package/src/console/ui-next/dist/assets/observability-page-CAYMyKb3.js +4 -0
  209. package/src/console/ui-next/dist/assets/react-dom-Ddte4I-Q.js +9 -0
  210. package/src/console/ui-next/dist/assets/replica-lag-yAQYLv75.js +18 -0
  211. package/src/console/ui-next/dist/assets/request-meta-D0yusGxJ.js +1 -0
  212. package/src/console/ui-next/dist/assets/shortcut-keys-3ILd8oGn.js +1 -0
  213. package/src/console/ui-next/dist/assets/{sql-BskegiFM.js → sql-BsFa4tDR.js} +1 -1
  214. package/src/console/ui-next/dist/assets/store-page-BTKJeJ02.js +41 -0
  215. package/src/console/ui-next/dist/assets/trace-detail-sheet-Bp-Yygs5.js +2 -0
  216. package/src/console/ui-next/dist/assets/tree-expand-toggle-DoaVDfAM.js +55 -0
  217. package/src/console/ui-next/dist/assets/units-page-BRz7xyYL.js +1 -0
  218. package/src/console/ui-next/dist/assets/{use-vault-list-uk4WVboC.js → use-vault-list-CT4-gajj.js} +1 -1
  219. package/src/console/ui-next/dist/assets/vault-page-3jQt-bOJ.js +2 -0
  220. package/src/console/ui-next/dist/assets/xyflow-CSyC6ryz.css +1 -0
  221. package/src/console/ui-next/dist/assets/xyflow-yApv7D4e.js +7 -0
  222. package/src/console/ui-next/dist/index.html +6 -13
  223. package/src/console/ui-next/seed-invoke-host.ts +21 -18
  224. package/src/console/ui-next/src/client.ts +7 -2
  225. package/src/console/ui-next/src/features/flows/graph/element-map.test.ts +1 -1
  226. package/src/console/ui-next/src/features/flows/graph/element-map.ts +5 -3
  227. package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +12 -2
  228. package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +4 -0
  229. package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +8 -0
  230. package/src/console/ui-next/src/features/flows/traces/trace-detail.test.ts +4 -5
  231. package/src/console/ui-next/src/features/flows/traces/trace-gates.ts +3 -6
  232. package/src/console/ui-next/src/features/flows/traces/waterfall-bars.ts +4 -0
  233. package/src/console/ui-next/src/features/flows/traces/waterfall-tooltip.ts +9 -3
  234. package/src/console/ui-next/src/features/store/detail/reveal-cell.tsx +7 -11
  235. package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +2 -2
  236. package/src/console/ui-next/src/features/store/lib/fields-from-table.ts +2 -0
  237. package/src/console/ui-next/src/features/store/lib/grid-model.test.ts +25 -1
  238. package/src/console/ui-next/src/features/store/lib/grid-model.ts +36 -1
  239. package/src/console/ui-next/src/features/store/query/query-results.tsx +3 -3
  240. package/src/console/ui-next/src/features/units/detail/effects-summary.tsx +2 -0
  241. package/src/console/ui-next/src/features/units/lib/call-read-safe.ts +3 -1
  242. package/src/console/ui-next/src/features/units/lib/fields-from-schema.ts +6 -0
  243. package/src/docker/ai-model-status.test.ts +1 -12
  244. package/src/docker/ai-model-status.ts +10 -38
  245. package/src/docker/compose-up.test.ts +100 -0
  246. package/src/docker/compose-up.ts +292 -0
  247. package/src/docker/compose.ts +4 -14
  248. package/src/docker/derive.ts +9 -34
  249. package/src/docker/docker.test.ts +16 -261
  250. package/src/docker/images-config.test.ts +13 -25
  251. package/src/docker/index.ts +1 -24
  252. package/src/docker/recipes/index.ts +0 -23
  253. package/src/docker/recipes/pgdog.ts +6 -4
  254. package/src/docker/stack-id.test.ts +2 -2
  255. package/src/docker/stack-id.ts +0 -1
  256. package/src/docker/types.ts +2 -2
  257. package/src/drivers/ai-anthropic.ts +10 -0
  258. package/src/drivers/ai-openai-compatible.ts +34 -7
  259. package/src/drivers/ai-providers.test.ts +0 -107
  260. package/src/drivers/ai-stream.test.ts +0 -34
  261. package/src/drivers/ai-types.ts +17 -8
  262. package/src/drivers/channel-fcm.ts +6 -1
  263. package/src/drivers/channel-msegat.ts +6 -1
  264. package/src/drivers/channel-resend.ts +5 -1
  265. package/src/drivers/channel-smtp.ts +5 -1
  266. package/src/drivers/channel-sndr.ts +10 -1
  267. package/src/drivers/channel-taqnyat-mail.ts +5 -1
  268. package/src/drivers/channel-taqnyat-whatsapp.ts +6 -1
  269. package/src/drivers/channel-taqnyat.ts +6 -1
  270. package/src/drivers/channel-types.ts +10 -0
  271. package/src/drivers/channel-unifonic.ts +6 -1
  272. package/src/drivers/channel-wa-cloud.ts +6 -1
  273. package/src/drivers/channel-webpush.ts +6 -1
  274. package/src/drivers/external.ts +33 -0
  275. package/src/drivers/index.ts +0 -10
  276. package/src/drivers/meilisearch.ts +6 -0
  277. package/src/drivers/oauth-types.ts +4 -0
  278. package/src/drivers/postgres.test.ts +74 -3
  279. package/src/drivers/postgres.ts +175 -6
  280. package/src/drivers/signal-types.ts +5 -5
  281. package/src/drivers/types.ts +7 -0
  282. package/src/drivers/vault-types.ts +5 -0
  283. package/src/elements/ai/declare.ts +17 -4
  284. package/src/elements/ai/eval.ts +1 -1
  285. package/src/elements/ai/mcp-http.ts +1 -1
  286. package/src/elements/ai/pii.ts +1 -1
  287. package/src/elements/ai/providers.test.ts +289 -0
  288. package/src/elements/ai/providers.ts +169 -0
  289. package/src/elements/ai/runtime.ts +68 -5
  290. package/src/elements/ai/schema.ts +2 -2
  291. package/src/elements/ai.test.ts +7 -7
  292. package/src/elements/ai.ts +17 -1
  293. package/src/elements/channel/runtime.ts +105 -12
  294. package/src/elements/clock/cron-fields.test.ts +144 -0
  295. package/src/elements/clock/cron-fields.ts +185 -0
  296. package/src/elements/clock/declare.ts +247 -3
  297. package/src/elements/clock.ts +21 -1
  298. package/src/elements/gate/declare.ts +1 -1
  299. package/src/elements/gate.ts +1 -1
  300. package/src/elements/index.ts +15 -0
  301. package/src/elements/signal/chaos-child.ts +1 -2
  302. package/src/elements/signal/declare.ts +65 -37
  303. package/src/elements/signal/delivery-modes.test.ts +11 -31
  304. package/src/elements/signal/dry-run-replay.test.ts +1 -7
  305. package/src/elements/signal/dry-run-write-isolation.test.ts +1 -7
  306. package/src/elements/signal/key-ordering.test.ts +6 -26
  307. package/src/elements/signal/lease-reclaim.test.ts +2 -6
  308. package/src/elements/signal/optional-emit.test.ts +6 -9
  309. package/src/elements/signal/order-lifecycle.test.ts +4 -17
  310. package/src/elements/signal/orphan-messages.test.ts +4 -15
  311. package/src/elements/signal/reconcile.test.ts +2 -2
  312. package/src/elements/signal/runtime.ts +1 -1
  313. package/src/elements/signal/schema-emit.test.ts +4 -8
  314. package/src/elements/signal.test.ts +24 -49
  315. package/src/elements/signal.ts +9 -2
  316. package/src/elements/store/domain-ddl.test.ts +1 -1
  317. package/src/elements/store/live-http.test.ts +9 -4
  318. package/src/elements/store/prepare-row.test.ts +123 -0
  319. package/src/elements/store/resource.ts +43 -12
  320. package/src/elements/store/schema-decl.ts +117 -0
  321. package/src/elements/store/search-backfill.ts +205 -0
  322. package/src/elements/store/search-bind.ts +66 -0
  323. package/src/elements/store/search-bm25.ts +78 -0
  324. package/src/elements/store/search-ddl.ts +156 -0
  325. package/src/elements/store/search-embed-flow.ts +141 -0
  326. package/src/elements/store/search-errors.ts +34 -0
  327. package/src/elements/store/search-fusion.ts +112 -0
  328. package/src/elements/store/search-lsh.ts +214 -0
  329. package/src/elements/store/search-runtime.pglite.test.ts +186 -0
  330. package/src/elements/store/search-runtime.ts +314 -0
  331. package/src/elements/store/search.test.ts +226 -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 +52 -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 +2 -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 -144
  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 -114
  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-3-EFj-2G.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-cVFnA4HH.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-CMIgUbD0.js +0 -66
  484. package/src/console/ui-next/dist/assets/index-Ck88Jmv8.css +0 -2
  485. package/src/console/ui-next/dist/assets/link-BX6Vqztd.js +0 -1
  486. package/src/console/ui-next/dist/assets/observability-page-CQ3p34ip.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-l8FeKfnP.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-CL-d_mLE.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,597 @@
1
+ ---
2
+ title: "Once"
3
+ description: "Competing workers, visibility leases, retries, dead letters, and per-key ordering for signal.once."
4
+ icon: "ListOrdered"
5
+ source: "docs/spec/unified-theory.md"
6
+ ---
7
+
8
+ `signal.once` processes background work with competing consumers — exactly one worker claims each
9
+ message; failed attempts retry, then dead-letter.
10
+
11
+ For developers shipping jobs on okengine — declare the Signal, bind `on(signal, flow)`, emit with
12
+ `fx.emit`.
13
+
14
+ <Callout title="The one rule">
15
+ Physics live on the Signal (`retries`, `deadLetter`, `optional`). The Flow is the subscriber.
16
+ Visibility lease defaults to **30s** — an inflight worker that dies is reclaimed on the next
17
+ claim.
18
+ </Callout>
19
+
20
+ <SignalOnceLease />
21
+
22
+ ## Smallest Example
23
+
24
+ <Steps>
25
+
26
+ <Step>
27
+ ### Define the once signal
28
+
29
+ ```typescript title="src/signals/email.ts"
30
+ import { signal } from "okengine";
31
+ import { z } from "zod";
32
+
33
+ export const emailTask = signal.once("tasks.email", {
34
+ schema: z.object({ to: z.string().email(), body: z.string() }),
35
+ retries: 3,
36
+ deadLetter: true,
37
+ });
38
+ ```
39
+
40
+ </Step>
41
+
42
+ <Step>
43
+ ### Attach a worker and emit
44
+
45
+ ```typescript title="src/flows/workers/email.ts"
46
+ import { on, flow } from "okengine";
47
+ import { emailTask } from "@/signals/email";
48
+ import { rawEmail } from "@/channels/email";
49
+
50
+ export const processEmail = on(
51
+ emailTask,
52
+ flow("workers.email", {
53
+ do: async ({ to, body }, fx) => {
54
+ await fx.send(rawEmail, { to, body });
55
+ },
56
+ }),
57
+ );
58
+ ```
59
+
60
+ ```typescript
61
+ await fx.emit(emailTask, { to: "alice@example.com", body: "Welcome" });
62
+ ```
63
+
64
+ The emit resolves when the outbox commits. The worker runs asynchronously — the producer does not
65
+ wait for `fx.send` to finish.
66
+
67
+ </Step>
68
+
69
+ </Steps>
70
+
71
+ <Callout title="Competing, not fan-out">
72
+ Two Flows bound to the same `once` Signal compete — exactly one claims each message. For every
73
+ subscriber to run, use [`broadcast`](/docs/elements/signal/broadcast).
74
+ </Callout>
75
+
76
+ ## Progressive Patterns
77
+
78
+ From a minimal job to ordered partitions and DLQ inspection:
79
+
80
+ <Tabs items={["Minimal", "Retries", "Ordered", "Dead letters"]}>
81
+
82
+ <Tab value="Minimal">
83
+
84
+ Defaults are `retries: 3`, `deadLetter: true`, `optional: false`:
85
+
86
+ ```typescript title="src/signals/orders.ts"
87
+ import { signal } from "okengine";
88
+ import { z } from "zod";
89
+
90
+ export const orderPlaced = signal.once("orders.placed", {
91
+ schema: z.object({
92
+ orderId: z.string(),
93
+ amount: z.number(),
94
+ userId: z.string(),
95
+ }),
96
+ });
97
+ ```
98
+
99
+ ```typescript title="src/flows/orders/fulfill.ts"
100
+ import { on, flow } from "okengine";
101
+ import { orderPlaced } from "@/signals/orders";
102
+
103
+ export const fulfill = on(
104
+ orderPlaced,
105
+ flow("orders.fulfill", {
106
+ do: async ({ orderId }, fx) => {
107
+ await fx.call(chargeAndShip, { orderId });
108
+ },
109
+ }),
110
+ );
111
+ ```
112
+
113
+ </Tab>
114
+
115
+ <Tab value="Retries">
116
+
117
+ `retries` is extra attempts after the first — total handler invocations are `retries + 1`.
118
+ Failed attempts requeue immediately for another claim (no delay between attempts).
119
+ Make consumers idempotent; at-least-once delivery can re-run after lease reclaim:
120
+
121
+ ```typescript
122
+ export const syncPayment = signal.once("payments.sync", {
123
+ retries: 5,
124
+ deadLetter: true,
125
+ schema: z.object({ chargeId: z.string() }),
126
+ });
127
+ ```
128
+
129
+ </Tab>
130
+
131
+ <Tab value="Ordered">
132
+
133
+ Pass `{ key }` on emit. No two `once` messages sharing `(signal, key)` are claimed at once —
134
+ the in-flight visibility lease is the lock:
135
+
136
+ ```typescript
137
+ await fx.emit(emailTask, payload, { key: user.id });
138
+ ```
139
+
140
+ Same key → FIFO. Different keys run in parallel. Omit `key` for a pure competing pool.
141
+
142
+ </Tab>
143
+
144
+ <Tab value="Dead letters">
145
+
146
+ After `retries + 1` failures with `deadLetter: true`, the message is `dead`. Inspect with
147
+ `fx.deadLetters` (records `reads` on `signal:<name>`):
148
+
149
+ ```typescript title="src/flows/ops/email-dlq.ts"
150
+ import { on, flow, http } from "okengine";
151
+ import { emailTask } from "@/signals/email";
152
+
153
+ export const list = on(
154
+ http.get(),
155
+ flow({
156
+ effects: { reads: ["signal:tasks.email"] },
157
+ do: async (_, fx) => {
158
+ return await fx.deadLetters(emailTask);
159
+ },
160
+ }),
161
+ );
162
+ ```
163
+
164
+ `deadLetter: false` marks the message delivered after the last attempt — nothing lands in the DLQ.
165
+
166
+ </Tab>
167
+
168
+ </Tabs>
169
+
170
+ ## Delivery Reference
171
+
172
+ | Surface | Signature | Purpose |
173
+ | ----------- | ------------------------------------- | --------------------------------------------------- |
174
+ | Declare | `signal.once(name, options?)` | Competing queue — one claim per message |
175
+ | Bind | `on(signalHandle, flow)` | Worker Flow; payload is `do`'s input |
176
+ | Emit | `fx.emit(signal, payload?, { key? })` | Enrol in the outbox; records `emits` |
177
+ | Inspect DLQ | `fx.deadLetters(signal)` | Exhausted messages; records `reads` `signal:<name>` |
178
+
179
+ | Need | Use instead |
180
+ | -------------------------------------- | -------------------------------------------------- |
181
+ | Every active subscriber gets a copy | [`broadcast`](/docs/elements/signal/broadcast) |
182
+ | Browser / SSE resume after disconnect | [`live`](/docs/elements/signal/live) |
183
+ | Durable multi-step work with a journal | [Durable Workflows](/docs/elements/flow/workflows) |
184
+
185
+ ## Options for once
186
+
187
+ Optional second argument to `signal.once`. Delivery is the helper name — not an option.
188
+
189
+ | Option | Type | Default | Meaning |
190
+ | ------------- | --------------- | -------- | ----------------------------------------------------------------------------------------- |
191
+ | `schema` | Standard Schema | omitted | **Emit** contract — validated at `fx.emit` (**OKE1250**). Workers inherit payload typing. |
192
+ | `retries` | `number` | `3` | Extra attempts after the first (`retries + 1` total) |
193
+ | `deadLetter` | `boolean` | `true` | Keep exhausted messages; not a queue name |
194
+ | `optional` | `boolean` | `false` | Allow emit with zero subscribers |
195
+ | `description` | `string` | the name | Console / docs blurb |
196
+
197
+ `retention` is a type error on `signal.once` — that option is live-only:
198
+
199
+ ```text
200
+ signal.once("…"): retention is only valid with signal.live
201
+ ```
202
+
203
+ **Consequence:** `deadLetter` is a boolean flag, not a DLQ signal name string.
204
+
205
+ ## Binding Workers
206
+
207
+ Each worker binds with `on(signalHandle, flow(...))`. The Signal carries delivery physics; the
208
+ Flow is only the handler.
209
+
210
+ <Tabs items={["Declare", "Bind", "Competing", "Optional"]}>
211
+
212
+ <Tab value="Declare">
213
+
214
+ Name the Signal and attach retry / DLQ / schema policy:
215
+
216
+ ```typescript title="src/signals/email.ts"
217
+ import { signal } from "okengine";
218
+ import { z } from "zod";
219
+
220
+ export const emailTask = signal.once("tasks.email", {
221
+ schema: z.object({ to: z.string().email(), body: z.string() }),
222
+ retries: 3,
223
+ deadLetter: true,
224
+ });
225
+ ```
226
+
227
+ </Tab>
228
+
229
+ <Tab value="Bind">
230
+
231
+ Subscribe with the same handle. Payload fields destructure in `do`:
232
+
233
+ ```typescript title="src/flows/workers/email.ts"
234
+ import { on, flow } from "okengine";
235
+ import { emailTask } from "@/signals/email";
236
+ import { rawEmail } from "@/channels/email";
237
+
238
+ export const processEmail = on(
239
+ emailTask,
240
+ flow("workers.email", {
241
+ do: async ({ to, body }, fx) => {
242
+ await fx.send(rawEmail, { to, body });
243
+ },
244
+ }),
245
+ );
246
+ ```
247
+
248
+ The compiler records `emits: ["tasks.email"]` on producers that call `fx.emit(emailTask, …)`.
249
+
250
+ </Tab>
251
+
252
+ <Tab value="Competing">
253
+
254
+ Two Flows on the same `once` Signal share the pool — exactly one claims each message:
255
+
256
+ ```typescript title="src/flows/orders/fulfill-a.ts"
257
+ import { on, flow } from "okengine";
258
+ import { orderPlaced } from "@/signals/orders";
259
+
260
+ export const fulfillA = on(
261
+ orderPlaced,
262
+ flow("orders.fulfillA", {
263
+ do: async ({ orderId }, fx) => {
264
+ await fx.call(chargeAndShip, { orderId });
265
+ },
266
+ }),
267
+ );
268
+ ```
269
+
270
+ ```typescript title="src/flows/orders/fulfill-b.ts"
271
+ import { on, flow } from "okengine";
272
+ import { orderPlaced } from "@/signals/orders";
273
+
274
+ export const fulfillB = on(
275
+ orderPlaced,
276
+ flow("orders.fulfillB", {
277
+ do: async ({ orderId }, fx) => {
278
+ await fx.call(chargeAndShip, { orderId });
279
+ },
280
+ }),
281
+ );
282
+ ```
283
+
284
+ **Consequence:** this is not fan-out. For every Flow to run, use `signal.broadcast`.
285
+
286
+ </Tab>
287
+
288
+ <Tab value="Optional">
289
+
290
+ Hooks that may have no worker yet need `optional: true` or emit throws **OKE1240**:
291
+
292
+ ```typescript
293
+ export const webhook = signal.once("hooks.inbound", {
294
+ optional: true,
295
+ schema: z.object({ id: z.string() }),
296
+ });
297
+ ```
298
+
299
+ </Tab>
300
+
301
+ </Tabs>
302
+
303
+ ## Emit
304
+
305
+ Before any worker runs, `fx.emit` validates `schema` (when set) and enrols the message in the
306
+ outbox. The call resolves on commit — not when the handler finishes.
307
+
308
+ ```typescript title="src/flows/orders/create.ts"
309
+ import { on, flow, http } from "okengine";
310
+ import { z } from "zod";
311
+ import { orderPlaced } from "@/signals/orders";
312
+
313
+ export const create = on(
314
+ http.post({
315
+ in: z.object({ userId: z.string(), amount: z.number() }),
316
+ }),
317
+ flow({
318
+ do: async ({ userId, amount }, fx) => {
319
+ const orderId = fx.id();
320
+ await fx.emit(orderPlaced, { orderId, amount, userId }, { key: userId });
321
+ return { orderId };
322
+ },
323
+ }),
324
+ );
325
+ ```
326
+
327
+ | Call | Records | Meaning |
328
+ | ----------------------------------- | ----------------------- | ------------------------------------- |
329
+ | `fx.emit(signal, payload?)` | `emits` | Competing pool — no ordering |
330
+ | `fx.emit(signal, payload, { key })` | `emits` | Per-key FIFO for that `(signal, key)` |
331
+ | `fx.deadLetters(signal)` | `reads` `signal:<name>` | Inspect `dead` messages |
332
+
333
+ `parentRunId` is stamped automatically from the producer run for Console trace chains — you do
334
+ not set it by hand in app code.
335
+
336
+ ## Lease and reclaim
337
+
338
+ <Callout title="Detailed section">
339
+ If you only need the default 30s lease, jump to Ordering. Claims set `lockedBy` +
340
+ `leaseExpiresAt`. There is no background sweeper — reclaim happens lazily on the next claim after
341
+ expiry.
342
+ </Callout>
343
+
344
+ | Concept | Default | Meaning |
345
+ | ---------------- | ---------------------------------------------- | -------------------------------------------- |
346
+ | Visibility lease | `30_000` ms | How long an inflight claim holds the message |
347
+ | Reclaim | next claim after expiry | Another worker may take the same message |
348
+ | Status path | `pending` → `inflight` → `delivered` \| `dead` | Operator inspect / Console |
349
+
350
+ **Consequence:** treat every `once` handler as at-least-once. Prefer idempotent `do` bodies
351
+ (or durable steps for side effects that must not double-fire).
352
+
353
+ <Accordions>
354
+
355
+ <Accordion title="Claim">
356
+ Eligible messages are `pending` unlocked, or `inflight` whose `leaseExpiresAt` has passed. Claim
357
+ sets status to `inflight`, stamps `lockedBy` + `leaseExpiresAt`, and increments `attempts` before
358
+ the handler runs — so a crash mid-handler leaves a reclaimable row.
359
+ </Accordion>
360
+
361
+ <Accordion title="Reclaim">
362
+ There is no timeout daemon. After the lease expires, the next drain/claim may hand the same
363
+ message to another (or the same) worker. Slow handlers that outlive the lease can overlap with a
364
+ reclaim — keep side effects short or journal them.
365
+ </Accordion>
366
+
367
+ <Accordion title="Status path">
368
+ Successful `do` → `delivered`. Exhausted retries with `deadLetter: true` → `dead`. With
369
+ `deadLetter: false` → `delivered` and nothing in the DLQ.
370
+ </Accordion>
371
+
372
+ <Accordion title="Lease is not a declare option">
373
+ The 30s default lives on the signal bus open options. App authors do not pass `leaseMs` on
374
+ `signal.once(…)`. Tests and custom runtimes may override it when opening the bus.
375
+ </Accordion>
376
+
377
+ </Accordions>
378
+
379
+ ## Ordering
380
+
381
+ <Callout title="Detailed section">
382
+ Partition with `{key}` only when you need per-tenant / per-user FIFO. Unkeyed messages stay a
383
+ competing pool and may run concurrently.
384
+ </Callout>
385
+
386
+ ```typescript title="src/flows/orders/ship.ts"
387
+ import { on, flow } from "okengine";
388
+ import { orderPlaced } from "@/signals/orders";
389
+
390
+ export const shipOrder = on(
391
+ orderPlaced,
392
+ flow("orders.ship", {
393
+ do: async ({ orderId }, fx) => {
394
+ await fx.call(fulfillOrder, { orderId });
395
+ },
396
+ }),
397
+ );
398
+ ```
399
+
400
+ ```typescript
401
+ await fx.emit(orderPlaced, { orderId: "ord_1", userId: "usr_1" }, { key: "usr_1" });
402
+ ```
403
+
404
+ | Emit | Concurrency | Order |
405
+ | -------------- | ----------------------- | ----------------- |
406
+ | No `key` | Competing — may overlap | None |
407
+ | Same `key` | Serialized by lease | FIFO for that key |
408
+ | Different keys | May overlap | Independent |
409
+
410
+ <Accordions>
411
+
412
+ <Accordion title="Lease is the lock">
413
+ No two messages sharing `(signal, key)` are claimed while one holds an unexpired lease. When the
414
+ first completes (or its lease expires and is reclaimed), the next same-key message becomes
415
+ eligible — emission order is preserved for that key.
416
+ </Accordion>
417
+
418
+ <Accordion title="Unkeyed pool">
419
+ Omit `key` for maximum parallelism across workers. There is no global FIFO across unkeyed messages
420
+ — only competing claim exclusivity per message.
421
+ </Accordion>
422
+
423
+ </Accordions>
424
+
425
+ ## Retries and dead letters
426
+
427
+ <Callout title="Detailed section">
428
+ If you only need defaults (`retries: 3`, `deadLetter: true`), jump to Idempotency. Retries requeue
429
+ immediately — there is no delay backoff between attempts.
430
+ </Callout>
431
+
432
+ <Accordions>
433
+
434
+ <Accordion title="Attempt budget">
435
+ Handler invocations = `retries + 1`. On each failure the bus records a typed
436
+ `{ code, message, at, attempt }` reason. When `attempts` exceeds `retries` and
437
+ `deadLetter: true`, status becomes `dead`.
438
+ </Accordion>
439
+
440
+ <Accordion title="Failure reasons">
441
+ Each failed attempt appends a `SignalFailureReason`:
442
+
443
+ | Field | Meaning |
444
+ | --------- | ---------------------------------- |
445
+ | `code` | Machine-readable failure code |
446
+ | `message` | Human-readable detail |
447
+ | `at` | Epoch-ms when the attempt failed |
448
+ | `attempt` | 1-based attempt number that failed |
449
+
450
+ The full history survives on the dead-letter entry for operator inspect.
451
+
452
+ </Accordion>
453
+
454
+ <Accordion title="deadLetter: false">
455
+ Exhausted messages are marked `delivered` instead of entering the DLQ. Use when dropping is
456
+ acceptable and you do not want operator replay.
457
+ </Accordion>
458
+
459
+ <Accordion title="fx.deadLetters">
460
+ Requires a bound signal runtime and `effects.reads` including `signal:<name>`.
461
+ Cross-signal reads throw **OKE1001**. Without a runtime:
462
+ `fx.deadLetters requires a bound signal runtime`.
463
+
464
+ Returned entries include `payload`, `attempts`, `failures`, `key`, `createdAt`, and
465
+ `status: "dead"`.
466
+
467
+ </Accordion>
468
+
469
+ <Accordion title="Schema at emit">
470
+ Invalid payloads fail at `fx.emit` with **OKE1250** before any worker runs. The Signal's
471
+ `schema` is an **emit** contract (like Channel `schema` at `fx.send`) — distinct from HTTP
472
+ invoke contracts on `http.*` / `call` / `mcp.tool`.
473
+
474
+ Cause: `"{resource}": {detail}`.
475
+
476
+ </Accordion>
477
+
478
+ <Accordion title="Orphan emit">
479
+ Zero subscribers + `optional: false` → **OKE1240**.
480
+ Cause: `Flow "{flow}" emits signal "{resource}" with no subscriber.`
481
+ Fix: add `on(signal, …)` or mark `{ optional: true }`.
482
+ </Accordion>
483
+
484
+ </Accordions>
485
+
486
+ ## Idempotency
487
+
488
+ Lease reclaim and retries mean a handler can run more than once for the same message.
489
+
490
+ | Approach | When |
491
+ | --------------------------- | -------------------------------------------------------- |
492
+ | Idempotent `do` | Side effects are safe to repeat (upsert, set-once flags) |
493
+ | `durable: true` + `fx.step` | Multi-step work that must not double-fire |
494
+ | Short handlers | Finish before the 30s lease so reclaim does not overlap |
495
+
496
+ ```typescript title="src/flows/payments/sync.ts"
497
+ import { on, flow } from "okengine";
498
+ import { syncPayment } from "@/signals/payments";
499
+
500
+ export const runSync = on(
501
+ syncPayment,
502
+ flow("payments.sync", {
503
+ durable: true,
504
+ do: async ({ chargeId }, fx) => {
505
+ await fx.step("charge", async () => {
506
+ await fx.call(applyCharge, { chargeId });
507
+ });
508
+ await fx.step("receipt", async () => {
509
+ await fx.call(sendReceipt, { chargeId });
510
+ });
511
+ },
512
+ }),
513
+ );
514
+ ```
515
+
516
+ See [Durable Workflows](/docs/elements/flow/workflows).
517
+
518
+ ## Troubleshooting
519
+
520
+ <Accordions>
521
+
522
+ <Accordion title="Worker never runs after emit">
523
+ Confirm `on(signalHandle, flow)` uses the same declared handle (or the same name) and is adopted
524
+ into the app. Check **OKE1240** if the emit itself threw. Live Signals are not workers — use Once
525
+ or Broadcast.
526
+ </Accordion>
527
+
528
+ <Accordion title="Same message processed twice">
529
+ Lease reclaim after a crash or slow handler is expected at-least-once physics. Make `do`
530
+ idempotent, or journal side effects with `durable: true` + `fx.step`.
531
+ </Accordion>
532
+
533
+ <Accordion title="Both of two Flows ran on one once message">
534
+ That is broadcast physics, not once. Confirm both files import the same `signal.once` handle —
535
+ competing workers share one claim. If you need fan-out, switch the declaration to
536
+ `signal.broadcast`.
537
+ </Accordion>
538
+
539
+ <Accordion title="Messages stuck inflight">
540
+ Wait for the 30s lease and the next drain/claim. There is no separate timeout daemon. A handler
541
+ still running past the lease can overlap with a reclaim — shorten the work or journal it.
542
+ </Accordion>
543
+
544
+ <Accordion title="OKE1250 on emit">
545
+ Cause: `"{resource}": {detail}`. Align the payload with `schema` — the worker never started.
546
+ </Accordion>
547
+
548
+ <Accordion title="OKE1240 on emit">
549
+ Cause: `Flow "{flow}" emits signal "{resource}" with no subscriber.`
550
+ Add a subscriber or set `{ optional: true }` on the Signal.
551
+ </Accordion>
552
+
553
+ <Accordion title="OKE1001 on fx.deadLetters">
554
+ Cause: `Flow "…" reads "signal:…" without declaring it.`
555
+ Add `effects.reads: ["signal:<name>"]` (or the matching `signalReadRef`) on that Flow.
556
+ </Accordion>
557
+
558
+ <Accordion title="deadLetter as a string name">
559
+ `deadLetter` is `boolean` (default `true`). There is no separate named DLQ signal string — inspect
560
+ with `fx.deadLetters(signal)`.
561
+ </Accordion>
562
+
563
+ <Accordion title="TypeError: retention is only valid with signal.live">
564
+ Drop `retention` on `signal.once`, or switch the helper to `signal.live`.
565
+ </Accordion>
566
+
567
+ </Accordions>
568
+
569
+ ## Learn more
570
+
571
+ - [Signal Overview](/docs/elements/signal) — delivery matrix and drivers
572
+ - [Broadcast](/docs/elements/signal/broadcast) — fan-out without leases
573
+ - [Live](/docs/elements/signal/live) — retained SSE tapes
574
+ - [Consumers](/docs/elements/flow/consumers) — `on(signal)` next to Clock / CDC
575
+ - [Workflows](/docs/elements/flow/workflows) — `durable` + `fx.step` for idempotent side effects
576
+ - [fx](/docs/reference/fx) — `fx.emit`, `fx.deadLetters`
577
+ - [Errors](/docs/reference/errors) — OKE1240 · OKE1250 · OKE1001
578
+
579
+ ## Next
580
+
581
+ <Cards>
582
+ <Card
583
+ title="Broadcast"
584
+ description="Ephemeral fan-out across subscribed Flows."
585
+ href="/docs/elements/signal/broadcast"
586
+ />
587
+ <Card
588
+ title="Live"
589
+ description="Retained SSE tapes for browsers."
590
+ href="/docs/elements/signal/live"
591
+ />
592
+ <Card
593
+ title="Consumers"
594
+ description="Signal workers, Clock jobs, and SQL CDC."
595
+ href="/docs/elements/flow/consumers"
596
+ />
597
+ </Cards>