okengine 0.14.1 → 0.16.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 (285) hide show
  1. package/package.json +3 -4
  2. package/site/content/docs/elements/ai.mdx +2 -1
  3. package/site/content/docs/elements/channel.mdx +1 -0
  4. package/site/content/docs/elements/clock.mdx +3 -0
  5. package/site/content/docs/elements/flow.mdx +9 -0
  6. package/site/content/docs/elements/gate.mdx +80 -22
  7. package/site/content/docs/elements/signal.mdx +4 -4
  8. package/site/content/docs/elements/store.mdx +84 -27
  9. package/site/content/docs/elements/vault.mdx +4 -0
  10. package/site/content/docs/get-started/basic-usage.mdx +22 -13
  11. package/site/content/docs/get-started/index.mdx +5 -0
  12. package/site/content/docs/get-started/introduction.mdx +11 -3
  13. package/site/content/docs/get-started/meta.json +1 -1
  14. package/site/content/docs/get-started/project-structure.mdx +932 -0
  15. package/site/content/docs/reference/cli.md +13 -10
  16. package/site/content/docs/reference/client.mdx +28 -21
  17. package/site/content/docs/reference/fx.mdx +29 -18
  18. package/site/content/docs/reference/security.md +2 -2
  19. package/src/auth/api-key-sql.test.ts +47 -0
  20. package/src/auth/api-key-sql.ts +254 -0
  21. package/src/auth/api-keys.ts +273 -14
  22. package/src/auth/auth.test.ts +56 -0
  23. package/src/auth/bindings.ts +14 -5
  24. package/src/auth/config.ts +5 -5
  25. package/src/auth/cross-plane.ts +10 -3
  26. package/src/auth/index.ts +25 -1
  27. package/src/auth/schema.ts +8 -0
  28. package/src/cli/ai-setup/ai-setup.test.ts +62 -0
  29. package/src/cli/ai-setup/apply.ts +32 -1
  30. package/src/cli/attach-host-console.test.ts +40 -2
  31. package/src/cli/attach-host-console.ts +9 -0
  32. package/src/cli/build.test.ts +4 -2
  33. package/src/cli/db-seed.ts +48 -15
  34. package/src/cli/db.test.ts +104 -0
  35. package/src/cli/db.ts +122 -10
  36. package/src/cli/dev-controls.test.ts +20 -0
  37. package/src/cli/dev-controls.ts +13 -0
  38. package/src/cli/dev.test.ts +15 -4
  39. package/src/cli/dev.ts +102 -29
  40. package/src/cli/ensure-drizzle-config.ts +2 -1
  41. package/src/cli/tui/DevLive.tsx +12 -2
  42. package/src/cli/tui/keys.test.ts +2 -1
  43. package/src/cli/tui/keys.ts +3 -2
  44. package/src/compiler/aot.test.ts +1 -1
  45. package/src/compiler/effects-infer.ts +17 -2
  46. package/src/compiler/extract.test.ts +409 -33
  47. package/src/compiler/extract.ts +318 -67
  48. package/src/compiler/flow-path.test.ts +96 -0
  49. package/src/compiler/flow-path.ts +173 -0
  50. package/src/compiler/generate-adopt.test.ts +112 -3
  51. package/src/compiler/generate-adopt.ts +280 -26
  52. package/src/compiler/schema-from-ast.ts +609 -0
  53. package/src/console/server/access.test.ts +116 -8
  54. package/src/console/server/access.ts +70 -23
  55. package/src/console/server/app.ts +1 -0
  56. package/src/console/server/bind.ts +2 -2
  57. package/src/console/server/console.test.ts +3 -0
  58. package/src/console/server/dev-identities.test.ts +127 -0
  59. package/src/console/server/dev-identities.ts +229 -0
  60. package/src/console/server/flows-invoke.test.ts +21 -4
  61. package/src/console/server/flows.ts +233 -98
  62. package/src/console/server/gates.test.ts +24 -0
  63. package/src/console/server/gates.ts +8 -21
  64. package/src/console/server/index.ts +1 -0
  65. package/src/console/server/invoke-user-flow.test.ts +3 -1
  66. package/src/console/server/invoke-user-flow.ts +17 -2
  67. package/src/console/server/iso-at.test.ts +9 -0
  68. package/src/console/server/iso-at.ts +11 -0
  69. package/src/console/server/live.test.ts +26 -1
  70. package/src/console/server/live.ts +4 -2
  71. package/src/console/server/operator-db.ts +2 -0
  72. package/src/console/server/security-headers.ts +6 -3
  73. package/src/console/server/spa-proxy.test.ts +1 -0
  74. package/src/console/server/sql-catalog.ts +147 -7
  75. package/src/console/server/sql-rls.test.ts +16 -0
  76. package/src/console/server/state.ts +37 -55
  77. package/src/console/server/store.test.ts +7 -4
  78. package/src/console/server/store.ts +142 -58
  79. package/src/console/ui-next/dist/assets/access-page-CAGHrA9H.js +4 -0
  80. package/src/console/ui-next/dist/assets/agent-disclosure-BP0Y0Sux.js +1 -0
  81. package/src/console/ui-next/dist/assets/cache-glyph-B5X-NM0-.js +1 -0
  82. package/src/console/ui-next/dist/assets/call-pii-button-ChAUiLo9.js +1 -0
  83. package/src/console/ui-next/dist/assets/collapsible-DGnOM2ph.js +1 -0
  84. package/src/console/ui-next/dist/assets/copy-inline-button-DBHhgHKP.js +1 -0
  85. package/src/console/ui-next/dist/assets/detail-header-DhHM1iaZ.js +1 -0
  86. package/src/console/ui-next/dist/assets/dropdown-menu-_NjTEo5_.js +1 -0
  87. package/src/console/ui-next/dist/assets/{duration-tone-oiRxPVsZ.js → duration-tone-sC3lGABz.js} +1 -1
  88. package/src/console/ui-next/dist/assets/element-icons-BVXtRyd3.js +1 -0
  89. package/src/console/ui-next/dist/assets/explorer-empty-2uIhBu0_.js +1 -0
  90. package/src/console/ui-next/dist/assets/flows-page-B-OUtiAu.js +1 -0
  91. package/src/console/ui-next/dist/assets/{highlighted-json-CvDPvveV.js → highlighted-json-Awq7gYdu.js} +1 -1
  92. package/src/console/ui-next/dist/assets/http-method-_UHM2ODJ.js +1 -0
  93. package/src/console/ui-next/dist/assets/index-CGoZkILK.js +66 -0
  94. package/src/console/ui-next/dist/assets/index-Ck88Jmv8.css +2 -0
  95. package/src/console/ui-next/dist/assets/link-BX6Vqztd.js +1 -0
  96. package/src/console/ui-next/dist/assets/observability-page-BDyXalNR.js +4 -0
  97. package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +1 -0
  98. package/src/console/ui-next/dist/assets/replica-lag-DKRrbvdo.js +18 -0
  99. package/src/console/ui-next/dist/assets/request-meta-DV0ywz7t.js +1 -0
  100. package/src/console/ui-next/dist/assets/shortcut-JQIZlWfm.js +1 -0
  101. package/src/console/ui-next/dist/assets/shortcut-keys-DKxNTe_m.js +1 -0
  102. package/src/console/ui-next/dist/assets/{skeleton-CL_X0GCj.js → skeleton-D-czQJT6.js} +1 -1
  103. package/src/console/ui-next/dist/assets/store-page-Xh8Kn3rx.js +41 -0
  104. package/src/console/ui-next/dist/assets/trace-detail-sheet-B09O8rA6.js +2 -0
  105. package/src/console/ui-next/dist/assets/tree-expand-toggle-DkOXA12R.js +54 -0
  106. package/src/console/ui-next/dist/assets/units-page-Dpk40kOQ.js +1 -0
  107. package/src/console/ui-next/dist/assets/{use-vault-list-Cl79j_ku.js → use-vault-list-uk4WVboC.js} +1 -1
  108. package/src/console/ui-next/dist/assets/useMutation-B8EO02Ej.js +1 -0
  109. package/src/console/ui-next/dist/assets/useRender-BE2A9BWC.js +1 -0
  110. package/src/console/ui-next/dist/assets/vault-page-B1dB9Ft0.js +2 -0
  111. package/src/console/ui-next/dist/index.html +12 -10
  112. package/src/console/ui-next/index.html +3 -3
  113. package/src/console/ui-next/src/client.ts +202 -7
  114. package/src/console/ui-next/src/components/motion/table/index.tsx +16 -0
  115. package/src/console/ui-next/src/components/motion/table/types.ts +4 -0
  116. package/src/console/ui-next/src/components/shell/app-sidebar.tsx +2 -0
  117. package/src/console/ui-next/src/components/shell/command-items.test.ts +1 -0
  118. package/src/console/ui-next/src/components/shell/command-items.ts +8 -0
  119. package/src/console/ui-next/src/components/shell/shell-layout.tsx +2 -1
  120. package/src/console/ui-next/src/components/ui/kbd.tsx +2 -2
  121. package/src/console/ui-next/src/components/ui/sheet-form.tsx +8 -3
  122. package/src/console/ui-next/src/features/access/access-page.tsx +347 -0
  123. package/src/console/ui-next/src/features/access/data/use-access.ts +23 -0
  124. package/src/console/ui-next/src/features/access/detail/access-detail.tsx +552 -0
  125. package/src/console/ui-next/src/features/access/explorer/access-list.tsx +125 -0
  126. package/src/console/ui-next/src/features/access/lib/format-when.test.ts +139 -0
  127. package/src/console/ui-next/src/features/access/lib/format-when.ts +296 -0
  128. package/src/console/ui-next/src/features/access/lib/scope-groups.test.ts +34 -0
  129. package/src/console/ui-next/src/features/access/lib/scope-groups.ts +40 -0
  130. package/src/console/ui-next/src/features/access/sheets/access-allow-fields.tsx +236 -0
  131. package/src/console/ui-next/src/features/access/sheets/access-create-sheet.tsx +316 -0
  132. package/src/console/ui-next/src/features/access/sheets/access-edit-sheet.tsx +240 -0
  133. package/src/console/ui-next/src/features/access/sheets/access-expiry-fields.tsx +66 -0
  134. package/src/console/ui-next/src/features/access/sheets/access-refresh-sheet.tsx +104 -0
  135. package/src/console/ui-next/src/features/access/sheets/access-scope-field.tsx +115 -0
  136. package/src/console/ui-next/src/features/access/sheets/access-secret-sheet.tsx +89 -0
  137. package/src/console/ui-next/src/features/access/sheets/access-sheet-search.tsx +49 -0
  138. package/src/console/ui-next/src/features/access/state/access-selection.ts +84 -0
  139. package/src/console/ui-next/src/features/auth/auth-redirect.test.ts +5 -0
  140. package/src/console/ui-next/src/features/auth/auth-redirect.ts +18 -2
  141. package/src/console/ui-next/src/features/flows/traces/http-method.ts +19 -0
  142. package/src/console/ui-next/src/features/flows/traces/trace-detail.test.ts +19 -1
  143. package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +10 -3
  144. package/src/console/ui-next/src/features/not-found/not-found-page.tsx +1 -1
  145. package/src/console/ui-next/src/features/store/detail/browse-section.tsx +45 -1
  146. package/src/console/ui-next/src/features/store/detail/resource-panel.tsx +78 -16
  147. package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +155 -14
  148. package/src/console/ui-next/src/features/store/explorer/store-tree.tsx +33 -133
  149. package/src/console/ui-next/src/features/store/files/file-preview.tsx +4 -1
  150. package/src/console/ui-next/src/features/store/grid/rls-policy-sheet.tsx +326 -165
  151. package/src/console/ui-next/src/features/store/grid/sql-insert-sheet.tsx +6 -0
  152. package/src/console/ui-next/src/features/store/grid/store-data-grid.tsx +50 -3
  153. package/src/console/ui-next/src/features/store/lib/files-meta.test.ts +3 -0
  154. package/src/console/ui-next/src/features/store/lib/files-meta.ts +12 -0
  155. package/src/console/ui-next/src/features/store/lib/query-defaults.test.ts +25 -0
  156. package/src/console/ui-next/src/features/store/lib/query-defaults.ts +85 -3
  157. package/src/console/ui-next/src/features/store/lib/query-gate.ts +30 -4
  158. package/src/console/ui-next/src/features/store/lib/rls-policy.test.ts +139 -0
  159. package/src/console/ui-next/src/features/store/lib/rls-policy.ts +223 -21
  160. package/src/console/ui-next/src/features/store/lib/schema-graph.ts +4 -0
  161. package/src/console/ui-next/src/features/store/lib/sql-catalog.test.ts +24 -0
  162. package/src/console/ui-next/src/features/store/lib/sql-catalog.ts +19 -0
  163. package/src/console/ui-next/src/features/store/lib/store-tree.test.ts +6 -0
  164. package/src/console/ui-next/src/features/store/lib/store-tree.ts +13 -0
  165. package/src/console/ui-next/src/features/store/query/query-console.tsx +48 -14
  166. package/src/console/ui-next/src/features/store/query/query-gate-menu.tsx +19 -20
  167. package/src/console/ui-next/src/features/store/query/query-gate-parts.tsx +2 -1
  168. package/src/console/ui-next/src/features/store/query/query-results.tsx +110 -83
  169. package/src/console/ui-next/src/features/store/schema/schema-table-node.tsx +17 -0
  170. package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +112 -72
  171. package/src/console/ui-next/src/features/units/lib/contract-input.test.ts +206 -1
  172. package/src/console/ui-next/src/features/units/lib/contract-input.ts +55 -0
  173. package/src/console/ui-next/src/features/vault/lib/types.ts +1 -1
  174. package/src/console/ui-next/src/lib/document-meta.test.ts +1 -0
  175. package/src/console/ui-next/src/lib/document-meta.ts +2 -1
  176. package/src/console/ui-next/src/lib/last-module-search.test.ts +1 -0
  177. package/src/console/ui-next/src/lib/last-module-search.ts +2 -1
  178. package/src/console/ui-next/src/lib/shortcut.test.ts +2 -0
  179. package/src/console/ui-next/src/lib/shortcut.ts +3 -0
  180. package/src/console/ui-next/src/router.tsx +10 -1
  181. package/src/console/ui-next/ui-next-seed-app-schema.ts +88 -37
  182. package/src/console/ui-next/ui-next-seed-store.ts +43 -15
  183. package/src/console/xss.gate.test.ts +1 -0
  184. package/src/docker/compose-health.test.ts +27 -0
  185. package/src/docker/compose-health.ts +19 -3
  186. package/src/docker/docker.test.ts +3 -0
  187. package/src/docker/recipes/pgdog.ts +2 -0
  188. package/src/drivers/clock-postgres.ts +13 -9
  189. package/src/drivers/instances-postgres.ts +4 -6
  190. package/src/drivers/journal-postgres.ts +13 -9
  191. package/src/drivers/pg-rls.test.ts +87 -0
  192. package/src/drivers/pg-rls.ts +233 -7
  193. package/src/drivers/pglite.ts +26 -3
  194. package/src/drivers/postgres.test.ts +23 -0
  195. package/src/drivers/postgres.ts +133 -9
  196. package/src/drivers/types.ts +10 -0
  197. package/src/elements/clock/declare.ts +26 -1
  198. package/src/elements/gate/boot.ts +5 -5
  199. package/src/elements/gate/declare.ts +38 -4
  200. package/src/elements/gate/flatten.ts +1 -1
  201. package/src/elements/gate/runtime.ts +1 -1
  202. package/src/elements/store/cache.ts +1 -1
  203. package/src/elements/store/declare.ts +9 -6
  204. package/src/elements/store/emit-drizzle.ts +44 -8
  205. package/src/elements/store/resource.test.ts +1 -1
  206. package/src/elements/store/rls-identity.test.ts +95 -0
  207. package/src/elements/store/rls-identity.ts +116 -0
  208. package/src/elements/store/runtime.ts +18 -0
  209. package/src/elements/store/schema-decl.test.ts +37 -0
  210. package/src/elements/store/schema-decl.ts +260 -41
  211. package/src/elements/store/seed.ts +1 -1
  212. package/src/elements/store/sql-condition.test.ts +12 -0
  213. package/src/elements/store/sql-condition.ts +26 -2
  214. package/src/elements/store/sql-rls-isolation.test.ts +165 -0
  215. package/src/elements/store/sql-rls-stamp.test.ts +117 -0
  216. package/src/elements/store/sql-select.types.test.ts +71 -0
  217. package/src/elements/store/sql-session.ts +135 -35
  218. package/src/elements/store/upsert-app.test.ts +1 -1
  219. package/src/elements/store.ts +17 -0
  220. package/src/http.ts +20 -3
  221. package/src/index.ts +11 -1
  222. package/src/kernel/adopt-barrel-fresh.test.ts +1 -1
  223. package/src/kernel/adopt-routes.ts +30 -2
  224. package/src/kernel/api-key-host-persist.test.ts +143 -0
  225. package/src/kernel/app.ts +145 -16
  226. package/src/kernel/auth-resolve.ts +10 -5
  227. package/src/kernel/auto-cache.test.ts +8 -8
  228. package/src/kernel/auto-registry.test.ts +49 -9
  229. package/src/kernel/boot.test.ts +5 -5
  230. package/src/kernel/boot.ts +2 -2
  231. package/src/kernel/effects-stamping.test.ts +3 -3
  232. package/src/kernel/element-registries.ts +12 -5
  233. package/src/kernel/errors.ts +24 -0
  234. package/src/kernel/flow-units.ts +64 -0
  235. package/src/kernel/flow.ts +29 -17
  236. package/src/kernel/fx-auth-keys.test.ts +88 -0
  237. package/src/kernel/fx-auth-keys.ts +198 -0
  238. package/src/kernel/fx.test.ts +17 -1
  239. package/src/kernel/fx.ts +59 -18
  240. package/src/kernel/horizontal-child.ts +4 -4
  241. package/src/kernel/http-path-pending.ts +20 -0
  242. package/src/kernel/http-resource.ts +4 -0
  243. package/src/kernel/http-stream.test.ts +4 -4
  244. package/src/kernel/pipeline-api-key.test.ts +192 -7
  245. package/src/kernel/pipeline.test.ts +2 -2
  246. package/src/kernel/pipeline.ts +4 -2
  247. package/src/kernel/plugin-elements.test.ts +1 -1
  248. package/src/kernel/plugin-needs.test.ts +1 -1
  249. package/src/kernel/ready.test.ts +2 -2
  250. package/src/kernel/resource-mount.test.ts +14 -0
  251. package/src/kernel/run-duration.test.ts +2 -2
  252. package/src/kernel/stamp-http.test.ts +79 -0
  253. package/src/kernel/stamp-http.ts +75 -0
  254. package/src/kernel/triggers.ts +52 -27
  255. package/src/manifest/types.ts +22 -3
  256. package/src/release/build-lib.ts +1 -0
  257. package/src/release/measure.ts +2 -2
  258. package/src/runtime/json-code-block.test.ts +2 -2
  259. package/src/runtime/serve.test.ts +4 -4
  260. package/src/term.test.ts +52 -4
  261. package/src/term.ts +125 -17
  262. package/src/test/reset-element-registries.ts +8 -3
  263. package/src/cli/ask-seed.test.ts +0 -96
  264. package/src/cli/ask-seed.ts +0 -82
  265. package/src/console/ui-next/dist/assets/cache-glyph-BanhLsEY.js +0 -1
  266. package/src/console/ui-next/dist/assets/call-pii-button-bqkxMrJH.js +0 -1
  267. package/src/console/ui-next/dist/assets/collapsible-DYb0xU8C.js +0 -1
  268. package/src/console/ui-next/dist/assets/confirm-sheet-DDCRmG62.js +0 -1
  269. package/src/console/ui-next/dist/assets/explorer-empty-HjTnVQoR.js +0 -1
  270. package/src/console/ui-next/dist/assets/flows-page-DxDsOd4f.js +0 -1
  271. package/src/console/ui-next/dist/assets/http-method-CJCBYL2j.js +0 -1
  272. package/src/console/ui-next/dist/assets/index-Ce6WKWKM.js +0 -66
  273. package/src/console/ui-next/dist/assets/index-D4Ldtj79.css +0 -2
  274. package/src/console/ui-next/dist/assets/link-DF7SZ9Ek.js +0 -1
  275. package/src/console/ui-next/dist/assets/observability-page-BEZDzyYh.js +0 -4
  276. package/src/console/ui-next/dist/assets/query-gate-parts-1m8m1iNp.js +0 -2
  277. package/src/console/ui-next/dist/assets/react-B1ML8gxg.js +0 -1
  278. package/src/console/ui-next/dist/assets/replica-lag-C8_BRt2x.js +0 -18
  279. package/src/console/ui-next/dist/assets/sheet-form-D-ata7jy.js +0 -1
  280. package/src/console/ui-next/dist/assets/shortcut-Cgzuv4k1.js +0 -1
  281. package/src/console/ui-next/dist/assets/shortcut-keys-DO4IsVqv.js +0 -1
  282. package/src/console/ui-next/dist/assets/store-page-v3LXdYpr.js +0 -45
  283. package/src/console/ui-next/dist/assets/trace-detail-sheet-DFLFfUUX.js +0 -2
  284. package/src/console/ui-next/dist/assets/units-page-C0gW6Kdo.js +0 -1
  285. package/src/console/ui-next/dist/assets/vault-page-DuKzqwzW.js +0 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okengine",
