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
@@ -59,7 +59,7 @@ export interface ConfigSourceOptions<T extends object> {
59
59
  /**
60
60
  * A live config box for one plugin. `current()` is synchronous and never
61
61
  * throws — safe to read from any hook. `sync()` is the refresh flow the
62
- * app binds once: `on(every("30s"), source.sync())`.
62
+ * app binds once: `on(syncClock, source.sync())`.
63
63
  */
64
64
  export interface ConfigSource<T extends object> {
65
65
  readonly [configSourceBrand]: true;
@@ -117,3 +117,68 @@ describe("csrf plugin", () => {
117
117
  expect(res.status).toBe(200);
118
118
  });
119
119
  });
120
+
121
+ describe("csrf soft-require when cookies enabled", () => {
122
+ test("prod boots refuse without csrf plugin", async () => {
123
+ const app = oke({
124
+ autoBoot: false,
125
+ name: `csrf-cookie-${crypto.randomUUID()}`,
126
+ env: "prod",
127
+ registry: "ignore",
128
+ gate: {
129
+ auth: {
130
+ secret: "test-secret-at-least-32-characters!!",
131
+ cookies: { enabled: true },
132
+ http: false,
133
+ },
134
+ },
135
+ });
136
+ await expect(app.boot()).rejects.toThrow(/csrf plugin/);
137
+ });
138
+
139
+ test("test/dev boots warn without csrf plugin", async () => {
140
+ const warnings: string[] = [];
141
+ const prev = console.warn;
142
+ console.warn = (...args: unknown[]) => {
143
+ warnings.push(args.map(String).join(" "));
144
+ };
145
+ try {
146
+ const app = oke({
147
+ autoBoot: false,
148
+ name: `csrf-cookie-dev-${crypto.randomUUID()}`,
149
+ env: "test",
150
+ registry: "ignore",
151
+ gate: {
152
+ auth: {
153
+ secret: "test-secret-at-least-32-characters!!",
154
+ cookies: { enabled: true },
155
+ http: false,
156
+ },
157
+ },
158
+ });
159
+ await app.boot({ env: "test" });
160
+ expect(warnings.some((w) => w.includes("csrf"))).toBe(true);
161
+ await app.stop();
162
+ } finally {
163
+ console.warn = prev;
164
+ }
165
+ });
166
+
167
+ test("boots with csrf plugin when cookies enabled", async () => {
168
+ const app = oke({
169
+ autoBoot: false,
170
+ name: `csrf-cookie-ok-${crypto.randomUUID()}`,
171
+ env: "test",
172
+ registry: "ignore",
173
+ gate: {
174
+ auth: {
175
+ secret: "test-secret-at-least-32-characters!!",
176
+ cookies: { enabled: true },
177
+ http: false,
178
+ },
179
+ },
180
+ }).plug(csrf({ allowNoHeader: false }));
181
+ await app.boot({ env: "test" });
182
+ await app.stop();
183
+ });
184
+ });
@@ -15,6 +15,7 @@ export {
15
15
  type ConfigSourceOptions,
16
16
  } from "./config-source.ts";
17
17
  export { cors, type CorsOptions } from "./cors.ts";
18
+ export { csrf, type CsrfOptions } from "./csrf.ts";
18
19
  export { ipAllowlist, type IpAllowlistOptions } from "./ip-allowlist.ts";
