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
@@ -2,15 +2,27 @@
2
2
  * Console helpers for RLS policy create (SQL preview + templates).
3
3
  */
4
4
 
5
+ import type {
6
+ ColumnClassification,
7
+ DeclaredColumn,
8
+ Manifest,
9
+ Table,
10
+ } from "../../../../../../manifest/types.ts";
5
11
  import {
6
12
  buildCreatePolicySql,
7
13
  buildRowSecuritySql,
8
14
  parseSqlPolicySpec,
9
15
  quotePgIdent,
16
+ formatPolicyRole,
17
+ emitPgPolicySource,
18
+ emitStoreSchemaPolicySource,
10
19
  type SqlPolicyBehavior,
11
20
  type SqlPolicyCommand,
12
21
  type SqlPolicySpec,
13
22
  } from "../../../../../../drivers/pg-rls.ts";
23
+ import { fkColumnStem } from "./schema-graph.ts";
24
+
25
+ export { emitPgPolicySource, emitStoreSchemaPolicySource };
14
26
 
15
27
  export type { SqlPolicyBehavior, SqlPolicyCommand, SqlPolicySpec };
16
28
  export { buildCreatePolicySql, buildRowSecuritySql, parseSqlPolicySpec };
@@ -28,7 +40,8 @@ export type RlsPolicyTemplate = {
28
40
  readonly withCheck?: string;
29
41
  };
30
42
 
31
- const OKE_GATE = "current_setting('oke.gate', true)";
43
+ const OKE_GATE = "oke.gate()";
44
+ const OKE_USER = "oke.user()";
32
45
 
33
46
  /** Common Postgres RLS starters — OKE Gate / owner / join, no vendor auth helpers. */