3
- "version": "0.14.1",
3
+ "version": "0.16.0",
4
4
  "description": "One law. Eight elements. Ten exports. One package. One manifest. Every backend need is derived, never added.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -174,10 +174,9 @@
174
174
  "d3-array": "^3.2.4",
175
175
  "d3-shape": "^3.2.0",
176
176
  "date-fns": "^4.1.0",
177
- "drizzle-kit": "1.0.0-rc.4",
178
- "drizzle-orm": "1.0.0-rc.4",
177
+ "drizzle-kit": "1.0.0-rc.5-ab785fc",
178
+ "drizzle-orm": "1.0.0-rc.5-169397b",
179
179
  "drizzle-seed": "^0.3.1",
180
- "drizzle-zod": "^0.8.3",
181
180
  "gflows": "^1.2.1",
182
181
  "happy-dom": "^20.11.1",
183
182
  "hast-util-to-jsx-runtime": "^2.3.6",
@@ -182,7 +182,7 @@ Return those chunks on HTTP with `fx.json.stream(...)`. The kernel answers `text
182
182
 
183
183
  ```typescript
184
184
  on(
185
- http.post("/complete").gate.public,
185
+ http.post("/complete").public(),
186
186
  flow("chat.complete", {
187
187
  do: (input, fx) => fx.json.stream(fx.stream(smart, { prompt: input.prompt })),
188
188
  }),
@@ -364,6 +364,7 @@ A hang-up is `AbortError` — it does not retry or advance `via`.
364
364
 
365
365
  ## Learn more
366
366
 
367
+ - [Project structure](/docs/get-started/project-structure) — agent `tools` are Flow names (`notes.get`), not URLs
367
368
  - [Flow](/docs/elements/flow) — `fx.ask` and `fx.search` inside `do`
368
369
  - [Store](/docs/elements/store) — `store.index`, the home of embeddings
369
370
  - [MCP](/docs/ai/mcp) — serve the app to agents, or consume external servers as tools
@@ -327,6 +327,7 @@ Confirm a visible OS notification titled `okengine webpush interop`.
327
327
 
328
328
  ## Learn more
329
329
 
330
+ - [Project structure](/docs/get-started/project-structure) — `flow({…})` names as `unit.export`; explicit `flow("unit.action")` still wins
330
331
  - [Flow](/docs/elements/flow) — `fx.send` inside `do`
331
332
  - [Environment Variables](/docs/reference/environment-variables) — Channel boot credentials
332
333
  - [Configuration](/docs/reference/configuration) — `drivers.channel.*` maps
@@ -52,6 +52,8 @@ export const dailyReport = clock("daily-report", {
52
52
  });
53
53
  ```
54
54
 
55
+ Import the module before `oke()` — named clocks auto-register. No `oke({ clocks })` list.
56
+
55
57
  ```typescript title="src/flows/reports/daily.ts"
56
58
  export const sendDaily = on(
57
59
  dailyReport,
@@ -218,6 +220,7 @@ The registry is off in `test` and when there is no shared SQL URL. `dev`/`prod`
218
220
 
219
221
  ## Learn more
220
222
 
223
+ - [Project structure](/docs/get-started/project-structure) — clock flows in the tree get a name, never a URL
221
224
  - [Flow](/docs/elements/flow) — `on(trigger, flow)` and the `fx` surface
222
225
  - [fx · Runs](/docs/reference/fx#runs-observability-read) — native SLO checkers via `on(every(…))` + `fx.runs`
223
226
  - [Signal](/docs/elements/signal) — reacting to events instead of time
@@ -104,6 +104,12 @@ Built-in codes attach a localized `message` ([Errors](/docs/reference/errors));
104
104
  [Store](/docs/elements/store#handwritten-http-lists).
105
105
  </Callout>
106
106
 
107
+ <Callout title="Helpers take the real Fx">
108
+ Type a helper's `fx` as the exported `Fx` (`import type { Fx } from
109
+ "okengine"`). A narrower `{ store: … }` will not match `store()` overloads
110
+ — that is the `as never` trap. See [fx](/docs/reference/fx).
111
+ </Callout>
112
+
107
113
  ## The five triggers
108
114
 
109
115
  <FlowTriggers />
@@ -112,6 +118,8 @@ Built-in codes attach a localized `message` ([Errors](/docs/reference/errors));
112
118
 
113
119
  `http.get` · `http.post` · `http.put` · `http.patch` · `http.delete` · `http.head` · `http.options` · `http.query`. QUERY (RFC 10008) is a safe, idempotent read with a JSON body.
114
120
 
121
+ `http.get()` with no path is filled from the file tree (`flows/notes/[id]/get.ts` → `/notes/:id`). `flow({ do })` is named `unit.export`. Explicit `http.get("/me/tasks")` and `flow("notes.get")` still win. Folder conventions live on [Project structure](/docs/get-started/project-structure).
122
+
115
123
  `Content-Type: application/json` is required — missing → **400** `InvalidQuery`, anything else → **415** `UnsupportedMediaType` with `Accept-Query: "application/json"`. Invalid JSON is **400** (no sniffing). Responses advertise `Accept-Query`. okengine does not assign `Location` / `Content-Location` result URIs.
116
124
 
117
125
  A browser `GET` (`Accept` prefers `text/html`) paints the `{ data, error }` envelope in traces chrome — status, latency, and cache, Routes rail (static GET links), copy, line numbers, Pretty / Raw, Console on **6533**. `curl`, `Accept: application/json`, and `?format=json` stay JSON.
@@ -369,6 +377,7 @@ a **hit**. A matching write turns the next read into a miss.
369
377
 
370
378
  ## Learn more
371
379
 
380
+ - [Project structure](/docs/get-started/project-structure) — folders are the URL; `unit.export` is the name
372
381
  - [fx](/docs/reference/fx) — full `fx` surface, including ICU `fx.t` / `fx.locale`
373
382
  - [Signal](/docs/elements/signal) — delivery physics (`once` · `broadcast` · `live`)
374
383
  - [Clock](/docs/elements/clock) — schedules and durable sleep
@@ -10,7 +10,7 @@ burned its minute quota? Attach gates to the HTTP trigger — the pipeline check
10
10
  store write, emit, or channel send runs.
11
11
 
12
12
  <Callout title="The one rule">
13
- Every HTTP trigger declares posture: attach a real gate, or attach `.gate.public`. Omitting both
13
+ Every HTTP trigger declares posture: attach a real gate, or attach `.public()`. Omitting both
14
14
  fails boot. Denial is a typed error value — never a thrown exception mid-`do`.
15
15
  </Callout>
16
16
 
@@ -19,10 +19,10 @@ store write, emit, or channel send runs.
19
19
  <Steps>
20
20
 
21
21
  <Step>
22
- ### Declare policies and register the Gate bag
22
+ ### Declare policies
23
23
 
24
- Policies are named predicates; rate limits are declarative budgets. Put the ones you reuse in
25
- `oke({ gate: { policies } })`:
24
+ Policies are named predicates; rate limits are declarative budgets. Import the module before
25
+ `oke()` — they auto-register. `oke({ gate: { policies } })` is still valid and additive.
26
26
 
27
27
  ```typescript title="src/gates.ts"
28
28
  import { gate } from "okengine";
@@ -43,14 +43,10 @@ export const fair = gate.rate({
43
43
  the scope checked.
44
44
 
45
45
  ```typescript title="src/app.ts"
46
+ import "./gates";
46
47
  import { oke } from "okengine";
47
- import { member, canBook, fair } from "./gates";
48
48
 
49
- export const app = oke({
50
- name: "shop",
51
- env: "dev",
52
- gate: { policies: [member, canBook, fair] },
53
- });
49
+ export const app = oke({ name: "shop", env: "dev" });
54
50
  ```
55
51
 
56
52
  </Step>
@@ -58,14 +54,14 @@ export const app = oke({
58
54
  <Step>
59
55
  ### Attach them to triggers
60
56
 
61
- `.gate(...)` composes left to right — first denial wins. Public routes must say so explicitly:
57
+ `.gate(...)` composes left to right — first denial wins. Public routes must say so explicitly. Path and name can come from the file tree — see [Project structure](/docs/get-started/project-structure).
62
58
 
63
59
  ```typescript title="src/flows/links/shorten.ts"
64
60
  import { on, flow, http, gate } from "okengine";
65
61
  import { member, canBook, fair } from "../../gates";
66
62
 
67
63
  export const health = on(
68
- http.get("/health").gate.public,
64
+ http.get("/health").public(),
69
65
  flow("health.check", {
70
66
  out: HealthOut,
71
67
  do: () => ({ ok: true as const }),
@@ -88,8 +84,8 @@ export const shorten = on(
88
84
  ```
89
85
 
90
86
  A single gate stays `.gate(member)`. Reuse two or more with `gate.all` — `.gate(write)`
91
- flattens. `http.resource(path, ops).gate(...)` stamps every verb. Do **not** put
92
- `gate.public` in `gate.policies` — attach it on the trigger only.
87
+ flattens. `http.resource(path, ops).gate(...)` stamps every verb. Public routes use
88
+ `.public()` — do **not** put `gate.public` in `gate.policies`.
93
89
 
94
90
  </Step>
95
91
 
@@ -131,16 +127,16 @@ gates are skipped. `do` runs only when every gate passed.
131
127
  | ------------------------------------- | ------------------------------------------- | ---------------------------------- |
132
128
  | `gate.policy(name, check \| options)` | Is this principal allowed? (ABAC) | auth / operator / request metadata |
133
129
  | `gate.scope(name)` | Does `auth.scopes` contain this exact name? | `auth.scopes` |
134
- | `gate.public` / `.gate.public` | Intentionally unauthenticated surface | always allows |
130
+ | `gate.public` / `.public()` | Intentionally unauthenticated surface | always allows |
135
131
  | `gate.rate(options)` | Is there budget left for this subject? | an atomic counter on the kv driver |
136
132
  | `gate.all(...members)` | Reuse this chain on many triggers | flattens; each member is evaluated |
137
133
 
138
134
  Pass `{ check, description }` on `gate.policy` when you want a human title in the Console (falls
139
- back to the policy name). The name `"public"` is reserved — use `gate.public`, not
140
- `gate.policy("public", …)`.
135
+ back to the policy name). The name `"public"` is reserved — mark the trigger with
136
+ `.public()`, not `gate.policy("public", …)`.
141
137
 
142
138
  `gate.all()` with no members throws. Nested `all` handles flatten.
143
- `oke({ gate: { policies: [write] } })` registers the members.
139
+ Members auto-register when declared; `oke({ gate: { policies: [write] } })` is additive.
144
140
 
145
141
  ### Policy context
146
142
 
@@ -178,11 +174,11 @@ driver in tests. Boot opens a dedicated `oke:gates` namespace on **`drivers.stor
178
174
 
179
175
  ## Auth posture at boot
180
176
 
181
- Every HTTP trigger must carry a gate or `.gate.public`. Omitting both fails boot with
177
+ Every HTTP trigger must carry a gate or `.public()`. Omitting both fails boot with
182
178
  `GateBootError` — message starts with `gate boot failed —` and lists every gap.
183
179
 
184
180
  ```typescript
185
- on(http.get("/health").gate.public, flow("health.check", {/* … */}));
181
+ on(http.get("/health").public(), flow("health.check", {/* … */}));
186
182
  on(http.post("/bookings").gate(member, canBook), flow("bookings.create", {/* … */}));
187
183
  ```
188
184
 
@@ -191,7 +187,7 @@ on(http.post("/bookings").gate(member, canBook), flow("bookings.create", {/* …
191
187
  posture but does **not** authenticate.
192
188
 
193
189
  `gate: { unguardedHttp: "allow" }` skips the audit **only** when `env === "test"`.
194
- In `dev` / `prod` migrate with per-trigger `.gate.public`.
190
+ In `dev` / `prod` migrate with per-trigger `.public()`.
195
191
 
196
192
  ## Built-in auth (`gate.auth`)
197
193
 
@@ -260,6 +256,38 @@ Customize tables, then `oke schema generate` (`.oke/schema/oke.ts`; `--check` in
260
256
  sign-in shapes: [Plugins](/docs/plugins). Call auth from
261
257
  [createClient](/docs/reference/client) — helpers in `okengine/client/auth`.
262
258
 
259
+ ## API keys
260
+
261
+ A key is the **issuer with fewer gates** — not a second permission system.
262
+ Bearer secrets authenticate as the creator; `fx.auth.scopes` shrink to the key.
263
+
264
+ | Field | Session | API key |
265
+ | ----------------------------------- | -------------- | ----------------------- |
266
+ | `fx.auth.userId` / `fx.operator.id` | subject | creator (`creatorId`) |
267
+ | `fx.auth.scopes` | session scopes | key scopes (attenuated) |
268
+ | `fx.auth.apiKeyId` | `null` | key id |
269
+ | WideEvent `principal` | subject | issuer |
270
+ | `dimensions.api_key` | absent | key id |
271
+
272
+ `gate.auth` always creates a shared key store (HMAC-SHA-256 with `gate.auth.secret`).
273
+ Do not pass `apiKeyStore` unless you are injecting a test Map.
274
+
275
+ <Callout title="Keys live on the app database">
276
+ With `store.sql()`, boot hydrates public `oke_api_keys` on that connection (`DATABASE_URL`) and
277
+ write-through persists create / rotate / revoke. Console **Access** attaches this host store — it
278
+ does not keep a second key database.
279
+ </Callout>
280
+
281
+ Session Flows manage keys via [fx](/docs/reference/fx) (`createApiKey`, `listApiKeys`,
282
+ `revokeApiKey`, `rotateApiKey`, `updateApiKey`). A key-authenticated caller cannot.
283
+
284
+ Declare `effects.reads: ["auth:api-keys"]` / `effects.writes: ["auth:api-keys"]` — never
285
+ `sql:oke_api_keys`. Console **Access** (`:6533` → `/access`) mints operator keys as the
286
+ operator, and user-plane keys only as a picked issuer (`creatorUserId`).
287
+
288
+ Verify rejects expired, revoked, allowlist-miss, and over-rate secrets with `401`
289
+ and no `api_key` dimension.
290
+
263
291
  ## Identity and decisions
264
292
 
265
293
  Gates decide on principals that already exist. `gate.auth` issues hybrid sessions onto
@@ -270,12 +298,18 @@ Pass **and** deny land on the run's `gates` dimension. Console (`:6533` → `/fl
270
298
  chain on the flow contract. Rate `overridable` is a catalog flag only — Clock schedules are what the Console can
271
299
  edit live today.
272
300
 
301
+ <Callout title="RLS reads the stamped principal">
302
+ After HTTP Gate passes, user-plane `fx.store` stamps `oke.gate()`, `oke.user()`, and
303
+ `oke.has_scope()`. Rate, cron, CDC, and signal stay unstamped. Helpers:
304
+ [Store](/docs/elements/store).
305
+ </Callout>
306
+
273
307
  ## Troubleshooting
274
308
 
275
309
  <Accordions>
276
310
  <Accordion title="Boot: gate boot failed — N HTTP trigger(s) missing auth posture">
277
311
 
278
- Empty `.gate(...)` chain. Attach a policy/rate gate, or `.gate.public`. First `fetch`
312
+ Empty `.gate(...)` chain. Attach a policy/rate gate, or `.public()`. First `fetch`
279
313
  auto-boots — do not rely on `gate.unguardedHttp: "allow"` outside `env: "test"`.
280
314
 
281
315
  </Accordion>
@@ -308,11 +342,35 @@ character classes.
308
342
  `"ip"` for public surfaces (sign-up, password reset), `"user"` for authenticated quotas.
309
343
  Keying an authenticated endpoint by IP punishes shared NAT.
310
344
 
345
+ </Accordion>
346
+ <Accordion title="API key Bearer returns 401">
347
+
348
+ Expired, revoked, allowlist miss, or over the key's `rateLimit`. Check
349
+ `ipAllowlist` against the same X-Forwarded-For hop as `ip-allowlist`.
350
+ Entries are IPs or hostnames — a host resolves at verify time and must
351
+ include the client IP (lookup failure is closed). Rotate if the secret
352
+ was shown once and then lost.
353
+
354
+ </Accordion>
355
+ <Accordion title="Keys vanish after an app restart">
356
+
357
+ No SQL store, or a new `gate.auth.secret`. Declare `store.sql()`, keep
358
+ `DATABASE_URL` (or the same PGlite datadir) and the auth secret stable.
359
+ Console does not persist keys in `oke_console`.
360
+
361
+ </Accordion>
362
+ <Accordion title="Key can call a route the issuer cannot">
363
+
364
+ That is a bug in the mint. `createApiKey` / `fx.auth.createApiKey` re-run
365
+ `assertAttenuated` against the issuer's live scopes. Custom `gate.policy`
366
+ predicates that ignore scopes still see `verified: true`.
367
+
311
368
  </Accordion>
312
369
  </Accordions>
313
370
 
314
371
  ## Learn more
315
372
 
373
+ - [Project structure](/docs/get-started/project-structure) — `http.get()` and `flow({…})` fill path and name from the file tree
316
374
  - [Flow](/docs/elements/flow) — the trigger pipeline gates plug into
317
375
  - [fx](/docs/reference/fx) — `fx.auth`, `fx.operator`, `fx.principal`
318
376
  - [Client](/docs/reference/client) — Bearer `createClient` + `okengine/client/auth`
@@ -131,7 +131,7 @@ A Flow **writes** all three tapes. `once` / `broadcast` **listen** with `on(sign
131
131
 
132
132
  ```typescript title="src/flows/orders/place.ts"
133
133
  export const placeOrder = on(
134
- http.post("/orders").gate.public,
134
+ http.post("/orders").public(),
135
135
  flow("orders.place", {
136
136
  in: z.object({ id: z.string(), total: z.number() }),
137
137
  do: async (input, fx) => {
@@ -256,9 +256,8 @@ Committed `once` messages survive process death. A claim sets `lockedBy` and a v
256
256
 
257
257
  <Callout title="Client subscription is not shipped yet">
258
258
  `delivery: "live"` means the **driver** retains and replays. Listen with `bus.live()` on the
259
- server — not `on(signal, flow)`. `createClient` has no SSE / WebSocket / `client.live` yet — poll
260
- an HTTP Flow. See [Client · Signal and live
261
- queries](/docs/reference/client#signal-and-live-queries).
259
+ server — not `on(signal, flow)`. `createClient` has no SSE yet poll HTTP. See
260
+ [Client](/docs/reference/client#signal-and-live-queries).
262
261
  </Callout>
263
262
 
264
263
  ## Orphaned signal config
@@ -335,6 +334,7 @@ At-least-once: crash-after-claim reclaims when the lease expires, and a handler
335
334
 
336
335
  ## Learn more
337
336
 
337
+ - [Project structure](/docs/get-started/project-structure) — signal files get a name, never a URL; `http.get("/…")` still wins
338
338
  - [Flow](/docs/elements/flow) — `on(trigger, flow)` and `fx.emit`
339
339
  - [fx](/docs/reference/fx) — `fx.emit` and `fx.deadLetters`
340
340
  - [Clock](/docs/elements/clock) — scheduled and delayed work
@@ -19,6 +19,9 @@ only the driver changes.
19
19
  Every facet accepts optional `description` — a human title in the Console (falls back to the store
20
20
  name).
21
21
 
22
+ `store.sql` / `kv` / `files` / `index` auto-register when the declaring module is imported before
23
+ `oke()`. `oke({ stores })` is still valid and additive.
24
+
22
25
  ## SQL
23
26
 
24
27
  <StoreFacetMark facet="sql" />
@@ -39,9 +42,9 @@ Manifest tables, columns, and keywords.
39
42
  Classified PII stays masked unless the toolbar **PII** control is on
40
43
  (`revealPii: true`, audited).
41
44
 
42
- **Gate** views the result as Operator (default, bypasses RLS), public, a
43
- selected user (via their matching policy), or a Manifest policy gate.
44
- `asGate` sets `oke.gate` on postgres / pglite for `current_setting('oke.gate', true)`.
45
+ **Gate** views as Operator (bypass), public, a user, or a policy. SQL,
46
+ browse, and grid edit send `{ asGate, asUserId }`. postgres / pglite stamp
47
+ `oke.gate()` / `oke.user()` / `oke.has_scope()`; memory is catalog-only.
45
48
 
46
49
  The SQL band **Schema** control opens a visualizer of Manifest tables and
47
50
  relations — declared `.references()` plus inferred `*_id` columns — not the
@@ -88,17 +91,21 @@ Install reviews the `CREATE EXTENSION` SQL; **Advanced** can set `SCHEMA`,
88
91
  When a newer packaged version exists, the name chip shows **Upgrade**
89
92
  (`ALTER EXTENSION … UPDATE`). Names link to the project or Postgres docs.
90
93
 
91
- Each SQL table row shows whether Postgres RLS is on (emerald shield)
92
- or off (muted shield). KV, files, and index have no badge. Enabling
93
- RLS on create flips that shield.
94
+ SQL table rows and the resource header show RLS (emerald / muted shield;
95
+ **N RLS** when policies exist) before **PII**. KV, files, and index have
96
+ no badge — enabling RLS on create flips the shield.
97
+
98
+ **RLS Policies** lists `pg_policies` plus Manifest `declared` / `live`.
99
+ Two-finger click a row for its sheet, or empty grid to create. Create
100
+ pins SQL | Code (`store.schema.policy` + `pgPolicy`); drag to resize.
94
101
 
95
- **RLS Policies** lists `pg_policies`. Create policy pins the SQL preview;
96
- **Templates** slides a flush library beside it (every `FOR` command, owner,
97
- `oke.gate`). Or edit `roles` / `USING` / `WITH CHECK` in the grid (`ALTER POLICY`).
102
+ **Templates** use `oke.user()` / `oke.gate()` / `oke.has_scope()`.
103
+ Owner cards pick a **Column** (guess `creator_email` / `owner_email`)
104
+ with PK / FK marks. Create stays blocked until a column is picked.
98
105
 
99
106
  Create policy picks a Gate posture (Read / Write / Both), or **Policy &
100
- scope** for `gate.public`, `gate.policy`, and `gate.scope`. `TO public`
101
- stays on the SQL — those picks are not Postgres roles.
107
+ scope**. Those picks fill `USING` / `WITH CHECK`. `TO` stays `public`
108
+ Gate names are not Postgres roles.
102
109
 
103
110
  ### Quick start
104
111
 
@@ -192,7 +199,7 @@ const notesR = store.resource(db, notes, {
192
199
  },
193
200
  });
194
201
 
195
- const mounted = on(http.resource("/notes", notesR.all()).gate.public.live());
202
+ const mounted = on(http.resource("/notes", notesR.all()).public().live());
196
203
  ```
197
204
 
198
205
  `.gate(...)` / `.live()` chain like `http.get` — gates on every verb, live on list and get.
@@ -240,7 +247,7 @@ list: { mode: "offset", filter: "none", limit: 20 },
240
247
 
241
248
  ```typescript
242
249
  export const list = on(
243
- http.get("/notes").gate.public,
250
+ http.get("/notes").public(),
244
251
  flow("notes.list", {
245
252
  out: z.array(Note),
246
253
  do: async (input, fx) => {
@@ -251,6 +258,8 @@ export const list = on(
251
258
  );
252
259
  ```
253
260
 
261
+ `list.ts` + `http.get()` fills `GET /notes` named `notes.list` — explicit strings still win. See [Project structure](/docs/get-started/project-structure).
262
+
254
263
  Zero-config: `?q=` searches every string field, extra keys auto-eq except path `id`, and PostgREST `?col=op.value` / `?order=` / `?select=` are open. Nested lists filter the parent in the load — `input.id` is not a column eq.
255
264
 
256
265
  <Callout title="Preferred, not required">
@@ -282,6 +291,9 @@ await fx.store(db).update(notes).set({ title: input.title }).where(eq(notes.id,
282
291
  await fx.store(db).delete(notes).where(lt(notes.createdAt, cutoff));
283
292
  ```
284
293
 
294
+ `select().from(notes)` infers the declared row (`string` / `number` / `null` from
295
+ `field.*`). A `select({ title: notes.title })` projection stays untyped.
296
+
285
297
  <Callout title="One table per call — no relational with:">
286
298
  `fx.store` is deliberately **single-table**: Drizzle's relational `findMany({ with: … })` is not
287
299
  available through `fx`. Compose joins as separate single-table reads (or `fx.call`) so every
@@ -292,15 +304,44 @@ await fx.store(db).delete(notes).where(lt(notes.createdAt, cutoff));
292
304
 
293
305
  The recommended path: declare tables ORM-agnostically, then let `oke db` emit real Drizzle (`pgTable` for Postgres / PGLite) into `src/db/schema.drizzle.ts`.
294
306
 
295
- | Field API | Meaning |
296
- | -------------------------------------------- | ----------------------------------------------- |
297
- | `field.text()` / `field.integer()` | v1 column primitives |
298
- | `.primaryKey()` · `.notNull()` · `.unique()` | constraints |
299
- | `.default(v)` · `.defaultFn(id \| now)` | defaults |
300
- | `.pii()` · `.sensitive()` · `.retain("30d")` | privacy classification |
301
- | `.as("sql_name")` | override the automatic `camelCase → snake_case` |
302
- | `.describe("…")` | human title in the Console (falls back to key) |
303
- | `.references(() => col, { onDelete })` | foreign key |
307
+ | Field API | Meaning |
308
+ | -------------------------------------------- | ---------------------------------------------------------------- |
309
+ | `field.text()` / `field.integer()` | v1 column primitives |
310
+ | `.primaryKey()` · `.notNull()` · `.unique()` | constraints |
311
+ | `.default(v)` · `.defaultFn(id \| now)` | defaults |
312
+ | `.pii()` · `.sensitive()` · `.retain("30d")` | privacy classification |
313
+ | `.as("sql_name")` | override the automatic `camelCase → snake_case` |
314
+ | `.describe("…")` | human title in the Console (falls back to key) |
315
+ | `.references(() => col, { onDelete })` | foreign key |
316
+ | `store.schema.rls()` | `pgTable.withRLS` when there are no policies |
317
+ | `store.schema.policy.gate/owner/scope` | happy-path RLS (`oke.gate()` / `oke.user()` / `oke.has_scope()`) |
318
+
319
+ Third argument on `store.schema.table` is Drizzle-shaped extras:
320
+
321
+ ```typescript
322
+ export const bookings = store.schema.table(
323
+ "bookings",
324
+ {
325
+ id: field.text().primaryKey(),
326
+ owner: field.text().notNull(),
327
+ },
328
+ [
329
+ store.schema.policy.gate("member", { for: "select" }),
330
+ store.schema.policy.owner("owner", { for: "all" }),
331
+ store.schema.policy.scope("booking:create", { for: "insert" }),
332
+ ],
333
+ );
334
+ ```
335
+
336
+ Helpers emit stable names (`gate_member_select`). Raw
337
+ `store.schema.policy("name", { as, to, for, using, withCheck })` is the
338
+ escape hatch — predicates use `oke.*`, never `current_setting`.
339
+
340
+ <Callout title="User-plane fx.store applies RLS">
341
+ HTTP / resource flows stamp Gate identity onto every postgres / pglite statement (`SET LOCAL ROLE
342
+ oke_app` + `set_config` in one pinned transaction). Table-owner bypass ends there; operator / cron
343
+ / CDC / signal / catalog stay unstamped.
344
+ </Callout>
304
345
 
305
346
  #### Foreign keys and relations
306
347
 
@@ -356,9 +397,9 @@ Staging/prod accumulate versioned SQL under `drizzle/` (`oke db generate`).
356
397
  | Staging / prod | `oke db generate` → review files → `oke db migrate` on that DB |
357
398
 
358
399
  <Callout title="Connection pooling is infrastructure, not app code" type="info">
359
- Bun.SQL defaults to **10** connections per process — fine per instance. Scale out and `N × pool`
360
- can exceed Postgres `max_connections`. `dev`/`prod` put **PgDog** in front; `DATABASE_URL` → port
361
- `6432`. No app code changes.
400
+ One shared Bun.SQL pool (**8** connections) per URL. `dev`/`prod` put **PgDog** in front
401
+ (`DATABASE_URL` port `6432`, `pool_size` 20). RLS stamps `reserve` one slot, then `SET LOCAL` in
402
+ that transaction a leaked `BEGIN` exhausts checkout.
362
403
  </Callout>
363
404
 
364
405
  **Why PgDog (not PgBouncer as the default).** Transaction pooling fixes the
@@ -371,8 +412,9 @@ Not wired this round — readiness only.
371
412
 
372
413
  #### Seeding
373
414
 
374
- `oke db seed` loads `defineSeed` from `src/db/seed/index.ts` — **never at boot**.
375
- `name` is this app's seed (template or example). The prompt and `.oke/state.json` key off it.
415
+ `oke db seed` loads `defineSeed` from `src/db/seed/index.ts` — a separate
416
+ command, never at boot. Not every project has a seed. In `oke dev`, `s` runs
417
+ the same command. `name` is this app's seed (template or example).
376
418
 
377
419
  <StoreSeeding />
378
420
 
@@ -642,8 +684,12 @@ Default `store.kv("sessions")` is cache-shaped — a Redis recreate drops keys.
642
684
  ```typescript
643
685
  export const sessions = store.kv("sessions", { description: "Session cache" });
644
686
  export const ledger = store.kv("ledger", { durable: true, description: "Idempotency keys" });
687
+ export const drafts = store.kv("drafts", { durable: true, description: "Compose drafts" });
645
688
  ```
646
689
 
690
+ **Consequence:** `oke db seed` into cache Redis looks fine until compose recreates the
691
+ container — Console Store then shows **No rows.** Seeded namespaces need `{ durable: true }`.
692
+
647
693
  Gate rates and Signal stay on `REDIS_URL`. Missing `DATABASE_URL` with the postgres driver
648
694
  fails boot: `oke boot: durable store.kv needs DATABASE_URL`.
649
695
 
@@ -1014,6 +1060,11 @@ Inserts and selects now resolve it from the declaration. Keep the `name` column.
1014
1060
 
1015
1061
  Schema DDL never runs automatically in `prod`. Under `oke dev`, `db.autoPush` (default `true`) runs `oke db push`. For production use `oke db generate` + `oke db migrate`.
1016
1062
 
1063
+ </Accordion>
1064
+ <Accordion title="oke db push: missing_hints">
1065
+
1066
+ New tables are created automatically. Push only manages `public` — it will not drop schema `oke` (RLS helpers) or `oke_console`. Remaining `missing_hints` are destructive (drop a non-empty public table, type change). Review with `oke db generate`.
1067
+
1017
1068
  </Accordion>
1018
1069
  <Accordion title="I need a join — with: is not supported">
1019
1070
 
@@ -1064,11 +1115,17 @@ Unlike KV/Files, unset `drivers.store.index` does **not** promote to `pgvector`
1064
1115
 
1065
1116
  `.oke/state.json` stores `seededAt` plus this app's `defineSeed({ name })` — notes vs keel are different ids. A new name re-asks. Clear those keys to prompt again.
1066
1117
 
1118
+ </Accordion>
1119
+ <Accordion title="Console KV shows No rows after oke db seed">
1120
+
1121
+ Default `store.kv` is cache Redis with no AOF — a recreate drops keys. SQL seed still looks full. Mark seeded namespaces `{ durable: true }` so they live in `oke_kv`, then restart and seed again.
1122
+
1067
1123
  </Accordion>
1068
1124
  </Accordions>
1069
1125
 
1070
1126
  ## Learn more
1071
1127
 
1128
+ - [Project structure](/docs/get-started/project-structure) — `list.ts` + `http.get()` is `GET /notes` named `notes.list`
1072
1129
  - [Flow](/docs/elements/flow) — the `fx.store` session inside `do`
1073
1130
  - [AI](/docs/elements/ai) — `ai.embed` into a vector `store.index`, searched via `fx.search`
1074
1131
  - [Gate](/docs/elements/gate) — `pii:reveal` and other permissions on flows
@@ -201,6 +201,9 @@ The **Console** (`:6533`) **Vault** page (`/vault`) groups **Secrets** and
201
201
  **Config**. Secrets are write-only: a salted fingerprint (`sha256:…`), never
202
202
  the cleartext. Config rows show the value — that is `vault.config()`.
203
203
 
204
+ API keys are principals — mint them on **Access** (`/access`), not Vault.
205
+ See [Gate](/docs/elements/gate#api-keys).
206
+
204
207
  Add (`+`) creates a contract from Console as well as from source. Those rows
205
208
  show `console` until you declare the same name in the Manifest.
206
209
 
@@ -363,6 +366,7 @@ Schedule the live command with cron when you want automatic cleanup.
363
366
 
364
367
  ## Learn more
365
368
 
369
+ - [Project structure](/docs/get-started/project-structure) — `flow({…})` names as `unit.export`; `flow("billing.charge")` still wins
366
370
  - [Flow](/docs/elements/flow) — how `fx.vault.get` reads secrets inside `do`
367
371
  - [CLI Reference](/docs/reference/cli) — `oke vault set` · `list` · `import`
368
372
 
@@ -45,19 +45,21 @@ lists HTTP routes — static GET paths are links. `curl` still gets `{ data, err
45
45
 
46
46
  The standard starter already exports a named health Flow:
47
47
 
48
- ```typescript title="flows/main"
49
- import { on, flow, http, gate } from "okengine/http";
48
+ ```typescript title="flows/main/health.ts"
49
+ import { on, flow, http } from "okengine";
50
50
  import { z } from "zod";
51
51
 
52
52
  export const health = on(
53
- http.get("/health").gate.public,
54
- flow("main.health", {
53
+ http.get().public(),
54
+ flow({
55
55
  out: z.object({ ok: z.literal(true) }),
56
56
  do: () => ({ ok: true as const }),
57
57
  }),
58
58
  );
59
59
  ```
60
60
 
61
+ `main` omits the URL prefix, so this file is `GET /health` named `main.health`. See [Project structure](/docs/get-started/project-structure).
62
+
61
63
  Named flows show up as `main.health` (not `flow_1`) in `oke dev` logs, with the run id after the timestamp.
62
64
  Change the route or output and save — App and Console update together from the
63
65
  same Manifest.
@@ -67,20 +69,19 @@ same Manifest.
67
69
  <Step>
68
70
  ### Call it from a typed client
69
71
 
70
- Adopted modules become namespaces on the client:
72
+ Generated units become namespaces on the client:
71
73
 
72
74
  ```typescript title="app"
73
75
  import "@/core";
74
-
76
+ import "@/flows/generated";
75
77
  import { oke } from "okengine/http";
76
- import * as routes from "@/flows/generated";
77
78
 
78
- export const app = oke({ name: "notes" }).adopt(routes);
79
+ export const app = oke({ name: "notes" });
79
80
  export type App = typeof app;
80
81
  ```
81
82
 
82
- `import "@/core"` loads stores, secrets, channels, and message catalogs
83
- (via `locales/index.ts`) so they auto-register before `oke()`.
83
+ `import "@/core"` loads stores, secrets, gates, clocks, channels, and
84
+ message catalogs (via `locales/index.ts`) so they auto-register before `oke()`.
84
85
 
85
86
  ```typescript title="client"
86
87
  import { createClient } from "okengine/client";
@@ -107,7 +108,7 @@ Every backend behavior has one shape:
107
108
 
108
109
  ## From Flow to client
109
110
 
110
- Export → adopt → call or test. Same App type — no separate codegen project.
111
+ Export → generated barrel → call or test. Same App type — no separate codegen project.
111
112
 
112
113
  <ClientLoop />
113
114
 
@@ -137,8 +138,10 @@ bun test
137
138
  <Accordions>
138
139
 
139
140
  <Accordion title="api.main.health is not a function / type error">
140
- Confirm the Flow is `export`ed from the module you `.adopt({main})`, and that `createClient` is
141
- typed with your `App` type. Restart `oke dev` after renaming exports so the Manifest refreshes.
141
+ Confirm the Flow is `export`ed from a generated unit (`import "@/flows/generated"` then `oke({ name })`), or from a module you still `.adopt({ main })`.
142
+
143
+ Type `createClient` with your `App` type. Restart `oke dev` after renaming exports so the Manifest refreshes.
144
+
142
145
  </Accordion>
143
146
 
144
147
  <Accordion title="createTestApp fails to boot">
@@ -151,6 +154,7 @@ bun test
151
154
 
152
155
  ## Learn more
153
156
 
157
+ - [Project structure](/docs/get-started/project-structure) — folders are the URL
154
158
  - [Flow](/docs/elements/flow) — triggers, contracts, effects, and composition
155
159
  - [Store](/docs/elements/store) — SQL, KV, files, index, and the preferred list envelope
156
160
  - [fx](/docs/reference/fx) — the full effect surface
@@ -158,6 +162,11 @@ bun test
158
162
  ## Next
159
163
 
160
164
  <Cards>
165
+ <Card
166
+ title="Project structure"
167
+ description="Folders are the URL; unit plus export is the name."
168
+ href="/docs/get-started/project-structure"
169
+ />
161
170
  <Card
162
171
  title="Flow"
163
172
  description="Triggers, contracts, effects, and composition."