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
@@ -5,74 +5,122 @@ icon: "BookOpen"
5
5
  source: "docs/spec/unified-theory.md"
6
6
  ---
7
7
 
8
- Welcome to the okengine handbook. The backend model stays small; operational surfaces are derived from it instead of maintained separately.
8
+ Welcome to the OKEngine handbook. Keep the model small; let the backend grow without growing the mental model.
9
9
 
10
- <Callout title="The one law">
10
+ <Callout title="The Law">
11
11
  Every backend behavior is a Flow: `on(Trigger) → Effects`. One species; triggers are typed values.
12
12
  </Callout>
13
13
 
14
14
  ```ts
15
- on(http.post("/bookings"), createBooking);
16
- on(every("10m"), expireStale);
15
+ on(http.post(), createBooking); // path + name from the file tree
16
+ on(expireStaleClock, expireStale);
17
17
  on(orderPlaced, sendReceipt);
18
18
  ```
19
19
 
20
- ## Understand OKE (Core Model)
20
+ ## 01 Understand
21
+
22
+ Master the mental model before exploring features:
21
23
 
22
24
  <Cards>
23
25
  <Card
24
- title="Why OKE"
25
- description="From forty tools to one species — the problem, the law, and the Manifest contract."
26
- href="/docs/get-started/why"
26
+ title="The Problem"
27
+ description="Three unrelated features that hit the exact same wall, and the timeline that shows why."
28
+ href="/docs/understand/the-problem"
27
29
  />
28
30
  <Card
29
- title="Elements"
30
- description="The eight closed core elements: Flow, Signal, Store, Clock, Gate, Vault, Channel, AI."
31
- href="/docs/elements"
31
+ title="The Model"
32
+ description="The one rule that removes the disagreement between systems stated plainly, in two parts."
33
+ href="/docs/understand/the-model"
34
+ />
35
+ <Card
36
+ title="The Vocabulary"
37
+ description="The eight things the door recognizes — what each one replaces, and why nothing else made the cut."
38
+ href="/docs/understand/the-vocabulary"
32
39
  />
33
40
  <Card
34
- title="Introduction"
35
- description="The one law, eight elements, and one contract."
36
- href="/docs/get-started/introduction"
41
+ title="The Anatomy"
42
+ description="The five pieces behind on(trigger, flow) — on, trigger, flow, do, and fx — explained one at a time."
43
+ href="/docs/understand/the-anatomy"
44
+ />
45
+ <Card
46
+ title="Try It"
47
+ description="From an empty folder to a Flow running in the Console — one sitting, minimal detour."
48
+ href="/docs/ai/try-it"
37
49
  />
38
50
  </Cards>
39
51
 
40
- ## Build with OKE (Surfaces & Infrastructure)
52
+ ## 02 Elements
53
+
54
+ Deep primitives and execution law:
41
55
 
42
56
  <Cards>
43
57
  <Card
44
- title="Get Started"
45
- description="Installation, basic usage, project structure, and testing."
46
- href="/docs/get-started"
58
+ title="Elements"
59
+ description="Flow, Signal, Store, Clock, Gate, Vault, Channel, AI."
60
+ href="/docs/elements"
61
+ />
62
+ <Card
63
+ title="Reference"
64
+ description="CLI, configuration, fx, errors, security, and plugins."
65
+ href="/docs/reference"
66
+ />
67
+ <Card
68
+ title="AI Resources"
69
+ description="Runtime MCP (:6535), docs MCP (:6536), agent skills, and machine-readable docs."
70
+ href="/docs/ai"
47
71
  />
72
+ </Cards>
73
+
74
+ ## 03 Extend
75
+
76
+ Connect to infrastructure and extend the model:
77
+
78
+ <Cards>
48
79
  <Card
49
80
  title="Plugins"
50
- description="22 official extensions authentication, OAuth, security, ops, performance."
81
+ description="Official extensions that add capabilities without creating a ninth element."
51
82
  href="/docs/plugins"
52
83
  />
53
84
  <Card
54
85
  title="Providers"