34
47
  export const RLS_POLICY_TEMPLATES: readonly RlsPolicyTemplate[] = [
@@ -52,14 +65,14 @@ export const RLS_POLICY_TEMPLATES: readonly RlsPolicyTemplate[] = [
52
65
  id: "owner-read",
53
66
  name: "owner_read",
54
67
  title: "Enable read for owners",
55
- detail: "SELECT when a text owner column matches current_user.",
68
+ detail: "SELECT when a text owner column matches oke.user().",
56
69
  command: "SELECT",
57
- using: "owner = current_user",
70
+ using: `owner = ${OKE_USER}`,
58
71
  },
59
72
  {
60
73
  id: "gate-read",
61
74
  name: "gate_read",
62
- title: "Enable read when oke.gate matches",
75
+ title: "Enable read when oke.gate() matches",
63
76
  detail: `SELECT when ${OKE_GATE} equals a Gate name (pair Policy & scope).`,
64
77
  command: "SELECT",
65
78
  using: `${OKE_GATE} = 'member'`,
@@ -70,8 +83,7 @@ export const RLS_POLICY_TEMPLATES: readonly RlsPolicyTemplate[] = [
70
83
  title: "Enable read via a related table",
71
84
  detail: "SELECT with EXISTS against members — membership joins, not a column on this table.",
72
85
  command: "SELECT",
73
- using:
74
- "exists (select 1 from members m where m.user_id = current_user and m.team_id = team_id)",
86
+ using: "exists (select 1 from members m where m.user_id = oke.user() and m.team_id = team_id)",
75
87
  },
76
88
  {
77
89
  id: "insert-all",
@@ -85,14 +97,14 @@ export const RLS_POLICY_TEMPLATES: readonly RlsPolicyTemplate[] = [
85
97
  id: "insert-owner",
86
98
  name: "insert_owner",
87
99
  title: "Enable insert for owners",
88
- detail: "INSERT WITH CHECK so new rows set owner to current_user.",
100
+ detail: "INSERT WITH CHECK so new rows set owner to oke.user().",
89
101
  command: "INSERT",
90
- withCheck: "owner = current_user",
102
+ withCheck: `owner = ${OKE_USER}`,
91
103
  },
92
104
  {
93
105
  id: "insert-gate",
94
106
  name: "insert_gate",
95
- title: "Enable insert when oke.gate matches",
107
+ title: "Enable insert when oke.gate() matches",
96
108
  detail: `INSERT WITH CHECK when ${OKE_GATE} equals a Gate name.`,
97
109
  command: "INSERT",
98
110
  withCheck: `${OKE_GATE} = 'member'`,
@@ -101,15 +113,15 @@ export const RLS_POLICY_TEMPLATES: readonly RlsPolicyTemplate[] = [
101
113
  id: "update-owner",
102
114
  name: "update_owner",
103
115
  title: "Enable update for owners",
104
- detail: "UPDATE own rows. USING and WITH CHECK both require owner = current_user.",
116
+ detail: "UPDATE own rows. USING and WITH CHECK both require owner = oke.user().",
105
117
  command: "UPDATE",
106
- using: "owner = current_user",
107
- withCheck: "owner = current_user",
118
+ using: `owner = ${OKE_USER}`,
119
+ withCheck: `owner = ${OKE_USER}`,
108
120
  },
109
121
  {
110
122
  id: "update-gate",
111
123
  name: "update_gate",
112
- title: "Enable update when oke.gate matches",
124
+ title: "Enable update when oke.gate() matches",
113
125
  detail: `UPDATE when ${OKE_GATE} equals a Gate name.`,
114
126
  command: "UPDATE",
115
127
  using: `${OKE_GATE} = 'member'`,
@@ -119,9 +131,9 @@ export const RLS_POLICY_TEMPLATES: readonly RlsPolicyTemplate[] = [
119
131
  id: "delete-owner",
120
132
  name: "delete_owner",
121
133
  title: "Enable delete for owners",
122
- detail: "DELETE when a text owner column matches current_user.",
134
+ detail: "DELETE when a text owner column matches oke.user().",
123
135
  command: "DELETE",
124
- using: "owner = current_user",
136
+ using: `owner = ${OKE_USER}`,
125
137
  },
126
138
  {
127
139
  id: "deny-delete",
@@ -135,15 +147,15 @@ export const RLS_POLICY_TEMPLATES: readonly RlsPolicyTemplate[] = [
135
147
  id: "all-owner",
136
148
  name: "owner_all",
137
149
  title: "Enable all commands for owners",
138
- detail: "ALL commands when owner = current_user (USING and WITH CHECK).",
150
+ detail: "ALL commands when owner = oke.user() (USING and WITH CHECK).",
139
151
  command: "ALL",
140
- using: "owner = current_user",
141
- withCheck: "owner = current_user",
152
+ using: `owner = ${OKE_USER}`,
153
+ withCheck: `owner = ${OKE_USER}`,
142
154
  },
143
155
  {
144
156
  id: "all-gate",
145
157
  name: "gate_all",
146
- title: "Enable all commands when oke.gate matches",
158
+ title: "Enable all commands when oke.gate() matches",
147
159
  detail: `ALL commands when ${OKE_GATE} equals a Gate name.`,
148
160
  command: "ALL",
149
161
  using: `${OKE_GATE} = 'member'`,
@@ -153,7 +165,7 @@ export const RLS_POLICY_TEMPLATES: readonly RlsPolicyTemplate[] = [
153
165
  id: "restrictive-gate",
154
166
  name: "restrictive_gate",
155
167
  title: "Restrictive extra Gate check",
156
- detail: "RESTRICTIVE policy ANDs with permissive ones. Requires oke.gate to be set.",
168
+ detail: "RESTRICTIVE policy ANDs with permissive ones. Requires oke.gate() to be set.",
157
169
  command: "ALL",
158
170
  behavior: "RESTRICTIVE",
159
171
  using: `${OKE_GATE} is not null`,
@@ -262,17 +274,207 @@ export function rlsGateActionsForMode(mode: RlsGateMode): readonly string[] {
262
274
  * @param spec - Policy fields
263
275
  * @param enableRls - Also enable RLS on the table
264
276
  */
277
+ /**
278
+ * Code-dock sources: `store.schema.policy` plus Drizzle `pgPolicy`.
279
+ *
280
+ * @param spec - Policy fields
281
+ */
282
+ export function rlsPolicyCodeSource(spec: SqlPolicySpec): string {
283
+ return `${emitStoreSchemaPolicySource(spec)}\n\n${emitPgPolicySource(spec)}`;
284
+ }
285
+
286
+ /**
287
+ * USING / WITH CHECK from Advanced Gate picks (never Postgres `TO`).
288
+ *
289
+ * @param gates - Selected policy / public / scope names
290
+ */
291
+ export function rlsGatePredicateSql(gates: readonly string[]): string {
292
+ if (gates.length === 0) return "true";
293
+ return gates
294
+ .map((gate) => {
295
+ const lit = `'${gate.replaceAll("'", "''")}'`;
296
+ return gate.includes(":") ? `oke.has_scope(${lit})` : `oke.gate() = ${lit}`;
297
+ })
298
+ .join(" OR ");
299
+ }
300
+
265
301
  export function rlsPolicyPreviewSql(spec: SqlPolicySpec, enableRls: boolean): string {
266
302
  const create = formatCreatePolicyPreview(spec);
267
303
  if (!enableRls) return `${create};`;
268
304
  return `${buildRowSecuritySql(spec.table, true)};\n${create};`;
269
305
  }
270
306
 
307
+ /** SQL names owner templates bind to, first match wins. */
308
+ const OWNER_SQL_NAMES = [
309
+ "owner",
310
+ "owner_id",
311
+ "owner_email",
312
+ "user_id",
313
+ "created_by",
314
+ "creator",
315
+ "creator_id",
316
+ "creator_email",
317
+ "author",
318
+ "author_id",
319
+ "author_email",
320
+ ] as const;
321
+
322
+ /** One Manifest column for the identity select (PK / FK / unique marks). */
323
+ export type RlsTableColumn = {
324
+ readonly sqlName: string;
325
+ readonly primaryKey: boolean;
326
+ readonly foreignKey: boolean;
327
+ readonly unique: boolean;
328
+ /** FK inferred from `*_id` when Manifest omitted `.references()`. */
329
+ readonly inferred: boolean;
330
+ };
331
+
332
+ /**
333
+ * Manifest columns with PK / FK / unique marks (`sqlName`, else snake_case).
334
+ *
335
+ * @param manifest - Current Manifest, or null
336
+ * @param storeRef - Store effect ref (`sql:db`)
337
+ * @param table - Table name
338
+ */
339
+ export function rlsTableColumns(
340
+ manifest: Manifest | null,
341
+ storeRef: string,
342
+ table: string,
343
+ ): readonly RlsTableColumn[] {
344
+ const cut = storeRef.indexOf(":");
345
+ const storeName = cut >= 0 ? storeRef.slice(cut + 1) : storeRef;
346
+ const cols = manifest?.stores?.[storeName]?.tables?.[table]?.columns ?? {};
347
+ return Object.entries(cols).map(([key, col]) => {
348
+ const sqlName = sqlNameOf(key, col);
349
+ const declared = isDeclaredColumn(col) ? col : null;
350
+ const primaryKey = declared?.primaryKey === true;
351
+ const declaredFk = declared?.references?.table !== undefined;
352
+ const inferred = !declaredFk && !primaryKey && fkColumnStem(sqlName) !== null;
353
+ return {
354
+ sqlName,
355
+ primaryKey,
356
+ foreignKey: declaredFk || inferred,
357
+ unique: declared?.unique === true && !primaryKey,
358
+ inferred,
359
+ };
360
+ });
361
+ }
362
+
363
+ /**
364
+ * SQL column names on a Manifest table (`sqlName`, else snake_case key).
365
+ *
366
+ * @param manifest - Current Manifest, or null
367
+ * @param storeRef - Store effect ref (`sql:db`)
368
+ * @param table - Table name
369
+ */
370
+ export function rlsTableSqlColumns(
371
+ manifest: Manifest | null,
372
+ storeRef: string,
373
+ table: string,
374
+ ): readonly string[] {
375
+ return rlsTableColumns(manifest, storeRef, table).map((col) => col.sqlName);
376
+ }
377
+
378
+ function isDeclaredColumn(col: DeclaredColumn | ColumnClassification): col is DeclaredColumn {
379
+ return (
380
+ "type" in col ||
381
+ "nullable" in col ||
382
+ "primaryKey" in col ||
383
+ "unique" in col ||
384
+ "sqlName" in col ||
385
+ "description" in col ||
386
+ "references" in col ||
387
+ "default" in col
388
+ );
389
+ }
390
+
391
+ /**
392
+ * Best owner-like SQL column for owner templates, or null.
393
+ *
394
+ * @param sqlNames - Table SQL column names
395
+ */
396
+ export function rlsOwnerColumn(sqlNames: readonly string[]): string | null {
397
+ const byLower = new Map(sqlNames.map((name) => [name.toLowerCase(), name]));
398
+ for (const name of OWNER_SQL_NAMES) {
399
+ const hit = byLower.get(name);
400
+ if (hit) return hit;
401
+ }
402
+ return null;
403
+ }
404
+
405
+ /**
406
+ * Replace the generic `owner` placeholder with the table's owner column.
407
+ *
408
+ * @param expr - USING / WITH CHECK body
409
+ * @param ownerCol - SQL column to bind
410
+ */
411
+ export function rlsBindOwnerExpr(expr: string, ownerCol: string): string {
412
+ return expr.replace(/\bowner\b/g, ownerCol);
413
+ }
414
+
415
+ /**
416
+ * True when the expression still uses the unbound `owner` placeholder.
417
+ *
418
+ * @param expr - USING / WITH CHECK body
419
+ */
420
+ export function rlsExprNeedsOwnerColumn(expr: string): boolean {
421
+ return /\bowner\b/.test(expr);
422
+ }
423
+
424
+ /**
425
+ * True when a template compares a row to `oke.user()` via `owner`.
426
+ *
427
+ * @param tpl - Policy template
428
+ */
429
+ export function rlsTemplateUsesOwner(tpl: Pick<RlsPolicyTemplate, "using" | "withCheck">): boolean {
430
+ return rlsExprNeedsOwnerColumn(tpl.using ?? "") || rlsExprNeedsOwnerColumn(tpl.withCheck ?? "");
431
+ }
432
+
433
+ /**
434
+ * True when the expression is an owner / `oke.user()` identity check.
435
+ *
436
+ * @param expr - USING / WITH CHECK body
437
+ */
438
+ export function rlsExprUsesUserIdentity(expr: string): boolean {
439
+ return rlsExprNeedsOwnerColumn(expr) || /oke\.user\(\)/.test(expr);
440
+ }
441
+
442
+ /**
443
+ * Rewrite `owner` or a previous identity column to `to`.
444
+ *
445
+ * @param expr - USING / WITH CHECK body
446
+ * @param from - Previous SQL column (or `owner`)
447
+ * @param to - Selected SQL column
448
+ */
449
+ export function rlsRewriteIdentityColumn(expr: string, from: string, to: string): string {
450
+ if (!to || from === to) return expr;
451
+ let next = expr;
452
+ if (rlsExprNeedsOwnerColumn(next)) next = rlsBindOwnerExpr(next, to);
453
+ if (from !== "" && from !== "owner") {
454
+ next = next.replace(new RegExp(`\\b${escapeIdent(from)}\\b`, "g"), to);
455
+ }
456
+ return next;
457
+ }
458
+
459
+ function escapeIdent(name: string): string {
460
+ return name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
461
+ }
462
+
463
+ function sqlNameOf(key: string, col: NonNullable<Table["columns"]>[string]): string {
464
+ if (col && typeof col === "object" && "sqlName" in col && typeof col.sqlName === "string") {
465
+ return col.sqlName;
466
+ }
467
+ return key
468
+ .replace(/([a-z0-9])([A-Z])/g, "$1_$2")
469
+ .replace(/([A-Z])([A-Z][a-z])/g, "$1_$2")
470
+ .toLowerCase();
471
+ }
472
+
271
473
  function formatCreatePolicyPreview(spec: SqlPolicySpec): string {
272
474
  const roles =
273
475
  spec.roles.length === 0
274
476
  ? "public"
275
- : spec.roles.map((role) => (role === "public" ? "public" : quotePgIdent(role))).join(", ");
477
+ : spec.roles.map((role) => formatPolicyRole(role)).join(", ");
276
478
  const lines = [
277
479
  `CREATE POLICY ${quotePgIdent(spec.name)}`,
278
480
  ` ON ${quotePgIdent(spec.table)}`,
@@ -35,6 +35,8 @@ export type SchemaGraphTable = {
35
35
  readonly storeName: string;
36
36
  readonly storeRef: string;
37
37
  readonly columns: readonly SchemaGraphColumn[];
38
+ /** Manifest `rls` or any declared policies. */
39
+ readonly rls?: boolean;
38
40
  };
39
41
 
40
42
  /** Custom data on a schema-visualizer table node. */
@@ -138,11 +140,13 @@ export function schemaGraphTables(
138
140
  if (store.facet !== "sql") continue;
139
141
  for (const child of store.children) {
140
142
  if (isSqlCatalogChild(child)) continue;
143
+ const declared = manifest?.stores?.[store.name]?.tables?.[child.name];
141
144
  out.push({
142
145
  id: child.effectRef,
143
146
  name: child.name,
144
147
  storeName: store.name,
145
148
  storeRef: store.ref,
149
+ rls: declared?.rls === true || Object.keys(declared?.policies ?? {}).length > 0,
146
150
  columns: columnsForChild(
147
151
  store,
148
152
  child.name,
@@ -6,6 +6,7 @@ import {
6
6
  isSqlExtensionChild,
7
7
  isSqlPolicyChild,
8
8
  storeChildLabel,
9
+ storeChildRlsPolicyCount,
9
10
  storeChildShowsRls,
10
11
  } from "./sql-catalog.ts";
11
12
 
@@ -48,6 +49,29 @@ describe("sql catalog children", () => {
48
49
  expect(storeChildShowsRls(child("policies", "policy"))).toBe(false);
49
50
  });
50
51
 
52
+ test("RLS policy count prefers the store-list stamp", () => {
53
+ const stamped = { ...child("bookings", "table"), rls: true, rlsPolicyCount: 2 };
54
+ expect(storeChildRlsPolicyCount(stamped, null, "db")).toBe(2);
55
+ expect(
56
+ storeChildRlsPolicyCount(
57
+ child("bookings", "table"),
58
+ {
59
+ oke: "1.0",
60
+ app: "t",
61
+ stores: {
62
+ db: {
63
+ facet: "sql",
64
+ tables: {
65
+ bookings: { policies: { mine: { using: "true" }, team: { using: "true" } } },
66
+ },
67
+ },
68
+ },
69
+ },
70
+ "db",
71
+ ),
72
+ ).toBe(2);
73
+ });
74
+
51
75
  test("splits tables from catalog folders", () => {
52
76
  const grouped = groupSqlChildren([
53
77
  child("bookings", "table"),
@@ -3,6 +3,7 @@
3
3
  * extensions / RLS policies).
4
4
  */
5
5
 
6
+ import type { Manifest } from "../../../../../../manifest/types.ts";
6
7
  import type { StoreListChild } from "@/client.ts";
7
8
 
8
9
  /** Catalog kind on a SQL store child. */
@@ -64,6 +65,24 @@ export function storeChildShowsRls(child: StoreListChild): boolean {
64
65
  return child.kind === "table" && typeof child.rls === "boolean";
65
66
  }
66
67
 
68
+ /**
69
+ * Policy count for the resource-header RLS chip.
70
+ *
71
+ * Prefers the store-list stamp; falls back to Manifest `table.policies`.
72
+ *
73
+ * @param child - Store-list child
74
+ * @param manifest - Current Manifest, or null
75
+ * @param storeName - Manifest store key
76
+ */
77
+ export function storeChildRlsPolicyCount(
78
+ child: StoreListChild,
79
+ manifest: Manifest | null,
80
+ storeName: string,
81
+ ): number {
82
+ if (typeof child.rlsPolicyCount === "number") return child.rlsPolicyCount;
83
+ return Object.keys(manifest?.stores?.[storeName]?.tables?.[child.name]?.policies ?? {}).length;
84
+ }
85
+
67
86
  /**
68
87
  * Tree / header label for a child (`Indexes` instead of `indexes`).
69
88
  *
@@ -18,6 +18,7 @@ import {
18
18
  storeTreeIsOpen,
19
19
  storeTreeOpenKeys,
20
20
  isSingletonStoreLeaf,
21
+ resourceHeaderChildRef,
21
22
  storeTreeTablesKey,
22
23
  toggleHiddenFacet,
23
24
  visibleFacetBands,
@@ -143,6 +144,11 @@ describe("store-tree", () => {
143
144
  expect(storeTreeAncestorKeys(STORES, "index:comments")).toEqual(["facet:index"]);
144
145
  });
145
146
 
147
+ test("resource header drops the child ref when it repeats the store", () => {
148
+ expect(resourceHeaderChildRef("kv:reminders", "kv:reminders")).toBeNull();
149
+ expect(resourceHeaderChildRef("sql:db", "sql:shipments")).toBe("sql:shipments");
150
+ });
151
+
146
152
  test("ancestor keys open the facet, store, and Tables folder for a table", () => {
147
153
  expect(storeTreeAncestorKeys(STORES, "sql:shipments")).toEqual([
148
154
  "facet:sql",
@@ -6,6 +6,8 @@ import { DatabaseIcon, File01Icon, Key01Icon, Search01Icon } from "@hugeicons/co
6
6
  import type { StoreFacet, StoreListChild, StoreListStore } from "@/client.ts";
7
7
  import type { ElementHugeIcon } from "@/lib/element-icons.ts";
8
8
  import { isStoreFacet, STORE_FACETS } from "./parse-resource-ref.ts";
9
+
10
+ export { STORE_FACETS };
9
11
  import { isSqlCatalogChild, storeChildLabel } from "./sql-catalog.ts";
10
12
 
11
13
  /** Facet band label for the left explorer. */
@@ -153,6 +155,17 @@ export function isSingletonStoreLeaf(store: StoreListStore): boolean {
153
155
  return store.children.length === 1 && only !== undefined && only.name === store.name;
154
156
  }
155
157
 
158
+ /**
159
+ * Child ref in the resource header. Null when it repeats the store
160
+ * (`kv:reminders · kv:reminders`).
161
+ *
162
+ * @param storeRef - Store identity (`kv:reminders`)
163
+ * @param effectRef - Selected child (`kv:reminders` or `sql:tasks`)
164
+ */
165
+ export function resourceHeaderChildRef(storeRef: string, effectRef: string): string | null {
166
+ return storeRef === effectRef ? null : effectRef;
167
+ }
168
+
156
169
  /**
157
170
  * Open-state key for a facet band in {@link StoreTree}.
158
171
  *
@@ -60,9 +60,10 @@ import { kvSetPatch, parseKvQuery } from "../lib/kv-query.ts";
60
60
  import {
61
61
  defaultKvQuery,
62
62
  defaultSqlQuery,
63
- firstChildName,
63
+ randomChildName,
64
64
  isConsoleAuthStore,
65
65
  pickQueryStore,
66
+ reconcileDefaultQuery,
66
67
  } from "../lib/query-defaults.ts";
67
68
  import {
68
69
  addQueryTab,
@@ -138,10 +139,12 @@ export function QueryConsole({
138
139
  );
139
140
  const store = ofFacet.find((s) => s.ref === storeRef) ?? pickQueryStore(ofFacet, facet, null);
140
141
  const seed = seedEditor(store, facet, selectedEffectRef);
142
+ const childNames = store?.children.map((child) => child.name) ?? [];
141
143
 
142
- const [tabs, setTabs] = useState<readonly QueryTab[]>(
143
- () => loadQueryTabs(facet) ?? [{ id: "q_1", title: "Query 1", text: seed }],
144
- );
144
+ const [tabs, setTabs] = useState<readonly QueryTab[]>(() => {
145
+ const loaded = loadQueryTabs(facet) ?? [{ id: "q_1", title: "Query 1", text: seed }];
146
+ return reconcileLoadedTabs(loaded, childNames, facet);
147
+ });
145
148
  const [activeId, setActiveId] = useState(() => tabs[0]?.id ?? "q_1");
146
149
  const active = tabs.find((t) => t.id === activeId) ?? tabs[0];
147
150
  const text = active?.text ?? seed;
@@ -153,7 +156,10 @@ export function QueryConsole({
153
156
  const [kvPending, setKvPending] = useState(false);
154
157
  const [schemaOpen, setSchemaOpen] = useState(false);
155
158
  const [piiMasked, setPiiMasked] = useState(true);
156
- const [asGate, setAsGate] = useState<string | null>(null);
159
+ const [rlsAs, setRlsAs] = useState<{ asGate: string | null; asUserId: string | null }>({
160
+ asGate: null,
161
+ asUserId: null,
162
+ });
157
163
  const [resultsOpen, setResultsOpen] = useState(true);
158
164
  const [history, setHistory] = useState<readonly QueryHistoryEntry[]>(() =>
159
165
  loadQueryHistory(facet),
@@ -194,6 +200,13 @@ export function QueryConsole({
194
200
  setStoreRef((prev) => (ofFacet.some((s) => s.ref === prev) ? prev : next.ref));
195
201
  }, [stores, facet, selectedEffectRef, ofFacet]);
196
202
 
203
+ const childKey = childNames.join("\0");
204
+ useEffect(() => {
205
+ const names = childKey.length === 0 ? [] : childKey.split("\0");
206
+ if (names.length === 0) return;
207
+ setTabs((prev) => reconcileLoadedTabs(prev, names, facet));
208
+ }, [childKey, facet]);
209
+
197
210
  useEffect(() => {
198
211
  saveQueryTabs(facet, tabs);
199
212
  }, [facet, tabs]);
@@ -256,7 +269,7 @@ export function QueryConsole({
256
269
  setExecuted(set?.executed ?? next.map((row) => row.executed).join("\n") ?? null);
257
270
  };
258
271
 
259
- const runBatch = (texts: readonly string[], includePii = !piiMasked, gate = asGate): void => {
272
+ const runBatch = (texts: readonly string[], includePii = !piiMasked, gate = rlsAs): void => {
260
273
  if (!store) return;
261
274
  setResultsOpen(true);
262
275
  setError(null);
@@ -290,7 +303,8 @@ export function QueryConsole({
290
303
  sql: sqlText,
291
304
  ...(writing ? { allowWrite: true } : {}),
292
305
  ...(includePii ? { revealPii: true } : {}),
293
- ...(gate ? { asGate: gate } : {}),
306
+ ...(gate.asGate ? { asGate: gate.asGate } : {}),
307
+ ...(gate.asUserId ? { asUserId: gate.asUserId } : {}),
294
308
  ...(tenant ? { tenant } : {}),
295
309
  });
296
310
  const changed = writeChanges(data.rows);
@@ -302,8 +316,13 @@ export function QueryConsole({
302
316
  ];
303
317
  if (data.masked) bits.push("PII masked");
304
318
  else if (includePii) bits.push("PII visible");
305
- if (data.asGate) {
306
- bits.push(data.gateApplied ? `as ${data.asGate}` : `as ${data.asGate} (not applied)`);
319
+ if (data.rls?.gate ?? data.asGate) {
320
+ const label = data.rls?.userId
321
+ ? `${data.rls.gate} / ${data.rls.userId}`
322
+ : (data.rls?.gate ?? data.asGate);
323
+ bits.push(
324
+ data.gateApplied || data.rls?.applied ? `as ${label}` : `as ${label} (not applied)`,
325
+ );
307
326
  }
308
327
  nextSets.push({
309
328
  label: sqlStatementLabel(sqlText),
@@ -351,7 +370,7 @@ export function QueryConsole({
351
370
  })();
352
371
  };
353
372
 
354
- const runText = (sqlText: string, includePii = !piiMasked, gate = asGate): void => {
373
+ const runText = (sqlText: string, includePii = !piiMasked, gate = rlsAs): void => {
355
374
  if (!store) return;
356
375
  setResultsOpen(true);
357
376
  setError(null);
@@ -876,9 +895,9 @@ export function QueryConsole({
876
895
  {isSql ? (
877
896
  <QueryGateMenu
878
897
  manifest={manifest}
879
- asGate={asGate}
898
+ value={rlsAs}
880
899
  onChange={(next) => {
881
- setAsGate(next);
900
+ setRlsAs(next);
882
901
  if (executed && !isSqlWrite(executed)) {
883
902
  runText(executed, !piiMasked, next);
884
903
  }
@@ -916,7 +935,6 @@ export function QueryConsole({
916
935
  <HugeiconsIcon icon={PlayIcon} className="size-3.5" />
917
936
  )}
918
937
  {selected ? "Run selection" : "Run"}
919
- <ShortcutKeys keys={runKeys} className="hidden sm:inline-flex" />
920
938
  </Button>
921
939
  </ToolbarTip>
922
940
  {isSql ? (
@@ -1060,6 +1078,22 @@ function writeChanges(rows: readonly QueryResultRow[]): number | null {
1060
1078
  return typeof changes === "number" && Number.isFinite(changes) ? changes : null;
1061
1079
  }
1062
1080
 
1081
+ /** Rewrite leftover default seeds that name a table the live store dropped. */
1082
+ function reconcileLoadedTabs(
1083
+ tabs: readonly QueryTab[],
1084
+ names: readonly string[],
1085
+ facet: StoreQueryFacet,
1086
+ ): readonly QueryTab[] {
1087
+ let changed = false;
1088
+ const next = tabs.map((tab) => {
1089
+ const text = reconcileDefaultQuery(tab.text, names, facet);
1090
+ if (text === tab.text) return tab;
1091
+ changed = true;
1092
+ return { ...tab, text };
1093
+ });
1094
+ return changed ? next : tabs;
1095
+ }
1096
+
1063
1097
  function seedEditor(
1064
1098
  store: StoreListStore | null | undefined,
1065
1099
  facet: StoreQueryFacet,
@@ -1069,7 +1103,7 @@ function seedEditor(
1069
1103
  const selected = selectedEffectRef
1070
1104
  ? store.children.find((c) => c.effectRef === selectedEffectRef)?.name
1071
1105
  : undefined;
1072
- const name = selected ?? firstChildName(store);
1106
+ const name = selected ?? randomChildName(store);
1073
1107
  return facet === "sql" ? defaultSqlQuery(name) : defaultKvQuery(name ? `${name}:` : undefined);
1074
1108
  }
1075
1109