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,596 @@
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
+ flow({
316
+ in: z.object({ userId: z.string(), amount: z.number() }),
317
+ do: async ({ userId, amount }, fx) => {
318
+ const orderId = fx.id();
319
+ await fx.emit(orderPlaced, { orderId, amount, userId }, { key: userId });
320
+ return { orderId };
321
+ },
322
+ }),
323
+ );
324
+ ```
325
+
326
+ | Call | Records | Meaning |
327
+ | ----------------------------------- | ----------------------- | ------------------------------------- |
328
+ | `fx.emit(signal, payload?)` | `emits` | Competing pool — no ordering |
329
+ | `fx.emit(signal, payload, { key })` | `emits` | Per-key FIFO for that `(signal, key)` |
330
+ | `fx.deadLetters(signal)` | `reads` `signal:<name>` | Inspect `dead` messages |
331
+
332
+ `parentRunId` is stamped automatically from the producer run for Console trace chains — you do
333
+ not set it by hand in app code.
334
+
335
+ ## Lease and reclaim
336
+
337
+ <Callout title="Detailed section">
338
+ If you only need the default 30s lease, jump to Ordering. Claims set `lockedBy` +
339
+ `leaseExpiresAt`. There is no background sweeper — reclaim happens lazily on the next claim after
340
+ expiry.
341
+ </Callout>
342
+
343
+ | Concept | Default | Meaning |
344
+ | ---------------- | ---------------------------------------------- | -------------------------------------------- |
345
+ | Visibility lease | `30_000` ms | How long an inflight claim holds the message |
346
+ | Reclaim | next claim after expiry | Another worker may take the same message |
347
+ | Status path | `pending` → `inflight` → `delivered` \| `dead` | Operator inspect / Console |
348
+
349
+ **Consequence:** treat every `once` handler as at-least-once. Prefer idempotent `do` bodies
350
+ (or durable steps for side effects that must not double-fire).
351
+
352
+ <Accordions>
353
+
354
+ <Accordion title="Claim">
355
+ Eligible messages are `pending` unlocked, or `inflight` whose `leaseExpiresAt` has passed. Claim
356
+ sets status to `inflight`, stamps `lockedBy` + `leaseExpiresAt`, and increments `attempts` before
357
+ the handler runs — so a crash mid-handler leaves a reclaimable row.
358
+ </Accordion>
359
+
360
+ <Accordion title="Reclaim">
361
+ There is no timeout daemon. After the lease expires, the next drain/claim may hand the same
362
+ message to another (or the same) worker. Slow handlers that outlive the lease can overlap with a
363
+ reclaim — keep side effects short or journal them.
364
+ </Accordion>
365
+
366
+ <Accordion title="Status path">
367
+ Successful `do` → `delivered`. Exhausted retries with `deadLetter: true` → `dead`. With
368
+ `deadLetter: false` → `delivered` and nothing in the DLQ.
369
+ </Accordion>
370
+
371
+ <Accordion title="Lease is not a declare option">
372
+ The 30s default lives on the signal bus open options. App authors do not pass `leaseMs` on
373
+ `signal.once(…)`. Tests and custom runtimes may override it when opening the bus.
374
+ </Accordion>
375
+
376
+ </Accordions>
377
+
378
+ ## Ordering
379
+
380
+ <Callout title="Detailed section">
381
+ Partition with `{key}` only when you need per-tenant / per-user FIFO. Unkeyed messages stay a
382
+ competing pool and may run concurrently.
383
+ </Callout>
384
+
385
+ ```typescript title="src/flows/orders/ship.ts"
386
+ import { on, flow } from "okengine";
387
+ import { orderPlaced } from "@/signals/orders";
388
+
389
+ export const shipOrder = on(
390
+ orderPlaced,
391
+ flow("orders.ship", {
392
+ do: async ({ orderId }, fx) => {
393
+ await fx.call(fulfillOrder, { orderId });
394
+ },
395
+ }),
396
+ );
397
+ ```
398
+
399
+ ```typescript
400
+ await fx.emit(orderPlaced, { orderId: "ord_1", userId: "usr_1" }, { key: "usr_1" });
401
+ ```
402
+
403
+ | Emit | Concurrency | Order |
404
+ | -------------- | ----------------------- | ----------------- |
405
+ | No `key` | Competing — may overlap | None |
406
+ | Same `key` | Serialized by lease | FIFO for that key |
407
+ | Different keys | May overlap | Independent |
408
+
409
+ <Accordions>
410
+
411
+ <Accordion title="Lease is the lock">
412
+ No two messages sharing `(signal, key)` are claimed while one holds an unexpired lease. When the
413
+ first completes (or its lease expires and is reclaimed), the next same-key message becomes
414
+ eligible — emission order is preserved for that key.
415
+ </Accordion>
416
+
417
+ <Accordion title="Unkeyed pool">
418
+ Omit `key` for maximum parallelism across workers. There is no global FIFO across unkeyed messages
419
+ — only competing claim exclusivity per message.
420
+ </Accordion>
421
+
422
+ </Accordions>
423
+
424
+ ## Retries and dead letters
425
+
426
+ <Callout title="Detailed section">
427
+ If you only need defaults (`retries: 3`, `deadLetter: true`), jump to Idempotency. Retries requeue
428
+ immediately — there is no delay backoff between attempts.
429
+ </Callout>
430
+
431
+ <Accordions>
432
+
433
+ <Accordion title="Attempt budget">
434
+ Handler invocations = `retries + 1`. On each failure the bus records a typed
435
+ `{ code, message, at, attempt }` reason. When `attempts` exceeds `retries` and
436
+ `deadLetter: true`, status becomes `dead`.
437
+ </Accordion>
438
+
439
+ <Accordion title="Failure reasons">
440
+ Each failed attempt appends a `SignalFailureReason`:
441
+
442
+ | Field | Meaning |
443
+ | --------- | ---------------------------------- |
444
+ | `code` | Machine-readable failure code |
445
+ | `message` | Human-readable detail |
446
+ | `at` | Epoch-ms when the attempt failed |
447
+ | `attempt` | 1-based attempt number that failed |
448
+
449
+ The full history survives on the dead-letter entry for operator inspect.
450
+
451
+ </Accordion>
452
+
453
+ <Accordion title="deadLetter: false">
454
+ Exhausted messages are marked `delivered` instead of entering the DLQ. Use when dropping is
455
+ acceptable and you do not want operator replay.
456
+ </Accordion>
457
+
458
+ <Accordion title="fx.deadLetters">
459
+ Requires a bound signal runtime and `effects.reads` including `signal:<name>`.
460
+ Cross-signal reads throw **OKE1001**. Without a runtime:
461
+ `fx.deadLetters requires a bound signal runtime`.
462
+
463
+ Returned entries include `payload`, `attempts`, `failures`, `key`, `createdAt`, and
464
+ `status: "dead"`.
465
+
466
+ </Accordion>
467
+
468
+ <Accordion title="Schema at emit">
469
+ Invalid payloads fail at `fx.emit` with **OKE1250** before any worker runs. The Signal's
470
+ `schema` is an **emit** contract (like Channel `schema` at `fx.send`) — distinct from HTTP
471
+ invoke contracts on `http.*` / `call` / `mcp.tool`.
472
+
473
+ Cause: `"{resource}": {detail}`.
474
+
475
+ </Accordion>
476
+
477
+ <Accordion title="Orphan emit">
478
+ Zero subscribers + `optional: false` → **OKE1240**.
479
+ Cause: `Flow "{flow}" emits signal "{resource}" with no subscriber.`
480
+ Fix: add `on(signal, …)` or mark `{ optional: true }`.
481
+ </Accordion>
482
+
483
+ </Accordions>
484
+
485
+ ## Idempotency
486
+
487
+ Lease reclaim and retries mean a handler can run more than once for the same message.
488
+
489
+ | Approach | When |
490
+ | --------------------------- | -------------------------------------------------------- |
491
+ | Idempotent `do` | Side effects are safe to repeat (upsert, set-once flags) |
492
+ | `durable: true` + `fx.step` | Multi-step work that must not double-fire |
493
+ | Short handlers | Finish before the 30s lease so reclaim does not overlap |
494
+
495
+ ```typescript title="src/flows/payments/sync.ts"
496
+ import { on, flow } from "okengine";
497
+ import { syncPayment } from "@/signals/payments";
498
+
499
+ export const runSync = on(
500
+ syncPayment,
501
+ flow("payments.sync", {
502
+ durable: true,
503
+ do: async ({ chargeId }, fx) => {
504
+ await fx.step("charge", async () => {
505
+ await fx.call(applyCharge, { chargeId });
506
+ });
507
+ await fx.step("receipt", async () => {
508
+ await fx.call(sendReceipt, { chargeId });
509
+ });
510
+ },
511
+ }),
512
+ );
513
+ ```
514
+
515
+ See [Durable Workflows](/docs/elements/flow/workflows).
516
+
517
+ ## Troubleshooting
518
+
519
+ <Accordions>
520
+
521
+ <Accordion title="Worker never runs after emit">
522
+ Confirm `on(signalHandle, flow)` uses the same declared handle (or the same name) and is adopted
523
+ into the app. Check **OKE1240** if the emit itself threw. Live Signals are not workers — use Once
524
+ or Broadcast.
525
+ </Accordion>
526
+
527
+ <Accordion title="Same message processed twice">
528
+ Lease reclaim after a crash or slow handler is expected at-least-once physics. Make `do`
529
+ idempotent, or journal side effects with `durable: true` + `fx.step`.
530
+ </Accordion>
531
+
532
+ <Accordion title="Both of two Flows ran on one once message">
533
+ That is broadcast physics, not once. Confirm both files import the same `signal.once` handle —
534
+ competing workers share one claim. If you need fan-out, switch the declaration to
535
+ `signal.broadcast`.
536
+ </Accordion>
537
+
538
+ <Accordion title="Messages stuck inflight">
539
+ Wait for the 30s lease and the next drain/claim. There is no separate timeout daemon. A handler
540
+ still running past the lease can overlap with a reclaim — shorten the work or journal it.
541
+ </Accordion>
542
+
543
+ <Accordion title="OKE1250 on emit">
544
+ Cause: `"{resource}": {detail}`. Align the payload with `schema` — the worker never started.
545
+ </Accordion>
546
+
547
+ <Accordion title="OKE1240 on emit">
548
+ Cause: `Flow "{flow}" emits signal "{resource}" with no subscriber.`
549
+ Add a subscriber or set `{ optional: true }` on the Signal.
550
+ </Accordion>
551
+
552
+ <Accordion title="OKE1001 on fx.deadLetters">
553
+ Cause: `Flow "…" reads "signal:…" without declaring it.`
554
+ Add `effects.reads: ["signal:<name>"]` (or the matching `signalReadRef`) on that Flow.
555
+ </Accordion>
556
+
557
+ <Accordion title="deadLetter as a string name">
558
+ `deadLetter` is `boolean` (default `true`). There is no separate named DLQ signal string — inspect
559
+ with `fx.deadLetters(signal)`.
560
+ </Accordion>
561
+
562
+ <Accordion title="TypeError: retention is only valid with signal.live">
563
+ Drop `retention` on `signal.once`, or switch the helper to `signal.live`.
564
+ </Accordion>
565
+
566
+ </Accordions>
567
+
568
+ ## Learn more
569
+
570
+ - [Signal Overview](/docs/elements/signal) — delivery matrix and drivers
571
+ - [Broadcast](/docs/elements/signal/broadcast) — fan-out without leases
572
+ - [Live](/docs/elements/signal/live) — retained SSE tapes
573
+ - [Consumers](/docs/elements/flow/consumers) — `on(signal)` next to Clock / CDC
574
+ - [Workflows](/docs/elements/flow/workflows) — `durable` + `fx.step` for idempotent side effects
575
+ - [fx](/docs/reference/fx) — `fx.emit`, `fx.deadLetters`
576
+ - [Errors](/docs/reference/errors) — OKE1240 · OKE1250 · OKE1001
577
+
578
+ ## Next
579
+
580
+ <Cards>
581
+ <Card
582
+ title="Broadcast"
583
+ description="Ephemeral fan-out across subscribed Flows."
584
+ href="/docs/elements/signal/broadcast"
585
+ />
586
+ <Card
587
+ title="Live"
588
+ description="Retained SSE tapes for browsers."
589
+ href="/docs/elements/signal/live"
590
+ />
591
+ <Card
592
+ title="Consumers"
593
+ description="Signal workers, Clock jobs, and SQL CDC."
594
+ href="/docs/elements/flow/consumers"
595
+ />
596
+ </Cards>