55
- description="12 managed cloud infrastructure options behind protocol-named drivers."
86
+ description="Managed cloud infrastructure behind protocol-named drivers."
56
87
  href="/docs/providers"
57
88
  />
58
89
  <Card
59
90
  title="Recipes"
60
- description="20 self-hosted infrastructure recipes via Docker compose."
91
+ description="Self-hosted Docker compose recipes for SQL, KV, search, and local AI."
61
92
  href="/docs/recipes"
62
93
  />
94
+ </Cards>
95
+
96
+ ## 04 Client
97
+
98
+ Call your Flows from a browser, CLI, or another service — typed, zero separate codegen:
99
+
100
+ <Cards>
63
101
  <Card
64
- title="Reference"
65
- description="Configuration, fx ledger, environment variables, errors, client, and security."
66
- href="/docs/reference"
102
+ title="Overview"
103
+ description="createClient, envelopes as values, and how Client relates to the eight elements."
104
+ href="/docs/client"
67
105
  />
68
106
  <Card
69
- title="AI Resources & MCP"
70
- description="Runtime MCP (:6535), docs MCP (:6536), skills, and /llms.txt."
71
- href="/docs/ai"
107
+ title="Calling"
108
+ description="REST vs RPC, options, result helpers, resources, and remote types."
109
+ href="/docs/client/calling"
110
+ />
111
+ <Card
112
+ title="Auth"
113
+ description="Bearer sessions, refresh once on 401, and gate denials as values."
114
+ href="/docs/client/auth"
115
+ />
116
+ <Card
117
+ title="Live"
118
+ description="api.live SSE, resume, and live resource queries."
119
+ href="/docs/client/live"
72
120
  />
73
121
  <Card
74
- title="Deployment"
75
- description="Docker, Swarm, Kubernetes, and reverse proxy deployment."
76
- href="/docs/deployment"
122
+ title="React"
123
+ description="useSession, useLive, and useLiveQuery from okengine/client-react."
124
+ href="/docs/client/react"
77
125
  />
78
126
  </Cards>
@@ -3,16 +3,19 @@
3
3
  "icon": "BookOpen",
4
4
  "pages": [
5
5
  "index",
6
- "---Understand OKE---",
6
+ "---01 Understand---",
7
+ "understand",
8
+ "---02 Elements---",
7
9
  "elements",
8
- "---Build with OKE---",
9
- "get-started",
10
- "ai",
11
- "deployment",
12
- "reference",
10
+ "---03 Extend---",
13
11
  "plugins",
14
- "---Drivers---",
15
12
  "providers",
16
- "recipes"
13
+ "recipes",
14
+ "---04 Client---",
15
+ "client",
16
+ "---05 Reference---",
17
+ "reference",
18
+ "---06 AI Resources---",
19
+ "ai"
17
20
  ]
18
21
  }
@@ -71,7 +71,8 @@ const origins = configSource({
71
71
  db: { store: db },
72
72
  kv: cache,
73
73
  });
74
- on(every("30s"), origins.sync());
74
+ const corsSyncClock = clock("cors.sync", { every: "30s" });
75
+ on(corsSyncClock, origins.sync());
75
76
  export const app = oke({ name: "shop", env: "dev" }).plug(cors(origins));
76
77
  ```
77
78
 
@@ -18,6 +18,8 @@ export const app = oke({ name: "shop", env: "dev" }).plug(csrf());
18
18
 
19
19
  `GET`/`HEAD`/`OPTIONS`/`QUERY` always pass. A `POST` with `Sec-Fetch-Site: cross-site` now gets the gate element's typed `Forbidden` — unless its `Origin` is yours or allow-listed.
20
20
 
21
+ When `gate.auth.cookies.enabled`, the server **soft-requires** this plugin: prod boots refuse without it; dev/test warn. Use `allowNoHeader: false` for cookie-only SPAs.
22
+
21
23
  ## How it decides
22
24
 
23
25
  <Steps>
@@ -73,7 +75,8 @@ const rules = configSource({
73
75
  db: { store: db },
74
76
  kv: cache,
75
77
  });
76
- on(every("30s"), rules.sync());
78
+ const csrfSyncClock = clock("csrf.sync", { every: "30s" });
79
+ on(csrfSyncClock, rules.sync());
77
80
  export const app = oke({ name: "shop", env: "dev" }).plug(csrf(rules));
78
81
  ```
