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
@@ -0,0 +1,371 @@
1
+ ---
2
+ title: "Authentication"
3
+ description: "Enable gate.auth so sessions and API keys fill fx.auth — then protect Flows with policy gates."
4
+ icon: "Lock"
5
+ source: "docs/spec/unified-theory.md"
6
+ ---
7
+
8
+ Authentication is configured as `oke({ gate: { auth } })`. That bag issues `/auth/*` Flows
9
+ (unless `http: false`), fills `fx.auth`, and leaves permission to policy gates — there is no
10
+ `gate.auth` handle for `.gate(...)`.
11
+
12
+ For developers shipping signed-in APIs on okengine — turn on auth, plug a method, attach policies.
13
+
14
+ <Callout title="The one rule">
15
+ Turn on `gate.auth` for identity. Attach `gate.policy` / `gate.scope` (or `.public()`) for
16
+ permission. Boot fails if an HTTP trigger has neither.
17
+ </Callout>
18
+
19
+ ## Smallest Example
20
+
21
+ <Steps>
22
+
23
+ <Step>
24
+ ### Enable auth on the app
25
+
26
+ ```typescript title="src/app.ts"
27
+ import { oke } from "okengine";
28
+ import { username } from "okengine/plugins";
29
+
30
+ export const app = oke({
31
+ name: "notes",
32
+ env: "dev",
33
+ gate: {
34
+ auth: {
35
+ // secret required in prod; minted in dev when omitted
36
+ // basePath defaults to "/auth"
37
+ },
38
+ },
39
+ }).plug(username());
40
+ ```
41
+
42
+ </Step>
43
+
44
+ <Step>
45
+ ### Declare a signed-in policy and attach it
46
+
47
+ ```typescript title="src/core/gate.ts"
48
+ import { gate } from "okengine";
49
+
50
+ export const member = gate.policy("member", {
51
+ description: "Signed-in user",
52
+ check: ({ auth }) => !!auth.verified,
53
+ });
54
+ ```
55
+
56
+ ```typescript title="src/flows/profile/get.ts"
57
+ import { on, flow, http } from "okengine";
58
+ import { member } from "@/core/gate";
59
+
60
+ export const get = on(
61
+ http.get().gate(member),
62
+ flow({
63
+ do: async (_, fx) => ({ userId: fx.auth.userId }),
64
+ }),
65
+ );
66
+ ```
67
+
68
+ </Step>
69
+
70
+ <Step>
71
+ ### Sign in and call
72
+
73
+ ```bash
74
+ # Method routes live under basePath (default /auth) — see your plugged method docs
75
+ curl -X GET http://localhost:6530/profile \
76
+ -H "accept: application/json" \
77
+ -H "authorization: Bearer …"
78
+ ```
79
+
80
+ Authenticated callers reach `do` with `fx.auth.userId` and `fx.auth.scopes` set. Anonymous
81
+ callers fail the policy → typed `Unauthorized`.
82
+
83
+ </Step>
84
+
85
+ </Steps>
86
+
87
+ ## Progressive Patterns
88
+
89
+ From Bearer-only identity to cookies, API keys, and method plugins:
90
+
91
+ <Tabs items={["Bearer", "Cookies", "API keys", "Plugins"]}>
92
+
93
+ <Tab value="Bearer">
94
+
95
+ Default transport is `Authorization: Bearer <access>`. The pipeline verifies the token into
96
+ `fx.auth` before gate evaluation:
97
+
98
+ ```typescript
99
+ export const app = oke({
100
+ name: "notes",
101
+ env: "dev",
102
+ gate: { auth: {} },
103
+ });
104
+ ```
105
+
106
+ In production, set `gate.auth.secret` (or `OKE_AUTH_SECRET`). Omitting it in `prod` throws:
107
+ `gate.auth: secret is required in production (set gate.auth.secret or OKE_AUTH_SECRET)`.
108
+
109
+ Forged, expired, or revoked access tokens map to typed `Unauthorized` — they never become a
110
+ principal.
111
+
112
+ </Tab>
113
+
114
+ <Tab value="Cookies">
115
+
116
+ Opt-in HttpOnly cookie mirror (Bearer remains default). Enable under `gate.auth.cookies`:
117
+
118
+ ```typescript
119
+ gate: {
120
+ auth: {
121
+ cookies: {
122
+ enabled: true,
123
+ prefix: "oke", // default
124
+ sameSite: "lax", // default
125
+ // secure defaults true; path defaults "/"
126
+ },
127
+ },
128
+ }
129
+ ```
130
+
131
+ **Consequence:** cookie sessions need the same CSRF / CORS posture as any cookie app — plug
132
+ [`csrf`](/docs/plugins/csrf) and [`cors`](/docs/plugins/cors) when browsers call cross-origin.
133
+
134
+ </Tab>
135
+
136
+ <Tab value="API keys">
137
+
138
+ Machine principals authenticate with a key secret. Inside `do`, `fx.auth.apiKeyId` is set;
139
+ session-only methods (`fx.auth.createApiKey`, tenant admin, …) refuse keys.
140
+
141
+ ```typescript title="src/flows/keys/create.ts"
142
+ import { on, flow, http } from "okengine";
143
+ import { member } from "@/core/gate";
144
+
145
+ export const create = on(
146
+ http.post().gate(member),
147
+ flow({
148
+ do: async (_, fx) => {
149
+ const { key, secret } = await fx.auth.createApiKey({
150
+ name: "ci",
151
+ scopes: ["notes:read"],
152
+ expiresIn: "90d",
153
+ });
154
+ return { id: key.id, secret }; // secret shown once
155
+ },
156
+ }),
157
+ );
158
+ ```
159
+
160
+ Key methods: `createApiKey` · `listApiKeys` · `revokeApiKey` · `rotateApiKey` · `updateApiKey`.
161
+
162
+ </Tab>
163
+
164
+ <Tab value="Plugins">
165
+
166
+ `gate.auth` alone does not ship a login UI — plug a method from `okengine/plugins`:
167
+
168
+ | Plugin | Docs |
169
+ | ------------------- | -------------------------------------- |
170
+ | `username` | [Username](/docs/plugins/username) |
171
+ | `magicLink` | [Magic link](/docs/plugins/magic-link) |
172
+ | `passkey` | [Passkey](/docs/plugins/passkey) |
173
+ | `oauth` / providers | [OAuth](/docs/plugins/oauth) |
174
+ | `anonymous` | [Anonymous](/docs/plugins/anonymous) |
175
+ | `twoFactor` | [Two-factor](/docs/plugins/two-factor) |
176
+ | `otp` | [OTP](/docs/plugins/otp) |
177
+
178
+ </Tab>
179
+
180
+ </Tabs>
181
+
182
+ ## Options
183
+
184
+ | Option | Type | Default | Meaning |
185
+ | ------------------------------------------- | ------------- | ------------------ | ---------------------------------------------------------- |
186
+ | `secret` | `string` | minted in non-prod | HMAC for access tokens; required in prod |
187
+ | `basePath` | `string` | `"/auth"` | HTTP prefix for auth Flows |
188
+ | `http` | `boolean` | `true` | `false` skips `/auth/*` bindings (secret + tables only) |
189
+ | `audience` | `string` | `"oke-app"` | Access-token audience claim |
190
+ | `emailAndPassword.enabled` | `boolean` | `false` | Credential method knobs |
191
+ | `emailAndPassword.requireEmailVerification` | `boolean` | `false` | Block sign-in until verified |
192
+ | `session.accessTtlMs` | `number` | `14m` | Access token lifetime |
193
+ | `session.refreshTtlMs` | `number` | `30d` | Refresh token lifetime |
194
+ | `session.freshAgeMs` | `number` | `24h` | Max age for "fresh" step-up policies |
195
+ | `session.idleTtlMs` | `number` | off | Idle timeout from last activity |
196
+ | `session.absoluteTtlMs` | `number` | off | Absolute lifetime from creation |
197
+ | `session.singleSessionPerUser` | `boolean` | `false` | One live family per user |
198
+ | `cookies` | bag | off | HttpOnly cookie mirror |
199
+ | `secondaryStorage` | bag | off | Hot-path KV cache (`prefix` default `"auth:"`) |
200
+ | `tenant` | `true` \| bag | off | Multi-tenancy — see [Tenancy](/docs/elements/gate/tenancy) |
201
+
202
+ ## What `fx.auth` carries
203
+
204
+ | Field | Meaning |
205
+ | --------------- | -------------------------------------------------------------------------- |
206
+ | `userId` | Principal id, or `null` when anonymous |
207
+ | `scopes` | `ReadonlySet<string>` used by `gate.scope` (may include tenant-role union) |
208
+ | `sessionScopes` | Session / JWT scopes before tenant-role union |
209
+ | `verified` | Session / credential passed verification |
210
+ | `apiKeyId` | Present when the principal is an API key |
211
+
212
+ Inside `do`, read identity from `fx.auth` — not `fx.user`. World access stays on `fx`.
213
+
214
+ ## Sessions & Cookies
215
+
216
+ <Callout title="Detailed section">
217
+ Defaults match a short-lived access token plus a long-lived refresh family. Override only what
218
+ your product needs.
219
+ </Callout>
220
+
221
+ ```typescript title="src/app.ts"
222
+ export const app = oke({
223
+ name: "notes",
224
+ env: "prod",
225
+ gate: {
226
+ auth: {
227
+ secret: process.env.OKE_AUTH_SECRET!,
228
+ session: {
229
+ accessTtlMs: 14 * 60 * 1000,
230
+ refreshTtlMs: 30 * 24 * 60 * 60 * 1000,
231
+ freshAgeMs: 24 * 60 * 60 * 1000,
232
+ // idleTtlMs / absoluteTtlMs / singleSessionPerUser when needed
233
+ },
234
+ cookies: {
235
+ enabled: true,
236
+ prefix: "oke",
237
+ sameSite: "lax",
238
+ secure: true,
239
+ path: "/",
240
+ },
241
+ },
242
+ },
243
+ });
244
+ ```
245
+
246
+ | Cookie option | Default | Meaning |
247
+ | ---------------- | ------- | --------------------------------- |
248
+ | `enabled` | `false` | Opt-in HttpOnly mirror |
249
+ | `prefix` | `"oke"` | Cookie name prefix |
250
+ | `secure` | `true` | HTTPS-only |
251
+ | `sameSite` | `"lax"` | `"strict"` \| `"lax"` \| `"none"` |
252
+ | `path` | `"/"` | Cookie path |
253
+ | `crossSubdomain` | `false` | Share across subdomains |
254
+ | `domain` | — | Explicit cookie domain |
255
+
256
+ **Freshness:** policies that require a recent sign-in should compare session age against
257
+ `session.freshAgeMs` (default 24h). Step-up plugins (e.g. [two-factor](/docs/plugins/two-factor))
258
+ build on the same window.
259
+
260
+ ## API Keys
261
+
262
+ <Accordions>
263
+
264
+ <Accordion title="createApiKey options">
265
+
266
+ | Field | Type | Meaning |
267
+ | ------------- | ---------------------- | ------------------------------------------ |
268
+ | `name` | `string` | Label for Console / list |
269
+ | `scopes` | `string[]` | Cannot exceed the creator’s session scopes |
270
+ | `expiresIn` | duration string | Optional (`"90d"`, `"1h"`, …) |
271
+ | `ipAllowlist` | `string[]` | Optional source IP allowlist |
272
+ | `rateLimit` | `{ max, per } \| null` | Optional per-key throttle |
273
+
274
+ Return shape: `{ key, secret }` — the secret is shown once at create / rotate.
275
+
276
+ </Accordion>
277
+
278
+ <Accordion title="Session-only refusals">
279
+ Key management and tenant admin refuse API-key principals:
280
+
281
+ ```json
282
+ {
283
+ "data": null,
284
+ "error": {
285
+ "code": "Forbidden",
286
+ "message": "You are not allowed to perform this action.",
287
+ "data": { "gate": "auth:api-keys", "reason": "session_only" }
288
+ }
289
+ }
290
+ ```
291
+
292
+ Call those methods from a user session. Machine keys authenticate _into_ Flows; they do not
293
+ mint more keys.
294
+
295
+ </Accordion>
296
+
297
+ </Accordions>
298
+
299
+ ## Public routes
300
+
301
+ Health checks and login endpoints must declare open posture explicitly:
302
+
303
+ ```typescript
304
+ http.get().public();
305
+ // equivalent: http.get().gate(gate.public)
306
+ ```
307
+
308
+ Auth method Flows under `basePath` register their own posture; your app routes still need
309
+ `.gate(...)` or `.public()`.
310
+
311
+ Set `gate.auth.http: false` when you want tables + Bearer verify without materializing
312
+ `/auth/*` HTTP bindings (embedding / Console-style hosts).
313
+
314
+ ## Troubleshooting
315
+
316
+ <Accordions>
317
+
318
+ <Accordion title="gate.auth: secret is required in production">
319
+ Cause: `gate.auth: secret is required in production (set gate.auth.secret or OKE_AUTH_SECRET)`.
320
+ Set an explicit secret before shipping — never rely on the minted dev secret in prod.
321
+ </Accordion>
322
+
323
+ <Accordion title="401 on every gated route after sign-in">
324
+ Token missing, expired, wrong audience, or cookies enabled without sending credentials. Check
325
+ `Authorization: Bearer`, `audience`, and cookie `SameSite` / CORS.
326
+ </Accordion>
327
+
328
+ <Accordion title="Forbidden · session_only on createApiKey / listTenants">
329
+ Those methods refuse API-key principals (`error.data.reason: "session_only"`). Call them from a
330
+ user session, not a machine key.
331
+ </Accordion>
332
+
333
+ <Accordion title="Forbidden · not_owner on revokeApiKey">
334
+ Keys are owned by the creator. A different session cannot revoke or rotate another user’s key
335
+ (`reason: "not_owner"`).
336
+ </Accordion>
337
+
338
+ <Accordion title="GateBootError after enabling auth">
339
+ Enabling `gate.auth` does not auto-gate your routes. Attach `member` (or `.public()`) on every
340
+ HTTP trigger — see [Boot Posture](/docs/elements/gate#boot-posture).
341
+ </Accordion>
342
+
343
+ </Accordions>
344
+
345
+ ## Learn more
346
+
347
+ - [Username plugin](/docs/plugins/username) — email-free sign-up on `gate.auth`
348
+ - [Authorization](/docs/elements/gate/authorization) — scopes and ABAC policies
349
+ - [RLS](/docs/elements/gate/rls) — row policies from stamped identity
350
+ - [Tenancy](/docs/elements/gate/tenancy) — `fx.tenant.id`
351
+ - [HTTP](/docs/elements/flow/http) — `.gate` / `.public` on triggers
352
+
353
+ ## Next
354
+
355
+ <Cards>
356
+ <Card
357
+ title="Authorization"
358
+ description="gate.policy and gate.scope for permission checks."
359
+ href="/docs/elements/gate/authorization"
360
+ />
361
+ <Card
362
+ title="RLS"
363
+ description="Stamp Gate identity into SQL row policies."
364
+ href="/docs/elements/gate/rls"
365
+ />
366
+ <Card
367
+ title="Tenancy"
368
+ description="Resolve fx.tenant.id from claims, headers, or subdomains."
369
+ href="/docs/elements/gate/tenancy"
370
+ />
371
+ </Cards>
@@ -0,0 +1,298 @@
1
+ ---
2
+ title: "Authorization"
3
+ description: "gate.policy and gate.scope — reusable permission checks composed with gate.all and attached via .gate."
4
+ icon: "ShieldAlert"
5
+ source: "docs/spec/unified-theory.md"
6
+ ---
7
+
8
+ Authorization answers “may this principal do this?” after identity is known. Declare reusable
9
+ `gate.policy` / `gate.scope` handles, compose them with `gate.all`, and attach the chain on the
10
+ trigger.
11
+
12
+ For developers enforcing RBAC / ABAC on okengine — name the check once, reuse it on every route.
13
+
14
+ <Callout title="The one rule">
15
+ Policies receive `GatePolicyContext` (`auth`, `operator`, optional `meta`) — never invent
16
+ `ctx.user` or `ctx.store`. World access stays inside `do` via `fx`.
17
+ </Callout>
18
+
19
+ ## Smallest Example
20
+
21
+ <Steps>
22
+
23
+ <Step>
24
+ ### Declare policies and scopes
25
+
26
+ ```typescript title="src/core/gate.ts"
27
+ import { gate } from "okengine";
28
+
29
+ /** Signed-in member. */
30
+ export const member = gate.policy("member", {
31
+ check: ({ auth }) => !!auth.verified,
32
+ });
33
+
34
+ /** Holds the notes:write scope (name is the scope string). */
35
+ export const notesWrite = gate.scope("notes:write");
36
+
37
+ /** Admin-only ABAC example. */
38
+ export const adminOnly = gate.policy("adminOnly", ({ auth }) => auth.scopes.has("admin"));
39
+ ```
40
+
41
+ `gate.scope(name)` is shorthand for
42
+ `gate.policy(name, ({ auth }) => auth.scopes.has(name))` with `scopes: [name]` recorded.
43
+
44
+ </Step>
45
+
46
+ <Step>
47
+ ### Compose and attach
48
+
49
+ ```typescript title="src/flows/notes/create.ts"
50
+ import { on, flow, http, gate } from "okengine";
51
+ import { member, notesWrite } from "@/core/gate";
52
+
53
+ export const notesMutate = gate.all(member, notesWrite);
54
+
55
+ export const create = on(
56
+ http.post().gate(notesMutate),
57
+ // or: .gate(member, notesWrite)
58
+ flow({
59
+ do: async ({ title }, fx) => fx.json.create({ id: fx.id(), title }),
60
+ }),
61
+ );
62
+ ```
63
+
64
+ </Step>
65
+
66
+ <Step>
67
+ ### See the denial
68
+
69
+ Missing `notes:write` on an authenticated caller:
70
+
71
+ ```json
72
+ {
73
+ "data": null,
74
+ "error": {
75
+ "code": "Forbidden",
76
+ "message": "You are not allowed to perform this action.",
77
+ "data": { "gate": "notes:write", "reason": "policy denied" }
78
+ }
79
+ }
80
+ ```
81
+
82
+ Anonymous callers denied earlier in the chain get `Unauthorized` instead.
83
+
84
+ </Step>
85
+
86
+ </Steps>
87
+
88
+ ## Progressive Patterns
89
+
90
+ From a single scope to Module:Action names, composition, and operator plane:
91
+
92
+ <Tabs items={["Scope", "ABAC", "Compose", "Operator"]}>
93
+
94
+ <Tab value="Scope">
95
+
96
+ Prefer `gate.scope` when the check is exactly “has this scope string”:
97
+
98
+ ```typescript
99
+ export const bookingCreate = gate.scope("booking:create");
100
+ export const notesRead = gate.scope("notes:read");
101
+ ```
102
+
103
+ Names containing `:` are Module:Action pairs — extracted into Manifest permissions for Console.
104
+
105
+ </Tab>
106
+
107
+ <Tab value="ABAC">
108
+
109
+ Use `gate.policy` when the predicate needs more than a single scope membership:
110
+
111
+ ```typescript
112
+ export const freshAdmin = gate.policy("freshAdmin", ({ auth, meta }) => {
113
+ if (!auth.scopes.has("admin")) return false;
114
+ // Example: combine scopes with request meta (ip allowlists live in plugins)
115
+ return !!auth.verified && meta?.ip !== undefined;
116
+ });
117
+ ```
118
+
119
+ Async predicates are allowed (`Promise<boolean>`).
120
+
121
+ </Tab>
122
+
123
+ <Tab value="Compose">
124
+
125
+ `gate.all` is every-member-must-pass, left to right. Nesting flattens:
126
+
127
+ ```typescript
128
+ const write = gate.all(member, notesWrite, gate.rate({ max: 60, per: "1m", keyBy: "user" }));
129
+ const strictWrite = gate.all(write, gate.scope("notes:admin"));
130
+ ```
131
+
132
+ Attach either the composed handle or list members on `.gate(...)` — order is declaration order;
133
+ first denial wins.
134
+
135
+ </Tab>
136
+
137
+ <Tab value="Operator">
138
+
139
+ Console / operator-plane Flows read `operator`, not `auth.userId`:
140
+
141
+ ```typescript title="src/core/gate.ts"
142
+ import { gate } from "okengine";
143
+
144
+ export const consoleOp = gate.policy("consoleOp", ({ operator }) => operator.id !== null);
145
+ ```
146
+
147
+ ```typescript title="src/flows/ops/cleanup.ts"
148
+ import { on, flow, http } from "okengine";
149
+ import { consoleOp } from "@/core/gate";
150
+
151
+ export const cleanup = on(
152
+ http.post().gate(consoleOp),
153
+ flow({
154
+ plane: "operator",
155
+ do: async (_, fx) => ({ operatorId: fx.operator.id }),
156
+ }),
157
+ );
158
+ ```
159
+
160
+ Rate `keyBy: "operator"` keys on `operator.id`.
161
+
162
+ </Tab>
163
+
164
+ </Tabs>
165
+
166
+ ## Policy Context
167
+
168
+ | Field | Type | Meaning |
169
+ | -------------------- | ----------------------------- | ---------------------------------------------- |
170
+ | `auth.userId` | `string \| null` | User-plane principal |
171
+ | `auth.scopes` | `ReadonlySet<string>` | Granted scopes (may include tenant-role union) |
172
+ | `auth.verified` | `boolean \| undefined` | Credential verified |
173
+ | `auth.apiKeyId` | `string \| null \| undefined` | API-key principal when present |
174
+ | `operator.id` | `string \| null` | Operator-plane principal |
175
+ | `meta.ip` / `userId` | optional | Subject dims for rate `keyBy` |
176
+
177
+ Policies must not touch the store, vault, or network — those belong in `do` via `fx`.
178
+
179
+ ## Declaration Forms
180
+
181
+ | Form | Example | Notes |
182
+ | --------------- | ------------------------------------------------------ | --------------------------------- |
183
+ | Predicate | `gate.policy("member", ({ auth }) => !!auth.verified)` | Shortest |
184
+ | Options | `gate.policy("member", { check, description? })` | Console / docs label |
185
+ | Scope shorthand | `gate.scope("notes:write")` | Records `scopes: ["notes:write"]` |
186
+ | Public sentinel | `gate.public` | Always allows; reserved name |
187
+ | Chain | `gate.all(a, b, c)` | Flattened at attach time |
188
+
189
+ Reserved: `gate.policy("public", …)` and `gate.scope("public")` throw — use `gate.public`.
190
+
191
+ ## Module:Action Permissions
192
+
193
+ <Callout title="Detailed section">
194
+ Scopes with a colon (`notes:write`) are Module:Action pairs. Manifest + Console derive the
195
+ permission catalog from Flows, effects, and gate scopes — you do not hand-maintain a second list.
196
+ </Callout>
197
+
198
+ | Source | Example pair |
199
+ | ------------------------------ | -------------------------------- |
200
+ | Flow id `notes.create` | `notes:create` |
201
+ | `gate.scope("booking:create")` | `booking:create` |
202
+ | Effect `reads: ["sql:notes"]` | `store.sql:read` |
203
+ | Operator-plane Flow | also `console:…` when applicable |
204
+
205
+ **Consequence:** prefer `gate.scope("notes:write")` over a one-off policy with the same string —
206
+ the scope is recorded on the declaration for Manifest / Console.
207
+
208
+ Tenant roles may grant **application** scopes only (`notes:write`), not `console:*`. See
209
+ [Tenancy](/docs/elements/gate/tenancy).
210
+
211
+ ## Attaching on Resources & Live
212
+
213
+ Chain once on the mount — every verb (and live, when present) inherits the same gates:
214
+
215
+ ```typescript title="src/flows/notes/index.ts"
216
+ import { on, http } from "okengine";
217
+ import { member, notesWrite } from "@/core/gate";
218
+ import { notesResource } from "./resource";
219
+
220
+ export const notes = on(http.resource("/notes", notesResource.all()).gate(member, notesWrite));
221
+ ```
222
+
223
+ Live firehoses use the same `.gate(...)` fluent:
224
+
225
+ ```typescript
226
+ on(http.live(orderStatus).gate(member));
227
+ ```
228
+
229
+ See [HTTP · Resources](/docs/elements/flow/http#resources) and
230
+ [HTTP · Live Streams](/docs/elements/flow/http#live-streams).
231
+
232
+ ## Denial Mapping
233
+
234
+ | Situation | Code | Status | `error.data` |
235
+ | -------------------------------- | -------------- | ------ | --------------------------- |
236
+ | Policy denied, no `auth.userId` | `Unauthorized` | 401 | `{}` |
237
+ | Policy denied, principal present | `Forbidden` | 403 | `{ gate, reason }` |
238
+ | Unknown gate name at runtime | deny | — | `reason: "unknown gate: …"` |
239
+
240
+ `reason` is `"policy denied"` for failed predicates (unless a rate gate burned earlier).
241
+
242
+ ## Troubleshooting
243
+
244
+ <Accordions>
245
+
246
+ <Accordion title="403 Forbidden but the user looks signed in">
247
+ A later gate in the chain failed — read `error.data.gate`. Confirm the session / key actually
248
+ carries that scope (`fx.auth.scopes`), including tenant-role unions when tenancy is on.
249
+ </Accordion>
250
+
251
+ <Accordion title="Policy always fails with verified users">
252
+ Check for `!!auth.verified` vs `auth.userId !== null`. Some principals have a `userId` before
253
+ verification completes — pick the predicate that matches your product rule.
254
+ </Accordion>
255
+
256
+ <Accordion title='TypeError: name "public" is reserved'>
257
+ Cause: `gate.policy: name "public" is reserved — use gate.public for intentionally unauthenticated
258
+ surfaces` (or the `gate.scope` variant). Rename the policy or use `.public()`.
259
+ </Accordion>
260
+
261
+ <Accordion title="TypeError: gate.all: at least one member is required">
262
+ Pass one or more policy / rate / nested `all` handles — empty `gate.all()` is invalid.
263
+ </Accordion>
264
+
265
+ <Accordion title="Scope present in JWT but route still Forbidden">
266
+ Tenant-role scopes union into `fx.auth.scopes` only when the Flow is tenant-scoped (default when
267
+ tenancy is on). A Flow with `tenantScoped: false` keeps session scopes only.
268
+ </Accordion>
269
+
270
+ </Accordions>
271
+
272
+ ## Learn more
273
+
274
+ - [Authentication](/docs/elements/gate/auth) — how `fx.auth` is filled
275
+ - [RLS](/docs/elements/gate/rls) — row policies from the same Gate identity
276
+ - [Rate Limits](/docs/elements/gate/rate-limits) — throttle on the same chain
277
+ - [Tenancy](/docs/elements/gate/tenancy) — tenant-role scope union
278
+ - [HTTP](/docs/elements/flow/http) — `.gate(...)` on triggers
279
+
280
+ ## Next
281
+
282
+ <Cards>
283
+ <Card
284
+ title="RLS"
285
+ description="Stamp Gate identity into SQL row policies."
286
+ href="/docs/elements/gate/rls"
287
+ />
288
+ <Card
289
+ title="Rate Limits"
290
+ description="Throttle with gate.rate on the same chain."
291
+ href="/docs/elements/gate/rate-limits"
292
+ />
293
+ <Card
294
+ title="Tenancy"
295
+ description="fx.tenant.id and membership-scoped data."
296
+ href="/docs/elements/gate/tenancy"
297
+ />
298
+ </Cards>