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,519 @@
1
+ ---
2
+ title: "Broadcast"
3
+ description: "Ephemeral fan-out — every active subscriber gets a copy; offline listeners miss the event."
4
+ icon: "Share2"
5
+ source: "docs/spec/unified-theory.md"
6
+ ---
7
+
8
+ `signal.broadcast` delivers each emission to every subscribed Flow at once. Use it when every
9
+ active listener should react and you do not need a retained history.
10
+
11
+ For developers invalidating caches or syncing in-process state — declare the Signal, bind one or
12
+ more `on(signal, flow)` subscribers, emit with `fx.emit`.
13
+
14
+ <Callout title="The one rule">
15
+ Broadcast is ephemeral. If a subscriber is offline or restarts during the emit, it does not
16
+ receive past events. Use [`live`](/docs/elements/signal/live) when clients need replay. Use
17
+ [`once`](/docs/elements/signal/once) when exactly one worker must claim the job.
18
+ </Callout>
19
+
20
+ <SignalBroadcastFanout />
21
+
22
+ ## Smallest Example
23
+
24
+ <Steps>
25
+
26
+ <Step>
27
+ ### Define the broadcast signal
28
+
29
+ ```typescript title="src/signals/cache.ts"
30
+ import { signal } from "okengine";
31
+ import { z } from "zod";
32
+
33
+ export const cacheInvalidated = signal.broadcast("cache.invalidated", {
34
+ schema: z.object({ key: z.string() }),
35
+ });
36
+ ```
37
+
38
+ </Step>
39
+
40
+ <Step>
41
+ ### Bind a subscriber
42
+
43
+ ```typescript title="src/flows/cache/purge.ts"
44
+ import { on, flow } from "okengine";
45
+ import { cacheInvalidated } from "@/signals/cache";
46
+
47
+ export const purgeLocalCache = on(
48
+ cacheInvalidated,
49
+ flow("cache.purgeLocal", {
50
+ do: async ({ key }, fx) => {
51
+ await fx.store.kv.delete(key);
52
+ },
53
+ }),
54
+ );
55
+ ```
56
+
57
+ </Step>
58
+
59
+ <Step>
60
+ ### Emit from any Flow
61
+
62
+ ```typescript title="src/flows/skus/[sku]/update.ts"
63
+ import { on, flow, http } from "okengine";
64
+ import { z } from "zod";
65
+ import { cacheInvalidated } from "@/signals/cache";
66
+
67
+ export const update = on(
68
+ http.patch({
69
+ in: z.object({ sku: z.string(), title: z.string().min(1) }),
70
+ }),
71
+ flow({
72
+ do: async ({ sku, title }, fx) => {
73
+ // … persist the change …
74
+ await fx.emit(cacheInvalidated, { key: `sku:${sku}` });
75
+ return { sku, title };
76
+ },
77
+ }),
78
+ );
79
+ ```
80
+
81
+ The compiler records `emits: ["cache.invalidated"]` on the producer. Emit resolves when the
82
+ outbox commits — the HTTP request does not wait for every subscriber to finish.
83
+
84
+ </Step>
85
+
86
+ </Steps>
87
+
88
+ <Callout title="Same handle everywhere">
89
+ Import the declared Signal handle (or the same name) in every subscriber and producer. A typo in
90
+ the name creates a different Manifest entry — fan-out never crosses names.
91
+ </Callout>
92
+
93
+ ## Progressive Patterns
94
+
95
+ Explore broadcast from one listener to multi-Flow fan-out, optional hooks, and schema-checked
96
+ payloads:
97
+
98
+ <Tabs items={["Minimal", "Fan-out", "Optional", "Schema"]}>
99
+
100
+ <Tab value="Minimal">
101
+
102
+ One Flow subscribed — still broadcast physics (no competing claim, no once DLQ path):
103
+
104
+ ```typescript title="src/signals/catalog.ts"
105
+ import { signal } from "okengine";
106
+ import { z } from "zod";
107
+
108
+ export const catalogChanged = signal.broadcast("catalog.changed", {
109
+ schema: z.object({ sku: z.string() }),
110
+ });
111
+ ```
112
+
113
+ ```typescript title="src/flows/cache/invalidate.ts"
114
+ import { on, flow } from "okengine";
115
+ import { catalogChanged } from "@/signals/catalog";
116
+
117
+ export const invalidate = on(
118
+ catalogChanged,
119
+ flow("cache.invalidate", {
120
+ do: async ({ sku }, fx) => {
121
+ await fx.store.kv.delete(`sku:${sku}`);
122
+ },
123
+ }),
124
+ );
125
+ ```
126
+
127
+ </Tab>
128
+
129
+ <Tab value="Fan-out">
130
+
131
+ Bind a second Flow to the same Signal — both run. That is the fan-out:
132
+
133
+ ```typescript title="src/flows/search/reindex.ts"
134
+ import { on, flow } from "okengine";
135
+ import { catalogChanged } from "@/signals/catalog";
136
+
137
+ export const reindexSku = on(
138
+ catalogChanged,
139
+ flow("search.reindexSku", {
140
+ do: async ({ sku }, fx) => {
141
+ await fx.call(rebuildSearchDoc, { sku });
142
+ },
143
+ }),
144
+ );
145
+ ```
146
+
147
+ **Consequence:** each subscribed Flow gets its own copy. A failure in one handler does not
148
+ claim-lock the message away from siblings the way `once` leases do.
149
+
150
+ </Tab>
151
+
152
+ <Tab value="Optional">
153
+
154
+ Hooks that may have no subscriber yet need `optional: true` or emit throws **OKE1240**:
155
+
156
+ ```typescript
157
+ export const clusterHint = signal.broadcast("cluster.hint", {
158
+ optional: true,
159
+ schema: z.object({ nodeId: z.string() }),
160
+ });
161
+ ```
162
+
163
+ **OKE1240** cause: `Flow "{flow}" emits signal "{resource}" with no subscriber.`
164
+ Fix: bind at least one `on(signal, flow)` or set `{ optional: true }`.
165
+
166
+ </Tab>
167
+
168
+ <Tab value="Schema">
169
+
170
+ Invalid payloads fail at `fx.emit` with **OKE1250** before any subscriber runs:
171
+
172
+ ```typescript
173
+ export const priceTick = signal.broadcast("prices.tick", {
174
+ schema: z.object({
175
+ sku: z.string(),
176
+ cents: z.number().int().nonnegative(),
177
+ }),
178
+ });
179
+ ```
180
+
181
+ Cause shape: `"{resource}": {detail}`. Align the payload with `schema` — no subscriber started.
182
+
183
+ </Tab>
184
+
185
+ </Tabs>
186
+
187
+ ## Delivery Reference
188
+
189
+ How broadcast sits next to the other helpers when you are choosing physics:
190
+
191
+ | Helper | Pattern | Who runs | Replay | Typical use |
192
+ | ------------------ | ---------- | ---------------------------- | ---------------------- | ------------------------------------- |
193
+ | `signal.once` | Work queue | Exactly one competing worker | Retries + DLQ | Emails, fulfillment, sync |
194
+ | `signal.broadcast` | Pub/sub | Every active subscriber | None (miss if offline) | Cache bust, multi-side-effect fan-out |
195
+ | `signal.live` | SSE tape | HTTP clients via `http.live` | `Last-Event-ID` resume | Status feeds, progress |
196
+
197
+ | Guarantee | Broadcast | Once | Live |
198
+ | ---------------------------------- | ---------------------- | ------------------ | ----------------- |
199
+ | Competing claim / visibility lease | No | Yes (default 30s) | No |
200
+ | Dead-letter queue path | No | Yes (`deadLetter`) | No |
201
+ | Retained history for late joiners | No | No | Yes |
202
+ | Multiple Flows on one emit | Yes — each gets a copy | One claimer | Not a Flow worker |
203
+
204
+ ## Options for broadcast
205
+
206
+ Second argument to `signal.broadcast(name, options?)`. Delivery is the helper name — not an
207
+ option.
208
+
209
+ | Option | Type | Default | Meaning |
210
+ | ------------- | --------------- | -------- | -------------------------------------------------------- |
211
+ | `schema` | Standard Schema | omitted | **Emit** contract — validated at `fx.emit` (**OKE1250**) |
212
+ | `optional` | `boolean` | `false` | Allow emit with zero subscribers |
213
+ | `description` | `string` | the name | Console / docs blurb |
214
+ | `retries` | `number` | `3` | Declared; not the `once` lease / DLQ path |
215
+ | `deadLetter` | `boolean` | `true` | Declared; broadcast does not use once DLQ |
216
+
217
+ `retention` is a type error on `signal.broadcast` — that option is live-only:
218
+
219
+ ```text
220
+ signal.broadcast("…"): retention is only valid with signal.live
221
+ ```
222
+
223
+ ## Fan-out Physics
224
+
225
+ <Callout title="Detailed section">
226
+ If you only need one subscriber, jump to Emit. Fan-out is the distinctive physics: one emit, N
227
+ independent Flow runs, no competing claim.
228
+ </Callout>
229
+
230
+ Bind any number of Flows with `on(theSameSignal, flow(...))`. At dispatch, every matching
231
+ binding runs with the same payload.
232
+
233
+ ```typescript title="src/flows/orders/side-effects.ts"
234
+ import { on, flow } from "okengine";
235
+ import { orderChanged } from "@/signals/orders";
236
+
237
+ export const bustOrderCache = on(
238
+ orderChanged,
239
+ flow("orders.bustCache", {
240
+ do: async ({ orderId }, fx) => {
241
+ await fx.store.kv.delete(`order:${orderId}`);
242
+ },
243
+ }),
244
+ );
245
+
246
+ export const notifyOrderWatchers = on(
247
+ orderChanged,
248
+ flow("orders.notifyWatchers", {
249
+ do: async ({ orderId }, fx) => {
250
+ await fx.call(pushOrderWatchers, { orderId });
251
+ },
252
+ }),
253
+ );
254
+ ```
255
+
256
+ ```typescript
257
+ await fx.emit(orderChanged, { orderId: "ord_42", kind: "placed" });
258
+ ```
259
+
260
+ | Concept | Meaning |
261
+ | ---------- | ---------------------------------------------------------- |
262
+ | Subscriber | An adopted Flow bound with `on(broadcastSignal, flow)` |
263
+ | Copy | Each subscriber receives the payload independently |
264
+ | Isolation | One handler's failure does not lease-lock siblings |
265
+ | Offline | A process that was down at emit time does not get a replay |
266
+
267
+ **Consequence:** treat broadcast handlers as best-effort side effects. If the work must run
268
+ exactly once with retries and a DLQ, declare a separate `signal.once` for that job.
269
+
270
+ ## Emit and Effects
271
+
272
+ <Callout title="Detailed section">
273
+ If you only need `fx.emit(signal, payload)`, jump to the example. Emit commits the outbox when the
274
+ call resolves. Subscribers run asynchronously afterward.
275
+ </Callout>
276
+
277
+ | Call | Records | Use |
278
+ | ----------------------------------- | ------- | ----------------------------------------------------------- |
279
+ | `fx.emit(signal, payload?)` | `emits` | Publish to every active subscriber |
280
+ | `fx.emit(signal, payload, { key })` | `emits` | `key` is for `once` ordering — ignore for broadcast fan-out |
281
+
282
+ ```typescript title="src/flows/catalog/create.ts"
283
+ import { on, flow, http } from "okengine";
284
+ import { z } from "zod";
285
+ import { catalogChanged } from "@/signals/catalog";
286
+
287
+ export const create = on(
288
+ http.post({
289
+ in: z.object({ sku: z.string(), title: z.string().min(1) }),
290
+ }),
291
+ flow({
292
+ do: async ({ sku, title }, fx) => {
293
+ // … write …
294
+ await fx.emit(catalogChanged, { sku });
295
+ return { sku, title };
296
+ },
297
+ }),
298
+ );
299
+ ```
300
+
301
+ The producer WideEvent / run id is stamped as `parentRunId` for Console trace chains. Schema
302
+ mismatches fail at emit (**OKE1250**) before any subscriber runs.
303
+
304
+ ## Subscribers
305
+
306
+ Each verb of fan-out is a normal Flow. Bind with `on(signal, flow(...))` — same species as
307
+ [Consumers](/docs/elements/flow/consumers).
308
+
309
+ <Tabs items={["Cache", "Notify", "Reindex", "Compose"]}>
310
+
311
+ <Tab value="Cache">
312
+
313
+ Drop hot keys when the source of truth changes:
314
+
315
+ ```typescript title="src/flows/cache/on-catalog.ts"
316
+ import { on, flow } from "okengine";
317
+ import { catalogChanged } from "@/signals/catalog";
318
+
319
+ export const onCatalogCache = on(
320
+ catalogChanged,
321
+ flow("cache.onCatalog", {
322
+ do: async ({ sku }, fx) => {
323
+ await fx.store.kv.delete(`sku:${sku}`);
324
+ await fx.store.kv.delete(`sku:${sku}:meta`);
325
+ },
326
+ }),
327
+ );
328
+ ```
329
+
330
+ </Tab>
331
+
332
+ <Tab value="Notify">
333
+
334
+ Fan a domain event into a Channel or another Flow without competing for a lease:
335
+
336
+ ```typescript title="src/flows/notify/on-order.ts"
337
+ import { on, flow } from "okengine";
338
+ import { orderChanged } from "@/signals/orders";
339
+ import { orderWatchers } from "@/channels/orders";
340
+
341
+ export const onOrderNotify = on(
342
+ orderChanged,
343
+ flow("notify.onOrder", {
344
+ do: async ({ orderId }, fx) => {
345
+ await fx.send(orderWatchers, {
346
+ to: "ops@example.com",
347
+ data: { orderId },
348
+ });
349
+ },
350
+ }),
351
+ );
352
+ ```
353
+
354
+ </Tab>
355
+
356
+ <Tab value="Reindex">
357
+
358
+ Kick search / projection work as a side effect of the same emit:
359
+
360
+ ```typescript title="src/flows/search/on-catalog.ts"
361
+ import { on, flow } from "okengine";
362
+ import { catalogChanged } from "@/signals/catalog";
363
+
364
+ export const onCatalogReindex = on(
365
+ catalogChanged,
366
+ flow("search.onCatalog", {
367
+ do: async ({ sku }, fx) => {
368
+ await fx.call(rebuildSearchDoc, { sku });
369
+ },
370
+ }),
371
+ );
372
+ ```
373
+
374
+ </Tab>
375
+
376
+ <Tab value="Compose">
377
+
378
+ Need durable retries for one side effect? Emit a `once` job from the broadcast handler (or from
379
+ the producer) — do not stretch broadcast into a queue:
380
+
381
+ ```typescript title="src/flows/orders/on-changed.ts"
382
+ import { on, flow } from "okengine";
383
+ import { orderChanged, orderSyncJob } from "@/signals/orders";
384
+
385
+ export const onOrderChanged = on(
386
+ orderChanged,
387
+ flow("orders.onChanged", {
388
+ do: async ({ orderId }, fx) => {
389
+ await fx.store.kv.delete(`order:${orderId}`);
390
+ await fx.emit(orderSyncJob, { orderId });
391
+ },
392
+ }),
393
+ );
394
+ ```
395
+
396
+ `orderSyncJob` is `signal.once(...)` — competing workers, retries, and DLQ live there.
397
+
398
+ </Tab>
399
+
400
+ </Tabs>
401
+
402
+ ## Choosing Physics
403
+
404
+ <Callout title="Detailed section">
405
+ Pick physics from the guarantee you need. Broadcast is the wrong tool when work must be claimed
406
+ once, or when a late client must catch up.
407
+ </Callout>
408
+
409
+ | Need | Use instead |
410
+ | ------------------------------------- | ---------------------------------------------------- |
411
+ | Exactly one worker processes the job | [`once`](/docs/elements/signal/once) |
412
+ | Browser / SSE resume after disconnect | [`live`](/docs/elements/signal/live) |
413
+ | Durable multi-step work with journal | [Durable Workflows](/docs/elements/flow/workflows) |
414
+ | HTTP SSE firehose on a path | [`http.live`](/docs/elements/flow/http#live-streams) |
415
+
416
+ Broadcast does **not** use the `once` visibility lease. There is no competing claim and no
417
+ dead-letter queue for exhausted retries in the queue sense — see
418
+ [Once · Retries](/docs/elements/signal/once#retries-and-dead-letters).
419
+
420
+ <Accordions>
421
+
422
+ <Accordion title="Orphan emit (OKE1240)">
423
+ Cause: `Flow "{flow}" emits signal "{resource}" with no subscriber.`
424
+ Fix: bind at least one `on(signal, flow)` or set `{ optional: true }`.
425
+ </Accordion>
426
+
427
+ <Accordion title="Schema (OKE1250)">
428
+ Cause: `"{resource}": {detail}`. Fix the payload or the `schema`. No subscriber ran.
429
+ </Accordion>
430
+
431
+ <Accordion title="Missed while offline">
432
+ Expected. Restarted processes do not replay broadcast history. Emit again after boot if the
433
+ listener must refresh state, or switch to `live` for a retained tape.
434
+ </Accordion>
435
+
436
+ <Accordion title="retries / deadLetter on the declaration">
437
+ Those fields exist on the shared options type (defaults `3` / `true`) so Console and Manifest stay
438
+ uniform. Broadcast delivery does not follow the once lease + DLQ path — do not expect
439
+ `fx.deadLetters(broadcastSignal)` to behave like a queue operator surface.
440
+ </Accordion>
441
+
442
+ <Accordion title="retention on broadcast">
443
+ Throws at declare: `signal.broadcast("…"): retention is only valid with signal.live`. Cap a
444
+ client-visible tape with `signal.live({retention})` instead.
445
+ </Accordion>
446
+
447
+ </Accordions>
448
+
449
+ ## Troubleshooting
450
+
451
+ <Accordions>
452
+
453
+ <Accordion title="Only one of two subscribers runs">
454
+ Confirm both Flows import the same Signal handle / name and are adopted into the app. Names must
455
+ match the Manifest entry exactly. A second `signal.broadcast("catalog.changed")` with a different
456
+ spelling is a different signal.
457
+ </Accordion>
458
+
459
+ <Accordion title="Subscriber missed an event after restart">
460
+ Broadcast has no tape. Re-emit on boot, hydrate from Store, or use
461
+ [`signal.live`](/docs/elements/signal/live).
462
+ </Accordion>
463
+
464
+ <Accordion title="OKE1240 on emit">
465
+ Cause: `Flow "{flow}" emits signal "{resource}" with no subscriber.`
466
+ No subscriber is bound. Add `on(signal, …)` or `{ optional: true }`.
467
+ </Accordion>
468
+
469
+ <Accordion title="OKE1250 on emit">
470
+ Cause: `"{resource}": {detail}`. Align the payload with `schema` — no subscriber started.
471
+ </Accordion>
472
+
473
+ <Accordion title="Expecting retries / DLQ like a queue">
474
+ Use `signal.once`. Broadcast fan-out is not the lease + DLQ path documented under
475
+ [Once](/docs/elements/signal/once).
476
+ </Accordion>
477
+
478
+ <Accordion title="TypeError: retention is only valid with signal.live">
479
+ You passed `retention` to `signal.broadcast`. Drop it, or switch the helper to `signal.live` if
480
+ you need a retained SSE tape.
481
+ </Accordion>
482
+
483
+ <Accordion title="HTTP client never sees the event">
484
+ Broadcast is for Flow subscribers, not browsers. Expose a
485
+ [`signal.live`](/docs/elements/signal/live) tape with
486
+ [`http.live`](/docs/elements/flow/http#live-streams) (or a gated GET `.live(signal)`).
487
+ </Accordion>
488
+
489
+ </Accordions>
490
+
491
+ ## Learn more
492
+
493
+ - [Signal Overview](/docs/elements/signal) — delivery matrix and drivers
494
+ - [Once](/docs/elements/signal/once) — competing workers and DLQ
495
+ - [Live](/docs/elements/signal/live) — retained SSE
496
+ - [Consumers](/docs/elements/flow/consumers) — binding `on(signal)`
497
+ - [HTTP · Live Streams](/docs/elements/flow/http#live-streams) — when the consumer is a browser
498
+ - [fx](/docs/reference/fx) — `fx.emit`
499
+ - [Errors](/docs/reference/errors) — OKE1240 · OKE1250
500
+
501
+ ## Next
502
+
503
+ <Cards>
504
+ <Card
505
+ title="Live"
506
+ description="Retained event tapes over HTTP SSE."
507
+ href="/docs/elements/signal/live"
508
+ />
509
+ <Card
510
+ title="Once"
511
+ description="Competing workers with leases and dead letters."
512
+ href="/docs/elements/signal/once"
513
+ />
514
+ <Card
515
+ title="Consumers"
516
+ description="Signal workers, Clock jobs, and SQL CDC."
517
+ href="/docs/elements/flow/consumers"
518
+ />
519
+ </Cards>