79
82
 
@@ -103,7 +103,8 @@ const headerConfig = configSource({
103
103
  db: { store: db },
104
104
  kv: cache,
105
105
  });
106
- on(every("30s"), headerConfig.sync());
106
+ const headerSyncClock = clock("headers.sync", { every: "30s" });
107
+ on(headerSyncClock, headerConfig.sync());
107
108
  export const app = oke({ name: "shop", env: "dev" }).plug(headers(headerConfig));
108
109
  ```
109
110
 
@@ -69,7 +69,8 @@ const rules = configSource({
69
69
  db: { store: db },
70
70
  kv: cache,
71
71
  });
72
- on(every("30s"), rules.sync());
72
+ const ipRulesSyncClock = clock("ip-allowlist.sync", { every: "30s" });
73
+ on(ipRulesSyncClock, rules.sync());
73
74
  export const app = oke({ name: "shop", env: "dev" }).plug(ipAllowlist(rules));
74
75
  ```
75
76
 
@@ -63,6 +63,12 @@ const { data } = await api.auth.verifyMagicLink({ token });
63
63
 
64
64
  </Steps>
65
65
 
66
+ <Callout type="info" title="Pre-account hijack defense">
67
+ Completing verify proves inbox ownership. An **unverified** email+password account parked on that
68
+ address is reclaimed (sessions revoked, hashes cleared, `emailVerified` set). Verified owners
69
+ re-auth only — no credential purge.
70
+ </Callout>
71
+
66
72
  ## Options
67
73
 
68
74
  | Option | Type | Default | Meaning |
@@ -78,7 +78,8 @@ const maintenance = configSource({
78
78
  db: { store: db },
79
79
  kv: cache,
80
80
  });
81
- on(every("30s"), maintenance.sync());
81
+ const maintenanceSyncClock = clock("maintenance.sync", { every: "30s" });
82
+ on(maintenanceSyncClock, maintenance.sync());
82
83
  export const app = oke({ name: "shop", env: "dev" }).plug(maintenanceMode(maintenance));
