okengine 0.18.4 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (512) hide show
  1. package/AGENTS.md +18 -10
  2. package/README.md +2 -2
  3. package/package.json +51 -50
  4. package/site/content/docs/ai/index.mdx +25 -6
  5. package/site/content/docs/ai/llms-txt.mdx +1 -1
  6. package/site/content/docs/ai/meta.json +1 -1
  7. package/site/content/docs/ai/skills.mdx +4 -1
  8. package/site/content/docs/ai/try-it.mdx +58 -0
  9. package/site/content/docs/client/auth.mdx +208 -0
  10. package/site/content/docs/client/calling.mdx +451 -0
  11. package/site/content/docs/client/index.mdx +209 -0
  12. package/site/content/docs/client/live.mdx +234 -0
  13. package/site/content/docs/client/meta.json +5 -0
  14. package/site/content/docs/client/react.mdx +249 -0
  15. package/site/content/docs/elements/ai/agents.mdx +232 -0
  16. package/site/content/docs/elements/ai/index.mdx +341 -0
  17. package/site/content/docs/elements/ai/mcp.mdx +275 -0
  18. package/site/content/docs/elements/ai/meta.json +5 -0
  19. package/site/content/docs/elements/ai/models.mdx +283 -0
  20. package/site/content/docs/elements/ai/prompts.mdx +256 -0
  21. package/site/content/docs/elements/channel/email.mdx +266 -0
  22. package/site/content/docs/elements/channel/index.mdx +344 -0
  23. package/site/content/docs/elements/channel/meta.json +5 -0
  24. package/site/content/docs/elements/channel/push.mdx +218 -0
  25. package/site/content/docs/elements/channel/receipts.mdx +206 -0
  26. package/site/content/docs/elements/channel/sms.mdx +264 -0
  27. package/site/content/docs/elements/channel/whatsapp.mdx +220 -0
  28. package/site/content/docs/elements/clock/index.mdx +300 -0
  29. package/site/content/docs/elements/clock/meta.json +5 -0
  30. package/site/content/docs/elements/clock/schedules.mdx +508 -0
  31. package/site/content/docs/elements/clock/sleep.mdx +445 -0
  32. package/site/content/docs/elements/flow/consumers.mdx +707 -0
  33. package/site/content/docs/elements/flow/http.mdx +1080 -0
  34. package/site/content/docs/elements/flow/index.mdx +618 -0
  35. package/site/content/docs/elements/flow/meta.json +5 -0
  36. package/site/content/docs/elements/flow/routing.mdx +559 -0
  37. package/site/content/docs/elements/flow/workflows.mdx +640 -0
  38. package/site/content/docs/elements/gate/auth.mdx +371 -0
  39. package/site/content/docs/elements/gate/authorization.mdx +298 -0
  40. package/site/content/docs/elements/gate/index.mdx +418 -0
  41. package/site/content/docs/elements/gate/meta.json +5 -0
  42. package/site/content/docs/elements/gate/rate-limits.mdx +365 -0
  43. package/site/content/docs/elements/gate/rls.mdx +303 -0
  44. package/site/content/docs/elements/gate/tenancy.mdx +372 -0
  45. package/site/content/docs/elements/index.mdx +52 -11
  46. package/site/content/docs/elements/signal/broadcast.mdx +517 -0
  47. package/site/content/docs/elements/signal/index.mdx +352 -0
  48. package/site/content/docs/elements/signal/live.mdx +590 -0
  49. package/site/content/docs/elements/signal/meta.json +5 -0
  50. package/site/content/docs/elements/signal/once.mdx +596 -0
  51. package/site/content/docs/elements/store/files.mdx +659 -0
  52. package/site/content/docs/elements/store/index.mdx +310 -0
  53. package/site/content/docs/elements/store/kv.mdx +629 -0
  54. package/site/content/docs/elements/store/meta.json +5 -0
  55. package/site/content/docs/elements/store/search.mdx +946 -0
  56. package/site/content/docs/elements/store/sql.mdx +937 -0
  57. package/site/content/docs/elements/vault/config.mdx +256 -0
  58. package/site/content/docs/elements/vault/index.mdx +356 -0
  59. package/site/content/docs/elements/vault/meta.json +5 -0
  60. package/site/content/docs/elements/vault/rotation.mdx +273 -0
  61. package/site/content/docs/elements/vault/secrets.mdx +285 -0
  62. package/site/content/docs/index.mdx +78 -30
  63. package/site/content/docs/meta.json +11 -8
  64. package/site/content/docs/plugins/cors.mdx +2 -1
  65. package/site/content/docs/plugins/csrf.mdx +4 -1
  66. package/site/content/docs/plugins/headers.mdx +2 -1
  67. package/site/content/docs/plugins/ip-allowlist.mdx +2 -1
  68. package/site/content/docs/plugins/magic-link.mdx +6 -0
  69. package/site/content/docs/plugins/maintenance-mode.mdx +2 -1
  70. package/site/content/docs/plugins/oauth.mdx +1 -1
  71. package/site/content/docs/plugins/otp.mdx +12 -1
  72. package/site/content/docs/plugins/passkey.mdx +39 -15
  73. package/site/content/docs/plugins/two-factor.mdx +91 -34
  74. package/site/content/docs/plugins/username.mdx +1 -1
  75. package/site/content/docs/recipes/caddy.mdx +3 -3
  76. package/site/content/docs/recipes/index.mdx +18 -28
  77. package/site/content/docs/recipes/mailpit.mdx +1 -1
  78. package/site/content/docs/recipes/meilisearch.mdx +3 -3
  79. package/site/content/docs/recipes/meta.json +1 -4
  80. package/site/content/docs/recipes/nginx.mdx +2 -2
  81. package/site/content/docs/recipes/openrouter.mdx +283 -0
  82. package/site/content/docs/recipes/pgdog.mdx +3 -3
  83. package/site/content/docs/recipes/postgres.mdx +1 -2
  84. package/site/content/docs/recipes/rustfs.mdx +2 -2
  85. package/site/content/docs/recipes/traefik.mdx +3 -3
  86. package/site/content/docs/reference/cli.mdx +231 -0
  87. package/site/content/docs/reference/configuration.mdx +30 -10
  88. package/site/content/docs/reference/environment-variables.mdx +93 -20
  89. package/site/content/docs/reference/errors.mdx +132 -29
  90. package/site/content/docs/reference/fx.mdx +137 -73
  91. package/site/content/docs/reference/index.mdx +13 -21
  92. package/site/content/docs/reference/meta.json +4 -5
  93. package/site/content/docs/reference/okid.mdx +42 -17
  94. package/site/content/docs/reference/plugins.mdx +4 -3
  95. package/site/content/docs/reference/security.mdx +197 -0
  96. package/site/content/docs/understand/meta.json +5 -0
  97. package/site/content/docs/understand/the-anatomy.mdx +132 -0
  98. package/site/content/docs/understand/the-model.mdx +32 -0
  99. package/site/content/docs/understand/the-problem.mdx +74 -0
  100. package/site/content/docs/understand/the-vocabulary.mdx +26 -0
  101. package/src/auth/api-keys.ts +2 -1
  102. package/src/auth/bindings.ts +64 -16
  103. package/src/auth/gate-auth.test.ts +6 -1
  104. package/src/auth/identity.ts +152 -5
  105. package/src/auth/index.ts +29 -0
  106. package/src/auth/invites.ts +2 -1
  107. package/src/auth/method-context.ts +41 -0
  108. package/src/auth/oauth-as/crypto.ts +2 -1
  109. package/src/auth/oauth-as/stores.ts +2 -1
  110. package/src/auth/operator.ts +2 -1
  111. package/src/auth/sessions-jwt.test.ts +77 -0
  112. package/src/auth/sessions.ts +20 -4
  113. package/src/auth/tenants.ts +3 -2
  114. package/src/auth/two-factor-challenge.test.ts +61 -0
  115. package/src/auth/two-factor-challenge.ts +284 -0
  116. package/src/auth/verification.ts +5 -0
  117. package/src/bench/REPORT.md +49 -0
  118. package/src/bench/g03-signal-once.bench.ts +1 -1
  119. package/src/bench/g10-observability-contention.bench.ts +1 -1
  120. package/src/bench/g17-hybrid-search.bench.ts +572 -0
  121. package/src/bench/load-app.ts +2 -10
  122. package/src/cli/ai-setup/ai-setup.test.ts +331 -42
  123. package/src/cli/ai-setup/apply.ts +294 -52
  124. package/src/cli/ai-setup/catalog.ts +238 -1342
  125. package/src/cli/ai-setup/index.ts +14 -99
  126. package/src/cli/ai-setup/prompts.ts +60 -443
  127. package/src/cli/ask-seed.test.ts +96 -0
  128. package/src/cli/ask-seed.ts +82 -0
  129. package/src/cli/ask-vault-gaps.test.ts +84 -0
  130. package/src/cli/ask-vault-gaps.ts +146 -0
  131. package/src/cli/build.ts +2 -2
  132. package/src/cli/client-add.test.ts +26 -1
  133. package/src/cli/client-add.ts +106 -27
  134. package/src/cli/competitor-mention-removal.test.ts +1 -1
  135. package/src/cli/db-seed.ts +2 -0
  136. package/src/cli/db.ts +150 -11
  137. package/src/cli/dev.ts +119 -211
  138. package/src/cli/docker-clean.ts +2 -2
  139. package/src/cli/docker-cli.test.ts +1 -1
  140. package/src/cli/doctor-diff.ts +4 -2
  141. package/src/cli/doctor-pii.test.ts +1 -1
  142. package/src/cli/doctor.ts +14 -1
  143. package/src/cli/hero-meta.test.ts +4 -2
  144. package/src/cli/load-config.images.test.ts +8 -10
  145. package/src/cli/load-config.ts +1 -1
  146. package/src/cli/registry.ts +22 -10
  147. package/src/cli/replay.ts +3 -1
  148. package/src/cli/start.ts +1 -1
  149. package/src/cli/tui/keys.ts +2 -3
  150. package/src/client/auth/cookies.ts +74 -0
  151. package/src/client/auth/create-auth-client.ts +636 -0
  152. package/src/client/auth/denials.ts +99 -0
  153. package/src/client/auth/session.ts +283 -0
  154. package/src/client/auth.test.ts +251 -0
  155. package/src/client/auth.ts +39 -114
  156. package/src/client/create-with-session.ts +245 -0
  157. package/src/client/create.ts +51 -5
  158. package/src/client/index.ts +11 -1
  159. package/src/client/live.ts +11 -79
  160. package/src/client/notes-contract.test.ts +5 -10
  161. package/src/client/sse.ts +137 -0
  162. package/src/client/stream.ts +147 -0
  163. package/src/client/transport.test.ts +32 -0
  164. package/src/client/transport.ts +123 -26
  165. package/src/client/types.ts +118 -14
  166. package/src/client-react/index.ts +185 -24
  167. package/src/client-react/use-live-query.ts +13 -3
  168. package/src/compiler/aot.test.ts +7 -4
  169. package/src/compiler/effects-embed.test.ts +56 -0
  170. package/src/compiler/effects-fetch.test.ts +43 -0
  171. package/src/compiler/effects-infer.ts +58 -2
  172. package/src/compiler/extract.test.ts +258 -44
  173. package/src/compiler/extract.ts +566 -48
  174. package/src/compiler/fixtures/skyport/src/flows/bookings/index.ts +7 -4
  175. package/src/compiler/fixtures/skyport/src/flows/bookings/signals.ts +2 -8
  176. package/src/compiler/fixtures/skyport.expected.json +4 -4
  177. package/src/compiler/fixtures/triggers/five-triggers.ts +14 -10
  178. package/src/compiler/response.ts +2 -2
  179. package/src/compiler/search-writer-isolation.test.ts +40 -0
  180. package/src/config/index.ts +11 -0
  181. package/src/console/server/app.ts +17 -23
  182. package/src/console/server/bind.ts +4 -0
  183. package/src/console/server/console.test.ts +2 -2
  184. package/src/console/server/flows-invoke.test.ts +50 -34
  185. package/src/console/server/flows.ts +436 -255
  186. package/src/console/server/runs-ingest.test.ts +6 -3
  187. package/src/console/server/serve.ts +1 -1
  188. package/src/console/server/signals.test.ts +1 -5
  189. package/src/console/server/signals.ts +8 -6
  190. package/src/console/server/state.ts +6 -1
  191. package/src/console/server/store.ts +1 -1
  192. package/src/console/ui-next/dist/assets/FileExportIcon-Ck-5od4R.js +1 -0
  193. package/src/console/ui-next/dist/assets/MoreHorizontalCircle01Icon-gMNGsE37.js +1 -0
  194. package/src/console/ui-next/dist/assets/PlusSignIcon-CwG3nxfu.js +1 -0
  195. package/src/console/ui-next/dist/assets/UnavailableIcon-D9cvHVPr.js +1 -0
  196. package/src/console/ui-next/dist/assets/UserIcon-DaE2PB5_.js +1 -0
  197. package/src/console/ui-next/dist/assets/access-page-DFeymU07.js +4 -0
  198. package/src/console/ui-next/dist/assets/agent-disclosure-U1rdfblp.js +1 -0
  199. package/src/console/ui-next/dist/assets/cache-glyph-BeFJeqBG.js +1 -0
  200. package/src/console/ui-next/dist/assets/call-pii-button-CVAONPii.js +1 -0
  201. package/src/console/ui-next/dist/assets/collapsible-D2A6NJ-3.js +1 -0
  202. package/src/console/ui-next/dist/assets/copy-inline-button-CAYD18cr.js +1 -0
  203. package/src/console/ui-next/dist/assets/dagre.esm-B1_XeuLP.js +1 -0
  204. package/src/console/ui-next/dist/assets/detail-header-DVWNjWjg.js +1 -0
  205. package/src/console/ui-next/dist/assets/dropdown-menu-4h2LOVXM.js +1 -0
  206. package/src/console/ui-next/dist/assets/duration-tone-Cgk_h5ja.js +9 -0
  207. package/src/console/ui-next/dist/assets/element-icons-BI8cJgdh.js +1 -0
  208. package/src/console/ui-next/dist/assets/explorer-empty-CJs5A-wm.js +1 -0
  209. package/src/console/ui-next/dist/assets/flows-page-Dss7941e.js +1 -0
  210. package/src/console/ui-next/dist/assets/highlighted-json-MYZQtRnw.js +154 -0
  211. package/src/console/ui-next/dist/assets/http-method-Jrh39p7A.js +1 -0
  212. package/src/console/ui-next/dist/assets/index-D0zS5rKO.css +2 -0
  213. package/src/console/ui-next/dist/assets/index-DXP2dBIF.js +63 -0
  214. package/src/console/ui-next/dist/assets/observability-page-HvolXxTI.js +4 -0
  215. package/src/console/ui-next/dist/assets/react-dom-Ddte4I-Q.js +9 -0
  216. package/src/console/ui-next/dist/assets/replica-lag-CSh2dzrb.js +18 -0
  217. package/src/console/ui-next/dist/assets/request-meta-BatF8KrK.js +1 -0
  218. package/src/console/ui-next/dist/assets/shortcut-keys-3ILd8oGn.js +1 -0
  219. package/src/console/ui-next/dist/assets/{sql-BskegiFM.js → sql-BsFa4tDR.js} +1 -1
  220. package/src/console/ui-next/dist/assets/store-page-eiKiHnNe.js +41 -0
  221. package/src/console/ui-next/dist/assets/trace-detail-sheet-CZkMeKS-.js +2 -0
  222. package/src/console/ui-next/dist/assets/tree-expand-toggle-CW8y5A2h.js +55 -0
  223. package/src/console/ui-next/dist/assets/units-page-B_RWJrEO.js +1 -0
  224. package/src/console/ui-next/dist/assets/{use-vault-list-uk4WVboC.js → use-vault-list-CT4-gajj.js} +1 -1
  225. package/src/console/ui-next/dist/assets/vault-page-CWrg-A68.js +2 -0
  226. package/src/console/ui-next/dist/assets/xyflow-CSyC6ryz.css +1 -0
  227. package/src/console/ui-next/dist/assets/xyflow-yApv7D4e.js +7 -0
  228. package/src/console/ui-next/dist/index.html +6 -13
  229. package/src/console/ui-next/seed-invoke-host.ts +21 -18
  230. package/src/console/ui-next/src/client.ts +7 -2
  231. package/src/console/ui-next/src/features/flows/graph/element-map.ts +2 -0
  232. package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +12 -2
  233. package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +4 -0
  234. package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +8 -0
  235. package/src/console/ui-next/src/features/flows/traces/waterfall-bars.ts +4 -0
  236. package/src/console/ui-next/src/features/flows/traces/waterfall-tooltip.ts +9 -3
  237. package/src/console/ui-next/src/features/store/detail/reveal-cell.tsx +7 -11
  238. package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +2 -2
  239. package/src/console/ui-next/src/features/store/lib/fields-from-table.ts +2 -0
  240. package/src/console/ui-next/src/features/store/lib/grid-model.test.ts +25 -1
  241. package/src/console/ui-next/src/features/store/lib/grid-model.ts +36 -1
  242. package/src/console/ui-next/src/features/store/query/query-results.tsx +3 -3
  243. package/src/console/ui-next/src/features/units/detail/effects-summary.tsx +2 -0
  244. package/src/console/ui-next/src/features/units/lib/call-read-safe.ts +3 -1
  245. package/src/console/ui-next/src/features/units/lib/fields-from-schema.ts +6 -0
  246. package/src/docker/ai-model-status.test.ts +1 -12
  247. package/src/docker/ai-model-status.ts +10 -38
  248. package/src/docker/compose-up.test.ts +100 -0
  249. package/src/docker/compose-up.ts +292 -0
  250. package/src/docker/compose.ts +4 -14
  251. package/src/docker/derive.ts +9 -34
  252. package/src/docker/docker.test.ts +16 -261
  253. package/src/docker/images-config.test.ts +13 -25
  254. package/src/docker/index.ts +1 -24
  255. package/src/docker/recipes/index.ts +0 -23
  256. package/src/docker/recipes/pgdog.ts +6 -4
  257. package/src/docker/stack-id.test.ts +2 -2
  258. package/src/docker/stack-id.ts +0 -1
  259. package/src/docker/types.ts +2 -2
  260. package/src/drivers/ai-anthropic.ts +10 -0
  261. package/src/drivers/ai-openai-compatible.ts +34 -7
  262. package/src/drivers/ai-providers.test.ts +0 -107
  263. package/src/drivers/ai-stream.test.ts +0 -34
  264. package/src/drivers/ai-types.ts +17 -8
  265. package/src/drivers/channel-fcm.ts +6 -1
  266. package/src/drivers/channel-msegat.ts +6 -1
  267. package/src/drivers/channel-resend.ts +5 -1
  268. package/src/drivers/channel-smtp.ts +5 -1
  269. package/src/drivers/channel-sndr.ts +10 -1
  270. package/src/drivers/channel-taqnyat-mail.ts +5 -1
  271. package/src/drivers/channel-taqnyat-whatsapp.ts +6 -1
  272. package/src/drivers/channel-taqnyat.ts +6 -1
  273. package/src/drivers/channel-types.ts +10 -0
  274. package/src/drivers/channel-unifonic.ts +6 -1
  275. package/src/drivers/channel-wa-cloud.ts +6 -1
  276. package/src/drivers/channel-webpush.ts +6 -1
  277. package/src/drivers/external.ts +33 -0
  278. package/src/drivers/index.ts +0 -10
  279. package/src/drivers/meilisearch.ts +6 -0
  280. package/src/drivers/oauth-types.ts +4 -0
  281. package/src/drivers/postgres.test.ts +74 -3
  282. package/src/drivers/postgres.ts +175 -6
  283. package/src/drivers/signal-types.ts +5 -5
  284. package/src/drivers/types.ts +7 -0
  285. package/src/drivers/vault-types.ts +5 -0
  286. package/src/elements/ai/declare.ts +17 -4
  287. package/src/elements/ai/eval.ts +1 -1
  288. package/src/elements/ai/mcp-http.ts +1 -1
  289. package/src/elements/ai/pii.ts +1 -1
  290. package/src/elements/ai/providers.test.ts +289 -0
  291. package/src/elements/ai/providers.ts +169 -0
  292. package/src/elements/ai/runtime.ts +68 -5
  293. package/src/elements/ai/schema.ts +2 -2
  294. package/src/elements/ai.test.ts +7 -7
  295. package/src/elements/ai.ts +17 -1
  296. package/src/elements/channel/runtime.ts +105 -12
  297. package/src/elements/clock/cron-fields.test.ts +144 -0
  298. package/src/elements/clock/cron-fields.ts +185 -0
  299. package/src/elements/clock/declare.ts +247 -3
  300. package/src/elements/clock.ts +21 -1
  301. package/src/elements/index.ts +15 -0
  302. package/src/elements/signal/chaos-child.ts +1 -2
  303. package/src/elements/signal/declare.ts +65 -37
  304. package/src/elements/signal/delivery-modes.test.ts +11 -31
  305. package/src/elements/signal/dry-run-replay.test.ts +1 -7
  306. package/src/elements/signal/dry-run-write-isolation.test.ts +1 -7
  307. package/src/elements/signal/key-ordering.test.ts +6 -26
  308. package/src/elements/signal/lease-reclaim.test.ts +2 -6
  309. package/src/elements/signal/optional-emit.test.ts +6 -9
  310. package/src/elements/signal/order-lifecycle.test.ts +4 -17
  311. package/src/elements/signal/orphan-messages.test.ts +4 -15
  312. package/src/elements/signal/reconcile.test.ts +2 -2
  313. package/src/elements/signal/runtime.ts +1 -1
  314. package/src/elements/signal/schema-emit.test.ts +4 -8
  315. package/src/elements/signal.test.ts +24 -49
  316. package/src/elements/signal.ts +9 -2
  317. package/src/elements/store/domain-ddl.test.ts +1 -1
  318. package/src/elements/store/live-http.test.ts +9 -4
  319. package/src/elements/store/prepare-row.test.ts +123 -0
  320. package/src/elements/store/resource.ts +43 -12
  321. package/src/elements/store/schema-decl.ts +117 -0
  322. package/src/elements/store/search-backfill.ts +205 -0
  323. package/src/elements/store/search-bind.ts +66 -0
  324. package/src/elements/store/search-bm25.ts +78 -0
  325. package/src/elements/store/search-ddl.ts +156 -0
  326. package/src/elements/store/search-embed-flow.ts +141 -0
  327. package/src/elements/store/search-errors.ts +34 -0
  328. package/src/elements/store/search-fusion.ts +112 -0
  329. package/src/elements/store/search-lsh.ts +179 -0
  330. package/src/elements/store/search-runtime.ts +300 -0
  331. package/src/elements/store/search.test.ts +144 -0
  332. package/src/elements/store/sql-session.test.ts +1 -0
  333. package/src/elements/store/sql-session.ts +83 -3
  334. package/src/elements/store/table.ts +23 -2
  335. package/src/elements/store/upsert-app.test.ts +1 -3
  336. package/src/elements/store.ts +50 -0
  337. package/src/full.ts +4 -2
  338. package/src/http.ts +4 -1
  339. package/src/i18n/catalogs/ar.ts +14 -6
  340. package/src/i18n/catalogs/en.ts +14 -6
  341. package/src/index.ts +6 -1
  342. package/src/kernel/adopt-barrel-fresh.test.ts +7 -7
  343. package/src/kernel/adopt-routes.ts +9 -1
  344. package/src/kernel/app-auth.ts +5 -0
  345. package/src/kernel/app.ts +102 -9
  346. package/src/kernel/auto-cache.test.ts +6 -12
  347. package/src/kernel/auto-registry.test.ts +3 -3
  348. package/src/kernel/boot-bind/ai.test.ts +22 -19
  349. package/src/kernel/boot-bind/ai.ts +5 -43
  350. package/src/kernel/boot-bind/clock.ts +23 -5
  351. package/src/kernel/boot-bind/honor-config.test.ts +4 -4
  352. package/src/kernel/boot.test.ts +11 -6
  353. package/src/kernel/boot.ts +66 -16
  354. package/src/kernel/boundary-contract.ts +93 -0
  355. package/src/kernel/budget.test.ts +1 -1
  356. package/src/kernel/call.ts +89 -0
  357. package/src/kernel/capability.ts +6 -0
  358. package/src/kernel/client-descriptor.ts +123 -0
  359. package/src/kernel/clock-timezone.test.ts +80 -0
  360. package/src/kernel/correlation.test.ts +1 -1
  361. package/src/kernel/dry-run.ts +11 -8
  362. package/src/kernel/effects-stamping.test.ts +42 -9
  363. package/src/kernel/effects.test.ts +4 -2
  364. package/src/kernel/effects.ts +51 -9
  365. package/src/kernel/errors-channel.ts +17 -0
  366. package/src/kernel/errors-live-resume.ts +3 -2
  367. package/src/kernel/errors-tenant.ts +7 -4
  368. package/src/kernel/errors.registry-helpers.ts +96 -0
  369. package/src/kernel/errors.registry.test.ts +139 -25
  370. package/src/kernel/errors.ts +102 -23
  371. package/src/kernel/external-effects.test.ts +196 -0
  372. package/src/kernel/flow.test.ts +13 -4
  373. package/src/kernel/flow.ts +69 -71
  374. package/src/kernel/fx-ask-telemetry.test.ts +2 -2
  375. package/src/kernel/fx-dead-letters.test.ts +3 -3
  376. package/src/kernel/fx-fetch.ts +55 -0
  377. package/src/kernel/fx-live-stream.ts +2 -2
  378. package/src/kernel/fx-live.test.ts +8 -8
  379. package/src/kernel/fx.test.ts +17 -11
  380. package/src/kernel/fx.ts +180 -57
  381. package/src/kernel/horizontal-child.ts +1 -1
  382. package/src/kernel/http-query.test.ts +3 -6
  383. package/src/kernel/index.ts +13 -3
  384. package/src/kernel/instance-id.ts +1 -1
  385. package/src/kernel/instances.test.ts +40 -0
  386. package/src/kernel/instances.ts +47 -9
  387. package/src/kernel/live-http.test.ts +6 -7
  388. package/src/kernel/live-http.ts +6 -2
  389. package/src/kernel/live-resume.test.ts +1 -1
  390. package/src/kernel/mcp-tool.test.ts +3 -3
  391. package/src/kernel/on.ts +98 -4
  392. package/src/kernel/pipeline.test.ts +12 -15
  393. package/src/kernel/plugin-elements.test.ts +1 -1
  394. package/src/kernel/stamp-http.test.ts +3 -3
  395. package/src/kernel/triggers.ts +210 -95
  396. package/src/kernel-entry.ts +0 -1
  397. package/src/manifest/diff.ts +11 -1
  398. package/src/manifest/fixtures/skyport.excerpt.json +1 -1
  399. package/src/manifest/types.ts +46 -2
  400. package/src/mcp/docs-index.ts +3 -3
  401. package/src/mcp/docs-mcp.test.ts +2 -2
  402. package/src/mcp/docs-tools.ts +1 -1
  403. package/src/okid.test.ts +56 -0
  404. package/src/okid.ts +55 -11
  405. package/src/plugins/anonymous.ts +8 -4
  406. package/src/plugins/auth/shared.ts +27 -3
  407. package/src/plugins/auth-delivery.mailpit.integration.test.ts +1 -1
  408. package/src/plugins/auth-methods.security.test.ts +561 -23
  409. package/src/plugins/config-source.ts +1 -1
  410. package/src/plugins/csrf.test.ts +65 -0
  411. package/src/plugins/index.ts +2 -0
  412. package/src/plugins/magic-link.ts +36 -16
  413. package/src/plugins/oauth/shared.ts +3 -1
  414. package/src/plugins/oauth.ts +22 -14
  415. package/src/plugins/otp.ts +37 -21
  416. package/src/plugins/passkey-webauthn.ts +9 -6
  417. package/src/plugins/passkey.ts +98 -26
  418. package/src/plugins/pre-account-hijack.test.ts +223 -0
  419. package/src/plugins/two-factor.ts +466 -37
  420. package/src/plugins/username.ts +19 -7
  421. package/src/release/build-lib.ts +1 -0
  422. package/src/release/limits.ts +2 -2
  423. package/src/release/measure.ts +1 -1
  424. package/src/runs/collect.test.ts +0 -2
  425. package/src/runtime/json-code-block.test.ts +250 -20
  426. package/src/runtime/json-code-block.ts +1261 -41
  427. package/src/term.test.ts +1 -1
  428. package/src/test/create-test-app.test.ts +12 -12
  429. package/src/test/live-signals.test.ts +6 -6
  430. package/src/test/provisions.integration.test.ts +10 -14
  431. package/src/test/reset-element-registries.ts +1 -1
  432. package/src/test/tenant-isolation.test.ts +12 -6
  433. package/site/content/docs/deployment/docker-swarm.mdx +0 -164
  434. package/site/content/docs/deployment/docker.mdx +0 -227
  435. package/site/content/docs/deployment/index.mdx +0 -83
  436. package/site/content/docs/deployment/kubernetes.mdx +0 -176
  437. package/site/content/docs/deployment/meta.json +0 -5
  438. package/site/content/docs/deployment/reverse-proxy.mdx +0 -234
  439. package/site/content/docs/elements/ai.mdx +0 -385
  440. package/site/content/docs/elements/channel.mdx +0 -346
  441. package/site/content/docs/elements/clock.mdx +0 -244
  442. package/site/content/docs/elements/flow.mdx +0 -420
  443. package/site/content/docs/elements/gate.mdx +0 -436
  444. package/site/content/docs/elements/signal.mdx +0 -380
  445. package/site/content/docs/elements/store.mdx +0 -1099
  446. package/site/content/docs/elements/vault.mdx +0 -405
  447. package/site/content/docs/get-started/basic-usage.mdx +0 -173
  448. package/site/content/docs/get-started/index.mdx +0 -43
  449. package/site/content/docs/get-started/installation.mdx +0 -220
  450. package/site/content/docs/get-started/introduction.mdx +0 -132
  451. package/site/content/docs/get-started/meta.json +0 -13
  452. package/site/content/docs/get-started/project-structure.mdx +0 -925
  453. package/site/content/docs/get-started/testing.mdx +0 -328
  454. package/site/content/docs/get-started/why.mdx +0 -162
  455. package/site/content/docs/recipes/llama-cpp.mdx +0 -151
  456. package/site/content/docs/recipes/ollama.mdx +0 -142
  457. package/site/content/docs/recipes/sglang.mdx +0 -105
  458. package/site/content/docs/recipes/vllm.mdx +0 -106
  459. package/site/content/docs/reference/cli.md +0 -232
  460. package/site/content/docs/reference/client.mdx +0 -469
  461. package/site/content/docs/reference/security.md +0 -74
  462. package/src/cli/ai-setup/detect-ollama.ts +0 -213
  463. package/src/cli/ai-setup/recommend.test.ts +0 -225
  464. package/src/cli/ai-setup/recommend.ts +0 -225
  465. package/src/cli/dev-controls.test.ts +0 -122
  466. package/src/cli/dev-controls.ts +0 -164
  467. package/src/cli/tui/DevLive.tsx +0 -126
  468. package/src/console/ui-next/dist/assets/access-page-De7Lc2JC.js +0 -4
  469. package/src/console/ui-next/dist/assets/agent-disclosure-BP0Y0Sux.js +0 -1
  470. package/src/console/ui-next/dist/assets/cache-glyph-B5X-NM0-.js +0 -1
  471. package/src/console/ui-next/dist/assets/call-pii-button-ChAUiLo9.js +0 -1
  472. package/src/console/ui-next/dist/assets/collapsible-DGnOM2ph.js +0 -1
  473. package/src/console/ui-next/dist/assets/copy-inline-button-DBHhgHKP.js +0 -1
  474. package/src/console/ui-next/dist/assets/dagre.esm-ZwcdTuZZ.js +0 -1
  475. package/src/console/ui-next/dist/assets/detail-header-DhHM1iaZ.js +0 -1
  476. package/src/console/ui-next/dist/assets/dropdown-menu-_NjTEo5_.js +0 -1
  477. package/src/console/ui-next/dist/assets/duration-tone-sC3lGABz.js +0 -9
  478. package/src/console/ui-next/dist/assets/element-icons-BVXtRyd3.js +0 -1
  479. package/src/console/ui-next/dist/assets/explorer-empty-2uIhBu0_.js +0 -1
  480. package/src/console/ui-next/dist/assets/flows-page-RGy7VEA_.js +0 -1
  481. package/src/console/ui-next/dist/assets/highlighted-json-Awq7gYdu.js +0 -154
  482. package/src/console/ui-next/dist/assets/http-method-_UHM2ODJ.js +0 -1
  483. package/src/console/ui-next/dist/assets/index-Ck88Jmv8.css +0 -2
  484. package/src/console/ui-next/dist/assets/index-_rgpdVzo.js +0 -66
  485. package/src/console/ui-next/dist/assets/link-BX6Vqztd.js +0 -1
  486. package/src/console/ui-next/dist/assets/observability-page-Ds6pcnh-.js +0 -4
  487. package/src/console/ui-next/dist/assets/preload-helper-oH4irX4C.js +0 -1
  488. package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +0 -1
  489. package/src/console/ui-next/dist/assets/react-dom-Bph1y7z7.js +0 -9
  490. package/src/console/ui-next/dist/assets/replica-lag-DKRrbvdo.js +0 -18
  491. package/src/console/ui-next/dist/assets/request-meta-DV0ywz7t.js +0 -1
  492. package/src/console/ui-next/dist/assets/shortcut-JQIZlWfm.js +0 -1
  493. package/src/console/ui-next/dist/assets/shortcut-keys-DKxNTe_m.js +0 -1
  494. package/src/console/ui-next/dist/assets/skeleton-D-czQJT6.js +0 -1
  495. package/src/console/ui-next/dist/assets/store-page-02xOiqIK.js +0 -41
  496. package/src/console/ui-next/dist/assets/trace-detail-sheet-B09O8rA6.js +0 -2
  497. package/src/console/ui-next/dist/assets/tree-expand-toggle-BtyhmWb4.js +0 -55
  498. package/src/console/ui-next/dist/assets/units-page-4rHOePuE.js +0 -1
  499. package/src/console/ui-next/dist/assets/useMutation-B8EO02Ej.js +0 -1
  500. package/src/console/ui-next/dist/assets/useRender-BE2A9BWC.js +0 -1
  501. package/src/console/ui-next/dist/assets/vault-page-DISPgxLM.js +0 -2
  502. package/src/console/ui-next/dist/assets/xyflow-D7n4g6go.js +0 -7
  503. package/src/console/ui-next/dist/assets/xyflow-DZ0Ws1xk.css +0 -1
  504. package/src/docker/ollama-pull.ts +0 -232
  505. package/src/docker/recipes/llama-cpp.ts +0 -298
  506. package/src/docker/recipes/ollama.ts +0 -44
  507. package/src/docker/recipes/sglang.ts +0 -55
  508. package/src/docker/recipes/vllm.ts +0 -44
  509. package/src/drivers/ai-ollama-tools.integration.test.ts +0 -109
  510. package/src/drivers/ai-ollama.integration.test.ts +0 -184
  511. package/src/drivers/ai-ollama.ts +0 -389
  512. package/src/drivers/ollama.ts +0 -14
@@ -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)),