okengine 0.18.5 → 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 (510) 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/meta.json +1 -1
  6. package/site/content/docs/ai/skills.mdx +4 -1
  7. package/site/content/docs/ai/try-it.mdx +58 -0
  8. package/site/content/docs/client/auth.mdx +208 -0
  9. package/site/content/docs/client/calling.mdx +451 -0
  10. package/site/content/docs/client/index.mdx +209 -0
  11. package/site/content/docs/client/live.mdx +234 -0
  12. package/site/content/docs/client/meta.json +5 -0
  13. package/site/content/docs/client/react.mdx +249 -0
  14. package/site/content/docs/elements/ai/agents.mdx +232 -0
  15. package/site/content/docs/elements/ai/index.mdx +341 -0
  16. package/site/content/docs/elements/ai/mcp.mdx +275 -0
  17. package/site/content/docs/elements/ai/meta.json +5 -0
  18. package/site/content/docs/elements/ai/models.mdx +283 -0
  19. package/site/content/docs/elements/ai/prompts.mdx +256 -0
  20. package/site/content/docs/elements/channel/email.mdx +266 -0
  21. package/site/content/docs/elements/channel/index.mdx +344 -0
  22. package/site/content/docs/elements/channel/meta.json +5 -0
  23. package/site/content/docs/elements/channel/push.mdx +218 -0
  24. package/site/content/docs/elements/channel/receipts.mdx +206 -0
  25. package/site/content/docs/elements/channel/sms.mdx +264 -0
  26. package/site/content/docs/elements/channel/whatsapp.mdx +220 -0
  27. package/site/content/docs/elements/clock/index.mdx +300 -0
  28. package/site/content/docs/elements/clock/meta.json +5 -0
  29. package/site/content/docs/elements/clock/schedules.mdx +508 -0
  30. package/site/content/docs/elements/clock/sleep.mdx +445 -0
  31. package/site/content/docs/elements/flow/consumers.mdx +707 -0
  32. package/site/content/docs/elements/flow/http.mdx +1080 -0
  33. package/site/content/docs/elements/flow/index.mdx +618 -0
  34. package/site/content/docs/elements/flow/meta.json +5 -0
  35. package/site/content/docs/elements/flow/routing.mdx +559 -0
  36. package/site/content/docs/elements/flow/workflows.mdx +640 -0
  37. package/site/content/docs/elements/gate/auth.mdx +371 -0
  38. package/site/content/docs/elements/gate/authorization.mdx +298 -0
  39. package/site/content/docs/elements/gate/index.mdx +418 -0
  40. package/site/content/docs/elements/gate/meta.json +5 -0
  41. package/site/content/docs/elements/gate/rate-limits.mdx +365 -0
  42. package/site/content/docs/elements/gate/rls.mdx +303 -0
  43. package/site/content/docs/elements/gate/tenancy.mdx +372 -0
  44. package/site/content/docs/elements/index.mdx +20 -10
  45. package/site/content/docs/elements/signal/broadcast.mdx +517 -0
  46. package/site/content/docs/elements/signal/index.mdx +352 -0
  47. package/site/content/docs/elements/signal/live.mdx +590 -0
  48. package/site/content/docs/elements/signal/meta.json +5 -0
  49. package/site/content/docs/elements/signal/once.mdx +596 -0
  50. package/site/content/docs/elements/store/files.mdx +659 -0
  51. package/site/content/docs/elements/store/index.mdx +310 -0
  52. package/site/content/docs/elements/store/kv.mdx +629 -0
  53. package/site/content/docs/elements/store/meta.json +5 -0
  54. package/site/content/docs/elements/store/search.mdx +946 -0
  55. package/site/content/docs/elements/store/sql.mdx +937 -0
  56. package/site/content/docs/elements/vault/config.mdx +256 -0
  57. package/site/content/docs/elements/vault/index.mdx +356 -0
  58. package/site/content/docs/elements/vault/meta.json +5 -0
  59. package/site/content/docs/elements/vault/rotation.mdx +273 -0
  60. package/site/content/docs/elements/vault/secrets.mdx +285 -0
  61. package/site/content/docs/index.mdx +63 -36
  62. package/site/content/docs/meta.json +8 -7
  63. package/site/content/docs/plugins/cors.mdx +2 -1
  64. package/site/content/docs/plugins/csrf.mdx +4 -1
  65. package/site/content/docs/plugins/headers.mdx +2 -1
  66. package/site/content/docs/plugins/ip-allowlist.mdx +2 -1
  67. package/site/content/docs/plugins/magic-link.mdx +6 -0
  68. package/site/content/docs/plugins/maintenance-mode.mdx +2 -1
  69. package/site/content/docs/plugins/oauth.mdx +1 -1
  70. package/site/content/docs/plugins/otp.mdx +12 -1
  71. package/site/content/docs/plugins/passkey.mdx +39 -15
  72. package/site/content/docs/plugins/two-factor.mdx +91 -34
  73. package/site/content/docs/plugins/username.mdx +1 -1
  74. package/site/content/docs/recipes/caddy.mdx +3 -3
  75. package/site/content/docs/recipes/index.mdx +18 -28
  76. package/site/content/docs/recipes/mailpit.mdx +1 -1
  77. package/site/content/docs/recipes/meilisearch.mdx +3 -3
  78. package/site/content/docs/recipes/meta.json +1 -4
  79. package/site/content/docs/recipes/nginx.mdx +2 -2
  80. package/site/content/docs/recipes/openrouter.mdx +283 -0
  81. package/site/content/docs/recipes/pgdog.mdx +3 -3
  82. package/site/content/docs/recipes/postgres.mdx +1 -2
  83. package/site/content/docs/recipes/rustfs.mdx +2 -2
  84. package/site/content/docs/recipes/traefik.mdx +3 -3
  85. package/site/content/docs/reference/cli.mdx +231 -0
  86. package/site/content/docs/reference/configuration.mdx +30 -10
  87. package/site/content/docs/reference/environment-variables.mdx +93 -20
  88. package/site/content/docs/reference/errors.mdx +132 -29
  89. package/site/content/docs/reference/fx.mdx +137 -73
  90. package/site/content/docs/reference/index.mdx +13 -21
  91. package/site/content/docs/reference/meta.json +4 -5
  92. package/site/content/docs/reference/okid.mdx +42 -17
  93. package/site/content/docs/reference/plugins.mdx +4 -3
  94. package/site/content/docs/reference/security.mdx +197 -0
  95. package/site/content/docs/understand/meta.json +5 -0
  96. package/site/content/docs/understand/the-anatomy.mdx +132 -0
  97. package/site/content/docs/understand/the-model.mdx +32 -0
  98. package/site/content/docs/understand/the-problem.mdx +74 -0
  99. package/site/content/docs/understand/the-vocabulary.mdx +26 -0
  100. package/src/auth/api-keys.ts +2 -1
  101. package/src/auth/bindings.ts +64 -16
  102. package/src/auth/gate-auth.test.ts +6 -1
  103. package/src/auth/identity.ts +152 -5
  104. package/src/auth/index.ts +29 -0
  105. package/src/auth/invites.ts +2 -1
  106. package/src/auth/method-context.ts +41 -0
  107. package/src/auth/oauth-as/crypto.ts +2 -1
  108. package/src/auth/oauth-as/stores.ts +2 -1
  109. package/src/auth/operator.ts +2 -1
  110. package/src/auth/sessions-jwt.test.ts +77 -0
  111. package/src/auth/sessions.ts +20 -4
  112. package/src/auth/tenants.ts +3 -2
  113. package/src/auth/two-factor-challenge.test.ts +61 -0
  114. package/src/auth/two-factor-challenge.ts +284 -0
  115. package/src/auth/verification.ts +5 -0
  116. package/src/bench/REPORT.md +49 -0
  117. package/src/bench/g03-signal-once.bench.ts +1 -1
  118. package/src/bench/g10-observability-contention.bench.ts +1 -1
  119. package/src/bench/g17-hybrid-search.bench.ts +572 -0
  120. package/src/bench/load-app.ts +2 -10
  121. package/src/cli/ai-setup/ai-setup.test.ts +331 -42
  122. package/src/cli/ai-setup/apply.ts +294 -52
  123. package/src/cli/ai-setup/catalog.ts +238 -1342
  124. package/src/cli/ai-setup/index.ts +14 -99
  125. package/src/cli/ai-setup/prompts.ts +60 -443
  126. package/src/cli/ask-seed.test.ts +96 -0
  127. package/src/cli/ask-seed.ts +82 -0
  128. package/src/cli/ask-vault-gaps.test.ts +84 -0
  129. package/src/cli/ask-vault-gaps.ts +146 -0
  130. package/src/cli/build.ts +2 -2
  131. package/src/cli/client-add.test.ts +26 -1
  132. package/src/cli/client-add.ts +106 -27
  133. package/src/cli/db-seed.ts +2 -0
  134. package/src/cli/db.ts +150 -11
  135. package/src/cli/dev.ts +119 -211
  136. package/src/cli/docker-clean.ts +2 -2
  137. package/src/cli/docker-cli.test.ts +1 -1
  138. package/src/cli/doctor-diff.ts +4 -2
  139. package/src/cli/doctor-pii.test.ts +1 -1
  140. package/src/cli/doctor.ts +14 -1
  141. package/src/cli/hero-meta.test.ts +4 -2
  142. package/src/cli/load-config.images.test.ts +8 -10
  143. package/src/cli/load-config.ts +1 -1
  144. package/src/cli/registry.ts +22 -10
  145. package/src/cli/replay.ts +3 -1
  146. package/src/cli/start.ts +1 -1
  147. package/src/cli/tui/keys.ts +2 -3
  148. package/src/client/auth/cookies.ts +74 -0
  149. package/src/client/auth/create-auth-client.ts +636 -0
  150. package/src/client/auth/denials.ts +99 -0
  151. package/src/client/auth/session.ts +283 -0
  152. package/src/client/auth.test.ts +251 -0
  153. package/src/client/auth.ts +39 -114
  154. package/src/client/create-with-session.ts +245 -0
  155. package/src/client/create.ts +51 -5
  156. package/src/client/index.ts +11 -1
  157. package/src/client/live.ts +11 -79
  158. package/src/client/notes-contract.test.ts +5 -10
  159. package/src/client/sse.ts +137 -0
  160. package/src/client/stream.ts +147 -0
  161. package/src/client/transport.test.ts +32 -0
  162. package/src/client/transport.ts +123 -26
  163. package/src/client/types.ts +118 -14
  164. package/src/client-react/index.ts +185 -24
  165. package/src/client-react/use-live-query.ts +13 -3
  166. package/src/compiler/aot.test.ts +7 -4
  167. package/src/compiler/effects-embed.test.ts +56 -0
  168. package/src/compiler/effects-fetch.test.ts +43 -0
  169. package/src/compiler/effects-infer.ts +58 -2
  170. package/src/compiler/extract.test.ts +257 -43
  171. package/src/compiler/extract.ts +566 -48
  172. package/src/compiler/fixtures/skyport/src/flows/bookings/index.ts +7 -4
  173. package/src/compiler/fixtures/skyport/src/flows/bookings/signals.ts +2 -8
  174. package/src/compiler/fixtures/skyport.expected.json +4 -4
  175. package/src/compiler/fixtures/triggers/five-triggers.ts +14 -10
  176. package/src/compiler/response.ts +2 -2
  177. package/src/compiler/search-writer-isolation.test.ts +40 -0
  178. package/src/config/index.ts +11 -0
  179. package/src/console/server/app.ts +17 -23
  180. package/src/console/server/bind.ts +4 -0
  181. package/src/console/server/console.test.ts +2 -2
  182. package/src/console/server/flows-invoke.test.ts +50 -34
  183. package/src/console/server/flows.ts +436 -255
  184. package/src/console/server/runs-ingest.test.ts +6 -3
  185. package/src/console/server/serve.ts +1 -1
  186. package/src/console/server/signals.test.ts +1 -5
  187. package/src/console/server/signals.ts +8 -6
  188. package/src/console/server/state.ts +6 -1
  189. package/src/console/server/store.ts +1 -1
  190. package/src/console/ui-next/dist/assets/FileExportIcon-Ck-5od4R.js +1 -0
  191. package/src/console/ui-next/dist/assets/MoreHorizontalCircle01Icon-gMNGsE37.js +1 -0
  192. package/src/console/ui-next/dist/assets/PlusSignIcon-CwG3nxfu.js +1 -0
  193. package/src/console/ui-next/dist/assets/UnavailableIcon-D9cvHVPr.js +1 -0
  194. package/src/console/ui-next/dist/assets/UserIcon-DaE2PB5_.js +1 -0
  195. package/src/console/ui-next/dist/assets/access-page-DFeymU07.js +4 -0
  196. package/src/console/ui-next/dist/assets/agent-disclosure-U1rdfblp.js +1 -0
  197. package/src/console/ui-next/dist/assets/cache-glyph-BeFJeqBG.js +1 -0
  198. package/src/console/ui-next/dist/assets/call-pii-button-CVAONPii.js +1 -0
  199. package/src/console/ui-next/dist/assets/collapsible-D2A6NJ-3.js +1 -0
  200. package/src/console/ui-next/dist/assets/copy-inline-button-CAYD18cr.js +1 -0
  201. package/src/console/ui-next/dist/assets/dagre.esm-B1_XeuLP.js +1 -0
  202. package/src/console/ui-next/dist/assets/detail-header-DVWNjWjg.js +1 -0
  203. package/src/console/ui-next/dist/assets/dropdown-menu-4h2LOVXM.js +1 -0
  204. package/src/console/ui-next/dist/assets/duration-tone-Cgk_h5ja.js +9 -0
  205. package/src/console/ui-next/dist/assets/element-icons-BI8cJgdh.js +1 -0
  206. package/src/console/ui-next/dist/assets/explorer-empty-CJs5A-wm.js +1 -0
  207. package/src/console/ui-next/dist/assets/flows-page-Dss7941e.js +1 -0
  208. package/src/console/ui-next/dist/assets/highlighted-json-MYZQtRnw.js +154 -0
  209. package/src/console/ui-next/dist/assets/http-method-Jrh39p7A.js +1 -0
  210. package/src/console/ui-next/dist/assets/index-D0zS5rKO.css +2 -0
  211. package/src/console/ui-next/dist/assets/index-DXP2dBIF.js +63 -0
  212. package/src/console/ui-next/dist/assets/observability-page-HvolXxTI.js +4 -0
  213. package/src/console/ui-next/dist/assets/react-dom-Ddte4I-Q.js +9 -0
  214. package/src/console/ui-next/dist/assets/replica-lag-CSh2dzrb.js +18 -0
  215. package/src/console/ui-next/dist/assets/request-meta-BatF8KrK.js +1 -0
  216. package/src/console/ui-next/dist/assets/shortcut-keys-3ILd8oGn.js +1 -0
  217. package/src/console/ui-next/dist/assets/{sql-BskegiFM.js → sql-BsFa4tDR.js} +1 -1
  218. package/src/console/ui-next/dist/assets/store-page-eiKiHnNe.js +41 -0
  219. package/src/console/ui-next/dist/assets/trace-detail-sheet-CZkMeKS-.js +2 -0
  220. package/src/console/ui-next/dist/assets/tree-expand-toggle-CW8y5A2h.js +55 -0
  221. package/src/console/ui-next/dist/assets/units-page-B_RWJrEO.js +1 -0
  222. package/src/console/ui-next/dist/assets/{use-vault-list-uk4WVboC.js → use-vault-list-CT4-gajj.js} +1 -1
  223. package/src/console/ui-next/dist/assets/vault-page-CWrg-A68.js +2 -0
  224. package/src/console/ui-next/dist/assets/xyflow-CSyC6ryz.css +1 -0
  225. package/src/console/ui-next/dist/assets/xyflow-yApv7D4e.js +7 -0
  226. package/src/console/ui-next/dist/index.html +6 -13
  227. package/src/console/ui-next/seed-invoke-host.ts +21 -18
  228. package/src/console/ui-next/src/client.ts +7 -2
  229. package/src/console/ui-next/src/features/flows/graph/element-map.ts +2 -0
  230. package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +12 -2
  231. package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +4 -0
  232. package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +8 -0
  233. package/src/console/ui-next/src/features/flows/traces/waterfall-bars.ts +4 -0
  234. package/src/console/ui-next/src/features/flows/traces/waterfall-tooltip.ts +9 -3
  235. package/src/console/ui-next/src/features/store/detail/reveal-cell.tsx +7 -11
  236. package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +2 -2
  237. package/src/console/ui-next/src/features/store/lib/fields-from-table.ts +2 -0
  238. package/src/console/ui-next/src/features/store/lib/grid-model.test.ts +25 -1
  239. package/src/console/ui-next/src/features/store/lib/grid-model.ts +36 -1
  240. package/src/console/ui-next/src/features/store/query/query-results.tsx +3 -3
  241. package/src/console/ui-next/src/features/units/detail/effects-summary.tsx +2 -0
  242. package/src/console/ui-next/src/features/units/lib/call-read-safe.ts +3 -1
  243. package/src/console/ui-next/src/features/units/lib/fields-from-schema.ts +6 -0
  244. package/src/docker/ai-model-status.test.ts +1 -12
  245. package/src/docker/ai-model-status.ts +10 -38
  246. package/src/docker/compose-up.test.ts +100 -0
  247. package/src/docker/compose-up.ts +292 -0
  248. package/src/docker/compose.ts +4 -14
  249. package/src/docker/derive.ts +9 -34
  250. package/src/docker/docker.test.ts +16 -261
  251. package/src/docker/images-config.test.ts +13 -25
  252. package/src/docker/index.ts +1 -24
  253. package/src/docker/recipes/index.ts +0 -23
  254. package/src/docker/recipes/pgdog.ts +6 -4
  255. package/src/docker/stack-id.test.ts +2 -2
  256. package/src/docker/stack-id.ts +0 -1
  257. package/src/docker/types.ts +2 -2
  258. package/src/drivers/ai-anthropic.ts +10 -0
  259. package/src/drivers/ai-openai-compatible.ts +34 -7
  260. package/src/drivers/ai-providers.test.ts +0 -107
  261. package/src/drivers/ai-stream.test.ts +0 -34
  262. package/src/drivers/ai-types.ts +17 -8
  263. package/src/drivers/channel-fcm.ts +6 -1
  264. package/src/drivers/channel-msegat.ts +6 -1
  265. package/src/drivers/channel-resend.ts +5 -1
  266. package/src/drivers/channel-smtp.ts +5 -1
  267. package/src/drivers/channel-sndr.ts +10 -1
  268. package/src/drivers/channel-taqnyat-mail.ts +5 -1
  269. package/src/drivers/channel-taqnyat-whatsapp.ts +6 -1
  270. package/src/drivers/channel-taqnyat.ts +6 -1
  271. package/src/drivers/channel-types.ts +10 -0
  272. package/src/drivers/channel-unifonic.ts +6 -1
  273. package/src/drivers/channel-wa-cloud.ts +6 -1
  274. package/src/drivers/channel-webpush.ts +6 -1
  275. package/src/drivers/external.ts +33 -0
  276. package/src/drivers/index.ts +0 -10
  277. package/src/drivers/meilisearch.ts +6 -0
  278. package/src/drivers/oauth-types.ts +4 -0
  279. package/src/drivers/postgres.test.ts +74 -3
  280. package/src/drivers/postgres.ts +175 -6
  281. package/src/drivers/signal-types.ts +5 -5
  282. package/src/drivers/types.ts +7 -0
  283. package/src/drivers/vault-types.ts +5 -0
  284. package/src/elements/ai/declare.ts +17 -4
  285. package/src/elements/ai/eval.ts +1 -1
  286. package/src/elements/ai/mcp-http.ts +1 -1
  287. package/src/elements/ai/pii.ts +1 -1
  288. package/src/elements/ai/providers.test.ts +289 -0
  289. package/src/elements/ai/providers.ts +169 -0
  290. package/src/elements/ai/runtime.ts +68 -5
  291. package/src/elements/ai/schema.ts +2 -2
  292. package/src/elements/ai.test.ts +7 -7
  293. package/src/elements/ai.ts +17 -1
  294. package/src/elements/channel/runtime.ts +105 -12
  295. package/src/elements/clock/cron-fields.test.ts +144 -0
  296. package/src/elements/clock/cron-fields.ts +185 -0
  297. package/src/elements/clock/declare.ts +247 -3
  298. package/src/elements/clock.ts +21 -1
  299. package/src/elements/index.ts +15 -0
  300. package/src/elements/signal/chaos-child.ts +1 -2
  301. package/src/elements/signal/declare.ts +65 -37
  302. package/src/elements/signal/delivery-modes.test.ts +11 -31
  303. package/src/elements/signal/dry-run-replay.test.ts +1 -7
  304. package/src/elements/signal/dry-run-write-isolation.test.ts +1 -7
  305. package/src/elements/signal/key-ordering.test.ts +6 -26
  306. package/src/elements/signal/lease-reclaim.test.ts +2 -6
  307. package/src/elements/signal/optional-emit.test.ts +6 -9
  308. package/src/elements/signal/order-lifecycle.test.ts +4 -17
  309. package/src/elements/signal/orphan-messages.test.ts +4 -15
  310. package/src/elements/signal/reconcile.test.ts +2 -2
  311. package/src/elements/signal/runtime.ts +1 -1
  312. package/src/elements/signal/schema-emit.test.ts +4 -8
  313. package/src/elements/signal.test.ts +24 -49
  314. package/src/elements/signal.ts +9 -2
  315. package/src/elements/store/domain-ddl.test.ts +1 -1
  316. package/src/elements/store/live-http.test.ts +9 -4
  317. package/src/elements/store/prepare-row.test.ts +123 -0
  318. package/src/elements/store/resource.ts +43 -12
  319. package/src/elements/store/schema-decl.ts +117 -0
  320. package/src/elements/store/search-backfill.ts +205 -0
  321. package/src/elements/store/search-bind.ts +66 -0
  322. package/src/elements/store/search-bm25.ts +78 -0
  323. package/src/elements/store/search-ddl.ts +156 -0
  324. package/src/elements/store/search-embed-flow.ts +141 -0
  325. package/src/elements/store/search-errors.ts +34 -0
  326. package/src/elements/store/search-fusion.ts +112 -0
  327. package/src/elements/store/search-lsh.ts +179 -0
  328. package/src/elements/store/search-runtime.ts +300 -0
  329. package/src/elements/store/search.test.ts +144 -0
  330. package/src/elements/store/sql-session.test.ts +1 -0
  331. package/src/elements/store/sql-session.ts +83 -3
  332. package/src/elements/store/table.ts +23 -2
  333. package/src/elements/store/upsert-app.test.ts +1 -3
  334. package/src/elements/store.ts +50 -0
  335. package/src/full.ts +4 -2
  336. package/src/http.ts +4 -1
  337. package/src/i18n/catalogs/ar.ts +14 -6
  338. package/src/i18n/catalogs/en.ts +14 -6
  339. package/src/index.ts +6 -1
  340. package/src/kernel/adopt-barrel-fresh.test.ts +7 -7
  341. package/src/kernel/adopt-routes.ts +9 -1
  342. package/src/kernel/app-auth.ts +5 -0
  343. package/src/kernel/app.ts +102 -9
  344. package/src/kernel/auto-cache.test.ts +6 -12
  345. package/src/kernel/auto-registry.test.ts +3 -3
  346. package/src/kernel/boot-bind/ai.test.ts +22 -19
  347. package/src/kernel/boot-bind/ai.ts +5 -43
  348. package/src/kernel/boot-bind/clock.ts +23 -5
  349. package/src/kernel/boot-bind/honor-config.test.ts +4 -4
  350. package/src/kernel/boot.test.ts +11 -6
  351. package/src/kernel/boot.ts +66 -16
  352. package/src/kernel/boundary-contract.ts +93 -0
  353. package/src/kernel/budget.test.ts +1 -1
  354. package/src/kernel/call.ts +89 -0
  355. package/src/kernel/capability.ts +6 -0
  356. package/src/kernel/client-descriptor.ts +123 -0
  357. package/src/kernel/clock-timezone.test.ts +80 -0
  358. package/src/kernel/correlation.test.ts +1 -1
  359. package/src/kernel/dry-run.ts +11 -8
  360. package/src/kernel/effects-stamping.test.ts +42 -9
  361. package/src/kernel/effects.test.ts +4 -2
  362. package/src/kernel/effects.ts +51 -9
  363. package/src/kernel/errors-channel.ts +17 -0
  364. package/src/kernel/errors-live-resume.ts +3 -2
  365. package/src/kernel/errors-tenant.ts +7 -4
  366. package/src/kernel/errors.registry-helpers.ts +96 -0
  367. package/src/kernel/errors.registry.test.ts +139 -25
  368. package/src/kernel/errors.ts +102 -23
  369. package/src/kernel/external-effects.test.ts +196 -0
  370. package/src/kernel/flow.test.ts +13 -4
  371. package/src/kernel/flow.ts +69 -71
  372. package/src/kernel/fx-ask-telemetry.test.ts +2 -2
  373. package/src/kernel/fx-dead-letters.test.ts +3 -3
  374. package/src/kernel/fx-fetch.ts +55 -0
  375. package/src/kernel/fx-live-stream.ts +2 -2
  376. package/src/kernel/fx-live.test.ts +8 -8
  377. package/src/kernel/fx.test.ts +17 -11
  378. package/src/kernel/fx.ts +180 -57
  379. package/src/kernel/horizontal-child.ts +1 -1
  380. package/src/kernel/http-query.test.ts +3 -6
  381. package/src/kernel/index.ts +13 -3
  382. package/src/kernel/instance-id.ts +1 -1
  383. package/src/kernel/instances.test.ts +40 -0
  384. package/src/kernel/instances.ts +47 -9
  385. package/src/kernel/live-http.test.ts +6 -7
  386. package/src/kernel/live-http.ts +6 -2
  387. package/src/kernel/live-resume.test.ts +1 -1
  388. package/src/kernel/mcp-tool.test.ts +3 -3
  389. package/src/kernel/on.ts +98 -4
  390. package/src/kernel/pipeline.test.ts +12 -15
  391. package/src/kernel/plugin-elements.test.ts +1 -1
  392. package/src/kernel/stamp-http.test.ts +3 -3
  393. package/src/kernel/triggers.ts +210 -95
  394. package/src/kernel-entry.ts +0 -1
  395. package/src/manifest/diff.ts +11 -1
  396. package/src/manifest/fixtures/skyport.excerpt.json +1 -1
  397. package/src/manifest/types.ts +46 -2
  398. package/src/mcp/docs-index.ts +3 -3
  399. package/src/mcp/docs-mcp.test.ts +2 -2
  400. package/src/mcp/docs-tools.ts +1 -1
  401. package/src/okid.test.ts +56 -0
  402. package/src/okid.ts +55 -11
  403. package/src/plugins/anonymous.ts +8 -4
  404. package/src/plugins/auth/shared.ts +27 -3
  405. package/src/plugins/auth-delivery.mailpit.integration.test.ts +1 -1
  406. package/src/plugins/auth-methods.security.test.ts +561 -23
  407. package/src/plugins/config-source.ts +1 -1
  408. package/src/plugins/csrf.test.ts +65 -0
  409. package/src/plugins/index.ts +2 -0
  410. package/src/plugins/magic-link.ts +36 -16
  411. package/src/plugins/oauth/shared.ts +3 -1
  412. package/src/plugins/oauth.ts +22 -14
  413. package/src/plugins/otp.ts +37 -21
  414. package/src/plugins/passkey-webauthn.ts +9 -6
  415. package/src/plugins/passkey.ts +98 -26
  416. package/src/plugins/pre-account-hijack.test.ts +223 -0
  417. package/src/plugins/two-factor.ts +466 -37
  418. package/src/plugins/username.ts +19 -7
  419. package/src/release/build-lib.ts +1 -0
  420. package/src/release/limits.ts +2 -2
  421. package/src/release/measure.ts +1 -1
  422. package/src/runs/collect.test.ts +0 -2
  423. package/src/runtime/json-code-block.test.ts +250 -20
  424. package/src/runtime/json-code-block.ts +1261 -41
  425. package/src/term.test.ts +1 -1
  426. package/src/test/create-test-app.test.ts +12 -12
  427. package/src/test/live-signals.test.ts +6 -6
  428. package/src/test/provisions.integration.test.ts +10 -14
  429. package/src/test/reset-element-registries.ts +1 -1
  430. package/src/test/tenant-isolation.test.ts +12 -6
  431. package/site/content/docs/deployment/docker-swarm.mdx +0 -164
  432. package/site/content/docs/deployment/docker.mdx +0 -227
  433. package/site/content/docs/deployment/index.mdx +0 -83
  434. package/site/content/docs/deployment/kubernetes.mdx +0 -176
  435. package/site/content/docs/deployment/meta.json +0 -5
  436. package/site/content/docs/deployment/reverse-proxy.mdx +0 -234
  437. package/site/content/docs/elements/ai.mdx +0 -385
  438. package/site/content/docs/elements/channel.mdx +0 -346
  439. package/site/content/docs/elements/clock.mdx +0 -244
  440. package/site/content/docs/elements/flow.mdx +0 -420
  441. package/site/content/docs/elements/gate.mdx +0 -436
  442. package/site/content/docs/elements/signal.mdx +0 -380
  443. package/site/content/docs/elements/store.mdx +0 -1099
  444. package/site/content/docs/elements/vault.mdx +0 -405
  445. package/site/content/docs/get-started/basic-usage.mdx +0 -173
  446. package/site/content/docs/get-started/index.mdx +0 -43
  447. package/site/content/docs/get-started/installation.mdx +0 -220
  448. package/site/content/docs/get-started/introduction.mdx +0 -144
  449. package/site/content/docs/get-started/meta.json +0 -13
  450. package/site/content/docs/get-started/project-structure.mdx +0 -925
  451. package/site/content/docs/get-started/testing.mdx +0 -328
  452. package/site/content/docs/get-started/why.mdx +0 -114
  453. package/site/content/docs/recipes/llama-cpp.mdx +0 -151
  454. package/site/content/docs/recipes/ollama.mdx +0 -142
  455. package/site/content/docs/recipes/sglang.mdx +0 -105
  456. package/site/content/docs/recipes/vllm.mdx +0 -106
  457. package/site/content/docs/reference/cli.md +0 -232
  458. package/site/content/docs/reference/client.mdx +0 -469
  459. package/site/content/docs/reference/security.md +0 -74
  460. package/src/cli/ai-setup/detect-ollama.ts +0 -213
  461. package/src/cli/ai-setup/recommend.test.ts +0 -225
  462. package/src/cli/ai-setup/recommend.ts +0 -225
  463. package/src/cli/dev-controls.test.ts +0 -122
  464. package/src/cli/dev-controls.ts +0 -164
  465. package/src/cli/tui/DevLive.tsx +0 -126
  466. package/src/console/ui-next/dist/assets/access-page-3-EFj-2G.js +0 -4
  467. package/src/console/ui-next/dist/assets/agent-disclosure-BP0Y0Sux.js +0 -1
  468. package/src/console/ui-next/dist/assets/cache-glyph-B5X-NM0-.js +0 -1
  469. package/src/console/ui-next/dist/assets/call-pii-button-ChAUiLo9.js +0 -1
  470. package/src/console/ui-next/dist/assets/collapsible-DGnOM2ph.js +0 -1
  471. package/src/console/ui-next/dist/assets/copy-inline-button-DBHhgHKP.js +0 -1
  472. package/src/console/ui-next/dist/assets/dagre.esm-ZwcdTuZZ.js +0 -1
  473. package/src/console/ui-next/dist/assets/detail-header-DhHM1iaZ.js +0 -1
  474. package/src/console/ui-next/dist/assets/dropdown-menu-_NjTEo5_.js +0 -1
  475. package/src/console/ui-next/dist/assets/duration-tone-sC3lGABz.js +0 -9
  476. package/src/console/ui-next/dist/assets/element-icons-BVXtRyd3.js +0 -1
  477. package/src/console/ui-next/dist/assets/explorer-empty-2uIhBu0_.js +0 -1
  478. package/src/console/ui-next/dist/assets/flows-page-cVFnA4HH.js +0 -1
  479. package/src/console/ui-next/dist/assets/highlighted-json-Awq7gYdu.js +0 -154
  480. package/src/console/ui-next/dist/assets/http-method-_UHM2ODJ.js +0 -1
  481. package/src/console/ui-next/dist/assets/index-CMIgUbD0.js +0 -66
  482. package/src/console/ui-next/dist/assets/index-Ck88Jmv8.css +0 -2
  483. package/src/console/ui-next/dist/assets/link-BX6Vqztd.js +0 -1
  484. package/src/console/ui-next/dist/assets/observability-page-CQ3p34ip.js +0 -4
  485. package/src/console/ui-next/dist/assets/preload-helper-oH4irX4C.js +0 -1
  486. package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +0 -1
  487. package/src/console/ui-next/dist/assets/react-dom-Bph1y7z7.js +0 -9
  488. package/src/console/ui-next/dist/assets/replica-lag-DKRrbvdo.js +0 -18
  489. package/src/console/ui-next/dist/assets/request-meta-DV0ywz7t.js +0 -1
  490. package/src/console/ui-next/dist/assets/shortcut-JQIZlWfm.js +0 -1
  491. package/src/console/ui-next/dist/assets/shortcut-keys-DKxNTe_m.js +0 -1
  492. package/src/console/ui-next/dist/assets/skeleton-D-czQJT6.js +0 -1
  493. package/src/console/ui-next/dist/assets/store-page-02xOiqIK.js +0 -41
  494. package/src/console/ui-next/dist/assets/trace-detail-sheet-B09O8rA6.js +0 -2
  495. package/src/console/ui-next/dist/assets/tree-expand-toggle-BtyhmWb4.js +0 -55
  496. package/src/console/ui-next/dist/assets/units-page-l8FeKfnP.js +0 -1
  497. package/src/console/ui-next/dist/assets/useMutation-B8EO02Ej.js +0 -1
  498. package/src/console/ui-next/dist/assets/useRender-BE2A9BWC.js +0 -1
  499. package/src/console/ui-next/dist/assets/vault-page-CL-d_mLE.js +0 -2
  500. package/src/console/ui-next/dist/assets/xyflow-D7n4g6go.js +0 -7
  501. package/src/console/ui-next/dist/assets/xyflow-DZ0Ws1xk.css +0 -1
  502. package/src/docker/ollama-pull.ts +0 -232
  503. package/src/docker/recipes/llama-cpp.ts +0 -298
  504. package/src/docker/recipes/ollama.ts +0 -44
  505. package/src/docker/recipes/sglang.ts +0 -55
  506. package/src/docker/recipes/vllm.ts +0 -44
  507. package/src/drivers/ai-ollama-tools.integration.test.ts +0 -109
  508. package/src/drivers/ai-ollama.integration.test.ts +0 -184
  509. package/src/drivers/ai-ollama.ts +0 -389
  510. package/src/drivers/ollama.ts +0 -14