83
84
  ```
84
85
 
@@ -173,7 +173,7 @@ via `POST /auth/oauth/{provider}/link`.
173
173
 
174
174
  - [Gate](/docs/elements/gate) — `gate.auth`
175
175
  - [Passkey](/docs/plugins/passkey) — phishing-resistant alternative
176
- - [Client](/docs/reference/client) — calling `/auth` from the browser
176
+ - [Client · Auth](/docs/client/auth) — calling `/auth` from the browser
177
177
 
178
178
  ## Next
179
179
 
@@ -100,6 +100,12 @@ const { data } = await api.auth.verifyOtp({
100
100
 
101
101
  </Steps>
102
102
 
103
+ <Callout type="info" title="Pre-account hijack defense (email)">
104
+ App-mode email verify proves inbox ownership. An **unverified** parked email+password account is
105
+ reclaimed (sessions revoked, hashes cleared, `emailVerified` set) before the OTP session. Verified
106
+ owners re-auth only.
107
+ </Callout>
108
+
103
109
  ## Modes
104
110
 
105
111
  | | Provider mode | App mode |
@@ -202,6 +208,7 @@ In `local` / `test` the `console` driver captures messages. Use
202
208
  ## Learn more
203
209
 
204
210
  - [Magic link](/docs/plugins/magic-link) — link instead of a code
211
+ - [Two-factor](/docs/plugins/two-factor) — email OTP / TOTP as a **second** factor after password (distinct from this primary `/auth/otp` sign-in)
205
212
  - [Channel](/docs/elements/channel) — `fx.send`, `fx.sendOtp`, drivers, Mailpit
206
213
  - [Gate](/docs/elements/gate) — `gate.auth`
207
214
 
@@ -209,6 +216,10 @@ In `local` / `test` the `console` driver captures messages. Use
209
216
 
210
217
  <Cards>
211
218
  <Card title="Magic link" description="Email link sign-in." href="/docs/plugins/magic-link" />
212
- <Card title="Two-factor" description="TOTP second factor." href="/docs/plugins/two-factor" />
219
+ <Card
220
+ title="Two-factor"
221
+ description="TOTP / email OTP second factor."
222
+ href="/docs/plugins/two-factor"
223
+ />
213
224
  <Card title="Channel" description="Delivery drivers and Mailpit." href="/docs/elements/channel" />
214
225
  </Cards>
@@ -1,17 +1,17 @@
1
1
  ---
2
2
  title: "Passkey"
3
- description: "Official plugin — WebAuthn register and authenticate under /auth with signature and origin checks."
3
+ description: "Official plugin — WebAuthn register and authenticate under /auth with UV, signCount, and ceremony session binding."
4
4
  icon: "FingerprintPattern"
5
5
  source: "docs/spec/unified-theory.md"
6
6
  ---
7
7
 
8
- `passkey()` adds register and authenticate Flows for WebAuthn credentials (`oke_passkeys`).
9
- Options return a challenge; register and authenticate verify `clientDataJSON` origin + challenge,
10
- `authenticatorData` rpId hash, and an ECDSA P-256 signature against the stored SPKI public key.
8
+ `passkey()` adds register and authenticate Flows for WebAuthn credentials
9
+ (`oke_passkeys`). Options return a challenge plus a ceremony `sessionId`;
10
+ register/authenticate verify client data, authenticator data, and ECDSA P-256.
11
11
 
12
12
  <Callout title="The one rule">
13
13
  Enable `gate.auth`, then `.plug(passkey())`. Registration needs a Bearer session; authenticate is
14
- public. Post the full ceremony fields presence of a stored credential alone never issues a
14
+ public. Echo the options `sessionId` with every ceremony UV=false assertions never mint a
15
15
  session.
16
16
  </Callout>
17
17
 
@@ -42,15 +42,16 @@ Wire Bearer on `createClient` (`auth.getToken` / `memorySession`) — calls take
42
42
 
43
43
  ```typescript
44
44
  const opts = await api.auth.passkeyRegisterOptions({});
45
- // opts.data: { challenge, rpId, userId }
45
+ // opts.data: { challenge, sessionId, rpId, userId }
46
46
 
47
47
  await api.auth.passkeyRegister({
48
48
  credentialId: "...", // base64url
49
49
  publicKey: "...", // base64url SPKI (ECDSA P-256)
50
50
  userId: opts.data!.userId,
51
51
  challenge: opts.data!.challenge,
52
- clientDataJSON: "...", // base64url JSON { type, challenge, origin }
53
- authenticatorData: "...", // base64url
52
+ sessionId: opts.data!.sessionId,
53
+ clientDataJSON: "...", // base64url JSON { type: "webauthn.create", challenge, origin }
54
+ authenticatorData: "...", // base64url (UP|UV required)
54
55
  signature: "...", // base64url ECDSA over authData || SHA-256(clientDataJSON)
55
56
  });
56
57
  ```
@@ -67,19 +68,30 @@ const opts = await api.auth.passkeyAuthenticateOptions({});
67
68
  const { data } = await api.auth.passkeyAuthenticate({
68
69
  credentialId: "...",
69
70
  challenge: opts.data!.challenge,
70
- clientDataJSON: "...",
71
+ sessionId: opts.data!.sessionId,
72
+ clientDataJSON: "...", // type must be "webauthn.get"
71
73
  authenticatorData: "...",
72
74
  signature: "...",
73
75
  });
74
76
  ```
75
77
 
76
78
  Paths: `POST /auth/passkey/authenticate/options`, `POST /auth/passkey/authenticate`.
77
- Challenges are single-use; wrong origin `invalid_origin`; bad signature → `invalid_credentials`.
79
+ Challenges are single-use and bound to `sessionId`; wrong origin → `invalid_origin`; UV
80
+ missing → `user_not_verified`; cloned counter → `reregister_required`.
78
81
 
