okengine 0.2.6 → 0.3.2

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 (681) hide show
  1. package/AGENTS.md +33 -30
  2. package/README.md +74 -340
  3. package/{spec/manifest.v1.schema.json → manifest.v1.schema.json} +40 -9
  4. package/package.json +47 -44
  5. package/site/content/docs/ai/llms-txt.mdx +54 -0
  6. package/site/content/docs/ai/mcp.mdx +123 -0
  7. package/site/content/docs/ai/meta.json +5 -0
  8. package/site/content/docs/ai/skills.mdx +53 -0
  9. package/site/content/docs/console/access.mdx +29 -0
  10. package/site/content/docs/console/ai.mdx +35 -0
  11. package/site/content/docs/console/architecture.mdx +35 -0
  12. package/site/content/docs/console/channels.mdx +37 -0
  13. package/site/content/docs/console/clock.mdx +31 -0
  14. package/site/content/docs/console/flows.mdx +31 -0
  15. package/site/content/docs/console/gates.mdx +35 -0
  16. package/site/content/docs/console/manifest-diff.mdx +34 -0
  17. package/site/content/docs/console/meta.json +23 -0
  18. package/site/content/docs/console/overview.mdx +40 -0
  19. package/site/content/docs/console/plugins.mdx +41 -0
  20. package/site/content/docs/console/privacy.mdx +32 -0
  21. package/site/content/docs/console/runs.mdx +40 -0
  22. package/site/content/docs/console/signals.mdx +31 -0
  23. package/site/content/docs/console/store.mdx +32 -0
  24. package/site/content/docs/console/tenancy.mdx +32 -0
  25. package/site/content/docs/console/traces.mdx +34 -0
  26. package/site/content/docs/console/vault.mdx +37 -0
  27. package/site/content/docs/elements/ai.mdx +180 -0
  28. package/site/content/docs/elements/channel.mdx +167 -0
  29. package/site/content/docs/elements/clock.mdx +182 -0
  30. package/site/content/docs/elements/flow.mdx +288 -0
  31. package/site/content/docs/elements/gate.mdx +171 -0
  32. package/site/content/docs/elements/meta.json +5 -0
  33. package/site/content/docs/elements/signal.mdx +171 -0
  34. package/site/content/docs/elements/store.mdx +320 -0
  35. package/site/content/docs/elements/vault.mdx +263 -0
  36. package/site/content/docs/get-started/basic-usage.mdx +124 -0
  37. package/site/content/docs/get-started/comparison.mdx +65 -0
  38. package/site/content/docs/get-started/installation.mdx +113 -0
  39. package/site/content/docs/get-started/introduction.mdx +123 -0
  40. package/site/content/docs/get-started/meta.json +5 -0
  41. package/site/content/docs/index.mdx +63 -0
  42. package/site/content/docs/meta.json +5 -0
  43. package/site/content/docs/plugins/compression.mdx +60 -0
  44. package/site/content/docs/plugins/cors.mdx +92 -0
  45. package/site/content/docs/plugins/csrf.mdx +96 -0
  46. package/site/content/docs/plugins/ip-allowlist.mdx +92 -0
  47. package/site/content/docs/plugins/maintenance-mode.mdx +101 -0
  48. package/site/content/docs/plugins/meta.json +15 -0
  49. package/site/content/docs/plugins/security-headers.mdx +136 -0
  50. package/site/content/docs/reference/cli.md +101 -0
  51. package/site/content/docs/reference/configuration.mdx +159 -0
  52. package/site/content/docs/reference/environment-variables.mdx +87 -0
  53. package/site/content/docs/reference/errors.mdx +80 -0
  54. package/site/content/docs/reference/fx.mdx +117 -0
  55. package/site/content/docs/reference/meta.json +5 -0
  56. package/site/content/docs/reference/plugins.mdx +249 -0
  57. package/site/content/docs/reference/security.md +63 -0
  58. package/src/auth/api-keys.ts +2 -5
  59. package/src/auth/attenuation.ts +2 -6
  60. package/src/auth/auth.test.ts +56 -31
  61. package/src/auth/invoke-as.ts +1 -3
  62. package/src/auth/operator.ts +8 -26
  63. package/src/auth/planes.ts +2 -7
  64. package/src/auth/plugin.ts +4 -7
  65. package/src/auth/roles.ts +1 -5
  66. package/src/auth/sessions.ts +10 -39
  67. package/src/cli/args.ts +1 -5
  68. package/src/cli/ask-dev-mode.ts +40 -0
  69. package/src/cli/build.ts +1 -2
  70. package/src/cli/client-add.test.ts +8 -15
  71. package/src/cli/client-add.ts +5 -14
  72. package/src/cli/completion.test.ts +1 -5
  73. package/src/cli/completion.ts +4 -17
  74. package/src/cli/db-auto-push.test.ts +64 -0
  75. package/src/cli/db-auto-push.ts +72 -0
  76. package/src/cli/db.test.ts +189 -0
  77. package/src/cli/db.ts +456 -0
  78. package/src/cli/dev-app-runner.ts +2 -8
  79. package/src/cli/dev-db-push.test.ts +193 -0
  80. package/src/cli/dev-mode.test.ts +45 -0
  81. package/src/cli/dev-mode.ts +78 -0
  82. package/src/cli/dev-schema-sync.test.ts +66 -0
  83. package/src/cli/dev-schema-sync.ts +139 -0
  84. package/src/cli/dev.test.ts +264 -15
  85. package/src/cli/dev.ts +355 -139
  86. package/src/cli/doc-staleness.test.ts +69 -0
  87. package/src/cli/docker-cli.test.ts +27 -8
  88. package/src/cli/docker.ts +11 -3
  89. package/src/cli/doctor-diff.test.ts +1 -5
  90. package/src/cli/doctor-diff.ts +17 -46
  91. package/src/cli/doctor-pii.ts +2 -7
  92. package/src/cli/doctor.test.ts +26 -4
  93. package/src/cli/doctor.ts +35 -11
  94. package/src/cli/drizzle-env.test.ts +67 -0
  95. package/src/cli/drizzle-env.ts +78 -0
  96. package/src/cli/ensure-drizzle-config.ts +50 -0
  97. package/src/cli/eval.ts +4 -14
  98. package/src/cli/gates-list.ts +1 -4
  99. package/src/cli/hero-meta.test.ts +17 -19
  100. package/src/cli/hero-meta.ts +20 -30
  101. package/src/cli/images.ts +3 -8
  102. package/src/cli/index.ts +11 -2
  103. package/src/cli/load-config.images.test.ts +14 -14
  104. package/src/cli/load-config.ts +47 -47
  105. package/src/cli/mcp-from-console.ts +1 -4
  106. package/src/cli/mode.ts +65 -0
  107. package/src/cli/openbao-bootstrap.test.ts +147 -0
  108. package/src/cli/openbao-bootstrap.ts +280 -0
  109. package/src/cli/openbao-restart.integration.test.ts +136 -0
  110. package/src/cli/ports.test.ts +7 -14
  111. package/src/cli/ports.ts +7 -5
  112. package/src/cli/privacy-erase.ts +1 -3
  113. package/src/cli/registry.ts +75 -25
  114. package/src/cli/resolve-dev-sql-env.test.ts +48 -0
  115. package/src/cli/resolve-dev-sql-env.ts +42 -0
  116. package/src/cli/schema.ts +3 -10
  117. package/src/cli/stack.ts +8 -10
  118. package/src/cli/start.ts +4 -15
  119. package/src/cli/upgrade.ts +1 -4
  120. package/src/cli/vault-cmd.ts +63 -0
  121. package/src/client/budget-entry.ts +1 -6
  122. package/src/client/create.test.ts +6 -12
  123. package/src/client/create.ts +2 -8
  124. package/src/client/errors.ts +1 -4
  125. package/src/client/index.ts +1 -6
  126. package/src/client/notes-contract.test.ts +4 -13
  127. package/src/client/transport.ts +9 -30
  128. package/src/client/types.ts +15 -30
  129. package/src/compiler/aot.test.ts +2 -9
  130. package/src/compiler/aot.ts +1 -4
  131. package/src/compiler/differential.test.ts +3 -11
  132. package/src/compiler/dynamic.ts +1 -4
  133. package/src/compiler/effects-infer.ts +8 -34
  134. package/src/compiler/emit.ts +1 -3
  135. package/src/compiler/extract.test.ts +136 -30
  136. package/src/compiler/extract.ts +485 -157
  137. package/src/compiler/fixtures/skyport/oke.config.ts +3 -3
  138. package/src/compiler/fixtures/skyport/src/flows/bookings/index.ts +7 -3
  139. package/src/compiler/fixtures/skyport/src/flows/payments/index.ts +1 -3
  140. package/src/compiler/fixtures/skyport/src/flows/support/index.ts +4 -1
  141. package/src/compiler/fixtures/skyport.expected.json +24 -83
  142. package/src/compiler/fixtures/triggers/five-triggers.ts +1 -2
  143. package/src/compiler/http-parse.ts +2 -9
  144. package/src/compiler/index.ts +1 -5
  145. package/src/compiler/response.ts +16 -7
  146. package/src/compiler/sucrose.ts +11 -31
  147. package/src/config/define-config.test.ts +55 -34
  148. package/src/config/index.ts +177 -46
  149. package/src/config/resolve-driver.test.ts +28 -17
  150. package/src/console/budget.test.ts +1 -4
  151. package/src/console/index.ts +1 -4
  152. package/src/console/server/access.test.ts +15 -43
  153. package/src/console/server/access.ts +19 -49
  154. package/src/console/server/ai.test.ts +3 -11
  155. package/src/console/server/ai.ts +23 -46
  156. package/src/console/server/app.ts +16 -45
  157. package/src/console/server/auth-rate.test.ts +4 -12
  158. package/src/console/server/bind.ts +1 -5
  159. package/src/console/server/channels.test.ts +2 -6
  160. package/src/console/server/channels.ts +10 -35
  161. package/src/console/server/claim.ts +2 -8
  162. package/src/console/server/clock.test.ts +2 -6
  163. package/src/console/server/clock.ts +8 -26
  164. package/src/console/server/console.test.ts +1 -3
  165. package/src/console/server/diff.test.ts +5 -17
  166. package/src/console/server/diff.ts +10 -31
  167. package/src/console/server/dry-run.audit.test.ts +2 -8
  168. package/src/console/server/flows.ts +64 -233
  169. package/src/console/server/gates.test.ts +14 -50
  170. package/src/console/server/gates.ts +13 -48
  171. package/src/console/server/index.ts +2 -9
  172. package/src/console/server/live.ts +3 -12
  173. package/src/console/server/operator-db.test.ts +4 -15
  174. package/src/console/server/operator-db.ts +13 -32
  175. package/src/console/server/plugin.ts +50 -53
  176. package/src/console/server/plugins.test.ts +2 -6
  177. package/src/console/server/plugins.ts +17 -56
  178. package/src/console/server/runs-pii.ts +2 -7
  179. package/src/console/server/security.gate.test.ts +18 -40
  180. package/src/console/server/serve.ts +10 -25
  181. package/src/console/server/signals.test.ts +1 -3
  182. package/src/console/server/signals.ts +17 -40
  183. package/src/console/server/state.ts +21 -59
  184. package/src/console/server/store.test.ts +20 -22
  185. package/src/console/server/store.ts +31 -76
  186. package/src/console/server/vault.test.ts +12 -29
  187. package/src/console/server/vault.ts +44 -66
  188. package/src/console/ui/access/AccessA11yView.tsx +6 -21
  189. package/src/console/ui/access/a11y.test.tsx +3 -12
  190. package/src/console/ui/access/acknowledgement.test.ts +1 -4
  191. package/src/console/ui/access/acknowledgement.ts +1 -3
  192. package/src/console/ui/access/blast-radius.ts +1 -3
  193. package/src/console/ui/access/confirmation.test.ts +1 -5
  194. package/src/console/ui/access/confirmation.ts +1 -4
  195. package/src/console/ui/access/fixture.ts +1 -5
  196. package/src/console/ui/access/grantable.test.ts +1 -3
  197. package/src/console/ui/access/grantable.ts +2 -7
  198. package/src/console/ui/access/index.ts +2 -9
  199. package/src/console/ui/access/provenance.test.ts +1 -3
  200. package/src/console/ui/access/provenance.ts +2 -6
  201. package/src/console/ui/access/search.test.ts +1 -5
  202. package/src/console/ui/access/search.ts +2 -6
  203. package/src/console/ui/ai/AiA11yView.tsx +6 -17
  204. package/src/console/ui/ai/a11y.test.tsx +2 -5
  205. package/src/console/ui/ai/fixture.ts +2 -7
  206. package/src/console/ui/ai/format.ts +1 -3
  207. package/src/console/ui/ai/group.test.ts +1 -3
  208. package/src/console/ui/ai/group.ts +4 -12
  209. package/src/console/ui/ai/index.ts +2 -9
  210. package/src/console/ui/ai/promotion.test.ts +2 -7
  211. package/src/console/ui/ai/promotion.ts +4 -13
  212. package/src/console/ui/ai/search.test.ts +2 -11
  213. package/src/console/ui/ai/search.ts +3 -13
  214. package/src/console/ui/ai/types.ts +1 -8
  215. package/src/console/ui/architecture/ArchitectureA11yView.tsx +8 -29
  216. package/src/console/ui/architecture/a11y.test.tsx +1 -2
  217. package/src/console/ui/architecture/declared.ts +1 -5
  218. package/src/console/ui/architecture/index.ts +7 -35
  219. package/src/console/ui/architecture/layout.ts +1 -3
  220. package/src/console/ui/architecture/pathologies.ts +10 -19
  221. package/src/console/ui/architecture/search.ts +6 -15
  222. package/src/console/ui/architecture/traffic.ts +4 -14
  223. package/src/console/ui/architecture/types.ts +2 -10
  224. package/src/console/ui/architecture/view.test.ts +7 -19
  225. package/src/console/ui/architecture/view.ts +9 -36
  226. package/src/console/ui/channels/ChannelsA11yView.tsx +4 -13
  227. package/src/console/ui/channels/a11y.test.tsx +1 -2
  228. package/src/console/ui/channels/confirmation.test.ts +1 -4
  229. package/src/console/ui/channels/confirmation.ts +1 -4
  230. package/src/console/ui/channels/findings.ts +1 -5
  231. package/src/console/ui/channels/index.ts +2 -8
  232. package/src/console/ui/channels/locale.test.ts +1 -3
  233. package/src/console/ui/channels/locale.ts +2 -8
  234. package/src/console/ui/channels/search.ts +4 -13
  235. package/src/console/ui/channels/taxonomy.ts +1 -3
  236. package/src/console/ui/clock/ClockA11yView.tsx +9 -26
  237. package/src/console/ui/clock/a11y.test.tsx +2 -5
  238. package/src/console/ui/clock/confirmation.test.ts +1 -3
  239. package/src/console/ui/clock/confirmation.ts +1 -4
  240. package/src/console/ui/clock/findings.ts +1 -4
  241. package/src/console/ui/clock/health.ts +2 -7
  242. package/src/console/ui/clock/index.ts +2 -10
  243. package/src/console/ui/clock/search.test.ts +1 -3
  244. package/src/console/ui/clock/search.ts +2 -6
  245. package/src/console/ui/clock/timeline.test.ts +1 -3
  246. package/src/console/ui/clock/waiting-on.test.ts +1 -5
  247. package/src/console/ui/clock/waiting-on.ts +2 -7
  248. package/src/console/ui/diff/DiffA11yView.tsx +8 -17
  249. package/src/console/ui/diff/a11y.test.tsx +2 -5
  250. package/src/console/ui/diff/group.test.ts +3 -13
  251. package/src/console/ui/diff/group.ts +2 -8
  252. package/src/console/ui/diff/index.ts +2 -8
  253. package/src/console/ui/diff/search.test.ts +1 -5
  254. package/src/console/ui/diff/search.ts +4 -16
  255. package/src/console/ui/diff/summary.ts +1 -2
  256. package/src/console/ui/dist/assets/{index-Dy4jht9P.js → index-BWo8R7NR.js} +2 -2
  257. package/src/console/ui/dist/assets/{panel-access-Dd37LU2c.js → panel-access-C0J2D-a2.js} +1 -1
  258. package/src/console/ui/dist/assets/{panel-ai-CC7LR6-J.js → panel-ai-D_m6WQI8.js} +1 -1
  259. package/src/console/ui/dist/assets/{panel-architecture-B5b3iKCz.js → panel-architecture-CKnXFyUx.js} +1 -1
  260. package/src/console/ui/dist/assets/{panel-channels-CeNjTKXp.js → panel-channels-BOmQ-onL.js} +1 -1
  261. package/src/console/ui/dist/assets/{panel-clock-DgFTLoHV.js → panel-clock-giAq0Ccv.js} +1 -1
  262. package/src/console/ui/dist/assets/{panel-diff-DxehccqB.js → panel-diff-cdonmH8c.js} +1 -1
  263. package/src/console/ui/dist/assets/{panel-flows-BtrVn-Eg.js → panel-flows-DlCU5zjA.js} +2 -2
  264. package/src/console/ui/dist/assets/{panel-gates-Z9MKRGdH.js → panel-gates-XclZxWD5.js} +1 -1
  265. package/src/console/ui/dist/assets/{panel-overview-Dt_AeXgd.js → panel-overview-BznEOTnb.js} +1 -1
  266. package/src/console/ui/dist/assets/{panel-plugins-DWd0TowH.js → panel-plugins-CcGM1g64.js} +1 -1
  267. package/src/console/ui/dist/assets/{panel-runs-DGstFHeq.js → panel-runs-CGWNHLR4.js} +1 -1
  268. package/src/console/ui/dist/assets/{panel-signals-DzEa2Fnt.js → panel-signals-CNywkdak.js} +1 -1
  269. package/src/console/ui/dist/assets/{panel-store-BJkbNgxx.js → panel-store-KmTbFHMH.js} +1 -1
  270. package/src/console/ui/dist/assets/{panel-traces-BaRVO3gM.js → panel-traces-DBLx2ilD.js} +1 -1
  271. package/src/console/ui/dist/assets/panel-vault-CEnFc0dk.js +1 -0
  272. package/src/console/ui/dist/index.html +1 -1
  273. package/src/console/ui/flows/FlowsA11yView.tsx +7 -22
  274. package/src/console/ui/flows/a11y.test.tsx +2 -8
  275. package/src/console/ui/flows/buffer.test.ts +3 -11
  276. package/src/console/ui/flows/buffer.ts +4 -11
  277. package/src/console/ui/flows/confirmation.ts +1 -3
  278. package/src/console/ui/flows/contract.test.ts +3 -7
  279. package/src/console/ui/flows/contract.ts +12 -36
  280. package/src/console/ui/flows/graph.test.ts +3 -11
  281. package/src/console/ui/flows/graph.ts +5 -15
  282. package/src/console/ui/flows/index.ts +1 -6
  283. package/src/console/ui/flows/save-as-test.ts +1 -3
  284. package/src/console/ui/flows/search.ts +8 -25
  285. package/src/console/ui/flows/tiers.ts +2 -9
  286. package/src/console/ui/gates/GatesA11yView.tsx +4 -13
  287. package/src/console/ui/gates/a11y.test.tsx +2 -5
  288. package/src/console/ui/gates/audit.ts +1 -3
  289. package/src/console/ui/gates/denial.test.ts +1 -3
  290. package/src/console/ui/gates/denial.ts +4 -12
  291. package/src/console/ui/gates/findings.ts +1 -3
  292. package/src/console/ui/gates/group.test.ts +3 -3
  293. package/src/console/ui/gates/group.ts +3 -11
  294. package/src/console/ui/gates/index.ts +2 -10
  295. package/src/console/ui/gates/search.ts +3 -10
  296. package/src/console/ui/overview/OverviewA11yView.tsx +9 -27
  297. package/src/console/ui/overview/a11y.test.tsx +1 -2
  298. package/src/console/ui/overview/busiest.ts +1 -3
  299. package/src/console/ui/overview/compose.ts +1 -13
  300. package/src/console/ui/overview/cost.ts +34 -38
  301. package/src/console/ui/overview/fixture.ts +21 -26
  302. package/src/console/ui/overview/golden.ts +4 -14
  303. package/src/console/ui/overview/index.ts +2 -9
  304. package/src/console/ui/overview/rank.test.ts +3 -10
  305. package/src/console/ui/overview/rank.ts +2 -7
  306. package/src/console/ui/overview/slo.test.ts +5 -9
  307. package/src/console/ui/overview/slo.ts +6 -23
  308. package/src/console/ui/overview/verdict.ts +3 -10
  309. package/src/console/ui/plugins/PluginsA11yView.tsx +8 -18
  310. package/src/console/ui/plugins/a11y.test.tsx +1 -2
  311. package/src/console/ui/plugins/fixture.ts +2 -4
  312. package/src/console/ui/plugins/group.test.ts +1 -3
  313. package/src/console/ui/plugins/group.ts +1 -6
  314. package/src/console/ui/plugins/index.ts +3 -13
  315. package/src/console/ui/plugins/search.ts +7 -19
  316. package/src/console/ui/runs/RunsA11yView.tsx +5 -20
  317. package/src/console/ui/runs/a11y.test.tsx +2 -5
  318. package/src/console/ui/runs/explain.ts +2 -6
  319. package/src/console/ui/runs/group.ts +3 -12
  320. package/src/console/ui/runs/histogram.test.ts +2 -11
  321. package/src/console/ui/runs/histogram.ts +1 -4
  322. package/src/console/ui/runs/index.ts +4 -18
  323. package/src/console/ui/runs/project.ts +2 -6
  324. package/src/console/ui/runs/query.test.ts +1 -3
  325. package/src/console/ui/runs/query.ts +12 -35
  326. package/src/console/ui/runs/search.ts +5 -19
  327. package/src/console/ui/runs/trace-link.test.ts +2 -6
  328. package/src/console/ui/runs/trace-link.ts +4 -16
  329. package/src/console/ui/runs/types.ts +1 -3
  330. package/src/console/ui/shell/App.tsx +2 -10
  331. package/src/console/ui/shell/client.ts +22 -115
  332. package/src/console/ui/shell/components/ui.tsx +1 -4
  333. package/src/console/ui/shell/layout/Shell.tsx +2 -4
  334. package/src/console/ui/shell/main.tsx +31 -96
  335. package/src/console/ui/shell/panels/access/AccessPanel.tsx +34 -98
  336. package/src/console/ui/shell/panels/ai/AiPanel.tsx +35 -106
  337. package/src/console/ui/shell/panels/architecture/ArchitecturePanel.tsx +18 -66
  338. package/src/console/ui/shell/panels/channels/ChannelsPanel.tsx +20 -57
  339. package/src/console/ui/shell/panels/clock/ClockPanel.tsx +22 -54
  340. package/src/console/ui/shell/panels/diff/DiffPanel.tsx +20 -61
  341. package/src/console/ui/shell/panels/flows/ContractEditor.tsx +6 -23
  342. package/src/console/ui/shell/panels/flows/FlowDrawer.tsx +13 -28
  343. package/src/console/ui/shell/panels/flows/FlowsPanel.tsx +9 -29
  344. package/src/console/ui/shell/panels/gates/GatesPanel.tsx +24 -80
  345. package/src/console/ui/shell/panels/overview/OverviewPanel.tsx +20 -58
  346. package/src/console/ui/shell/panels/plugins/PluginsPanel.tsx +16 -41
  347. package/src/console/ui/shell/panels/runs/RunsPanel.tsx +31 -99
  348. package/src/console/ui/shell/panels/signals/SignalsPanel.tsx +38 -101
  349. package/src/console/ui/shell/panels/store/StorePanel.tsx +27 -66
  350. package/src/console/ui/shell/panels/traces/TracesPanel.tsx +28 -89
  351. package/src/console/ui/shell/panels/vault/VaultPanel.tsx +22 -53
  352. package/src/console/ui/shell/setup/Wizard.tsx +3 -5
  353. package/src/console/ui/shell/styles.css +1 -2
  354. package/src/console/ui/signals/SignalsA11yView.tsx +9 -33
  355. package/src/console/ui/signals/a11y.test.tsx +2 -5
  356. package/src/console/ui/signals/confirmation.test.ts +1 -5
  357. package/src/console/ui/signals/confirmation.ts +2 -6
  358. package/src/console/ui/signals/dry-run.ts +1 -3
  359. package/src/console/ui/signals/durable.ts +1 -3
  360. package/src/console/ui/signals/findings.ts +1 -3
  361. package/src/console/ui/signals/group.test.ts +2 -8
  362. package/src/console/ui/signals/group.ts +2 -10
  363. package/src/console/ui/signals/index.ts +1 -4
  364. package/src/console/ui/signals/monitor.ts +2 -7
  365. package/src/console/ui/signals/schema-form.test.ts +3 -14
  366. package/src/console/ui/signals/schema-form.ts +1 -4
  367. package/src/console/ui/signals/search.ts +4 -13
  368. package/src/console/ui/store/StoreA11yView.tsx +3 -9
  369. package/src/console/ui/store/a11y.test.tsx +2 -5
  370. package/src/console/ui/store/confirmation.test.ts +1 -3
  371. package/src/console/ui/store/confirmation.ts +1 -4
  372. package/src/console/ui/store/dry-run.ts +2 -5
  373. package/src/console/ui/store/fixture.ts +1 -2
  374. package/src/console/ui/store/group.test.ts +1 -6
  375. package/src/console/ui/store/group.ts +1 -4
  376. package/src/console/ui/store/index.ts +1 -5
  377. package/src/console/ui/store/search.ts +2 -6
  378. package/src/console/ui/store/will-not-fire.ts +1 -2
  379. package/src/console/ui/traces/TracesA11yView.tsx +10 -29
  380. package/src/console/ui/traces/a11y.test.tsx +1 -2
  381. package/src/console/ui/traces/chain.test.ts +5 -19
  382. package/src/console/ui/traces/chain.ts +1 -3
  383. package/src/console/ui/traces/critical-path.test.ts +1 -3
  384. package/src/console/ui/traces/critical-path.ts +2 -6
  385. package/src/console/ui/traces/filter.test.ts +2 -6
  386. package/src/console/ui/traces/filter.ts +5 -13
  387. package/src/console/ui/traces/fold.test.ts +4 -12
  388. package/src/console/ui/traces/fold.ts +1 -3
  389. package/src/console/ui/traces/mini.ts +1 -3
  390. package/src/console/ui/traces/replay.test.ts +1 -3
  391. package/src/console/ui/traces/replay.ts +1 -3
  392. package/src/console/ui/traces/sampling.ts +1 -4
  393. package/src/console/ui/traces/search.ts +4 -17
  394. package/src/console/ui/traces/tier.ts +1 -3
  395. package/src/console/ui/vault/VaultA11yView.tsx +5 -17
  396. package/src/console/ui/vault/a11y.test.tsx +1 -2
  397. package/src/console/ui/vault/blast-radius.ts +1 -3
  398. package/src/console/ui/vault/confirmation.test.ts +1 -5
  399. package/src/console/ui/vault/confirmation.ts +1 -4
  400. package/src/console/ui/vault/dormant.test.ts +1 -4
  401. package/src/console/ui/vault/export-safe.test.ts +1 -5
  402. package/src/console/ui/vault/export-safe.ts +1 -4
  403. package/src/console/ui/vault/fixture.ts +3 -3
  404. package/src/console/ui/vault/group.ts +1 -2
  405. package/src/console/ui/vault/index.ts +2 -10
  406. package/src/console/ui/vault/search.ts +2 -6
  407. package/src/console/ui/vault/types.ts +1 -1
  408. package/src/console/xss.gate.test.ts +1 -3
  409. package/src/docker/compose.ts +215 -27
  410. package/src/docker/derive.ts +6 -14
  411. package/src/docker/docker.test.ts +118 -15
  412. package/src/docker/dockerfile.integration.test.ts +12 -21
  413. package/src/docker/helpers.ts +5 -4
  414. package/src/docker/index.ts +9 -6
  415. package/src/docker/pin.ts +1 -9
  416. package/src/docker/recipes/index.ts +6 -6
  417. package/src/docker/recipes/mailpit.ts +22 -0
  418. package/src/docker/recipes/openbao.ts +47 -0
  419. package/src/docker/recipes/postgres.ts +17 -6
  420. package/src/docker/recipes/redis.ts +11 -2
  421. package/src/docker/recipes/rustfs.ts +32 -0
  422. package/src/docker/stack-id.test.ts +73 -5
  423. package/src/docker/stack-id.ts +124 -33
  424. package/src/docker/stack.integration.test.ts +6 -21
  425. package/src/docker/stack.ts +36 -4
  426. package/src/docker/types.ts +16 -4
  427. package/src/docs-origin.ts +4 -4
  428. package/src/drivers/ai-anthropic.ts +5 -14
  429. package/src/drivers/ai-mock.ts +2 -5
  430. package/src/drivers/ai-openai-compatible.ts +4 -10
  431. package/src/drivers/ai-providers.test.ts +6 -13
  432. package/src/drivers/bun-native-completeness.test.ts +6 -15
  433. package/src/drivers/channel-console.ts +6 -7
  434. package/src/drivers/channel-fcm.ts +20 -30
  435. package/src/drivers/channel-resend.ts +1 -3
  436. package/src/drivers/channel-smtp.ts +2 -6
  437. package/src/drivers/channel-types.ts +2 -12
  438. package/src/drivers/channel-unifonic.ts +2 -6
  439. package/src/drivers/channel-wa-cloud.ts +2 -6
  440. package/src/drivers/channel-webpush.ts +21 -60
  441. package/src/drivers/conformance.test.ts +2 -10
  442. package/src/drivers/conformance.ts +8 -21
  443. package/src/drivers/drizzle-dialect.test.ts +20 -0
  444. package/src/drivers/drizzle-dialect.ts +37 -0
  445. package/src/drivers/fs.ts +2 -8
  446. package/src/drivers/index.ts +12 -57
  447. package/src/drivers/memory.ts +287 -75
  448. package/src/drivers/pgvector.ts +7 -11
  449. package/src/drivers/postgres.ts +7 -13
  450. package/src/drivers/redis.ts +13 -37
  451. package/src/drivers/s3.ts +15 -18
  452. package/src/drivers/signal-engine.ts +12 -49
  453. package/src/drivers/signal-memory.ts +2 -8
  454. package/src/drivers/signal-nats.ts +4 -9
  455. package/src/drivers/signal-postgres.ts +62 -129
  456. package/src/drivers/signal-redis.ts +7 -21
  457. package/src/drivers/signal-types.ts +1 -4
  458. package/src/drivers/sqlite.ts +3 -12
  459. package/src/drivers/types.ts +4 -23
  460. package/src/drivers/vault-driver-removal.test.ts +55 -0
  461. package/src/drivers/vault-env.ts +2 -6
  462. package/src/drivers/vault-infisical.ts +1 -5
  463. package/src/drivers/vault-managed.ts +1 -5
  464. package/src/drivers/vault-memory.ts +1 -5
  465. package/src/drivers/vault-openbao.test.ts +97 -0
  466. package/src/drivers/vault-openbao.ts +103 -40
  467. package/src/drivers/vault-types.ts +3 -16
  468. package/src/elements/ai/declare.ts +4 -14
  469. package/src/elements/ai/eval.ts +2 -6
  470. package/src/elements/ai/pii.ts +1 -3
  471. package/src/elements/ai/runtime.ts +12 -50
  472. package/src/elements/ai/schema.ts +4 -15
  473. package/src/elements/ai.test.ts +13 -24
  474. package/src/elements/ai.ts +1 -5
  475. package/src/elements/channel/consent.ts +3 -12
  476. package/src/elements/channel/costs.ts +4 -15
  477. package/src/elements/channel/declare.ts +1 -4
  478. package/src/elements/channel/email-auth.ts +1 -4
  479. package/src/elements/channel/locale.ts +2 -10
  480. package/src/elements/channel/mask.ts +1 -3
  481. package/src/elements/channel/mime.ts +2 -12
  482. package/src/elements/channel/outcomes.test.ts +1 -5
  483. package/src/elements/channel/outcomes.ts +5 -15
  484. package/src/elements/channel/receipts.ts +2 -7
  485. package/src/elements/channel/runtime.ts +28 -74
  486. package/src/elements/channel/suppression.ts +8 -14
  487. package/src/elements/channel.test.ts +8 -31
  488. package/src/elements/channel.ts +3 -14
  489. package/src/elements/clock/actions.ts +6 -18
  490. package/src/elements/clock/declare.ts +1 -3
  491. package/src/elements/clock/dst.ts +1 -5
  492. package/src/elements/clock/durable.ts +4 -16
  493. package/src/elements/clock/health.ts +2 -7
  494. package/src/elements/clock/leader.ts +2 -9
  495. package/src/elements/clock/reconcile.ts +4 -15
  496. package/src/elements/clock/runtime.ts +2 -5
  497. package/src/elements/clock/schedule.ts +4 -20
  498. package/src/elements/clock.test.ts +2 -7
  499. package/src/elements/clock.ts +5 -23
  500. package/src/elements/gate/permissions.ts +1 -4
  501. package/src/elements/gate/runtime.ts +7 -28
  502. package/src/elements/gate.ts +1 -5
  503. package/src/elements/index.ts +9 -7
  504. package/src/elements/signal/declare.ts +2 -7
  505. package/src/elements/signal/reconcile.test.ts +1 -4
  506. package/src/elements/signal/runtime.ts +1 -3
  507. package/src/elements/signal.test.ts +3 -15
  508. package/src/elements/signal.ts +2 -8
  509. package/src/elements/store/cache.test.ts +46 -0
  510. package/src/elements/store/cache.ts +3 -11
  511. package/src/elements/store/classify.ts +5 -22
  512. package/src/elements/store/declare.ts +11 -17
  513. package/src/elements/store/domain-ddl.test.ts +86 -0
  514. package/src/elements/store/emit-drizzle.ts +363 -0
  515. package/src/elements/store/files-policy.test.ts +1 -5
  516. package/src/elements/store/files-policy.ts +4 -12
  517. package/src/elements/store/load-plugin-tables.ts +103 -0
  518. package/src/elements/store/missing-relation.test.ts +83 -0
  519. package/src/elements/store/missing-relation.ts +38 -0
  520. package/src/elements/store/replica.ts +1 -4
  521. package/src/elements/store/resource-list-docs.fixture.ts +56 -0
  522. package/src/elements/store/resource-list-docs.test.ts +79 -0
  523. package/src/elements/store/resource.test.ts +253 -0
  524. package/src/elements/store/resource.ts +786 -0
  525. package/src/elements/store/runtime.ts +25 -28
  526. package/src/elements/store/schema-decl.test.ts +227 -0
  527. package/src/elements/store/schema-decl.ts +516 -0
  528. package/src/elements/store/sql-condition.test.ts +132 -0
  529. package/src/elements/store/sql-condition.ts +293 -65
  530. package/src/elements/store/sql-session.test.ts +135 -0
  531. package/src/elements/store/sql-session.ts +241 -84
  532. package/src/elements/store/table.ts +69 -23
  533. package/src/elements/store.test.ts +17 -39
  534. package/src/elements/store.ts +53 -31
  535. package/src/elements/vault/chain.ts +34 -0
  536. package/src/elements/vault/declare.ts +22 -32
  537. package/src/elements/vault/fingerprint.ts +2 -7
  538. package/src/elements/vault/redact.ts +2 -6
  539. package/src/elements/vault/runtime.ts +19 -33
  540. package/src/elements/vault.test.ts +12 -55
  541. package/src/elements/vault.ts +9 -13
  542. package/src/index.ts +5 -2
  543. package/src/kernel/adopt-routes.ts +15 -29
  544. package/src/kernel/app.ts +92 -118
  545. package/src/kernel/auth-resolve.ts +2 -9
  546. package/src/kernel/boot-bind/ai.ts +1 -4
  547. package/src/kernel/boot-bind/channel.test.ts +60 -0
  548. package/src/kernel/boot-bind/channel.ts +61 -5
  549. package/src/kernel/boot-bind/clock.ts +2 -6
  550. package/src/kernel/boot-bind/gate.ts +2 -8
  551. package/src/kernel/boot-bind/runs.ts +1 -3
  552. package/src/kernel/boot-bind/signal.ts +3 -10
  553. package/src/kernel/boot-bind/store.test.ts +27 -18
  554. package/src/kernel/boot-bind/store.ts +93 -52
  555. package/src/kernel/boot-bind/vault.ts +2 -8
  556. package/src/kernel/boot.test.ts +4 -10
  557. package/src/kernel/boot.ts +30 -77
  558. package/src/kernel/budget.test.ts +1 -3
  559. package/src/kernel/capability.ts +3 -10
  560. package/src/kernel/dry-run.test.ts +1 -4
  561. package/src/kernel/dry-run.ts +3 -11
  562. package/src/kernel/edge.test.ts +68 -0
  563. package/src/kernel/effects.test.ts +1 -5
  564. package/src/kernel/effects.ts +4 -11
  565. package/src/kernel/errors.registry.test.ts +2 -8
  566. package/src/kernel/errors.ts +23 -32
  567. package/src/kernel/flow.test.ts +12 -25
  568. package/src/kernel/flow.ts +13 -25
  569. package/src/kernel/fx.test.ts +31 -40
  570. package/src/kernel/fx.ts +135 -90
  571. package/src/kernel/hook-timing.test.ts +3 -6
  572. package/src/kernel/hook-timing.ts +5 -14
  573. package/src/kernel/hooks.test.ts +43 -10
  574. package/src/kernel/hooks.ts +25 -12
  575. package/src/kernel/index.ts +11 -17
  576. package/src/kernel/journal.ts +7 -31
  577. package/src/kernel/on.ts +45 -9
  578. package/src/kernel/pipeline.test.ts +10 -19
  579. package/src/kernel/pipeline.ts +5 -17
  580. package/src/kernel/plug.ts +4 -11
  581. package/src/kernel/plugin/conflicts.test.ts +13 -25
  582. package/src/kernel/plugin/decorate.test.ts +8 -13
  583. package/src/kernel/plugin/scoping.test.ts +4 -11
  584. package/src/kernel/plugin.ts +81 -27
  585. package/src/kernel/registry-isolation.test.ts +74 -0
  586. package/src/kernel/registry.ts +102 -30
  587. package/src/kernel/router.test.ts +15 -13
  588. package/src/kernel/router.ts +9 -29
  589. package/src/kernel/routing-budget.test.ts +1 -3
  590. package/src/kernel/run-telemetry.ts +1 -3
  591. package/src/kernel/triggers.ts +63 -21
  592. package/src/manifest/diff.test.ts +26 -91
  593. package/src/manifest/diff.ts +95 -225
  594. package/src/manifest/fixtures/skyport.excerpt.json +1 -1
  595. package/src/manifest/fixtures/skyport.manifest.json +1 -1
  596. package/src/manifest/index.ts +3 -10
  597. package/src/manifest/types.ts +30 -6
  598. package/src/manifest/undeclared.test.ts +4 -12
  599. package/src/manifest/undeclared.ts +1 -2
  600. package/src/manifest/validate.test.ts +2 -6
  601. package/src/manifest/validate.ts +5 -15
  602. package/src/mcp/authorization.ts +8 -27
  603. package/src/mcp/confirmation.ts +3 -10
  604. package/src/mcp/data.ts +2 -6
  605. package/src/mcp/docs-index.ts +4 -7
  606. package/src/mcp/docs-mcp.test.ts +3 -12
  607. package/src/mcp/docs-server.ts +7 -27
  608. package/src/mcp/docs-tools.ts +2 -6
  609. package/src/mcp/index.ts +1 -6
  610. package/src/mcp/injection.gate.test.ts +4 -18
  611. package/src/mcp/mcp.test.ts +18 -49
  612. package/src/mcp/protocol.ts +3 -10
  613. package/src/mcp/server.ts +11 -52
  614. package/src/mcp/session.ts +4 -14
  615. package/src/mcp/tools.ts +14 -50
  616. package/src/plugins/catalogue.test.ts +3 -7
  617. package/src/plugins/catalogue.ts +1 -3
  618. package/src/plugins/compression.test.ts +127 -0
  619. package/src/plugins/compression.ts +94 -0
  620. package/src/plugins/config-source.test.ts +204 -0
  621. package/src/plugins/config-source.ts +209 -0
  622. package/src/plugins/cors.test.ts +138 -0
  623. package/src/plugins/cors.ts +129 -0
  624. package/src/plugins/csrf.test.ts +102 -0
  625. package/src/plugins/csrf.ts +86 -0
  626. package/src/plugins/headers.ts +54 -0
  627. package/src/plugins/index.ts +26 -0
  628. package/src/plugins/ip-allowlist.test.ts +105 -0
  629. package/src/plugins/ip-allowlist.ts +76 -0
  630. package/src/plugins/maintenance-mode.test.ts +91 -0
  631. package/src/plugins/maintenance-mode.ts +85 -0
  632. package/src/plugins/node-import-scan.ts +2 -5
  633. package/src/plugins/security-headers.test.ts +243 -0
  634. package/src/plugins/security-headers.ts +255 -0
  635. package/src/plugins/supply-chain.ts +5 -17
  636. package/src/release/exports.ts +3 -8
  637. package/src/release/measure.exports.test.ts +1 -3
  638. package/src/release/measure.ts +16 -45
  639. package/src/runs/bench.test.ts +1 -3
  640. package/src/runs/collect.ts +8 -25
  641. package/src/runs/drivers/clickhouse.ts +2 -9
  642. package/src/runs/drivers/files.ts +9 -31
  643. package/src/runs/drivers/memory.ts +5 -22
  644. package/src/runs/drivers/postgres.ts +8 -20
  645. package/src/runs/duckdb.ts +2 -8
  646. package/src/runs/index.ts +3 -14
  647. package/src/runs/outlier.ts +3 -10
  648. package/src/runs/parquet.ts +8 -26
  649. package/src/runs/privacy.ts +2 -8
  650. package/src/runs/runs.test.ts +3 -9
  651. package/src/runs/runtime.ts +7 -32
  652. package/src/runs/shred.ts +10 -29
  653. package/src/runs/types.ts +1 -4
  654. package/src/runtime/bun.ts +4 -15
  655. package/src/runtime/cold-start.bench.ts +1 -3
  656. package/src/runtime/dev-request-log.test.ts +1 -4
  657. package/src/runtime/dev-request-log.ts +1 -4
  658. package/src/runtime/index.ts +1 -6
  659. package/src/runtime/primitives.ts +5 -4
  660. package/src/runtime/security.ts +3 -13
  661. package/src/runtime/serve.test.ts +4 -7
  662. package/src/runtime/types.ts +1 -4
  663. package/src/runtime/web-standard.ts +3 -15
  664. package/src/term.test.ts +4 -6
  665. package/src/term.ts +49 -98
  666. package/src/test/create-test-app.test.ts +4 -14
  667. package/src/test/create-test-app.ts +27 -33
  668. package/src/test/provisions.integration.test.ts +3 -11
  669. package/src/upgrade/codemods.test.ts +25 -6
  670. package/src/upgrade/codemods.ts +116 -3
  671. package/src/validation/standard-schema.test.ts +2 -5
  672. package/src/validation/standard-schema.ts +13 -33
  673. package/docs/spec/console.md +0 -739
  674. package/docs/spec/example.md +0 -1196
  675. package/docs/spec/four-applications.md +0 -1195
  676. package/docs/spec/unified-theory.md +0 -489
  677. package/src/cli/doc-drift.test.ts +0 -146
  678. package/src/cli/doc-drift.ts +0 -430
  679. package/src/cli/doctor-diff-examples.ts +0 -92
  680. package/src/console/ui/dist/assets/panel-vault-BbfWdox0.js +0 -1
  681. package/src/drivers/vault-sops.ts +0 -271
