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
@@ -41,6 +41,20 @@ const SEED_SPACES = [
41
41
  ] as const;
42
42
 
43
43
  const SEED_MEMBERS = [
44
+ {
45
+ id: "mem_demo",
46
+ space_id: "space_eng",
47
+ name: "Demo User",
48
+ email: "demo@example.com",
49
+ role: "owner",
50
+ },
51
+ {
52
+ id: "mem_admin",
53
+ space_id: "space_eng",
54
+ name: "Avery Cole",
55
+ email: "admin@keel.dev",
56
+ role: "admin",
57
+ },
44
58
  {
45
59
  id: "mem_aria",
46
60
  space_id: "space_eng",
@@ -48,6 +62,13 @@ const SEED_MEMBERS = [
48
62
  email: "aria@keel.dev",
49
63
  role: "project_manager",
50
64
  },
65
+ {
66
+ id: "mem_lead",
67
+ space_id: "space_eng",
68
+ name: "Jules Hart",
69
+ email: "jules@keel.dev",
70
+ role: "lead",
71
+ },
51
72
  {
52
73
  id: "mem_ben",
53
74
  space_id: "space_eng",
@@ -55,6 +76,13 @@ const SEED_MEMBERS = [
55
76
  email: "ben@keel.dev",
56
77
  role: "developer",
57
78
  },
79
+ {
80
+ id: "mem_eli",
81
+ space_id: "space_eng",
82
+ name: "Eli Park",
83
+ email: "eli@keel.dev",
84
+ role: "contributor",
85
+ },
58
86
  {
59
87
  id: "mem_cai",
60
88
  space_id: "space_des",
@@ -62,6 +90,20 @@ const SEED_MEMBERS = [
62
90
  email: "cai@keel.dev",
63
91
  role: "member",
64
92
  },
93
+ {
94
+ id: "mem_member",
95
+ space_id: "space_des",
96
+ name: "Member",
97
+ email: "member@example.com",
98
+ role: "member",
99
+ },
100
+ {
101
+ id: "mem_commenter",
102
+ space_id: "space_gtm",
103
+ name: "Kit Rowe",
104
+ email: "kit@keel.dev",
105
+ role: "commenter",
106
+ },
65
107
  {
66
108
  id: "mem_dia",
67
109
  space_id: "space_gtm",
@@ -69,13 +111,6 @@ const SEED_MEMBERS = [
69
111
  email: "dia@keel.dev",
70
112
  role: "guest",
71
113
  },
72
- {
73
- id: "mem_eli",
74
- space_id: "space_eng",
75
- name: "Eli Park",
76
- email: "eli@keel.dev",
77
- role: "developer",
78
- },
79
114
  ] as const;
80
115
 
81
116
  const SEED_GOALS = [
@@ -640,14 +675,7 @@ function generateKeelVolume(): {
640
675
  email: `${p.id}@keel.dev`,
641
676
  role: "member",
642
677
  }));
643
- const emails = [
644
- "aria@keel.dev",
645
- "ben@keel.dev",
646
- "cai@keel.dev",
647
- "dia@keel.dev",
648
- "eli@keel.dev",
649
- ...members.map((m) => m.email),
650
- ];
678
+ const emails = [...SEED_MEMBERS.map((m) => m.email), ...members.map((m) => m.email)];
651
679
 
652
680
  const projects = Array.from({ length: GENERATED.projects }, (_, i) => ({
653
681
  id: `proj_g_${String(i + 1).padStart(2, "0")}`,
@@ -21,6 +21,7 @@ const FEATURES_ROOT = `${UI_ROOT}/features`;
21
21
  * already covers it; the list makes coverage explicit in CI output.
22
22
  */
23
23
  export const CONSOLE_UI_FEATURE_DIRS = [
24
+ "access",
24
25
  "auth",
25
26
  "flows",
26
27
  "not-found",
@@ -16,6 +16,12 @@ describe("compose-health", () => {
16
16
  expect(composeRowToStatus({ State: "stopped" })).toBe("error");
17
17
  expect(composeRowToStatus({ Status: "Exited (0) 3 seconds ago" })).toBe("error");
18
18
  expect(composeRowToStatus({ State: "running" })).toBe("ready");
19
+ expect(composeRowToStatus({ State: "restarting" })).toBe("pending");
20
+ expect(composeRowToStatus({ State: "running", Status: "Up Less than a second" })).toBe(
21
+ "pending",
22
+ );
23
+ expect(composeRowToStatus({ State: "running", Status: "Up 2 seconds" })).toBe("pending");
24
+ expect(composeRowToStatus({ State: "running", Status: "Up 2 minutes" })).toBe("ready");
19
25
  expect(composeRowToStatus({ Status: "Up 2 seconds (health: starting)" })).toBe("pending");
20
26
  });
21
27
 
@@ -60,6 +66,27 @@ describe("compose-health", () => {
60
66
  expect(updates).toEqual(["store-sql:pending", "store-sql:ready"]);
61
67
  });
62
68
 
69
+ test("watchComposeHealth waits through compose restarting", async () => {
70
+ let calls = 0;
71
+ const map = await watchComposeHealth({
72
+ files: ["compose.yml"],
73
+ cwd: "/tmp",
74
+ env: {},
75
+ intervalMs: 1,
76
+ timeoutMs: 5_000,
77
+ sleep: async () => {},
78
+ run: async () => {
79
+ calls += 1;
80
+ if (calls === 1) {
81
+ return JSON.stringify([{ Service: "store-sql", State: "restarting" }]);
82
+ }
83
+ return JSON.stringify([{ Service: "store-sql", Health: "healthy", State: "running" }]);
84
+ },
85
+ });
86
+ expect(calls).toBe(2);
87
+ expect(map.get("store-sql")).toBe("ready");
88
+ });
89
+
63
90
  test("readComposeHealth / watch use ps -a", async () => {
64
91
  let seen: readonly string[] = [];
65
92
  await watchComposeHealth({
@@ -20,22 +20,27 @@ export type ComposePsRow = {
20
20
  export function composeRowToStatus(row: ComposePsRow): DevStatus {
21
21
  const health = (row.Health ?? "").trim().toLowerCase();
22
22
  const state = (row.State ?? "").trim().toLowerCase();
23
+ const statusText = (row.Status ?? "").toLowerCase();
23
24
  if (health === "healthy") return "ready";
24
25
  if (health === "unhealthy") return "error";
25
26
  if (health === "starting") return "pending";
26
- if (state === "running" || state === "up") return "ready";
27
+ if (state === "restarting") return "pending";
28
+ if (state === "running" || state === "up") {
29
+ // Right after `compose restart`, State is "running" before Health is
30
+ // "starting". Treat a fresh Up without (healthy) as still coming up.
31
+ if (isFreshComposeUp(statusText) && !statusText.includes("(healthy)")) return "pending";
32
+ return "ready";
33
+ }
27
34
  if (
28
35
  state === "exited" ||
29
36
  state === "dead" ||
30
37
  state === "stopped" ||
31
- state === "restarting" ||
32
38
  state === "removing" ||
33
39
  state === "oomkilled"
34
40
  ) {
35
41
  return "error";
36
42
  }
37
43
  // Status text for stopped containers (`Exited (0) 2 seconds ago`).
38
- const statusText = (row.Status ?? "").toLowerCase();
39
44
  if (statusText.startsWith("exited") || statusText.startsWith("dead")) return "error";
40
45
  if (state === "created" || state === "paused" || state.length === 0) return "pending";
41
46
  // Status text like "Up 3 seconds (healthy)"
@@ -48,6 +53,17 @@ export function composeRowToStatus(row: ComposePsRow): DevStatus {
48
53
  return "pending";
49
54
  }
50
55
 
56
+ /**
57
+ * True when compose `Status` is a just-started container (health not reported yet).
58
+ *
59
+ * @param statusText - Lowercased `Status` from `docker compose ps`
60
+ */
61
+ function isFreshComposeUp(statusText: string): boolean {
62
+ if (statusText.includes("less than a second")) return true;
63
+ const seconds = /up\s+(\d+)\s+seconds?/i.exec(statusText);
64
+ return seconds !== null && Number(seconds[1]) < 8;
65
+ }
66
+
51
67
  /**
52
68
  * Parse NDJSON or a JSON array from `docker compose ps --format json`.
53
69
  *
@@ -392,11 +392,13 @@ describe("image recipes", () => {
392
392
  expect(pgdogToml).toContain('host = "0.0.0.0"');
393
393
  expect(pgdogToml).toContain("port = 6432");
394
394
  expect(pgdogToml).toContain('pooler_mode = "transaction"');
395
+ expect(pgdogToml).toContain("default_pool_size = 20");
395
396
  expect(pgdogToml).toContain("[[databases]]");
396
397
  expect(pgdogToml).toContain('name = "oke"');
397
398
  expect(pgdogToml).toContain('host = "store-sql"');
398
399
  expect(pgdogToml).toContain("port = 5432");
399
400
  expect(pgdogToml).toContain('database_name = "oke"');
401
+ expect(pgdogToml).toContain("pool_size = 20");
400
402
 
401
403
  const usersToml = buildPgDogUsersToml({
402
404
  user: "oke",
@@ -936,6 +938,7 @@ describe("deriveInfrastructure", () => {
936
938
 
937
939
  const pgdogToml = result.files.find((f) => f.path === "pgdog/pgdog.toml")!.content;
938
940
  expect(pgdogToml).toContain('pooler_mode = "transaction"');
941
+ expect(pgdogToml).toContain("pool_size = 20");
939
942
  expect(pgdogToml).toContain('host = "store-sql"');
940
943
  expect(pgdogToml).not.toContain(fixedCreds["store.sql"].password);
941
944
 
@@ -35,12 +35,14 @@ export function buildPgDogToml(opts: {
35
35
  'host = "0.0.0.0"',
36
36
  "port = 6432",
37
37
  'pooler_mode = "transaction"',
38
+ "default_pool_size = 20",
38
39
  "",
39
40
  "[[databases]]",
40
41
  `name = ${tomlString(db)}`,
41
42
  `host = ${tomlString(host)}`,
42
43
  `port = ${port}`,
43
44
  `database_name = ${tomlString(db)}`,
45
+ "pool_size = 20",
44
46
  "",
45
47
  ].join("\n");
46
48
  }
@@ -7,7 +7,13 @@
7
7
  */
8
8
 
9
9
  import type { CronRow, CronStatus, CronStore } from "../elements/clock/reconcile.ts";
10
- import { toPostgresParams } from "./postgres.ts";
10
+ import {
11
+ resolvePostgresUrl,
12
+ sharedPostgresClient,
13
+ toPostgresParams,
14
+ withPinnedPostgres,
15
+ type PostgresClientLike,
16
+ } from "./postgres.ts";
11
17
 
12
18
  /** Row shape in `oke_crons` (lease columns mirror `oke_signal_messages`). */
13
19
  interface CronDbRow {
@@ -69,16 +75,16 @@ export interface BunCronClient {
69
75
  close?(options?: { timeout?: number }): Promise<void>;
70
76
  }
71
77
 
72
- function wrapBunClient(client: BunCronClient): PostgresCronSql {
78
+ function wrapBunClient(client: PostgresClientLike): PostgresCronSql {
73
79
  const api: PostgresCronSql = {
74
80
  async query(sql, params = []) {
75
- const pg = toPostgresParams(sql);
81
+ const pg = toPostgresParams(sql, params);
76
82
  const result = await client.unsafe(pg, [...params]);
77
83
  if (Array.isArray(result)) return result as Record<string, unknown>[];
78
84
  return Array.from(result as ArrayLike<Record<string, unknown>>);
79
85
  },
80
86
  async exec(sql, params = []) {
81
- const pg = toPostgresParams(sql);
87
+ const pg = toPostgresParams(sql, params);
82
88
  const result = await client.unsafe(pg, [...params]);
83
89
  if (
84
90
  result &&
@@ -92,7 +98,7 @@ function wrapBunClient(client: BunCronClient): PostgresCronSql {
92
98
  return { changes: 0 };
93
99
  },
94
100
  async begin(fn) {
95
- return client.begin(async (tx) => fn(wrapBunClient(tx)));
101
+ return withPinnedPostgres(client, (tx) => fn(wrapBunClient(tx)));
96
102
  },
97
103
  async close() {
98
104
  await client.close?.();
@@ -365,10 +371,8 @@ export async function createPostgresCronStore(
365
371
  const sql =
366
372
  options.sql ??
367
373
  wrapBunClient(
368
- options.client ??
369
- (new Bun.SQL(
370
- options.url ?? process.env.DATABASE_URL ?? "postgres://localhost:5432/oke",
371
- ) as unknown as BunCronClient),
374
+ (options.client ??
375
+ sharedPostgresClient(resolvePostgresUrl(options.url))) as PostgresClientLike,
372
376
  );
373
377
 
374
378
  await ensureSchema(sql);
@@ -7,7 +7,7 @@
7
7
 
8
8
  import type { ConfigEnv } from "../config/index.ts";
9
9
  import type { InstanceRow, InstanceStore } from "../kernel/instances.ts";
10
- import { toPostgresParams } from "./postgres.ts";
10
+ import { resolvePostgresUrl, sharedPostgresClient, toPostgresParams } from "./postgres.ts";
11
11
 
12
12
  /** Minimal SQL surface for the postgres instance store. */
13
13
  export interface PostgresInstanceSql {
@@ -40,13 +40,13 @@ const UPSERT_SQL = `INSERT INTO oke_instances (id, started_at, heartbeat_at, lea
40
40
  function wrapBunClient(client: BunInstanceClient): PostgresInstanceSql {
41
41
  return {
42
42
  async query(sql, params = []) {
43
- const pg = toPostgresParams(sql);
43
+ const pg = toPostgresParams(sql, params);
44
44
  const result = await client.unsafe(pg, [...params]);
45
45
  if (Array.isArray(result)) return result as Record<string, unknown>[];
46
46
  return Array.from(result as ArrayLike<Record<string, unknown>>);
47
47
  },
48
48
  async exec(sql, params = []) {
49
- const pg = toPostgresParams(sql);
49
+ const pg = toPostgresParams(sql, params);
50
50
  const result = await client.unsafe(pg, [...params]);
51
51
  if (
52
52
  result &&
@@ -109,9 +109,7 @@ export async function createPostgresInstanceStore(
109
109
  options.sql ??
110
110
  wrapBunClient(
111
111
  options.client ??
112
- (new Bun.SQL(
113
- options.url ?? process.env.DATABASE_URL ?? "postgres://localhost:5432/oke",
114
- ) as unknown as BunInstanceClient),
112
+ (sharedPostgresClient(resolvePostgresUrl(options.url)) as unknown as BunInstanceClient),
115
113
  );
116
114
 
117
115
  await ensureSchema(sql);
@@ -14,7 +14,13 @@ import {
14
14
  type JournalRun,
15
15
  type JournalStore,
16
16
  } from "../kernel/journal.ts";
17
- import { toPostgresParams } from "./postgres.ts";
17
+ import {
18
+ resolvePostgresUrl,
19
+ sharedPostgresClient,
20
+ toPostgresParams,
21
+ withPinnedPostgres,
22
+ type PostgresClientLike,
23
+ } from "./postgres.ts";
18
24
 
19
25
  /** Row shape in `oke_journal_runs` (lease columns mirror `oke_crons`). */
20
26
  interface JournalDbRow {
@@ -87,16 +93,16 @@ export interface BunJournalClient {
87
93
  close?(options?: { timeout?: number }): Promise<void>;
88
94
  }
89
95
 
90
- function wrapBunClient(client: BunJournalClient): PostgresJournalSql {
96
+ function wrapBunClient(client: PostgresClientLike): PostgresJournalSql {
91
97
  const api: PostgresJournalSql = {
92
98
  async query(sql, params = []) {
93
- const pg = toPostgresParams(sql);
99
+ const pg = toPostgresParams(sql, params);
94
100
  const result = await client.unsafe(pg, [...params]);
95
101
  if (Array.isArray(result)) return result as Record<string, unknown>[];
96
102
  return Array.from(result as ArrayLike<Record<string, unknown>>);
97
103
  },
98
104
  async exec(sql, params = []) {
99
- const pg = toPostgresParams(sql);
105
+ const pg = toPostgresParams(sql, params);
100
106
  const result = await client.unsafe(pg, [...params]);
101
107
  if (
102
108
  result &&
@@ -110,7 +116,7 @@ function wrapBunClient(client: BunJournalClient): PostgresJournalSql {
110
116
  return { changes: 0 };
111
117
  },
112
118
  async begin(fn) {
113
- return client.begin(async (tx) => fn(wrapBunClient(tx)));
119
+ return withPinnedPostgres(client, (tx) => fn(wrapBunClient(tx)));
114
120
  },
115
121
  async close() {
116
122
  await client.close?.();
@@ -437,10 +443,8 @@ export async function createPostgresJournalStore(
437
443
  const sql =
438
444
  options.sql ??
439
445
  wrapBunClient(
440
- options.client ??
441
- (new Bun.SQL(
442
- options.url ?? process.env.DATABASE_URL ?? "postgres://localhost:5432/oke",
443
- ) as unknown as BunJournalClient),
446
+ (options.client ??
447
+ sharedPostgresClient(resolvePostgresUrl(options.url))) as PostgresClientLike,
444
448
  );
445
449
 
446
450
  await ensureSchema(sql);
@@ -2,7 +2,13 @@ import { describe, expect, test } from "bun:test";
2
2
  import {
3
3
  buildAlterPolicySql,
4
4
  buildCreatePolicySql,
5
+ buildRlsIdentityPreludeSql,
6
+ emitPgPolicySource,
7
+ formatPolicyRole,
8
+ OKE_RLS_HELPER_STATEMENTS,
9
+ OKE_RLS_ROLE,
5
10
  parseSqlPolicySpec,
11
+ rlsScopesJson,
6
12
  sqlPolicyRowId,
7
13
  } from "./pg-rls.ts";
8
14
 
@@ -36,6 +42,22 @@ describe("parseSqlPolicySpec", () => {
36
42
  expect(spec.withCheck).toBe("true");
37
43
  });
38
44
 
45
+ test("accepts Drizzle as/to/for aliases", () => {
46
+ const spec = parseSqlPolicySpec({
47
+ name: "gate_read",
48
+ table: "bookings",
49
+ for: "select",
50
+ as: "permissive",
51
+ to: "current_user",
52
+ using: "oke.gate() = 'member'",
53
+ });
54
+ expect(spec.command).toBe("SELECT");
55
+ expect(spec.behavior).toBe("PERMISSIVE");
56
+ expect(spec.roles).toEqual(["current_user"]);
57
+ expect(buildCreatePolicySql(spec)).toContain("TO current_user");
58
+ expect(buildCreatePolicySql(spec)).not.toContain('"current_user"');
59
+ });
60
+
39
61
  test("rejects stacked statements in USING", () => {
40
62
  expect(() =>
41
63
  parseSqlPolicySpec({
@@ -69,3 +91,68 @@ describe("buildAlterPolicySql", () => {
69
91
  ).toThrow("invalid USING");
70
92
  });
71
93
  });
94
+
95
+ describe("formatPolicyRole", () => {
96
+ test("leaves Drizzle special roles unquoted", () => {
97
+ expect(formatPolicyRole("public")).toBe("public");
98
+ expect(formatPolicyRole("current_user")).toBe("current_user");
99
+ expect(formatPolicyRole("member")).toBe('"member"');
100
+ });
101
+ });
102
+
103
+ describe("emitPgPolicySource", () => {
104
+ test("emits drizzle pgPolicy with oke helpers", () => {
105
+ const spec = parseSqlPolicySpec({
106
+ name: "gate_member_select",
107
+ table: "bookings",
108
+ command: "SELECT",
109
+ using: "oke.gate() = 'member'",
110
+ });
111
+ expect(emitPgPolicySource(spec)).toContain('pgPolicy("gate_member_select"');
112
+ expect(emitPgPolicySource(spec)).toContain('for: "select"');
113
+ expect(emitPgPolicySource(spec)).toContain("oke.gate() = 'member'");
114
+ });
115
+ });
116
+
117
+ describe("rlsScopesJson", () => {
118
+ test("sorts unique scopes", () => {
119
+ expect(rlsScopesJson(["booking:create", "member", "member"])).toBe(
120
+ '["booking:create","member"]',
121
+ );
122
+ });
123
+ });
124
+
125
+ describe("buildRlsIdentityPreludeSql", () => {
126
+ test("emits one statement per exec — never a multi-command batch", () => {
127
+ const stmts = buildRlsIdentityPreludeSql({
128
+ gate: "member",
129
+ userId: "alice",
130
+ scopes: ["member"],
131
+ });
132
+ expect(stmts.map((s) => s.sql)).toEqual([
133
+ `SET LOCAL ROLE ${OKE_RLS_ROLE}`,
134
+ "SET LOCAL row_security = on",
135
+ "SELECT set_config('oke.gate', ?, true), set_config('oke.user', ?, true), set_config('oke.scopes', ?, true)",
136
+ ]);
137
+ for (const stmt of stmts) {
138
+ expect(stmt.sql.includes(";")).toBe(false);
139
+ }
140
+ expect(stmts[2]?.params).toEqual(["member", "alice", '["member"]']);
141
+ });
142
+ });
143
+
144
+ describe("OKE_RLS_HELPER_STATEMENTS", () => {
145
+ test("has_scope uses jsonb_exists so ? is not a placeholder", () => {
146
+ const hasScope = OKE_RLS_HELPER_STATEMENTS.find((s) => s.includes("oke.has_scope"));
147
+ expect(hasScope).toBeDefined();
148
+ expect(hasScope).toContain("jsonb_exists(");
149
+ expect(hasScope).not.toMatch(/jsonb\s+\?/);
150
+ });
151
+
152
+ test("pins search_path to public so schema oke is not the table home", () => {
153
+ expect(OKE_RLS_HELPER_STATEMENTS).toContain("SET search_path TO public, oke");
154
+ expect(
155
+ OKE_RLS_HELPER_STATEMENTS.some((s) => s.includes("ALTER TABLE oke.%I SET SCHEMA public")),
156
+ ).toBe(true);
157
+ });
158
+ });