@@ -0,0 +1,517 @@
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
+ flow({
70
+ in: z.object({ sku: z.string(), title: z.string().min(1) }),
71
+ do: async ({ sku, title }, fx) => {
72
+ // … persist the change …
73
+ await fx.emit(cacheInvalidated, { key: `sku:${sku}` });
74
+ return { sku, title };
75
+ },
76
+ }),
77
+ );
78
+ ```
79
+
80
+ The compiler records `emits: ["cache.invalidated"]` on the producer. Emit resolves when the
81
+ outbox commits — the HTTP request does not wait for every subscriber to finish.
82
+
83
+ </Step>
84
+
85
+ </Steps>
86
+
87
+ <Callout title="Same handle everywhere">
88
+ Import the declared Signal handle (or the same name) in every subscriber and producer. A typo in
89
+ the name creates a different Manifest entry — fan-out never crosses names.
90
+ </Callout>
91
+
92
+ ## Progressive Patterns
93
+
94
+ Explore broadcast from one listener to multi-Flow fan-out, optional hooks, and schema-checked
95
+ payloads:
96
+
97
+ <Tabs items={["Minimal", "Fan-out", "Optional", "Schema"]}>
98
+
99
+ <Tab value="Minimal">
100
+
101
+ One Flow subscribed — still broadcast physics (no competing claim, no once DLQ path):
102
+
103
+ ```typescript title="src/signals/catalog.ts"
104
+ import { signal } from "okengine";
105
+ import { z } from "zod";
106
+
107
+ export const catalogChanged = signal.broadcast("catalog.changed", {
108
+ schema: z.object({ sku: z.string() }),
109
+ });
110
+ ```
111
+
112
+ ```typescript title="src/flows/cache/invalidate.ts"
113
+ import { on, flow } from "okengine";
114
+ import { catalogChanged } from "@/signals/catalog";
115
+
116
+ export const invalidate = on(
117
+ catalogChanged,
118
+ flow("cache.invalidate", {
119
+ do: async ({ sku }, fx) => {
120
+ await fx.store.kv.delete(`sku:${sku}`);
121
+ },
122
+ }),
123
+ );
124
+ ```
125
+
126
+ </Tab>
127
+
128
+ <Tab value="Fan-out">
129
+
130
+ Bind a second Flow to the same Signal — both run. That is the fan-out:
131
+
132
+ ```typescript title="src/flows/search/reindex.ts"
133
+ import { on, flow } from "okengine";
134
+ import { catalogChanged } from "@/signals/catalog";
135
+
136
+ export const reindexSku = on(
137
+ catalogChanged,
138
+ flow("search.reindexSku", {
139
+ do: async ({ sku }, fx) => {
140
+ await fx.call(rebuildSearchDoc, { sku });
141
+ },
142
+ }),
143
+ );
144
+ ```
145
+
146
+ **Consequence:** each subscribed Flow gets its own copy. A failure in one handler does not
147
+ claim-lock the message away from siblings the way `once` leases do.
148
+
149
+ </Tab>
150
+
151
+ <Tab value="Optional">
152
+
153
+ Hooks that may have no subscriber yet need `optional: true` or emit throws **OKE1240**:
154
+
155
+ ```typescript
156
+ export const clusterHint = signal.broadcast("cluster.hint", {
157
+ optional: true,
158
+ schema: z.object({ nodeId: z.string() }),
159
+ });
160
+ ```
161
+
162
+ **OKE1240** cause: `Flow "{flow}" emits signal "{resource}" with no subscriber.`
163
+ Fix: bind at least one `on(signal, flow)` or set `{ optional: true }`.
164
+
165
+ </Tab>
166
+
167
+ <Tab value="Schema">
168
+
169
+ Invalid payloads fail at `fx.emit` with **OKE1250** before any subscriber runs:
170
+
171
+ ```typescript
172
+ export const priceTick = signal.broadcast("prices.tick", {
173
+ schema: z.object({
174
+ sku: z.string(),
175
+ cents: z.number().int().nonnegative(),
176
+ }),
177
+ });
178
+ ```
179
+
180
+ Cause shape: `"{resource}": {detail}`. Align the payload with `schema` — no subscriber started.
181
+
182
+ </Tab>
183
+
184
+ </Tabs>
185
+
186
+ ## Delivery Reference
187
+
188
+ How broadcast sits next to the other helpers when you are choosing physics:
189
+
190
+ | Helper | Pattern | Who runs | Replay | Typical use |
191
+ | ------------------ | ---------- | ---------------------------- | ---------------------- | ------------------------------------- |
192
+ | `signal.once` | Work queue | Exactly one competing worker | Retries + DLQ | Emails, fulfillment, sync |
193
+ | `signal.broadcast` | Pub/sub | Every active subscriber | None (miss if offline) | Cache bust, multi-side-effect fan-out |
194
+ | `signal.live` | SSE tape | HTTP clients via `http.live` | `Last-Event-ID` resume | Status feeds, progress |
195
+
196
+ | Guarantee | Broadcast | Once | Live |
197
+ | ---------------------------------- | ---------------------- | ------------------ | ----------------- |
198
+ | Competing claim / visibility lease | No | Yes (default 30s) | No |
199
+ | Dead-letter queue path | No | Yes (`deadLetter`) | No |
200
+ | Retained history for late joiners | No | No | Yes |
201
+ | Multiple Flows on one emit | Yes — each gets a copy | One claimer | Not a Flow worker |
202
+
203
+ ## Options for broadcast
204
+
205
+ Second argument to `signal.broadcast(name, options?)`. Delivery is the helper name — not an
206
+ option.
207
+
208
+ | Option | Type | Default | Meaning |
209
+ | ------------- | --------------- | -------- | -------------------------------------------------------- |
210
+ | `schema` | Standard Schema | omitted | **Emit** contract — validated at `fx.emit` (**OKE1250**) |
211
+ | `optional` | `boolean` | `false` | Allow emit with zero subscribers |
212
+ | `description` | `string` | the name | Console / docs blurb |
213
+ | `retries` | `number` | `3` | Declared; not the `once` lease / DLQ path |
214
+ | `deadLetter` | `boolean` | `true` | Declared; broadcast does not use once DLQ |
215
+
216
+ `retention` is a type error on `signal.broadcast` — that option is live-only:
217
+
218
+ ```text
219
+ signal.broadcast("…"): retention is only valid with signal.live
220
+ ```
221
+
222
+ ## Fan-out Physics
223
+
224
+ <Callout title="Detailed section">
225
+ If you only need one subscriber, jump to Emit. Fan-out is the distinctive physics: one emit, N
226
+ independent Flow runs, no competing claim.
227
+ </Callout>
228
+
229
+ Bind any number of Flows with `on(theSameSignal, flow(...))`. At dispatch, every matching
230
+ binding runs with the same payload.
231
+
232
+ ```typescript title="src/flows/orders/side-effects.ts"
233
+ import { on, flow } from "okengine";
234
+ import { orderChanged } from "@/signals/orders";
235
+
236
+ export const bustOrderCache = on(
237
+ orderChanged,
238
+ flow("orders.bustCache", {
239
+ do: async ({ orderId }, fx) => {
240
+ await fx.store.kv.delete(`order:${orderId}`);
241
+ },
242
+ }),
243
+ );
244
+
245
+ export const notifyOrderWatchers = on(
246
+ orderChanged,
247
+ flow("orders.notifyWatchers", {
248
+ do: async ({ orderId }, fx) => {
249
+ await fx.call(pushOrderWatchers, { orderId });
250
+ },
251
+ }),
252
+ );
253
+ ```
254
+
255
+ ```typescript
256
+ await fx.emit(orderChanged, { orderId: "ord_42", kind: "placed" });
257
+ ```
258
+
259
+ | Concept | Meaning |
260
+ | ---------- | ---------------------------------------------------------- |
261
+ | Subscriber | An adopted Flow bound with `on(broadcastSignal, flow)` |
262
+ | Copy | Each subscriber receives the payload independently |
263
+ | Isolation | One handler's failure does not lease-lock siblings |
264
+ | Offline | A process that was down at emit time does not get a replay |
265
+
266
+ **Consequence:** treat broadcast handlers as best-effort side effects. If the work must run
267
+ exactly once with retries and a DLQ, declare a separate `signal.once` for that job.
268
+
269
+ ## Emit and Effects
270
+
271
+ <Callout title="Detailed section">
272
+ If you only need `fx.emit(signal, payload)`, jump to the example. Emit commits the outbox when the
273
+ call resolves. Subscribers run asynchronously afterward.
274
+ </Callout>
275
+
276
+ | Call | Records | Use |
277
+ | ----------------------------------- | ------- | ----------------------------------------------------------- |
278
+ | `fx.emit(signal, payload?)` | `emits` | Publish to every active subscriber |
279
+ | `fx.emit(signal, payload, { key })` | `emits` | `key` is for `once` ordering — ignore for broadcast fan-out |
280
+
281
+ ```typescript title="src/flows/catalog/create.ts"
282
+ import { on, flow, http } from "okengine";
283
+ import { z } from "zod";
284
+ import { catalogChanged } from "@/signals/catalog";
285
+
286
+ export const create = on(
287
+ http.post(),
288
+ flow({
289
+ in: z.object({ sku: z.string(), title: z.string().min(1) }),
290
+ do: async ({ sku, title }, fx) => {
291
+ // … write …
292
+ await fx.emit(catalogChanged, { sku });
293
+ return { sku, title };
294
+ },
295
+ }),
296
+ );
297
+ ```
298
+
299
+ The producer WideEvent / run id is stamped as `parentRunId` for Console trace chains. Schema
300
+ mismatches fail at emit (**OKE1250**) before any subscriber runs.
301
+
302
+ ## Subscribers
303
+
304
+ Each verb of fan-out is a normal Flow. Bind with `on(signal, flow(...))` — same species as
305
+ [Consumers](/docs/elements/flow/consumers).
306
+
307
+ <Tabs items={["Cache", "Notify", "Reindex", "Compose"]}>
308
+
309
+ <Tab value="Cache">
310
+
311
+ Drop hot keys when the source of truth changes:
312
+
313
+ ```typescript title="src/flows/cache/on-catalog.ts"
314
+ import { on, flow } from "okengine";
315
+ import { catalogChanged } from "@/signals/catalog";
316
+
317
+ export const onCatalogCache = on(
318
+ catalogChanged,
319
+ flow("cache.onCatalog", {
320
+ do: async ({ sku }, fx) => {
321
+ await fx.store.kv.delete(`sku:${sku}`);
322
+ await fx.store.kv.delete(`sku:${sku}:meta`);
323
+ },
324
+ }),
325
+ );
326
+ ```
327
+
328
+ </Tab>
329
+
330
+ <Tab value="Notify">
331
+
332
+ Fan a domain event into a Channel or another Flow without competing for a lease:
333
+
334
+ ```typescript title="src/flows/notify/on-order.ts"
335
+ import { on, flow } from "okengine";
336
+ import { orderChanged } from "@/signals/orders";
337
+ import { orderWatchers } from "@/channels/orders";
338
+
339
+ export const onOrderNotify = on(
340
+ orderChanged,
341
+ flow("notify.onOrder", {
342
+ do: async ({ orderId }, fx) => {
343
+ await fx.send(orderWatchers, {
344
+ to: "ops@example.com",
345
+ data: { orderId },
346
+ });
347
+ },
348
+ }),
349
+ );
350
+ ```
351
+
352
+ </Tab>
353
+
354
+ <Tab value="Reindex">
355
+
356
+ Kick search / projection work as a side effect of the same emit:
357
+
358
+ ```typescript title="src/flows/search/on-catalog.ts"
359
+ import { on, flow } from "okengine";
360
+ import { catalogChanged } from "@/signals/catalog";
361
+
362
+ export const onCatalogReindex = on(
363
+ catalogChanged,
364
+ flow("search.onCatalog", {
365
+ do: async ({ sku }, fx) => {
366
+ await fx.call(rebuildSearchDoc, { sku });
367
+ },
368
+ }),
369
+ );
370
+ ```
371
+
372
+ </Tab>
373
+
374
+ <Tab value="Compose">
375
+
376
+ Need durable retries for one side effect? Emit a `once` job from the broadcast handler (or from
377
+ the producer) — do not stretch broadcast into a queue:
378
+
379
+ ```typescript title="src/flows/orders/on-changed.ts"
380
+ import { on, flow } from "okengine";
381
+ import { orderChanged, orderSyncJob } from "@/signals/orders";
382
+
383
+ export const onOrderChanged = on(
384
+ orderChanged,
385
+ flow("orders.onChanged", {
386
+ do: async ({ orderId }, fx) => {
387
+ await fx.store.kv.delete(`order:${orderId}`);
388
+ await fx.emit(orderSyncJob, { orderId });
389
+ },
390
+ }),
391
+ );
392
+ ```
393
+
394
+ `orderSyncJob` is `signal.once(...)` — competing workers, retries, and DLQ live there.
395
+
396
+ </Tab>
397
+
398
+ </Tabs>
399
+
400
+ ## Choosing Physics
401
+
402
+ <Callout title="Detailed section">
403
+ Pick physics from the guarantee you need. Broadcast is the wrong tool when work must be claimed
404
+ once, or when a late client must catch up.
405
+ </Callout>
406
+
407
+ | Need | Use instead |
408
+ | ------------------------------------- | ---------------------------------------------------- |
409
+ | Exactly one worker processes the job | [`once`](/docs/elements/signal/once) |
410
+ | Browser / SSE resume after disconnect | [`live`](/docs/elements/signal/live) |
411
+ | Durable multi-step work with journal | [Durable Workflows](/docs/elements/flow/workflows) |
412
+ | HTTP SSE firehose on a path | [`http.live`](/docs/elements/flow/http#live-streams) |
413
+
414
+ Broadcast does **not** use the `once` visibility lease. There is no competing claim and no
415
+ dead-letter queue for exhausted retries in the queue sense — see
416
+ [Once · Retries](/docs/elements/signal/once#retries-and-dead-letters).
417
+
418
+ <Accordions>
419
+
420
+ <Accordion title="Orphan emit (OKE1240)">
421
+ Cause: `Flow "{flow}" emits signal "{resource}" with no subscriber.`
422
+ Fix: bind at least one `on(signal, flow)` or set `{ optional: true }`.
423
+ </Accordion>
424
+
425
+ <Accordion title="Schema (OKE1250)">
426
+ Cause: `"{resource}": {detail}`. Fix the payload or the `schema`. No subscriber ran.
427
+ </Accordion>
428
+
429
+ <Accordion title="Missed while offline">
430
+ Expected. Restarted processes do not replay broadcast history. Emit again after boot if the
431
+ listener must refresh state, or switch to `live` for a retained tape.
432
+ </Accordion>
433
+
434
+ <Accordion title="retries / deadLetter on the declaration">
435
+ Those fields exist on the shared options type (defaults `3` / `true`) so Console and Manifest stay
436
+ uniform. Broadcast delivery does not follow the once lease + DLQ path — do not expect
437
+ `fx.deadLetters(broadcastSignal)` to behave like a queue operator surface.
438
+ </Accordion>
439
+
440
+ <Accordion title="retention on broadcast">
441
+ Throws at declare: `signal.broadcast("…"): retention is only valid with signal.live`. Cap a
442
+ client-visible tape with `signal.live({retention})` instead.
443
+ </Accordion>
444
+
445
+ </Accordions>
446
+
447
+ ## Troubleshooting
448
+
449
+ <Accordions>
450
+
451
+ <Accordion title="Only one of two subscribers runs">
452
+ Confirm both Flows import the same Signal handle / name and are adopted into the app. Names must
453
+ match the Manifest entry exactly. A second `signal.broadcast("catalog.changed")` with a different
454
+ spelling is a different signal.
455
+ </Accordion>
456
+
457
+ <Accordion title="Subscriber missed an event after restart">
458
+ Broadcast has no tape. Re-emit on boot, hydrate from Store, or use
459
+ [`signal.live`](/docs/elements/signal/live).
460
+ </Accordion>
461
+
462
+ <Accordion title="OKE1240 on emit">
463
+ Cause: `Flow "{flow}" emits signal "{resource}" with no subscriber.`
464
+ No subscriber is bound. Add `on(signal, …)` or `{ optional: true }`.
465
+ </Accordion>
466
+
467
+ <Accordion title="OKE1250 on emit">
468
+ Cause: `"{resource}": {detail}`. Align the payload with `schema` — no subscriber started.
469
+ </Accordion>
470
+
471
+ <Accordion title="Expecting retries / DLQ like a queue">
472
+ Use `signal.once`. Broadcast fan-out is not the lease + DLQ path documented under
473
+ [Once](/docs/elements/signal/once).
474
+ </Accordion>
475
+
476
+ <Accordion title="TypeError: retention is only valid with signal.live">
477
+ You passed `retention` to `signal.broadcast`. Drop it, or switch the helper to `signal.live` if
478
+ you need a retained SSE tape.
479
+ </Accordion>
480
+
481
+ <Accordion title="HTTP client never sees the event">
482
+ Broadcast is for Flow subscribers, not browsers. Expose a
483
+ [`signal.live`](/docs/elements/signal/live) tape with
484
+ [`http.live`](/docs/elements/flow/http#live-streams) (or a gated GET `.live(signal)`).
485
+ </Accordion>
486
+
487
+ </Accordions>
488
+
489
+ ## Learn more
490
+
491
+ - [Signal Overview](/docs/elements/signal) — delivery matrix and drivers
492
+ - [Once](/docs/elements/signal/once) — competing workers and DLQ
493
+ - [Live](/docs/elements/signal/live) — retained SSE
494
+ - [Consumers](/docs/elements/flow/consumers) — binding `on(signal)`
495
+ - [HTTP · Live Streams](/docs/elements/flow/http#live-streams) — when the consumer is a browser
496
+ - [fx](/docs/reference/fx) — `fx.emit`
497
+ - [Errors](/docs/reference/errors) — OKE1240 · OKE1250
498
+
499
+ ## Next
500
+
501
+ <Cards>
502
+ <Card
503
+ title="Live"
504
+ description="Retained event tapes over HTTP SSE."
505
+ href="/docs/elements/signal/live"
506
+ />
507
+ <Card
508
+ title="Once"
509
+ description="Competing workers with leases and dead letters."
510
+ href="/docs/elements/signal/once"
511
+ />
512
+ <Card
513
+ title="Consumers"
514
+ description="Signal workers, Clock jobs, and SQL CDC."
515
+ href="/docs/elements/flow/consumers"
516
+ />
517
+ </Cards>