@@ -0,0 +1,167 @@
1
+ ---
2
+ title: "Channel"
3
+ description: "Reaching humans — email, SMS, WhatsApp, and push with consent, locale, receipts, and fallback chains built in."
4
+ icon: "Mail"
5
+ source: "docs/spec/unified-theory.md"
6
+ ---
7
+
8
+ Channel is how your app **reaches humans**: the order-confirmation email, the OTP text, the WhatsApp notification. Reaching a person is not the same problem as moving data between machines — it needs consent (did they opt out?), locale (which language?), receipts (did it land?), and fallback (email failed, try SMS). Those physics are built into the element, so every flow gets them for free.
9
+
10
+ <Callout title="The one rule">
11
+ Sends go through `fx.send` and declared templates — never through a raw SMTP or provider client
12
+ inside a flow. That is what makes consent, locale, and receipts unavoidable rather than optional.
13
+ </Callout>
14
+
15
+ ## Quick start
16
+
17
+ <Steps>
18
+
19
+ <Step>
20
+ ### Declare a medium and a template
21
+
22
+ A medium binder carries your defaults; a template names the message and types its data:
23
+
24
+ ```typescript title="src/channels.ts"
25
+ import { channel } from "okengine";
26
+ import { z } from "zod";
27
+
28
+ export const mail = channel.email({ from: "Provisions <no-reply@provisions.sa>" });
29
+
30
+ export const orderConfirmed = mail.template("order-confirmed", {
31
+ schema: z.object({ name: z.string(), orderId: z.string(), total: z.number() }),
32
+ locales: ["en", "ar"],
33
+ });
34
+ ```
35
+
36
+ </Step>
37
+
38
+ <Step>
39
+ ### Send it from a Flow
40
+
41
+ One call — consent, locale resolution, and the receipt happen around it:
42
+
43
+ ```typescript title="src/flows/orders/confirm.ts"
44
+ do: async (input, fx) => {
45
+ await fx.send(orderConfirmed, {
46
+ to: input.email,
47
+ data: { name: input.name, orderId: input.id, total: input.total },
48
+ });
49
+ };
50
+ ```
51
+
52
+ </Step>
53
+
54
+ <Step>
55
+ ### Read it in development
56
+
57
+ Locally the `console` driver captures mail into an inbox instead of sending; in docker mode the stack runs **Mailpit**, a real SMTP catcher with a web UI — so you see the exact rendered message without ever touching a real mailbox.
58
+
59
+ </Step>
60
+
61
+ </Steps>
62
+
63
+ ## Mediums and templates
64
+
65
+ | Declaration | Produces |
66
+ | ------------------------------ | ----------------------------------------------- |
67
+ | `channel.email({ from })` | Email binder (default sender) |
68
+ | `channel.sms({ sender })` | SMS binder (sender id) |
69
+ | `channel.whatsapp()` | WhatsApp binder |
70
+ | `channel.push()` | Push binder |
71
+ | `binder.template(name, opts)` | Typed template bound to that medium |
72
+ | `channel.template(name, opts)` | Medium-agnostic template (one body, any medium) |
73
+
74
+ | Template option | Type | Meaning |
75
+ | --------------- | --------------------- | ------------------------------------------------- |
76
+ | `schema` | zod / Standard Schema | The data the template may reference — typed sends |
77
+ | `locales` | string[] | Languages this template is rendered in |
78
+
79
+ ## The human physics
80
+
81
+ ### Consent is checked before sending
82
+
83
+ Opt-out is first-class: a subject who opted out of a medium is **suppressed** — the send resolves without contacting the provider, and the receipt says so. You never hand-roll "did they unsubscribe?" checks.
84
+
85
+ ### Locale resolves through a chain
86
+
87
+ Templates render per recipient locale, falling back through your configured chain (`ar` → default `en`) instead of failing when a translation is missing. Locales and the default come from the `i18n` block in `oke.config.ts`.
88
+
89
+ ### Fallback chains are explicit
90
+
91
+ `via` orders the mediums to try — first success wins, and **every** attempt is recorded:
92
+
93
+ ```typescript
94
+ await fx.send(otpCode, { to: user.phone, data: { code }, via: [sms, wa] });
95
+ // sms fails → whatsapp tried → receipt status "fallback" with both attempts
96
+ ```
97
+
98
+ ### Receipts for everything
99
+
100
+ Each send records its attempts — driver, ok/error, timestamp, message id — so "did the user actually get it?" is a Console query, not a guess. In a dry run, sends are recorded as _would have fired_ and never contact a real provider.
101
+
102
+ ## Per-environment drivers
103
+
104
+ ```typescript title="oke.config.ts"
105
+ drivers: {
106
+ channel: {
107
+ email: { local: "console", docker: "smtp", test: "console", prod: "smtp" },
108
+ },
109
+ },
110
+ images: {
111
+ "channel.email": "axllent/mailpit:v1.22.3", // SMTP catcher for the docker stack
112
+ },
113
+ ```
114
+
115
+ | Driver | Medium | Behavior |
116
+ | ---------- | ------ | ---------------------------------------------------- |
117
+ | `console` | any | Captures into a readable inbox — local + tests |
118
+ | `smtp` | email | Real SMTP — Mailpit in docker, your provider in prod |
119
+ | `resend` | email | Resend API |
120
+ | `unifonic` | sms | Unifonic SMS API |
121
+
122
+ ## Troubleshooting
123
+
124
+ <Accordions>
125
+ <Accordion title="I sent an email in dev but nothing arrived">
126
+
127
+ Nothing _should_ arrive — the `console` driver captures mail instead of sending. Read the dev inbox, or run `oke dev --docker` and open Mailpit's web UI to see the rendered message.
128
+
129
+ </Accordion>
130
+ <Accordion title="A user says they stopped receiving messages">
131
+
132
+ Check consent first: if they opted out, sends to them are suppressed by design. The Console shows the suppression on the receipt — it is a delivered-as-intended outcome, not a bug.
133
+
134
+ </Accordion>
135
+ <Accordion title="The Arabic version didn't render">
136
+
137
+ Locale resolution falls back through the chain to your default locale when a translation is missing — the send still succeeds with the fallback body. Check that the template declares `locales: ["en", "ar"]` and that the Arabic body exists in the catalog.
138
+
139
+ </Accordion>
140
+ <Accordion title="How do I know which medium finally delivered?">
141
+
142
+ The receipt keeps every attempt in order with its outcome. A send that succeeded on a later medium reports status `fallback` — you can see the full chain in Console → Channels.
143
+
144
+ </Accordion>
145
+ </Accordions>
146
+
147
+ ## Learn more
148
+
149
+ - [Flow](/docs/elements/flow) — `fx.send` inside `do`
150
+ - [Console · Channels](/docs/console/channels) — receipts, attempts, suppression
151
+ - [Signal](/docs/elements/signal) — machine-to-machine messaging, the other side of the line
152
+
153
+ ## Next
154
+
155
+ <Cards>
156
+ <Card title="AI" description="Continue to AI." href="/docs/elements/ai" />
157
+ <Card
158
+ title="Introduction"
159
+ description="Eight elements overview."
160
+ href="/docs/get-started/introduction"
161
+ />
162
+ <Card
163
+ title="Console"
164
+ description="Panels derived from the Manifest."
165
+ href="/docs/console/overview"
166
+ />
167
+ </Cards>
@@ -0,0 +1,182 @@
1
+ ---
2
+ title: "Clock"
3
+ description: "Time — recurring schedules, intervals, and durable sleeps as first-class declarations, not a bolted-on cron library."
4
+ icon: "Clock"
5
+ source: "docs/spec/unified-theory.md"
6
+ ---
7
+
8
+ Clock is how your app deals with **time**: cleanup jobs that run every hour, reports due at 9am Riyadh time, a flow that pauses for seven days and wakes up even after a deploy. There is no separate scheduler to install — a schedule is a trigger, and the flow it fires is the same species as every other flow.
9
+
10
+ <Callout title="The one rule">
11
+ Flow code never calls `Date.now()` — it asks `fx.clock.now()`. Time is injected, which makes it
12
+ deterministic in tests and auditable in traces.
13
+ </Callout>
14
+
15
+ ## Quick start
16
+
17
+ <Steps>
18
+
19
+ <Step>
20
+ ### Fire a flow on an interval
21
+
22
+ The simplest schedule is an anonymous interval trigger:
23
+
24
+ ```typescript title="src/flows/links/purge.ts"
25
+ import { on, flow, every } from "okengine";
26
+
27
+ export const purgeOld = on(
28
+ every("1h"),
29
+ flow({
30
+ do: async (_, fx) => {
31
+ const cutoff = fx.clock.now() - 30 * 24 * 60 * 60 * 1000; // 30 days
32
+ await fx.store(db).delete(links).where(lt(links.createdAt, cutoff));
33
+ },
34
+ }),
35
+ );
36
+ ```
37
+
38
+ </Step>
39
+
40
+ <Step>
41
+ ### Or declare a named schedule
42
+
43
+ A named `clock()` shows up in the Console, supports cron expressions and timezones, and can be paused or retuned at runtime:
44
+
45
+ ```typescript title="src/clocks.ts"
46
+ import { clock } from "okengine";
47
+
48
+ export const dailyReport = clock("daily-report", {
49
+ cron: "0 9 * * *", // 09:00
50
+ timezone: "Asia/Riyadh",
51
+ overridable: true, // the Console may edit this schedule
52
+ });
53
+ ```
54
+
55
+ ```typescript title="src/flows/reports/daily.ts"
56
+ export const sendDaily = on(
57
+ dailyReport,
58
+ flow({
59
+ do: async (_, fx) => {
60
+ /* … */
61
+ },
62
+ }),
63
+ );
64
+ ```
65
+
66
+ </Step>
67
+
68
+ <Step>
69
+ ### Ask for time inside flows
70
+
71
+ `fx.clock` is the only clock a flow knows:
72
+
73
+ ```typescript
74
+ do: async (input, fx) => {
75
+ const now = fx.clock.now(); // epoch-ms, injectable
76
+ await fx.clock.sleep("wait-for-payment", "7d"); // durable — survives restarts
77
+ };
78
+ ```
79
+
80
+ </Step>
81
+
82
+ </Steps>
83
+
84
+ ## Two kinds of schedules
85
+
86
+ | Declaration | Shows in Console | Cron + timezone | Runtime-editable | Use for |
87
+ | ------------------- | ---------------- | --------------- | ------------------ | --------------------------------- |
88
+ | `every("1h")` | no | no | no | Simple fixed intervals |
89
+ | `clock(name, opts)` | yes | yes | when `overridable` | Business schedules operators tune |
90
+
91
+ Both are triggers consumed with the same `on(trigger, flow)` — the flow underneath does not know the difference.
92
+
93
+ ### `clock()` options
94
+
95
+ | Option | Type | Default | Meaning |
96
+ | ------------- | ------- | ------- | ------------------------------------------------------------ |
97
+ | `cron` | string | — | Cron expression `m h dom mon dow` (this or `every` required) |
98
+ | `every` | string | — | Fixed interval: `"30s"` · `"10m"` · `"1h"` · `"7d"` |
99
+ | `timezone` | string | `"UTC"` | IANA timezone for cron evaluation |
100
+ | `overridable` | boolean | `false` | Allow the Console to edit / pause the schedule |
101
+
102
+ ## Sleeping inside a flow
103
+
104
+ `fx.clock.sleep(label, duration)` is a **durable** sleep: in a `durable: true` flow the wake time is journaled, so the flow resumes after restarts and deploys instead of losing its place. The `label` names the step in the journal — it is what the Console shows when you inspect a sleeping run.
105
+
106
+ In a non-durable flow the same call resolves immediately, so code reads identically in tests.
107
+
108
+ ## What the runtime guarantees
109
+
110
+ | Guarantee | What it means |
111
+ | ------------------ | ------------------------------------------------------------------------------------------- |
112
+ | Leader election | With several replicas, only one instance fires each cron tick |
113
+ | Catch-up `"one"` | A schedule missed during downtime fires **once**, not once per missed tick |
114
+ | Reconciled at boot | Named clocks are written into the store (`oke_crons`) — the scheduler reads state, not code |
115
+ | DST-aware | Ambiguous local times around DST transitions are detected and handled |
116
+
117
+ ## Per-environment drivers
118
+
119
+ ```typescript title="oke.config.ts"
120
+ drivers: {
121
+ clock: { local: "memory", docker: "postgres", test: "frozen", prod: "postgres" },
122
+ },
123
+ ```
124
+
125
+ | Driver | Behavior |
126
+ | ---------- | ------------------------------------------------------------------- |
127
+ | `memory` | In-process timers — fast local loop, lost on exit |
128
+ | `postgres` | Schedules and wakes persisted in your Postgres — survives restarts |
129
+ | `frozen` | Deterministic test clock — time advances only when the test says so |
130
+
131
+ `frozen` is why the no-`Date.now()` rule pays off: tests inject time travel through `fx.clock` and every flow obeys it automatically.
132
+
133
+ ## Operating schedules from the Console
134
+
135
+ The Console (`:6533` → Clock) lists every named clock with a four-number health view — drift, past-due, next run, and which replica holds the leader lease. From there you can **pause**, **edit the schedule**, or **wake early** — but only for clocks declared with `overridable: true`. A non-overridable clock rejects edits with `ScheduleNotOverridableError`, so a mistyped production change cannot silently reshape your cadence.
136
+
137
+ ## Troubleshooting
138
+
139
+ <Accordions>
140
+ <Accordion title="I used Date.now() and tests are flaky">
141
+
142
+ Replace it with `fx.clock.now()`. Direct time calls bypass the injected clock, so the `frozen` test driver cannot control them — that is exactly the class of bug the rule exists to remove.
143
+
144
+ </Accordion>
145
+ <Accordion title="The server was down and the cron didn't catch up">
146
+
147
+ That is by design: catch-up policy is `"one"` — the schedule fires a single time after downtime, never a storm of one-run-per-missed-tick. If you genuinely need backfill, trigger the flow from the Console.
148
+
149
+ </Accordion>
150
+ <Accordion title="Console won't let me edit a schedule">
151
+
152
+ The clock was declared without `overridable: true`. Add it and redeploy — the restriction is deliberate, so only schedules you marked as operator-tunable can drift from code.
153
+
154
+ </Accordion>
155
+ <Accordion title="How do I run something once, later — not recurring?">
156
+
157
+ Emit it from inside a flow with `fx.clock.sleep(label, duration)` before the work, in a `durable: true` flow. The sleep survives restarts, so "remind me in 7 days" is one line, not a cron row.
158
+
159
+ </Accordion>
160
+ </Accordions>
161
+
162
+ ## Learn more
163
+
164
+ - [Flow](/docs/elements/flow) — `on(trigger, flow)` and the `fx` surface
165
+ - [Console · Clock](/docs/console/clock) — health numbers, pause / edit / wake early
166
+ - [Signal](/docs/elements/signal) — reacting to events instead of time
167
+
168
+ ## Next
169
+
170
+ <Cards>
171
+ <Card title="Gate" description="Continue to Gate." href="/docs/elements/gate" />
172
+ <Card
173
+ title="Introduction"
174
+ description="Eight elements overview."
175
+ href="/docs/get-started/introduction"
176
+ />
177
+ <Card
178
+ title="Console"
179
+ description="Panels derived from the Manifest."
180
+ href="/docs/console/overview"
181
+ />
182
+ </Cards>
@@ -0,0 +1,288 @@
1
+ ---
2
+ title: "Flow"
3
+ description: "Behavior — endpoints, jobs, consumers, and workflows as one species: a typed trigger, declared contracts, and a do that touches the world only through fx."
4
+ icon: "Workflow"
5
+ source: "docs/spec/unified-theory.md"
6
+ ---
7
+
8
+ Flow is how your backend **does anything**. An HTTP endpoint, a queue consumer, a cron job, a multi-step payment workflow — in other stacks these are four frameworks; here they are one species with one shape: `on(trigger, flow)`. Learn the shape once, and only the trigger ever changes.
9
+
10
+ <Callout title="The one rule">
11
+ All world access goes through `fx`. A direct `fetch`, `Date.now()`, or `node:` import inside a
12
+ flow is a defect — effects are **inferred from what a flow touches through `fx`**, and that
13
+ inference is what powers the Manifest, the Console, caching, and durability.
14
+ </Callout>
15
+
16
+ ## Quick start
17
+
18
+ <Steps>
19
+
20
+ <Step>
21
+ ### Write the flow
22
+
23
+ Four declarations plus a `do`:
24
+
25
+ ```typescript title="src/flows/orders/create.ts"
26
+ import { on, flow, http } from "okengine";
27
+ import { z } from "zod";
28
+
29
+ export const createOrder = on(
30
+ http.post("/orders"),
31
+ flow({
32
+ in: z.object({ sku: z.string(), qty: z.number().int().min(1) }),
33
+ out: z.object({ id: z.string() }),
34
+ errors: { OutOfStock: z.object({ left: z.number() }) },
35
+ do: async (input, fx) => {
36
+ const id = fx.id();
37
+ await fx
38
+ .store(db)
39
+ .insert(orders)
40
+ .values({ id, ...input, status: "pending" });
41
+ return { id };
42
+ },
43
+ }),
44
+ );
45
+ ```
46
+
47
+ </Step>
48
+
49
+ <Step>
50
+ ### Run it
51
+
52
+ `oke dev` picks up every exported flow — no route registration, no controller wiring:
53
+
54
+ ```bash
55
+ oke dev
56
+ ```
57
+
58
+ </Step>
59
+
60
+ <Step>
61
+ ### Call it
62
+
63
+ ```bash
64
+ curl -X POST localhost:6530/orders -d '{"sku":"SKU-1","qty":2}' -H 'content-type: application/json'
65
+ # { "data": { "id": "…" }, "error": null }
66
+ ```
67
+
68
+ </Step>
69
+
70
+ </Steps>
71
+
72
+ ## Anatomy of a Flow
73
+
74
+ | Part | Role |
75
+ | -------- | --------------------------------------------------------------- |
76
+ | trigger | What starts the flow — `http`, a signal, `every`, a row change |
77
+ | `in` | Input contract — validated before `do` runs; bad input is a 422 |
78
+ | `out` | Output contract — the return value is checked against it |
79
+ | `errors` | Typed failures — **returned** with `fx.fail`, never thrown |
80
+ | `do` | The work — every read, write, emit, and call goes through `fx` |
81
+
82
+ Failures are values, not exceptions:
83
+
84
+ ```typescript
85
+ do: async (input, fx) => {
86
+ const [product] = await fx.store(db).select().from(products).where(eq(products.sku, input.sku));
87
+ if (!product || product.stock < input.qty) {
88
+ return fx.fail("OutOfStock", { left: product?.stock ?? 0 }); // declared in errors
89
+ }
90
+ // …
91
+ };
92
+ ```
93
+
94
+ Every response follows one envelope — success `{ data, error: null }`, failure `{ data: null, error: { code, data } }` — so clients handle outcomes by `error.code`, not by parsing status text.
95
+
96
+ ## The five triggers
97
+
98
+ | Trigger | Starts when | Replaces |
99
+ | ---------------------------- | ---------------------------- | ------------------ |
100
+ | `http.post("/orders")` | a request arrives | endpoint · handler |
101
+ | `orderPlaced` (a signal) | another flow emits | queue consumer |
102
+ | `every("1h")` | time passes | cron job |
103
+ | `db.table(orders).changed()` | a row changes | CDC pipeline |
104
+ | — none | another flow calls `fx.call` | "private" helper |
105
+
106
+ ### http — a request arrives
107
+
108
+ ```typescript
109
+ export const findOrder = on(
110
+ http.get("/orders/:id").gate(member), // gates evaluate before do runs
111
+ flow({
112
+ in: z.object({ id: z.string() }),
113
+ out: Order,
114
+ errors: { NotFound: z.object({}) },
115
+ do: async ({ id }, fx) => (await fx.store(db).findById(orders, id)) ?? fx.fail("NotFound", {}),
116
+ }),
117
+ );
118
+ ```
119
+
120
+ ### signal — another flow emits
121
+
122
+ The producer emits through `fx` (transactionally with its writes); the consumer is the same species — no `subscribe()`, no listener registration:
123
+
124
+ ```typescript
125
+ await fx.emit(orderPlaced, { orderId: id }); // inside the producing flow
126
+
127
+ on(
128
+ orderPlaced,
129
+ flow({
130
+ do: async ({ orderId }, fx) => {
131
+ /* … */
132
+ },
133
+ }),
134
+ );
135
+ ```
136
+
137
+ ### clock — time passes
138
+
139
+ `every("1h")` is a trigger value, not a registration with a scheduler library:
140
+
141
+ ```typescript
142
+ on(
143
+ every("1h"),
144
+ flow({
145
+ do: async (_, fx) => {
146
+ const cutoff = fx.clock.now() - 30 * 24 * 60 * 60 * 1000;
147
+ await fx.store(db).delete(sessions).where(lt(sessions.createdAt, cutoff));
148
+ },
149
+ }),
150
+ );
151
+ ```
152
+
153
+ ### store change — a row changes
154
+
155
+ CDC is built in; the flow receives `{ before, after }`:
156
+
157
+ ```typescript
158
+ on(
159
+ db.table(orders).changed("status"),
160
+ flow({
161
+ do: ({ before, after }, fx) => fx.log.info("status", { from: before.status, to: after.status }),
162
+ }),
163
+ );
164
+ ```
165
+
166
+ ### no trigger — a callable flow
167
+
168
+ Drop `on()` and it is still a real Flow — contracts, Manifest entry, everything. Other flows call it through `fx.call`:
169
+
170
+ ```typescript
171
+ export const getOrder = flow({
172
+ in: OrderRef,
173
+ out: Order,
174
+ do: async ({ id }, fx) => {
175
+ /* … */
176
+ },
177
+ });
178
+
179
+ const order = await fx.call(getOrder, { id: orderId }); // from any other flow
180
+ ```
181
+
182
+ ## fx — the only door
183
+
184
+ Everything a flow may touch, on one object:
185
+
186
+ | Surface | Effect recorded | What it does |
187
+ | --------------------------------------- | --------------- | -------------------------------------------- |
188
+ | `fx.store(db).select/insert/…` | read / write | SQL, KV, files, index sessions |
189
+ | `fx.emit(signal, payload)` | emit | Publish a signal (transactional with writes) |
190
+ | `fx.send(template, opts)` | send | Reach a human (email · SMS · …) |
191
+ | `fx.ask(prompt, input)` | ask | Call a versioned AI prompt |
192
+ | `fx.run(agent, input)` | ask | Run a bounded agent |
193
+ | `fx.call(flow, input)` | call | Invoke another flow |
194
+ | `fx.vault(contract)` | read | Read a secret (redacted from logs) |
195
+ | `fx.clock.now()` / `.sleep(…)` | — | Injected time / durable sleep |
196
+ | `fx.cache.get/set` | — | Shared cache with effect-aware invalidation |
197
+ | `fx.step(name, fn)` | — | Named durable step — never re-runs on replay |
198
+ | `fx.id()` · `fx.log` · `fx.t` | — | UUIDs, redacting logger, i18n |
199
+ | `fx.auth` · `fx.operator` · `fx.tenant` | — | Who is calling (user / operator / tenant) |
200
+
201
+ <Callout title="Why this strictness pays off">
202
+ Effects are inferred from `fx` usage, so the Manifest knows exactly which flows read `orders` or
203
+ send PII to a model — without you declaring any of it. That one graph drives the Console panels,
204
+ cache invalidation, least-privilege tokens, and durable replay.
205
+ </Callout>
206
+
207
+ ## Durability — flows that survive the process
208
+
209
+ Set `durable: true` and every `fx` call is journaled. Wrap side effects in `fx.step` and they never re-run on replay:
210
+
211
+ ```typescript
212
+ export const chargeOrder = flow({
213
+ durable: true, // every fx call below is journaled
214
+ in: OrderRef,
215
+ out: z.boolean(),
216
+ do: async ({ orderId }, fx) => {
217
+ const intent = await fx.step("create-intent", () =>
218
+ stripe(fx.vault(stripeKey)).create(orderId),
219
+ );
220
+
221
+ await fx.clock.sleep("verify-window", "2m"); // survives restart and deploy
222
+
223
+ return fx.step("confirm", () => stripe(fx.vault(stripeKey)).confirm(intent));
224
+ },
225
+ });
226
+ ```
227
+
228
+ **Consequence:** kill the process between the two steps and the run **resumes at `confirm`** — completed steps replay from the journal, so the card is not charged twice. This is verified by the engine's own test suite: after resume, `create-intent` has run exactly once.
229
+
230
+ ## Composition is just calls
231
+
232
+ Wiring is values flowing between flows — declared in code, never configured in a dashboard:
233
+
234
+ ```typescript
235
+ on(http.post("/orders"), createOrder); // ① a request arrives
236
+ on(orderPlaced, sendReceipt); // ② its emit starts the consumer
237
+ on(every("1h"), sweepExpired); // ③ time passes
238
+ on(db.table(orders).changed("status"), reverify); // ④ a row changes
239
+ // ⑤ getOrder — nothing starts it; every flow above can fx.call it
240
+ ```
241
+
242
+ ## Troubleshooting
243
+
244
+ <Accordions>
245
+ <Accordion title="My flow used fetch / Date.now directly and weird things happened">
246
+
247
+ That bypasses the effect graph: the Manifest can't see the call, cache keys miss it, durable replay re-executes it, and tests can't freeze it. Move it behind `fx` — `fx.ask` / `fx.send` / `fx.clock.now()`, or a step inside a durable flow for true third-party calls.
248
+
249
+ </Accordion>
250
+ <Accordion title="Should I throw or fx.fail?">
251
+
252
+ `fx.fail(code, data)` for expected outcomes — anything in `errors`. A throw is a crash: the run fails with a 500-shaped error and no typed code for the client. Expected failures are values so clients can switch on `error.code`.
253
+
254
+ </Accordion>
255
+ <Accordion title="How do I share logic between flows — a private function?">
256
+
257
+ Make it a plain flow with no trigger and `fx.call` it. You keep contracts, the Manifest entry, and tracing; a bare function would hide the work from the effect graph.
258
+
259
+ </Accordion>
260
+ <Accordion title="What happens if a cron flow throws?">
261
+
262
+ Only that run fails — the schedule keeps firing and the process does not exit. The failed run lands in Console → Runs with its trace.
263
+
264
+ </Accordion>
265
+ </Accordions>
266
+
267
+ ## Learn more
268
+
269
+ - [Signal](/docs/elements/signal) — delivery physics (`once` · `broadcast` · `live`)
270
+ - [Clock](/docs/elements/clock) — schedules and durable sleep
271
+ - [Console · Flows](/docs/console/flows) — the Manifest-derived panel
272
+ - [Runs](/docs/console/runs) — how a flow execution is observed
273
+
274
+ ## Next
275
+
276
+ <Cards>
277
+ <Card title="Signal" description="Continue to Signal." href="/docs/elements/signal" />
278
+ <Card
279
+ title="Introduction"
280
+ description="Eight elements overview."
281
+ href="/docs/get-started/introduction"
282
+ />
283
+ <Card
284
+ title="Console"
285
+ description="Panels derived from the Manifest."
286
+ href="/docs/console/overview"
287
+ />
288
+ </Cards>