79
82
  </Step>
80
83
 
81
84
  </Steps>
82
85
 
86
+ ## Server checks
87
+
88
+ | Check | Behavior |
89
+ | ----------------- | ----------------------------------------------------------------------------------------------------------------------- |
90
+ | Ceremony type | `clientDataJSON.type` must be `webauthn.create` (register) or `webauthn.get` (authenticate) |
91
+ | User verification | AuthenticatorData UV bit must be set — UV=false is always rejected |
92
+ | Challenge binding | Challenge hash + `sessionId` must match; TTL ≤ 5 minutes; single-use |
93
+ | Signature counter | Stored per credential; if stored ≠ 0 and incoming `signCount` ≤ stored → delete credential, warn, `reregister_required` |
94
+
83
95
  ## Options
84
96
 
85
97
  | Option | Type | Default | Meaning |
@@ -101,8 +113,8 @@ Challenges are single-use; wrong origin → `invalid_origin`; bad signature →
101
113
  | `auth.passkeyAuthenticateOptions` | `POST /auth/passkey/authenticate/options` | `gate.public` + otp rate |
102
114
  | `auth.passkeyAuthenticate` | `POST /auth/passkey/authenticate` | `gate.public` + otp rate |
103
115
 
104
- **Consequence:** a stolen `credentialId` without the private key cannot mint a session. Set
105
- `origins` to your real app origins before production.
116
+ **Consequence:** a stolen `credentialId` without the private key (and UV) cannot mint a session.
117
+ Set `origins` to your real app origins before production.
106
118
 
107
119
  ## Troubleshooting
108
120
 
@@ -116,11 +128,23 @@ Sign in with another method first. `userId` in the body must match the Bearer se
116
128
 
117
129
  `clientDataJSON.origin` must be in `passkey({ origins })`. Default allows only localhost HTTP/S.
118
130
 
131
+ </Accordion>
132
+ <Accordion title="authenticate returns user_not_verified">
133
+
134
+ AuthenticatorData UV bit was not set. Require user verification on the client
135
+ (`userVerification: "required"`) — the server never accepts UV=false.
136
+
137
+ </Accordion>
138
+ <Accordion title="authenticate returns reregister_required">
139
+
140
+ Signature counter did not increase (possible cloned authenticator). That credential was
141
+ deleted — register a fresh passkey for the account.
142
+
119
143
  </Accordion>
120
144
  <Accordion title="authenticate returns invalid_credentials">
121
145
 
122
- Unknown `credentialId`, consumed/expired challenge, bad signature, or rpId hash mismatch.
123
- Re-run authenticate options for a fresh challenge, then sign with the enrolled private key.
146
+ Unknown `credentialId`, wrong/expired/`sessionId` mismatch, bad signature, wrong ceremony
147
+ type, or rpId hash mismatch. Re-run authenticate options for a fresh challenge + `sessionId`.
124
148
 
125
149
  </Accordion>
126
150
  </Accordions>
@@ -129,7 +153,7 @@ Re-run authenticate options for a fresh challenge, then sign with the enrolled p
129
153
 
130
154
  - [Two-factor](/docs/plugins/two-factor) — TOTP step-up
131
155
  - [Gate](/docs/elements/gate) — `gate.auth`
132
- - [Client](/docs/reference/client) — calling `/auth` from the browser
156
+ - [Client · Auth](/docs/client/auth) — calling `/auth` from the browser
133
157
 
134
158
  ## Next
135
159
 
@@ -1,17 +1,18 @@
1
1
  ---
2
2
  title: "Two-factor"
3
- description: "Official plugin — TOTP enable, verify, and disable for an authenticated session."
3
+ description: "Official plugin — method-locked TOTP or email OTP after first-factor sign-in, with step-up enrollment and method change."
4
4
  icon: "LockKeyhole"
5
5
  source: "docs/spec/unified-theory.md"
6
6
  ---
7
7
 