19
20
  export {
20
21
  magicLink,
@@ -67,6 +68,7 @@ export {
67
68
  type TwoFactorRow,
68
69
  type TwoFactorStore,
69
70
  } from "./two-factor.ts";
71
+ export type { TwoFactorMethod } from "../auth/two-factor-challenge.ts";
70
72
  export {
71
73
  assertUsernamePolicy,
72
74
  createUsernameStore,
@@ -6,7 +6,13 @@
6
6
  * local DX without Mailpit / SMTP.
7
7
  */
8
8
 
9
- import { linkOrProvision, normalizeEmail, type IdentityStore } from "../auth/identity.ts";
9
+ import {
10
+ completeVerifiedEmailSignIn,
11
+ IdentityError,
12
+ normalizeEmail,
13
+ type IdentityStore,
14
+ type UserIdentityRow,
15
+ } from "../auth/identity.ts";
10
16
  import { issueSessionWithScopes } from "../auth/sessions.ts";
11
17
  import {
12
18
  createVerificationStore,
@@ -28,6 +34,7 @@ import {
28
34
  z,
29
35
  type AuthMethodOptions,
30
36
  } from "./auth/shared.ts";
37
+ import { okid } from "../okid.ts";
31
38
 
32
39
  const DEFAULT_TTL_MS = 10 * 60 * 1000;
33
40
  const DEFAULT_FROM = "OKE <no-reply@oke.local>";
@@ -103,22 +110,25 @@ export function magicLink(opts: MagicLinkOptions = {}): PluginDef {
103
110
  })
104
111
  : magicLinkTemplate;
105
112
 
106
- const request = flow("auth.requestMagicLink", {
107
- plane: "user",
113
+ const requestContract = {
108
114
  in: z.object({ email: z.string().min(3) }),
109
115
  out: z.object({
110
116
  ok: z.literal(true),
111
117
  devToken: z.string().optional(),
112
118
  }),
113
119
  errors: { AuthFailed, AuthRateLimited },
120
+ };
121
+
122
+ const request = flow("auth.requestMagicLink", {
123
+ plane: "user",
114
124
  effects: { sends: ["auth-magic-link"] },
115
125
  do: async (input, fx) => {
116
126
  const email = normalizeEmail(input.email);
117
127
  if (!email.includes("@")) return fail("AuthFailed", { reason: "invalid_email" });
118
- const token = `ml_${crypto.randomUUID().replace(/-/g, "")}`;
128
+ const token = `ml_${okid()}`;
119
129
  const now = runtime.now();
120
130
  putVerification(verifications, {
121
- id: crypto.randomUUID(),
131
+ id: okid(),
122
132
  identifier: `magic:${email}`,
123
133
  value: await hashChallenge(token),
124
134
  expiresAt: now + ttlMs,
@@ -138,11 +148,14 @@ export function magicLink(opts: MagicLinkOptions = {}): PluginDef {
138
148
  },
139
149
  });
140
150
 
141
- const verify = flow("auth.verifyMagicLink", {
142
- plane: "user",
151
+ const verifyContract = {
143
152
  in: z.object({ token: z.string().min(1) }),
144
153
  out: SessionTokensOut,
145
154
  errors: { AuthFailed, AuthRateLimited },
155
+ };
156
+
157
+ const verify = flow("auth.verifyMagicLink", {
158
+ plane: "user",
146
159
  do: async (input) => {
147
160
  const hash = await hashChallenge(input.token);
148
161
  const now = runtime.now();
@@ -168,13 +181,20 @@ export function magicLink(opts: MagicLinkOptions = {}): PluginDef {
168
181
  if (!row) return fail("AuthFailed", { reason: "invalid_credentials" });
169
182
  row.consumedAt = now;
170
183
  const email = row.identifier.slice("magic:".length);
171
- const { user } = await linkOrProvision(identities, {
172
- provider: "magic-link",
173
- providerAccountId: email,
174
- email,
175
- emailVerified: true,
176
- now: () => now,
177
- });
184
+ let user: UserIdentityRow;
185
+ try {
186
+ ({ user } = await completeVerifiedEmailSignIn(identities, runtime.sessions, {
187
+ provider: "magic-link",
188
+ providerAccountId: email,
189
+ email,
190
+ now: () => now,
191
+ }));
192
+ } catch (err) {
193
+ if (err instanceof IdentityError) {
194
+ return fail("AuthFailed", { reason: "invalid_credentials" });
195
+ }
196
+ throw err;
197
+ }
178
198
  const issued = await issueSessionWithScopes(runtime.sessions, runtime.crypto, {
179
199
  id: user.id,
180
200
  plane: "user",
@@ -194,6 +214,6 @@ export function magicLink(opts: MagicLinkOptions = {}): PluginDef {
194
214
  .needs("channel")
195
215
  .channelTemplate(tmpl)
196
216
  .channelCatalog(magicLinkCatalog)
197
- .binding(bindPublicAuth("/magic-link/request", request, "otp"))
198
- .binding(bindPublicAuth("/magic-link/verify", verify, "otp"));
217
+ .binding(bindPublicAuth("/magic-link/request", request, "otp", requestContract))
218
+ .binding(bindPublicAuth("/magic-link/verify", verify, "otp", verifyContract));
199
219
  }
@@ -7,6 +7,7 @@ import type { VerificationStore } from "../../auth/verification.ts";
7
7
  import type { SessionStore } from "../../auth/sessions.ts";
8
8
  import { generateCodeVerifier, codeChallengeS256 } from "../../drivers/oauth-shared.ts";
9
9
  import type { OAuthDriverId } from "../../drivers/oauth-types.ts";
10
+ import type { BoundaryContract } from "../../kernel/boundary-contract.ts";
10
11
  import type { AnyFlowDef } from "../../kernel/flow.ts";
11
12
  import { bindAuthHttp } from "../../auth/bindings.ts";
12
13
  import { http } from "../auth/shared.ts";
@@ -97,10 +98,11 @@ export function bindPublicAuthGet(
97
98
  path: string,
98
99
  flowDef: AnyFlowDef,
99
100
  gates: readonly Parameters<ReturnType<typeof http.get>["gate"]>[0][] = [],
101
+ contract?: BoundaryContract,
100
102
  ): Binding {
101
103
  return bindAuthHttp(
102
104
  http
103
- .get(path)
105
+ .get(path, contract)
104
106
  .public()
105
107
  .gate(...gates),
106
108
  flowDef,
@@ -30,6 +30,7 @@ import {
30
30
  import { bindAuthHttp } from "../auth/bindings.ts";
31
31
  import { hashChallenge } from "../auth/verification.ts";
32
32
  import { issueSessionWithScopes } from "../auth/sessions.ts";
33
+ import type { BoundaryContract } from "../kernel/boundary-contract.ts";
33
34
  import type { Binding } from "../kernel/on.ts";
34
35
  import type { Fx } from "../kernel/fx.ts";
35
36
  import type { AnyFlowDef } from "../kernel/flow.ts";
@@ -117,12 +118,14 @@ export function oauth(options: OAuthOptions = {}) {
117
118
  const now: () => number = () => runtime.now();
118
119
  const providers = buildProviders(options);
119
120
 
120
- const start = flow("auth.oauthStart", {
121
- plane: "user",
121
+ const startContract = {
122
122
  in: StartIn,
123
123
  out: StartOut,
124
124
  errors: { AuthFailed, AuthRateLimited },
125
- effects: {},
125
+ };
126
+
127
+ const start = flow("auth.oauthStart", {
128
+ plane: "user",
126
129
  do: async (input) => {
127
130
  const p = providers.find((c) => c.id === input.provider);
128
131
  if (!p) return fail("AuthFailed", { reason: "provider_disabled" });
@@ -162,11 +165,14 @@ export function oauth(options: OAuthOptions = {}) {
162
165
  },
163
166
  });
164
167
 
165
- const callback = flow("auth.oauthCallback", {
166
- plane: "user",
168
+ const callbackContract = {
167
169
  in: CallbackIn,
168
170
  out: SessionTokensOut,
169
171
  errors: { AuthFailed, AuthRateLimited },
172
+ };
173
+
174
+ const callback = flow("auth.oauthCallback", {
175
+ plane: "user",
170
176
  effects: { secrets: callbackSecretRefs(providers) },
171
177
  do: async (input, fx) => {
172
178
  if (typeof input.error === "string" && input.error.length > 0) {
@@ -257,12 +263,14 @@ export function oauth(options: OAuthOptions = {}) {
257
263
  },
258
264
  });
259
265
 
260
- const linkStart = flow("auth.oauthLinkStart", {
261
- plane: "user",
266
+ const linkStartContract = {
262
267
  in: StartIn,
263
268
  out: StartOut,
264
269
  errors: { AuthFailed, AuthRateLimited },
265
- effects: {},
270
+ };
271
+
272
+ const linkStart = flow("auth.oauthLinkStart", {
273
+ plane: "user",
266
274
  do: async (input, fx) => {
267
275
  const currentUserId = fx.auth.userId;
268
276
  if (!currentUserId) return fail("AuthFailed", { reason: "unauthenticated" });
@@ -314,9 +322,9 @@ export function oauth(options: OAuthOptions = {}) {
314
322
  .flow(callback)
315
323
  .flow(linkStart);
316
324
 
317
- def.binding(bindPublicAuth("/oauth/:provider/start", start, "otp"));
318
- for (const b of bindCallbackBothMethods(callback)) def.binding(b);
319
- def.binding(bindSessionAuth("/oauth/:provider/link", linkStart));
325
+ def.binding(bindPublicAuth("/oauth/:provider/start", start, "otp", startContract));
326
+ for (const b of bindCallbackBothMethods(callback, callbackContract)) def.binding(b);
327
+ def.binding(bindSessionAuth("/oauth/:provider/link", linkStart, linkStartContract));
320
328
 
321
329
  for (const contract of vaultContracts(providers)) def.vault(contract);
322
330
 
@@ -335,12 +343,12 @@ const OAUTH_FLOW_TTL_MS = 10 * 60_000;
335
343
  *
336
344
  * @param flowDef - Callback flow
337
345
  */
338
- function bindCallbackBothMethods(flowDef: AnyFlowDef): Binding[] {
346
+ function bindCallbackBothMethods(flowDef: AnyFlowDef, contract: BoundaryContract): Binding[] {
339
347
  const gates = authPublicGates("otp");
340
- const getBinding = bindPublicAuthGet("/auth/oauth/callback/:provider", flowDef, gates);
348
+ const getBinding = bindPublicAuthGet("/auth/oauth/callback/:provider", flowDef, gates, contract);
341
349
  const postBinding = bindAuthHttp(
342
350
  http
343
- .post("/auth/oauth/callback/:provider")
351
+ .post("/auth/oauth/callback/:provider", contract)
344
352
  .public()
345
353
  .gate(...gates),
346
354
  flowDef,
@@ -5,6 +5,7 @@
5
5
 
6
6
  import { constantTimeEqual } from "../auth/constant-time.ts";
7
7
  import {
8
+ completeVerifiedEmailSignIn,
8
9
  IdentityError,
9
10
  linkOrProvision,
10
11
  normalizeEmail,
@@ -40,6 +41,7 @@ import {
40
41
  z,
41
42
  type AuthMethodOptions,
42
43
  } from "./auth/shared.ts";
44
+ import { okid } from "../okid.ts";
43
45
 
44
46
  const E164 = /^\+[1-9]\d{7,14}$/;
45
47
  const DEFAULT_TTL_MS = 10 * 60 * 1000;
@@ -238,14 +240,17 @@ export function otp(opts: OtpOptions): PluginDef {
238
240
  });
239
241
 
240
242
  if (opts.mode === "provider") {
241
- const request = flow("auth.requestOtp", {
242
- plane: "user",
243
+ const requestContract = {
243
244
  in: z.object({
244
245
  phone: z.string().min(8),
245
246
  lang: z.enum(["en", "ar"]).optional(),
246
247
  }),
247
248
  out: requestOut,
248
249
  errors: { AuthFailed, AuthRateLimited },
250
+ };
251
+
252
+ const request = flow("auth.requestOtp", {
253
+ plane: "user",
249
254
  effects: { sends: ["sms-otp"] },
250
255
  do: async (input, fx) => {
251
256
  const phone = input.phone.trim();
@@ -254,7 +259,7 @@ export function otp(opts: OtpOptions): PluginDef {
254
259
  const identifier = `otp:${phone}`;
255
260
  invalidateVerifications(verifications, identifier, now);
256
261
 
257
- const requestId = crypto.randomUUID();
262
+ const requestId = okid();
258
263
  await fx.sendOtp({
259
264
  to: phone,
260
265
  requestId,
@@ -262,7 +267,7 @@ export function otp(opts: OtpOptions): PluginDef {
262
267
  });
263
268
 
264
269
  putVerification(verifications, {
265
- id: crypto.randomUUID(),
270
+ id: okid(),
266
271
  identifier,
267
272
  value: `provider:${requestId}`,
268
273
  expiresAt: now + ttlMs,
@@ -277,8 +282,7 @@ export function otp(opts: OtpOptions): PluginDef {
277
282
  },
278
283
  });
279
284
 
280
- const verify = flow("auth.verifyOtp", {
281
- plane: "user",
285
+ const verifyContract = {
282
286
  in: z.object({
283
287
  phone: z.string().min(8),
284
288
  otp: z.string().min(4).max(8),
@@ -286,6 +290,10 @@ export function otp(opts: OtpOptions): PluginDef {
286
290
  }),
287
291
  out: SessionTokensOut,
288
292
  errors: { AuthFailed, AuthRateLimited },
293
+ };
294
+
295
+ const verify = flow("auth.verifyOtp", {
296
+ plane: "user",
289
297
  effects: { sends: ["sms-otp"] },
290
298
  do: async (input, fx) => {
291
299
  const phone = input.phone.trim();
@@ -352,13 +360,12 @@ export function otp(opts: OtpOptions): PluginDef {
352
360
 
353
361
  return plugin("otp", { version: "0.0.1", config: configSnapshot })
354
362
  .needs("auth")
355
- .binding(bindPublicAuth("/otp/request", request, "otp"))
356
- .binding(bindPublicAuth("/otp/verify", verify, "otp"));
363
+ .binding(bindPublicAuth("/otp/request", request, "otp", requestContract))
364
+ .binding(bindPublicAuth("/otp/verify", verify, "otp", verifyContract));
357
365
  }
358
366
 
359
367
  // ── App mode ───────────────────────────────────────────────────────────
360
- const request = flow("auth.requestOtp", {
361
- plane: "user",
368
+ const requestContract = {
362
369
  in: z.object({
363
370
  email: z.string().min(3).optional(),
364
371
  phone: z.string().min(8).optional(),
@@ -367,6 +374,10 @@ export function otp(opts: OtpOptions): PluginDef {
367
374
  }),
368
375
  out: requestOut,
369
376
  errors: { AuthFailed, AuthRateLimited },
377
+ };
378
+
379
+ const request = flow("auth.requestOtp", {
380
+ plane: "user",
370
381
  effects: { sends: ["auth-otp", "auth-otp-email", "auth-otp-sms", "auth-otp-whatsapp"] },
371
382
  do: async (input, fx) => {
372
383
  const email = input.email ? normalizeEmail(input.email) : undefined;
@@ -392,7 +403,7 @@ export function otp(opts: OtpOptions): PluginDef {
392
403
 
393
404
  const sealed = await sealOtp(secret, otpCode);
394
405
  putVerification(verifications, {
395
- id: crypto.randomUUID(),
406
+ id: okid(),
396
407
  identifier,
397
408
  value: await hashChallenge(otpCode),
398
409
  expiresAt: now + ttlMs,
@@ -423,8 +434,7 @@ export function otp(opts: OtpOptions): PluginDef {
423
434
  },
424
435
  });
425
436
 
426
- const resend = flow("auth.resendOtp", {
427
- plane: "user",
437
+ const resendContract = {
428
438
  in: z.object({
429
439
  email: z.string().min(3).optional(),
430
440
  phone: z.string().min(8).optional(),
@@ -433,6 +443,10 @@ export function otp(opts: OtpOptions): PluginDef {
433
443
  }),
434
444
  out: requestOut,
435
445
  errors: { AuthFailed, AuthRateLimited },
446
+ };
447
+
448
+ const resend = flow("auth.resendOtp", {
449
+ plane: "user",
436
450
  effects: { sends: ["auth-otp", "auth-otp-email", "auth-otp-sms", "auth-otp-whatsapp"] },
437
451
  do: async (input, fx) => {
438
452
  const email = input.email ? normalizeEmail(input.email) : undefined;
@@ -492,8 +506,7 @@ export function otp(opts: OtpOptions): PluginDef {
492
506
  },
493
507
  });
494
508
 
495
- const verify = flow("auth.verifyOtp", {
496
- plane: "user",
509
+ const verifyContract = {
497
510
  in: z.object({
498
511
  email: z.string().min(3).optional(),
499
512
  phone: z.string().min(8).optional(),
@@ -501,6 +514,10 @@ export function otp(opts: OtpOptions): PluginDef {
501
514
  }),
502
515
  out: SessionTokensOut,
503
516
  errors: { AuthFailed, AuthRateLimited },
517
+ };
518
+
519
+ const verify = flow("auth.verifyOtp", {
520
+ plane: "user",
504
521
  do: async (input) => {
505
522
  const email = input.email ? normalizeEmail(input.email) : undefined;
506
523
  const phone = input.phone?.trim();
@@ -563,11 +580,10 @@ export function otp(opts: OtpOptions): PluginDef {
563
580
  let user: UserIdentityRow;
564
581
  try {
565
582
  user = (
566
- await linkOrProvision(identities, {
583
+ await completeVerifiedEmailSignIn(identities, runtime.sessions, {
567
584
  provider: "otp",
568
585
  providerAccountId: email!,
569
- email,
570
- emailVerified: true,
586
+ email: email!,
571
587
  now: () => now,
572
588
  })
573
589
  ).user;
@@ -598,7 +614,7 @@ export function otp(opts: OtpOptions): PluginDef {
598
614
  .channelTemplate(otpSmsTemplate)
599
615
  .channelTemplate(otpWhatsappTemplate)
600
616
  .channelCatalog(otpCatalog)
601
- .binding(bindPublicAuth("/otp/request", request, "otp"))
602
- .binding(bindPublicAuth("/otp/verify", verify, "otp"))
603
- .binding(bindPublicAuth("/otp/resend", resend, "otp"));
617
+ .binding(bindPublicAuth("/otp/request", request, "otp", requestContract))
618
+ .binding(bindPublicAuth("/otp/verify", verify, "otp", verifyContract))
619
+ .binding(bindPublicAuth("/otp/resend", resend, "otp", resendContract));
604
620
  }
@@ -2,7 +2,7 @@
2
2
  * Minimal WebAuthn assertion / registration verifier (ES256 / P-256).
3
3
  *
4
4
  * Validates clientDataJSON (type · challenge · origin), authenticatorData
5
- * (rpId hash · user-presence), and ECDSA signature over
5
+ * (rpId hash · user-presence · user-verified), and ECDSA signature over
6
6
  * `authenticatorData || SHA-256(clientDataJSON)` against a stored SPKI public key.
7
7
  */
8
8
 
@@ -92,14 +92,17 @@ export async function verifyWebAuthnCeremony(
92
92
  const flags = authData[32]!;
93
93
  // User Present (bit 0) required.
94
94
  if ((flags & 0x01) === 0) return { ok: false, reason: "invalid_credentials" };
95
+ // User Verified (bit 2) required — never accept UV=false (even if RP policy is "preferred").
96
+ if ((flags & 0x04) === 0) return { ok: false, reason: "user_not_verified" };
95
97
 
96
98
  const signCount =
97
99
  ((authData[33]! << 24) | (authData[34]! << 16) | (authData[35]! << 8) | authData[36]!) >>> 0;
98
100
 
99
101
  if (opts.previousSignCount !== undefined) {
100
- // Spec: if both non-zero, authenticator count must strictly increase.
101
- if (opts.previousSignCount > 0 && signCount > 0 && signCount <= opts.previousSignCount) {
102
- return { ok: false, reason: "invalid_credentials" };
102
+ // Stored counter non-zero + non-increasing possible cloned / replayed authenticator.
103
+ // Skip when stored is 0 (synced / non-counter authenticators).
104
+ if (opts.previousSignCount !== 0 && signCount <= opts.previousSignCount) {
105
+ return { ok: false, reason: "cloned_authenticator" };
103
106
  }
104
107
  }
105
108
 
@@ -164,12 +167,12 @@ export function b64urlDecode(input: string): Uint8Array<ArrayBuffer> {
164
167
  *
165
168
  * @param rpId - Relying party id
166
169
  * @param signCount - Counter
167
- * @param flags - Flag byte (default UP=0x01)
170
+ * @param flags - Flag byte (default UP|UV = 0x05)
168
171
  */
169
172
  export async function buildAuthenticatorData(
170
173
  rpId: string,
171
174
  signCount: number,
172
- flags = 0x01,
175
+ flags = 0x05,
173
176
  ): Promise<Uint8Array> {
174
177
  const rpHash = new Uint8Array(
175
178
  await crypto.subtle.digest("SHA-256", new TextEncoder().encode(rpId)),