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
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import type { RateStrategy } from "../../manifest/types.ts";
8
+ import { gateRegistry } from "../../kernel/element-registries.ts";
8
9
  import { DEFAULT_RATE_STRATEGY } from "./constants.ts";
9
10
  import { GATE_PUBLIC_NAME, type GateAllDecl, type GateMember } from "./flatten.ts";
10
11
 
@@ -18,6 +19,8 @@ export interface GatePolicyContext {
18
19
  readonly userId: string | null;
19
20
  readonly scopes: ReadonlySet<string>;
20
21
  readonly verified?: boolean;
22
+ /** Authenticating API key id when the principal is a key; otherwise omitted. */
23
+ readonly apiKeyId?: string | null;
21
24
  };
22
25
  /** Operator-plane principal (`fx.operator`). */
23
26
  readonly operator: {
@@ -89,6 +92,29 @@ export interface RateGateDecl {
89
92
  /** Declared gate handle. */
90
93
  export type GateDecl = PolicyGateDecl | RateGateDecl;
91
94
 
95
+ /**
96
+ * `gate.policy` / `gate.scope` / `gate.rate` push into the shared
97
+ * {@link gateRegistry} (`src/kernel/element-registries.ts`) so {@link oke}
98
+ * can auto-populate `gate.policies` with zero explicit array — mirrors the
99
+ * {@link on} trigger-drain registry (`src/kernel/on.ts`). `gate.public` is
100
+ * built into the runtime; `gate.all` is a composition of already-registered
101
+ * members.
102
+ *
103
+ * Snapshot of every policy / rate declared since the last reset.
104
+ */
105
+ export function listGates(): readonly GateDecl[] {
106
+ return gateRegistry.slice();
107
+ }
108
+
109
+ /**
110
+ * Clear the gate registry (tests / fresh app adopt).
111
+ *
112
+ * @internal
113
+ */
114
+ export function resetGates(): void {
115
+ gateRegistry.length = 0;
116
+ }
117
+
92
118
  /**
93
119
  * Shape of the {@link gate} element namespace.
94
120
  */
@@ -151,9 +177,11 @@ export const gate: GateNamespace = {
151
177
  );
152
178
  }
153
179
  if (typeof checkOrOptions === "function") {
154
- return { kind: "policy", name, check: checkOrOptions };
180
+ const decl: PolicyGateDecl = { kind: "policy", name, check: checkOrOptions };
181
+ gateRegistry.push(decl);
182
+ return decl;
155
183
  }
156
- return {
184
+ const decl: PolicyGateDecl = {
157
185
  kind: "policy",
158
186
  name,
159
187
  check: checkOrOptions.check,
@@ -161,6 +189,8 @@ export const gate: GateNamespace = {
161
189
  ? { description: checkOrOptions.description }
162
190
  : {}),
163
191
  };
192
+ gateRegistry.push(decl);
193
+ return decl;
164
194
  },
165
195
 
166
196
  /**
@@ -172,12 +202,14 @@ export const gate: GateNamespace = {
172
202
  if (name === GATE_PUBLIC_NAME) {
173
203
  throw new TypeError('gate.scope: name "public" is reserved — use gate.public');
174
204
  }
175
- return {
205
+ const decl: PolicyGateDecl = {
176
206
  kind: "policy",
177
207
  name,
178
208
  check: ({ auth }) => auth.scopes.has(name),
179
209
  scopes: [name],
180
210
  };
211
+ gateRegistry.push(decl);
212
+ return decl;
181
213
  },
182
214
 
183
215
  public: {
@@ -201,7 +233,7 @@ export const gate: GateNamespace = {
201
233
  }
202
234
  const strategy = options.strategy ?? DEFAULT_RATE_STRATEGY;
203
235
  const name = `rate:${strategy}:${options.max}/${options.per}`;
204
- return {
236
+ const decl: RateGateDecl = {
205
237
  kind: "rate",
206
238
  name,
207
239
  strategy,
@@ -211,6 +243,8 @@ export const gate: GateNamespace = {
211
243
  overridable: options.overridable ?? false,
212
244
  ...(options.description !== undefined ? { description: options.description } : {}),
213
245
  };
246
+ gateRegistry.push(decl);
247
+ return decl;
214
248
  },
215
249
 
216
250
  /**
@@ -7,7 +7,7 @@
7
7
 
8
8
  import type { GateDecl } from "./declare.ts";
9
9
 
10
- /** Reserved policy name for `gate.public` / `.gate.public`. */
10
+ /** Reserved policy name for `gate.public` / `.public()`. */
11
11
  export const GATE_PUBLIC_NAME = "public";
12
12
 
13
13
  /** Member of `gate.all` — a policy/rate or a nested `all` handle. */
@@ -70,7 +70,7 @@ export interface GateRuntime {
70
70
  */
71
71
  export function createGateRuntime(options: CreateGateRuntimeOptions = {}): GateRuntime {
72
72
  const map = new Map<string, GateDecl>();
73
- // Built-in sentinel — `.gate.public` works without listing it in `oke({ gates })`.
73
+ // Built-in sentinel — `.public()` works without listing it in `oke({ gates })`.
74
74
  map.set(gate.public.name, gate.public);
75
75
  for (const g of options.gates ?? []) {
76
76
  if (isGateAllDecl(g)) {
@@ -63,7 +63,7 @@ export function tier1KeysForReads(
63
63
  * @param effects - Write flow effects
64
64
  */
65
65
  export function resourcesTouchedByWrites(effects: Effects): ResourceRef[] {
66
- return [...(effects.writes ?? [])];
66
+ return (effects.writes ?? []).filter((r): r is ResourceRef => isStoreResourceRef(r));
67
67
  }
68
68
 
69
69
  /**
@@ -122,13 +122,12 @@ export interface IndexStoreDecl extends StoreDeclBase {
122
122
  export type StoreDecl = SqlStoreDecl | KvStoreDecl | FilesStoreDecl | IndexStoreDecl;
123
123
 
124
124
  /**
125
- * `store.sql` / `store.files` push into the shared {@link storeRegistry}
125
+ * Every `store.*` facet pushes into the shared {@link storeRegistry}
126
126
  * (`src/kernel/element-registries.ts`) so {@link oke} can auto-populate
127
127
  * `stores` with zero explicit array — mirrors the {@link on} trigger-drain
128
- * registry (`src/kernel/on.ts`). `store.kv` / `store.index` are
129
- * intentionally not auto-registered (out of scope).
128
+ * registry (`src/kernel/on.ts`).
130
129
  *
131
- * Snapshot of every `store.sql` / `store.files` declared since the last reset.
130
+ * Snapshot of every store declared since the last reset.
132
131
  */
133
132
  export function listStores(): readonly StoreDecl[] {
134
133
  return storeRegistry.slice();
@@ -180,13 +179,15 @@ export function sql(name: string, options: SqlStoreOptions = {}): SqlStoreDecl {
180
179
  * @param options - Options
181
180
  */
182
181
  export function kv(name: string, options: KvStoreOptions = {}): KvStoreDecl {
183
- return {
182
+ const decl: KvStoreDecl = {
184
183
  facet: "kv",
185
184
  name,
186
185
  ref: `kv:${name}`,
187
186
  ...(options.description !== undefined ? { description: options.description } : {}),
188
187
  ...(options.durable === true ? { durable: true } : {}),
189
188
  };
189
+ storeRegistry.push(decl);
190
+ return decl;
190
191
  }
191
192
 
192
193
  /**
@@ -213,13 +214,15 @@ export function files(name: string, options: FilesStoreOptions = {}): FilesStore
213
214
  * @param options - Dimensions
214
215
  */
215
216
  export function index(name: string, options: IndexStoreOptions = {}): IndexStoreDecl {
216
- return {
217
+ const decl: IndexStoreDecl = {
217
218
  facet: "index",
218
219
  name,
219
220
  ref: `index:${name}`,
220
221
  ...(options.description !== undefined ? { description: options.description } : {}),
221
222
  dims: options.dims,
222
223
  };
224
+ storeRegistry.push(decl);
225
+ return decl;
223
226
  }
224
227
 
225
228
  /** Public `store` element — four facets + abstract schema declare + resource. */
@@ -21,9 +21,11 @@ import {
21
21
  type SchemaColumnDecl,
22
22
  type SchemaColumnInput,
23
23
  type SchemaRelationEntry,
24
+ type SchemaPolicyDecl,
24
25
  type SchemaRelationsDecl,
25
26
  type SchemaTableDecl,
26
27
  } from "./schema-decl.ts";
28
+ import { emitPgPolicySource, parseSqlPolicySpec } from "../../drivers/pg-rls.ts";
27
29
 
28
30
  /** Supported Drizzle dialects for domain schema emit (Postgres-family only). */
29
31
  export type SqlDialect = "postgres";
@@ -186,7 +188,12 @@ export function emitDrizzleSource(
186
188
  Object.values(t.columns).some((c) => c.sqlType === "integer"),
187
189
  );
188
190
  const pgIntImport = needsInteger ? "bigint" : null;
189
- const coreImport = `import { pgTable, text${pgIntImport ? `, ${pgIntImport}` : ""} } from "drizzle-orm/pg-core";`;
191
+ const needsPolicy = tables.some((t) => (t.policies?.length ?? 0) > 0);
192
+ const coreNames = ["pgTable", "text", pgIntImport, needsPolicy ? "pgPolicy" : null].filter(
193
+ (name): name is string => name !== null,
194
+ );
195
+ const coreImport = `import { ${coreNames.join(", ")} } from "drizzle-orm/pg-core";`;
196
+ const sqlImport = needsPolicy ? `import { sql } from "drizzle-orm";\n` : "";
190
197
 
191
198
  const needsId = tables.some((t) =>
192
199
  Object.values(t.columns).some((c) => c.defaultFnKind === "id"),
@@ -204,19 +211,14 @@ export function emitDrizzleSource(
204
211
  const relationsImport =
205
212
  relations.length > 0 ? `import { defineRelations } from "drizzle-orm";\n` : "";
206
213
 
207
- const blocks = tables.map((table) => {
208
- const lines = Object.values(table.columns).map(
209
- (col) => ` ${col.key}: ${emitColumnSource(col, dialect)},`,
210
- );
211
- const sqlName = schemaTableSqlName(table) ?? table.name;
212
- return `export const ${exportNameForTable(sqlName)} = pgTable(${JSON.stringify(sqlName)}, {\n${lines.join("\n")}\n});`;
213
- });
214
+ const blocks = tables.map((table) => emitTableBlock(table, dialect));
214
215
 
215
216
  const relationsBlock = emitRelationsSource(relations);
216
217
 
217
218
  return [
218
219
  GENERATED_SCHEMA_HEADER,
219
220
  coreImport,
221
+ sqlImport.trimEnd(),
220
222
  relationsImport.trimEnd(),
221
223
  helperImport.trimEnd(),
222
224
  "",
@@ -228,6 +230,40 @@ export function emitDrizzleSource(
228
230
  .join("\n");
229
231
  }
230
232
 
233
+ function emitTableBlock(table: SchemaTableDecl, dialect: SqlDialect): string {
234
+ const lines = Object.values(table.columns).map(
235
+ (col) => ` ${col.key}: ${emitColumnSource(col, dialect)},`,
236
+ );
237
+ const sqlName = schemaTableSqlName(table) ?? table.name;
238
+ const exportName = exportNameForTable(sqlName);
239
+ const cols = `{\n${lines.join("\n")}\n}`;
240
+ const policies = table.policies ?? [];
241
+ if (policies.length === 0) {
242
+ const ctor = table.rls === true ? "pgTable.withRLS" : "pgTable";
243
+ return `export const ${exportName} = ${ctor}(${JSON.stringify(sqlName)}, ${cols});`;
244
+ }
245
+ const extras = policies.map((policy) => ` ${emitPolicyExtra(policy, sqlName)},`).join("\n");
246
+ return `export const ${exportName} = pgTable(${JSON.stringify(sqlName)}, ${cols}, (_t) => [\n${extras}\n]);`;
247
+ }
248
+
249
+ function emitPolicyExtra(policy: SchemaPolicyDecl, table: string): string {
250
+ const roles = Array.isArray(policy.to)
251
+ ? policy.to.join(", ")
252
+ : typeof policy.to === "string"
253
+ ? policy.to
254
+ : "public";
255
+ const spec = parseSqlPolicySpec({
256
+ name: policy.name,
257
+ table,
258
+ as: policy.as,
259
+ to: roles,
260
+ for: policy.for,
261
+ using: policy.using,
262
+ withCheck: policy.withCheck,
263
+ });
264
+ return emitPgPolicySource(spec).replaceAll("\n", "\n ");
265
+ }
266
+
231
267
  function exportNameForTable(name: string): string {
232
268
  // Keep simple SQL names as-is; camelCase odd names.
233
269
  if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) return name;
@@ -7,7 +7,7 @@
7
7
 
8
8
  import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test";
9
9
  import { integer, pgTable, text } from "drizzle-orm/pg-core";
10
- import { createInsertSchema, createSelectSchema } from "drizzle-zod";
10
+ import { createInsertSchema, createSelectSchema } from "drizzle-orm/zod";
11
11
  import { z } from "zod";
12
12
  import { pgliteDriver } from "../../drivers/pglite.ts";
13
13
  import type { SqlConnection } from "../../drivers/types.ts";
@@ -0,0 +1,95 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type { Manifest } from "../../manifest/types.ts";
3
+ import { firstPolicyOrPublic, resolveRlsIdentity, rlsIdentityFromAuth } from "./rls-identity.ts";
4
+
5
+ const MANIFEST: Manifest = {
6
+ oke: "1.0",
7
+ app: "app",
8
+ gates: {
9
+ public: { kind: "policy" },
10
+ member: { kind: "policy", description: "Signed-in" },
11
+ "booking:create": { kind: "policy", scopes: ["booking:create"] },
12
+ "rate:x": { kind: "rate", max: 1, per: "1m" },
13
+ },
14
+ };
15
+
16
+ describe("resolveRlsIdentity", () => {
17
+ test("operator omit and bypass skip the stamp", () => {
18
+ expect(resolveRlsIdentity({})).toBeNull();
19
+ expect(resolveRlsIdentity({ asGate: "member", bypass: true })).toBeNull();
20
+ });
21
+
22
+ test("public and policy-only leave userId empty", () => {
23
+ expect(resolveRlsIdentity({ asGate: "public", manifest: MANIFEST })).toEqual({
24
+ gate: "public",
25
+ userId: "",
26
+ scopes: [],
27
+ });
28
+ expect(resolveRlsIdentity({ asGate: "member", manifest: MANIFEST })).toEqual({
29
+ gate: "member",
30
+ userId: "",
31
+ scopes: ["member"],
32
+ });
33
+ expect(resolveRlsIdentity({ asGate: "booking:create", manifest: MANIFEST })).toEqual({
34
+ gate: "booking:create",
35
+ userId: "",
36
+ scopes: ["booking:create"],
37
+ });
38
+ });
39
+
40
+ test("seeded user keeps real id and mapped gate", () => {
41
+ expect(
42
+ resolveRlsIdentity({
43
+ asUserId: "u1",
44
+ asGate: "member",
45
+ identities: [{ id: "u1", scopes: ["member"], status: "active" }],
46
+ manifest: MANIFEST,
47
+ }),
48
+ ).toEqual({ gate: "member", userId: "u1", scopes: ["member"] });
49
+ });
50
+
51
+ test("refuses rate gates", () => {
52
+ expect(resolveRlsIdentity({ asGate: "rate:x", manifest: MANIFEST })).toBeNull();
53
+ });
54
+ });
55
+
56
+ describe("rlsIdentityFromAuth", () => {
57
+ test("uses the first policy/public and live userId", () => {
58
+ expect(
59
+ rlsIdentityFromAuth({
60
+ userId: "u1",
61
+ scopes: new Set(["member", "booking:create"]),
62
+ gateNames: ["member", "booking:create"],
63
+ }),
64
+ ).toEqual({
65
+ gate: "member",
66
+ userId: "u1",
67
+ scopes: ["member", "booking:create"],
68
+ });
69
+ });
70
+
71
+ test("skips cron (no gates) and operator bypass", () => {
72
+ expect(
73
+ rlsIdentityFromAuth({
74
+ userId: null,
75
+ scopes: [],
76
+ gateNames: [],
77
+ }),
78
+ ).toBeNull();
79
+ expect(
80
+ rlsIdentityFromAuth({
81
+ userId: "op",
82
+ scopes: [],
83
+ gateNames: ["member"],
84
+ operator: true,
85
+ }),
86
+ ).toBeNull();
87
+ });
88
+ });
89
+
90
+ describe("firstPolicyOrPublic", () => {
91
+ test("skips rate ids", () => {
92
+ expect(firstPolicyOrPublic(["rate:x", "member"])).toBe("member");
93
+ expect(firstPolicyOrPublic(["public"])).toBe("public");
94
+ });
95
+ });
@@ -0,0 +1,116 @@
1
+ /**
2
+ * One Gate → RLS identity bag for Console, Call API, and `fx.store`.
3
+ */
4
+
5
+ import type { Manifest } from "../../manifest/types.ts";
6
+ import type { RlsIdentity } from "../../drivers/pg-rls.ts";
7
+
8
+ export type { RlsIdentity };
9
+
10
+ /** Seeded Console identity used by invoke-as / Gate picker. */
11
+ export type RlsIdentityRow = {
12
+ readonly id: string;
13
+ readonly scopes: readonly string[];
14
+ readonly status?: string;
15
+ };
16
+
17
+ /** Input to {@link resolveRlsIdentity}. */
18
+ export type ResolveRlsIdentityInput = {
19
+ readonly asGate?: string | null;
20
+ readonly asUserId?: string | null;
21
+ readonly bypass?: boolean;
22
+ readonly identities?: readonly RlsIdentityRow[];
23
+ readonly manifest?: Manifest | null;
24
+ };
25
+
26
+ /**
27
+ * Console / Call API bag. Operator and `bypass` return `null` (no stamp).
28
+ *
29
+ * @param input - Picker + Manifest
30
+ */
31
+ export function resolveRlsIdentity(input: ResolveRlsIdentityInput): RlsIdentity | null {
32
+ if (input.bypass === true) return null;
33
+ const asGate = input.asGate?.trim() || null;
34
+ const asUserId = input.asUserId?.trim() || null;
35
+ if (!asGate && !asUserId) return null;
36
+
37
+ if (asUserId) {
38
+ const identity = (input.identities ?? []).find(
39
+ (row) => row.id === asUserId && row.status !== "disabled",
40
+ );
41
+ if (!identity) return null;
42
+ const gate = asGate ?? policyGateForScopes(identity.scopes, input.manifest);
43
+ if (!gate) return null;
44
+ return { gate, userId: identity.id, scopes: [...identity.scopes] };
45
+ }
46
+
47
+ if (!asGate) return null;
48
+ if (asGate === "public") {
49
+ return { gate: "public", userId: "", scopes: [] };
50
+ }
51
+
52
+ const declared = input.manifest?.gates?.[asGate];
53
+ if (!declared || declared.kind === "rate" || asGate.startsWith("rate:")) return null;
54
+ const scopes = declared.scopes && declared.scopes.length > 0 ? declared.scopes : [asGate];
55
+ return { gate: asGate, userId: "", scopes: [...scopes] };
56
+ }
57
+
58
+ /**
59
+ * Live HTTP / resource-mount bag after Gate passes.
60
+ *
61
+ * @param input - `fx.auth` + trigger gate names
62
+ */
63
+ export function rlsIdentityFromAuth(input: {
64
+ readonly userId: string | null;
65
+ readonly scopes: ReadonlySet<string> | readonly string[];
66
+ readonly gateNames: readonly string[];
67
+ readonly bypass?: boolean;
68
+ readonly operator?: boolean;
69
+ }): RlsIdentity | null {
70
+ if (input.bypass === true || input.operator === true) return null;
71
+ const gate = firstPolicyOrPublic(input.gateNames);
72
+ if (!gate) return null;
73
+ const scopes = Array.isArray(input.scopes) ? input.scopes : [...input.scopes];
74
+ return {
75
+ gate,
76
+ userId: input.userId ?? "",
77
+ scopes,
78
+ };
79
+ }
80
+
81
+ /**
82
+ * First policy/public name (rate ids skipped).
83
+ *
84
+ * @param names - Trigger gate names
85
+ */
86
+ export function firstPolicyOrPublic(names: readonly string[]): string | null {
87
+ for (const name of names) {
88
+ if (name.startsWith("rate:")) continue;
89
+ if (name.length > 0) return name;
90
+ }
91
+ return null;
92
+ }
93
+
94
+ /**
95
+ * First Manifest policy implied by scopes (name, then declared roles).
96
+ *
97
+ * @param scopes - Identity scopes
98
+ * @param manifest - Current Manifest
99
+ */
100
+ export function policyGateForScopes(
101
+ scopes: readonly string[],
102
+ manifest: Manifest | null | undefined,
103
+ ): string | null {
104
+ const gates = manifest?.gates ?? {};
105
+ for (const scope of scopes) {
106
+ const gate = gates[scope];
107
+ if (gate && gate.kind !== "rate") return scope;
108
+ }
109
+ for (const scope of scopes) {
110
+ for (const [name, gate] of Object.entries(gates)) {
111
+ if (gate.kind === "rate") continue;
112
+ if ((gate.roles ?? []).includes(scope)) return name;
113
+ }
114
+ }
115
+ return null;
116
+ }
@@ -24,6 +24,7 @@ import type {
24
24
  VectorIndexStore,
25
25
  } from "../../drivers/types.ts";
26
26
  import { indexDriverNeedsSql } from "../../drivers/types.ts";
27
+ import type { RlsIdentity } from "../../drivers/pg-rls.ts";
27
28
  import { buildClassificationMap, type MaskRowsOptions } from "./classify.ts";
28
29
  import {
29
30
  createStoreCache,
@@ -112,6 +113,8 @@ export interface StoreInvokeContext {
112
113
  readonly effects: Effects;
113
114
  /** Reveal PII (gated elsewhere). */
114
115
  readonly revealPii?: boolean;
116
+ /** Gate identity for RLS (`oke.gate` / `oke.user` / `oke.has_scope`). */
117
+ readonly rls?: RlsIdentity;
115
118
  }
116
119
 
117
120
  /** Unified handle returned for any facet. */
@@ -236,6 +239,13 @@ export interface StoreRuntime {
236
239
  * @param ref - `kv:name`
237
240
  */
238
241
  kvNamespace(ref: ResourceRef): Promise<KvNamespace>;
242
+ /**
243
+ * Shared primary SQL connection (`sharedSqlConn`).
244
+ *
245
+ * Host auth persist (`oke_api_keys`) and durable KV borrow this same
246
+ * connection — never a second open against `store.sql()` / `DATABASE_URL`.
247
+ */
248
+ primarySql(): Promise<SqlConnection | undefined>;
239
249
  /**
240
250
  * Register a declaration (usually done at module load).
241
251
  *
@@ -347,6 +357,7 @@ export function createStoreRuntime(options: CreateStoreRuntimeOptions): StoreRun
347
357
  revealPii: ctx.revealPii,
348
358
  routedRole: target.role,
349
359
  domainDdl: options.domainDdl ?? "ensure",
360
+ ...(ctx.rls ? { rls: ctx.rls } : {}),
350
361
  });
351
362
  }
352
363
 
@@ -541,6 +552,13 @@ export function createStoreRuntime(options: CreateStoreRuntimeOptions): StoreRun
541
552
  if (!ns) throw new Error(`Unknown kv ref: ${ref}`);
542
553
  return ns;
543
554
  },
555
+ async primarySql() {
556
+ const sqlDriver = options.drivers.sql;
557
+ if (!sqlDriver) return undefined;
558
+ const firstSql = Object.values(options.sql ?? [])[0];
559
+ const url = options.sqlUrl ?? firstSql?.primary.url;
560
+ return sharedSqlConn(sqlDriver, "primary", { url });
561
+ },
544
562
  onWriteEffects(effects) {
545
563
  return cache.invalidateFromEffects(effects);
546
564
  },
@@ -260,3 +260,40 @@ describe("emitDrizzleSource — references + relations (Linkly-shaped)", () => {
260
260
  expect(relations.config.daily?.link?.target).toBe("links");
261
261
  });
262
262
  });
263
+
264
+ describe("store.schema RLS extras", () => {
265
+ test("helpers stamp rls + named policies", () => {
266
+ const bookings = store.schema.table(
267
+ "bookings",
268
+ {
269
+ id: field.text().primaryKey(),
270
+ owner: field.text().notNull(),
271
+ },
272
+ [
273
+ store.schema.policy.gate("member", { for: "select" }),
274
+ store.schema.policy.owner("owner", { for: "all" }),
275
+ store.schema.policy.scope("booking:create", { for: "insert" }),
276
+ ],
277
+ );
278
+ expect(bookings.rls).toBe(true);
279
+ expect(bookings.policies?.map((p) => p.name)).toEqual([
280
+ "gate_member_select",
281
+ "owner_owner_all",
282
+ "scope_booking_create_insert",
283
+ ]);
284
+ const src = emitDrizzleSource([bookings], "postgres");
285
+ expect(src).toContain("pgPolicy");
286
+ expect(src).toContain("oke.gate() = ");
287
+ expect(src).toContain("oke.user()");
288
+ expect(src).toContain("oke.has_scope(");
289
+ });
290
+
291
+ test("rls() without policies emits pgTable.withRLS", () => {
292
+ const notes = store.schema.table("notes", { id: field.text().primaryKey() }, [
293
+ store.schema.rls(),
294
+ ]);
295
+ expect(notes.rls).toBe(true);
296
+ expect(notes.policies).toBeUndefined();
297
+ expect(emitDrizzleSource([notes], "postgres")).toContain("pgTable.withRLS");
298
+ });
299
+ });