8
- `twoFactor()` adds RFC 6238 TOTP (HMAC-SHA1, 6 digits, 30s) after someone already has a session.
9
- Enable returns a secret, `otpauth://` URL, and recovery codes; verify mints a new session.
10
- TOTP codes are compared in constant time (±1 step window).
8
+ `twoFactor()` adds a second factor after password (or username) sign-in: RFC 6238
9
+ TOTP or email OTP. When 2FA is enabled, first-factor success withholds session
10
+ tokens and returns a **method-locked** challenge.
11
11
 
12
12
  <Callout title="The one rule">
13
- Enable `gate.auth`, then `.plug(twoFactor())`. Enable and disable need a Bearer session —
14
- verify is public and takes `{ userId, code }` after password (or other) sign-in.
13
+ Enable `gate.auth`, then `.plug(twoFactor())`. Login challenges lock the configured method (`totp`
14
+ or `email_otp`). Mid-challenge QR enrollment returns `Forbidden`. Method change needs step-up
15
+ verify → provision → confirm.
15
16
  </Callout>
16
17
 
17
18
  ## Quick start
@@ -35,69 +36,124 @@ export const app = oke({
35
36
  </Step>
36
37
 
37
38
  <Step>
38
- ### Enable (session required)
39
+ ### First-time enable (session required)
39
40
 
40
41
  Wire Bearer on `createClient` (`auth.getToken` / `memorySession`) — calls take input only.
41
42
 
42
43
  ```typescript
43
44
  const { data } = await api.auth.twoFactorEnable({});
44
- // data.secret, data.otpauthUrl, data.recoveryCodes
45
+ // data.secret, data.otpauthUrl, data.recoveryCodes, data.method === "totp"
45
46
  ```
46
47
 
47
- `POST /auth/two-factor/enable` — writes `oke_two_factor`. Store recovery codes once; they are
48
- shown only here.
48
+ `POST /auth/two-factor/enable` — writes `oke_two_factor`. Store recovery codes once.
49
+ Re-enrollment when 2FA is already enabled requires step-up first
50
+ (`twoFactorStepUp`). An active login challenge → `Forbidden` / `active_2fa_challenge`.
49
51
 
50
52
  </Step>
51
53
 
52
54
  <Step>
53
- ### Verify a code
55
+ ### Sign-in then verify
54
56
 
55
57
  ```typescript
56
- const { data } = await api.auth.twoFactorVerify({ userId, code: "123456" });
57
- // hybrid session tokens recovery codes also accepted once each
58
+ const signIn = await api.auth.signInUsername({ username, password });
59
+ if ("twoFactorRequired" in signIn.data && signIn.data.twoFactorRequired) {
60
+ const { data } = await api.auth.twoFactorVerify({
61
+ challengeId: signIn.data.challengeId,
62
+ code: "123456",
63
+ });
64
+ // hybrid session tokens
65
+ }
58
66
  ```
59
67
 
60
- `POST /auth/two-factor/verify`. Missing / disabled factor or bad code `AuthFailed` /
61
- `invalid_credentials`. Disable: `api.auth.twoFactorDisable` (Bearer via `auth.getToken`).
68
+ `POST /auth/two-factor/verify` takes `{ challengeId, code }` not a bare
69
+ `userId`. Missing challenge, method mismatch, or bad code → `AuthFailed` /
70
+ `invalid_credentials`.
62
71
 
63
72
  </Step>
64
73
 
65
74
  </Steps>
66
75
 
76
+ ## Method lock
77
+
78
+ When a login challenge is issued, the active method is recorded on the pending
79
+ challenge. Until that challenge is consumed or expires:
80
+
81
+ - Only codes for the **locked** method are accepted.
82
+ - `twoFactorEnable`, `twoFactorChangeMethod`, `twoFactorConfirmChange`, and
83
+ `twoFactorDisable` return `Forbidden` with `active_2fa_challenge` if called
84
+ for that user (including with an older Bearer session).
85
+
86
+ This closes the July–August 2026 method-switching bypass pattern (switch to
87
+ TOTP enrollment mid email-OTP challenge without proving the current factor).
88
+
89
+ ## Step-up and method change
90
+
91
+ 1. `POST /auth/two-factor/step-up` — `{ code, purpose }` verifies the **current**
92
+ method and grants a short-lived step-up (`enroll` | `change` | `disable`).
93
+ 2. `POST /auth/two-factor/change-method` — `{ method, email? }` provisions the new
94
+ method in a pending state (TOTP QR or email OTP). Requires step-up.
95
+ 3. `POST /auth/two-factor/confirm-change` — `{ code }` activates the new method and
96
+ **immediately invalidates** the old secret / recovery codes.
97
+
98
+ Disable also requires step-up when 2FA is already enabled.
99
+
67
100
  ## Options
68
101
 
69
- | Option | Type | Default | Meaning |
70
- | ---------- | ---------------- | ---------- | -------------------------------- |
71
- | `secret` | `string` | active\* | HMAC secret (\*from `gate.auth`) |
72
- | `sessions` | `SessionStore` | active\* | Session store |
73
- | `now` | `() => number` | `Date.now` | Injectable clock |
74
- | `factors` | `TwoFactorStore` | new | Per-user TOTP + recovery hashes |
75
- | `issuer` | `string` | `"oke"` | Label in `otpauth://` URLs |
102
+ | Option | Type | Default | Meaning |
103
+ | --------------- | ----------------------- | ---------- | -------------------------------------------- |
104
+ | `secret` | `string` | active\* | HMAC secret (\*from `gate.auth`) |
105
+ | `sessions` | `SessionStore` | active\* | Session store |
106
+ | `now` | `() => number` | `Date.now` | Injectable clock |
107
+ | `factors` | `TwoFactorStore` | new | Per-user method + TOTP / recovery |
108
+ | `issuer` | `string` | `"oke"` | Label in `otpauth://` URLs |
109
+ | `pending` | `PendingTwoFactorStore` | active\* | Login challenges |
110
+ | `stepUp` | `StepUpStore` | active\* | Privileged-op grants |
111
+ | `verifications` | `VerificationStore` | active\* | Email OTP as second factor (`2fa:{userId}`) |
112
+ | `exposeDevOtp` | `boolean` | `false` | Return `devOtp` on email_otp challenge paths |
76
113
 
77
114
  ## Surfaces
78
115
 
79
- | Flow | Path | Gate |
80
- | ----------------------- | ------------------------------- | ------------------------ |
81
- | `auth.twoFactorEnable` | `POST /auth/two-factor/enable` | session + bearer |
82
- | `auth.twoFactorVerify` | `POST /auth/two-factor/verify` | `gate.public` + otp rate |
83
- | `auth.twoFactorDisable` | `POST /auth/two-factor/disable` | session + bearer |
116
+ | Flow | Path | Gate |
117
+ | ------------------------------- | ----------------------------------------- | ------------------------ |
118
+ | `auth.twoFactorEnable` | `POST /auth/two-factor/enable` | session + bearer |
119
+ | `auth.twoFactorVerify` | `POST /auth/two-factor/verify` | `gate.public` + otp rate |
120
+ | `auth.twoFactorStepUp` | `POST /auth/two-factor/step-up` | session + bearer |
121
+ | `auth.twoFactorChangeMethod` | `POST /auth/two-factor/change-method` | session + bearer |
122
+ | `auth.twoFactorConfirmChange` | `POST /auth/two-factor/confirm-change` | session + bearer |
123
+ | `auth.twoFactorRequestEmailOtp` | `POST /auth/two-factor/request-email-otp` | public + otp rate |
124
+ | `auth.twoFactorDisable` | `POST /auth/two-factor/disable` | session + bearer |
84
125
 
85
- **Consequence:** verify issues a full session for `userId` — use it as the step after first-factor
86
- sign-in when the account has 2FA enabled.
126
+ **Consequence:** email/password and username sign-in return
127
+ `{ twoFactorRequired, challengeId, method, userId }` (no tokens) when the
128
+ account has 2FA enabled. Complete login only via `twoFactorVerify`.
87
129
 
88
130
  ## Troubleshooting
89
131
 
90
132
  <Accordions>
133
+ <Accordion title="twoFactorEnable returns Forbidden active_2fa_challenge">
134
+
135
+ Finish or wait out the login 2FA challenge first. Enrollment is blocked while
136
+ an unresolved challenge exists for that user.
137
+
138
+ </Accordion>
139
+ <Accordion title="twoFactorEnable returns Forbidden step_up_required">
140
+
141
+ 2FA is already enabled. Call `twoFactorStepUp` with a valid current-method code
142
+ (`purpose: "enroll"`), then enable again.
143
+
144
+ </Accordion>
91
145
  <Accordion title="twoFactorEnable returns AuthFailed unauthenticated">
92
146
 
93
- Wire a session into `createClient` (`auth.getToken` / `memorySession`) from email/password
94
- (or another method) first — enable is gated on Bearer.
147
+ Wire a session into `createClient` (`auth.getToken` / `memorySession`) first
148
+ enable is gated on Bearer. First-time enroll must happen **before** 2FA is
149
+ required on sign-in (or after a full session from a completed challenge).
95
150
 
96
151
  </Accordion>
97
152
  <Accordion title="verify always fails">
98
153
 
99
- Code must be six digits. Check clock skew (±1 window), and that you enrolled the `secret` from
100
- enable. A recovery code works once, then is consumed.
154
+ Submit `{ challengeId, code }` from the sign-in challenge response. TOTP must be
155
+ six digits (±1 window). Email OTP uses the code from the locked `email_otp`
156
+ challenge. A recovery code works once for TOTP, then is consumed.
101
157
 
102
158
  </Accordion>
103
159
  </Accordions>
@@ -107,7 +163,8 @@ enable. A recovery code works once, then is consumed.
107
163
  - [Passkey](/docs/plugins/passkey) — WebAuthn register / authenticate
108
164
  - [Gate](/docs/elements/gate) — session + policies
109
165
  - [Username](/docs/plugins/username) — first factor to enroll against
110
- - OTP over SMS or email instead of TOTP? See [OTP](/docs/plugins/otp) / [Magic link](/docs/plugins/magic-link)
166
+ - Primary OTP sign-in (not a second factor)? See [OTP](/docs/plugins/otp) /
167
+ [Magic link](/docs/plugins/magic-link)
111
168
 
112
169
  ## Next
113
170
 
@@ -102,7 +102,7 @@ on purpose — do not treat it as "exists" in the UI.
102
102
 
103
103
  - [Gate](/docs/elements/gate) — `gate.auth` and posture
104
104
  - [Plugins](/docs/plugins) — all auth method plugins
105
- - [Client](/docs/reference/client) — `createClient` + `memorySession`
105
+ - [Client · Auth](/docs/client/auth) — `createClient` + `memorySession`
106
106
 
107
107
  ## Next
108
108
 
@@ -81,7 +81,7 @@ Caddy has **no** service-discovery story for multiple `app` replicas. Once you r
81
81
  discovers replicas from Docker labels instead of a static `reverse_proxy` target.
82
82
 
83
83
  Also set `allowedHosts` to your public hostname before exposing the edge — see
84
- [Reverse proxy](/docs/deployment/reverse-proxy).
84
+ [Security](/docs/reference/security).
85
85
 
86
86
  ## What the recipe configures
87
87
 
@@ -111,8 +111,8 @@ Expected with the local CA. Install Caddy's local root for that machine, or set
111
111
 
112
112
  ## Learn more
113
113
 
114
- - [Reverse proxy](/docs/deployment/reverse-proxy) — Caddy / Traefik / nginx choice
115
- - [Docker · Reverse proxy](/docs/deployment/docker#reverse-proxy) — compose layer order
114
+ - [Traefik](/docs/recipes/traefik) — multi-replica discovery via Docker labels
115
+ - [nginx](/docs/recipes/nginx) — static HTTP reverse proxy
116
116
  - [Security](/docs/reference/security) — `allowedHosts`
117
117
 
118
118
  ## Next