okengine 0.13.0 → 0.15.2

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 (277) hide show
  1. package/manifest.v1.schema.json +7 -1
  2. package/package.json +4 -5
  3. package/site/content/docs/ai/mcp.mdx +27 -2
  4. package/site/content/docs/elements/ai.mdx +59 -11
  5. package/site/content/docs/elements/channel.mdx +1 -0
  6. package/site/content/docs/elements/clock.mdx +30 -12
  7. package/site/content/docs/elements/flow.mdx +9 -3
  8. package/site/content/docs/elements/gate.mdx +25 -22
  9. package/site/content/docs/elements/signal.mdx +71 -25
  10. package/site/content/docs/elements/store.mdx +87 -28
  11. package/site/content/docs/elements/vault.mdx +1 -0
  12. package/site/content/docs/get-started/basic-usage.mdx +22 -13
  13. package/site/content/docs/get-started/index.mdx +5 -0
  14. package/site/content/docs/get-started/installation.mdx +2 -2
  15. package/site/content/docs/get-started/introduction.mdx +11 -3
  16. package/site/content/docs/get-started/meta.json +1 -1
  17. package/site/content/docs/get-started/project-structure.mdx +932 -0
  18. package/site/content/docs/reference/cli.md +13 -10
  19. package/site/content/docs/reference/client.mdx +25 -21
  20. package/site/content/docs/reference/fx.mdx +27 -22
  21. package/site/content/docs/reference/security.md +1 -1
  22. package/src/auth/bindings.ts +14 -5
  23. package/src/auth/cross-plane.ts +10 -3
  24. package/src/cli/ai-setup/ai-setup.test.ts +62 -0
  25. package/src/cli/ai-setup/apply.ts +32 -1
  26. package/src/cli/ai-setup/recommend.test.ts +25 -0
  27. package/src/cli/ai-setup/recommend.ts +8 -3
  28. package/src/cli/attach-host-console.test.ts +3 -2
  29. package/src/cli/attach-host-console.ts +3 -0
  30. package/src/cli/build.test.ts +4 -2
  31. package/src/cli/db-seed.ts +48 -15
  32. package/src/cli/db.test.ts +104 -0
  33. package/src/cli/db.ts +122 -10
  34. package/src/cli/dev-controls.test.ts +20 -0
  35. package/src/cli/dev-controls.ts +13 -0
  36. package/src/cli/dev.test.ts +15 -4
  37. package/src/cli/dev.ts +102 -29
  38. package/src/cli/ensure-drizzle-config.ts +2 -1
  39. package/src/cli/tui/DevLive.tsx +12 -2
  40. package/src/cli/tui/keys.test.ts +2 -1
  41. package/src/cli/tui/keys.ts +3 -2
  42. package/src/compiler/aot.test.ts +1 -1
  43. package/src/compiler/effects-infer.ts +58 -3
  44. package/src/compiler/extract.test.ts +458 -29
  45. package/src/compiler/extract.ts +387 -69
  46. package/src/compiler/flow-path.test.ts +96 -0
  47. package/src/compiler/flow-path.ts +173 -0
  48. package/src/compiler/generate-adopt.test.ts +112 -3
  49. package/src/compiler/generate-adopt.ts +280 -26
  50. package/src/compiler/response.ts +45 -1
  51. package/src/compiler/schema-from-ast.ts +609 -0
  52. package/src/console/server/ai.ts +5 -2
  53. package/src/console/server/bind.ts +2 -2
  54. package/src/console/server/console.test.ts +3 -0
  55. package/src/console/server/flows.ts +121 -40
  56. package/src/console/server/invoke-user-flow.test.ts +3 -1
  57. package/src/console/server/invoke-user-flow.ts +17 -2
  58. package/src/console/server/iso-at.test.ts +9 -0
  59. package/src/console/server/iso-at.ts +11 -0
  60. package/src/console/server/security-headers.ts +6 -3
  61. package/src/console/server/serve.ts +17 -2
  62. package/src/console/server/spa-proxy.test.ts +1 -0
  63. package/src/console/server/sql-catalog.ts +147 -7
  64. package/src/console/server/sql-rls.test.ts +16 -0
  65. package/src/console/server/state.ts +8 -0
  66. package/src/console/server/store.test.ts +7 -4
  67. package/src/console/server/store.ts +142 -58
  68. package/src/console/ui-next/dist/assets/agent-disclosure-C0X1fbWF.js +1 -0
  69. package/src/console/ui-next/dist/assets/cache-glyph-F1FI122b.js +1 -0
  70. package/src/console/ui-next/dist/assets/{call-pii-button-bqkxMrJH.js → call-pii-button-DEDCl_j3.js} +1 -1
  71. package/src/console/ui-next/dist/assets/collapsible-LPqGvfoz.js +1 -0
  72. package/src/console/ui-next/dist/assets/{confirm-sheet-DDCRmG62.js → confirm-sheet-3ptDzXbz.js} +1 -1
  73. package/src/console/ui-next/dist/assets/{duration-tone-oiRxPVsZ.js → duration-tone-sC3lGABz.js} +1 -1
  74. package/src/console/ui-next/dist/assets/{explorer-empty-HjTnVQoR.js → explorer-empty-BvYrygyO.js} +1 -1
  75. package/src/console/ui-next/dist/assets/flows-page-Dluo1Bd7.js +1 -0
  76. package/src/console/ui-next/dist/assets/{highlighted-json-CvDPvveV.js → highlighted-json-CS_O8L-r.js} +1 -1
  77. package/src/console/ui-next/dist/assets/http-method--sWDdXSB.js +1 -0
  78. package/src/console/ui-next/dist/assets/index-Ca3HZMVq.js +66 -0
  79. package/src/console/ui-next/dist/assets/index-UtTDRQpS.css +2 -0
  80. package/src/console/ui-next/dist/assets/{link-DF7SZ9Ek.js → link-COeyggt-.js} +1 -1
  81. package/src/console/ui-next/dist/assets/observability-page-HK9-BO8z.js +4 -0
  82. package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +1 -0
  83. package/src/console/ui-next/dist/assets/replica-lag-DBIFFf7d.js +18 -0
  84. package/src/console/ui-next/dist/assets/sheet-form-Dnwa5oOR.js +1 -0
  85. package/src/console/ui-next/dist/assets/shortcut-keys-CoqwEIi0.js +1 -0
  86. package/src/console/ui-next/dist/assets/skeleton-BHcfDYcb.js +1 -0
  87. package/src/console/ui-next/dist/assets/store-page-BSohH3wM.js +41 -0
  88. package/src/console/ui-next/dist/assets/trace-detail-sheet-CFIGRnnA.js +2 -0
  89. package/src/console/ui-next/dist/assets/tree-expand-toggle-BgLl34w7.js +54 -0
  90. package/src/console/ui-next/dist/assets/units-page-B0cFE76A.js +1 -0
  91. package/src/console/ui-next/dist/assets/{use-vault-list-Cl79j_ku.js → use-vault-list-BLbZhzyF.js} +1 -1
  92. package/src/console/ui-next/dist/assets/vault-page-B1SbYe10.js +2 -0
  93. package/src/console/ui-next/dist/index.html +6 -6
  94. package/src/console/ui-next/seed-invoke-host.ts +2 -0
  95. package/src/console/ui-next/src/client.ts +35 -7
  96. package/src/console/ui-next/src/components/motion/table/index.tsx +16 -0
  97. package/src/console/ui-next/src/components/motion/table/types.ts +4 -0
  98. package/src/console/ui-next/src/components/ui/kbd.tsx +2 -2
  99. package/src/console/ui-next/src/features/flows/graph/build-flow-graph.test.ts +18 -0
  100. package/src/console/ui-next/src/features/flows/graph/build-flow-graph.ts +14 -1
  101. package/src/console/ui-next/src/features/flows/graph/neighborhood.test.ts +17 -0
  102. package/src/console/ui-next/src/features/flows/graph/neighborhood.ts +16 -3
  103. package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +3 -1
  104. package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +24 -0
  105. package/src/console/ui-next/src/features/flows/traces/http-method.ts +19 -0
  106. package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +9 -3
  107. package/src/console/ui-next/src/features/flows/traces/trace-detail.test.ts +29 -2
  108. package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +10 -3
  109. package/src/console/ui-next/src/features/observability/lib/ask-count.test.ts +25 -0
  110. package/src/console/ui-next/src/features/observability/lib/ask-count.ts +4 -1
  111. package/src/console/ui-next/src/features/store/detail/browse-section.tsx +45 -1
  112. package/src/console/ui-next/src/features/store/detail/resource-panel.tsx +78 -16
  113. package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +155 -14
  114. package/src/console/ui-next/src/features/store/explorer/store-tree.tsx +33 -133
  115. package/src/console/ui-next/src/features/store/files/file-preview.tsx +4 -1
  116. package/src/console/ui-next/src/features/store/grid/rls-policy-sheet.tsx +326 -165
  117. package/src/console/ui-next/src/features/store/grid/sql-insert-sheet.tsx +6 -0
  118. package/src/console/ui-next/src/features/store/grid/store-data-grid.tsx +50 -3
  119. package/src/console/ui-next/src/features/store/lib/files-meta.test.ts +3 -0
  120. package/src/console/ui-next/src/features/store/lib/files-meta.ts +12 -0
  121. package/src/console/ui-next/src/features/store/lib/query-defaults.test.ts +25 -0
  122. package/src/console/ui-next/src/features/store/lib/query-defaults.ts +85 -3
  123. package/src/console/ui-next/src/features/store/lib/query-gate.ts +30 -4
  124. package/src/console/ui-next/src/features/store/lib/rls-policy.test.ts +139 -0
  125. package/src/console/ui-next/src/features/store/lib/rls-policy.ts +223 -21
  126. package/src/console/ui-next/src/features/store/lib/schema-graph.ts +4 -0
  127. package/src/console/ui-next/src/features/store/lib/sql-catalog.test.ts +24 -0
  128. package/src/console/ui-next/src/features/store/lib/sql-catalog.ts +19 -0
  129. package/src/console/ui-next/src/features/store/lib/store-tree.test.ts +6 -0
  130. package/src/console/ui-next/src/features/store/lib/store-tree.ts +13 -0
  131. package/src/console/ui-next/src/features/store/query/query-console.tsx +48 -14
  132. package/src/console/ui-next/src/features/store/query/query-gate-menu.tsx +19 -20
  133. package/src/console/ui-next/src/features/store/query/query-gate-parts.tsx +2 -1
  134. package/src/console/ui-next/src/features/store/query/query-results.tsx +110 -83
  135. package/src/console/ui-next/src/features/store/schema/schema-table-node.tsx +17 -0
  136. package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +112 -72
  137. package/src/console/ui-next/src/features/units/detail/effects-summary.tsx +12 -4
  138. package/src/console/ui-next/src/features/units/lib/contract-input.test.ts +206 -1
  139. package/src/console/ui-next/src/features/units/lib/contract-input.ts +55 -0
  140. package/src/console/ui-next/src/features/vault/lib/types.ts +1 -1
  141. package/src/docker/compose-health.test.ts +27 -0
  142. package/src/docker/compose-health.ts +19 -3
  143. package/src/docker/docker.test.ts +4 -1
  144. package/src/docker/dockerfile.ts +1 -1
  145. package/src/docker/recipes/pgdog.ts +2 -0
  146. package/src/drivers/ai-anthropic.ts +5 -0
  147. package/src/drivers/ai-ollama.ts +49 -30
  148. package/src/drivers/ai-openai-compatible.ts +57 -46
  149. package/src/drivers/ai-providers.test.ts +3 -0
  150. package/src/drivers/bun-native-completeness.test.ts +7 -9
  151. package/src/drivers/clock-postgres.ts +13 -9
  152. package/src/drivers/instances-postgres.ts +4 -6
  153. package/src/drivers/journal-postgres.ts +13 -9
  154. package/src/drivers/pg-rls.test.ts +87 -0
  155. package/src/drivers/pg-rls.ts +233 -7
  156. package/src/drivers/pglite.ts +26 -3
  157. package/src/drivers/postgres.test.ts +23 -0
  158. package/src/drivers/postgres.ts +133 -9
  159. package/src/drivers/redis.ts +11 -4
  160. package/src/drivers/signal-redis.ts +24 -14
  161. package/src/drivers/signal-types.ts +2 -1
  162. package/src/drivers/types.ts +10 -0
  163. package/src/elements/ai/declare.ts +109 -0
  164. package/src/elements/ai/errors.test.ts +5 -1
  165. package/src/elements/ai/errors.ts +30 -2
  166. package/src/elements/ai/eval.ts +4 -6
  167. package/src/elements/ai/mcp-client.test.ts +206 -0
  168. package/src/elements/ai/mcp-client.ts +362 -0
  169. package/src/elements/ai/mcp-http.ts +159 -0
  170. package/src/elements/ai/mcp-mock.ts +134 -0
  171. package/src/elements/ai/mcp-protocol.ts +234 -0
  172. package/src/elements/ai/mcp-stdio.test.ts +50 -0
  173. package/src/elements/ai/mcp-stdio.ts +212 -0
  174. package/src/elements/ai/mcp-transport.ts +70 -0
  175. package/src/elements/ai/runtime.ts +159 -29
  176. package/src/elements/ai.test.ts +139 -0
  177. package/src/elements/ai.ts +16 -0
  178. package/src/elements/clock/declare.ts +26 -1
  179. package/src/elements/clock/health.test.ts +43 -0
  180. package/src/elements/clock/runtime.ts +55 -2
  181. package/src/elements/clock/schedule.ts +71 -142
  182. package/src/elements/clock.test.ts +9 -40
  183. package/src/elements/clock.ts +6 -1
  184. package/src/elements/gate/boot.ts +5 -5
  185. package/src/elements/gate/declare.ts +36 -4
  186. package/src/elements/gate/flatten.ts +1 -1
  187. package/src/elements/gate/runtime.ts +1 -1
  188. package/src/elements/index.ts +2 -0
  189. package/src/elements/signal/declare.ts +2 -1
  190. package/src/elements/signal/runtime.ts +16 -1
  191. package/src/elements/signal.ts +1 -0
  192. package/src/elements/store/cache.test.ts +2 -0
  193. package/src/elements/store/cache.ts +3 -3
  194. package/src/elements/store/declare.ts +9 -6
  195. package/src/elements/store/emit-drizzle.ts +44 -8
  196. package/src/elements/store/resource.test.ts +1 -1
  197. package/src/elements/store/rls-identity.test.ts +95 -0
  198. package/src/elements/store/rls-identity.ts +116 -0
  199. package/src/elements/store/runtime.ts +4 -0
  200. package/src/elements/store/schema-decl.test.ts +37 -0
  201. package/src/elements/store/schema-decl.ts +161 -0
  202. package/src/elements/store/seed.ts +1 -1
  203. package/src/elements/store/sql-condition.test.ts +12 -0
  204. package/src/elements/store/sql-condition.ts +26 -2
  205. package/src/elements/store/sql-rls-isolation.test.ts +165 -0
  206. package/src/elements/store/sql-rls-stamp.test.ts +117 -0
  207. package/src/elements/store/sql-session.ts +81 -3
  208. package/src/elements/store/upsert-app.test.ts +1 -1
  209. package/src/elements/store.ts +14 -0
  210. package/src/elements/vault/builtin-adapter.ts +17 -0
  211. package/src/full.ts +2 -0
  212. package/src/http.ts +20 -3
  213. package/src/index.ts +14 -1
  214. package/src/kernel/adopt-barrel-fresh.test.ts +1 -1
  215. package/src/kernel/adopt-routes.ts +30 -2
  216. package/src/kernel/app.ts +227 -77
  217. package/src/kernel/auto-cache.test.ts +8 -8
  218. package/src/kernel/auto-registry.test.ts +54 -9
  219. package/src/kernel/boot-bind/ai.ts +24 -0
  220. package/src/kernel/boot-bind/clock.ts +2 -0
  221. package/src/kernel/boot.test.ts +4 -4
  222. package/src/kernel/boot.ts +5 -3
  223. package/src/kernel/effects-stamping.test.ts +3 -3
  224. package/src/kernel/element-registries.ts +15 -5
  225. package/src/kernel/errors.ts +24 -0
  226. package/src/kernel/flow-units.ts +64 -0
  227. package/src/kernel/flow.ts +29 -17
  228. package/src/kernel/fx-dead-letters.test.ts +77 -0
  229. package/src/kernel/fx.test.ts +22 -0
  230. package/src/kernel/fx.ts +135 -6
  231. package/src/kernel/horizontal-child.ts +4 -4
  232. package/src/kernel/http-path-pending.ts +20 -0
  233. package/src/kernel/http-resource.ts +4 -0
  234. package/src/kernel/http-stream.test.ts +174 -0
  235. package/src/kernel/index.ts +2 -0
  236. package/src/kernel/pipeline.test.ts +2 -2
  237. package/src/kernel/plugin-elements.test.ts +1 -1
  238. package/src/kernel/plugin-needs.test.ts +1 -1
  239. package/src/kernel/ready.test.ts +2 -2
  240. package/src/kernel/resource-mount.test.ts +14 -0
  241. package/src/kernel/run-duration.test.ts +2 -2
  242. package/src/kernel/stamp-http.test.ts +79 -0
  243. package/src/kernel/stamp-http.ts +75 -0
  244. package/src/kernel/triggers.ts +52 -27
  245. package/src/manifest/mcp-ref.ts +88 -0
  246. package/src/manifest/types.ts +41 -2
  247. package/src/manifest/validate.test.ts +20 -0
  248. package/src/mcp/docs-server.ts +1 -1
  249. package/src/mcp/server.ts +1 -1
  250. package/src/plugins/compression.test.ts +21 -0
  251. package/src/plugins/compression.ts +1 -0
  252. package/src/release/measure.ts +2 -2
  253. package/src/runtime/bun.ts +41 -4
  254. package/src/runtime/json-code-block.test.ts +2 -2
  255. package/src/runtime/serve.test.ts +4 -4
  256. package/src/term.test.ts +52 -4
  257. package/src/term.ts +125 -17
  258. package/src/test/reset-element-registries.ts +10 -3
  259. package/src/cli/ask-seed.test.ts +0 -96
  260. package/src/cli/ask-seed.ts +0 -82
  261. package/src/console/ui-next/dist/assets/cache-glyph-CLPBqZeb.js +0 -1
  262. package/src/console/ui-next/dist/assets/collapsible-DYb0xU8C.js +0 -1
  263. package/src/console/ui-next/dist/assets/flows-page-Dg8CTE29.js +0 -1
  264. package/src/console/ui-next/dist/assets/http-method-BJ92Z_ke.js +0 -1
  265. package/src/console/ui-next/dist/assets/index-Bp-R7jtM.js +0 -66
  266. package/src/console/ui-next/dist/assets/index-D4Ldtj79.css +0 -2
  267. package/src/console/ui-next/dist/assets/observability-page-DAnpEaq1.js +0 -4
  268. package/src/console/ui-next/dist/assets/query-gate-parts-1m8m1iNp.js +0 -2
  269. package/src/console/ui-next/dist/assets/react-B1ML8gxg.js +0 -1
  270. package/src/console/ui-next/dist/assets/replica-lag-C8_BRt2x.js +0 -18
  271. package/src/console/ui-next/dist/assets/sheet-form-D-ata7jy.js +0 -1
  272. package/src/console/ui-next/dist/assets/shortcut-keys-DO4IsVqv.js +0 -1
  273. package/src/console/ui-next/dist/assets/skeleton-CL_X0GCj.js +0 -1
  274. package/src/console/ui-next/dist/assets/store-page-v3LXdYpr.js +0 -45
  275. package/src/console/ui-next/dist/assets/trace-detail-sheet-D16lWQMt.js +0 -2
  276. package/src/console/ui-next/dist/assets/units-page-BtQ0bqMe.js +0 -1
  277. package/src/console/ui-next/dist/assets/vault-page-Ca-MvcmJ.js +0 -2
@@ -244,7 +244,12 @@ export async function serveConsole(
244
244
  new Response("WebSocket upgrade failed", { status: 400 }),
245
245
  );
246
246
  }
247
- return fetchHandler(req);
247
+ return (async () => {
248
+ const res = await fetchHandler(req);
249
+ const ct = (res.headers.get("content-type") ?? "").split(";")[0]?.trim() ?? "";
250
+ if (/^text\/event-stream$/i.test(ct)) srv.timeout(req, 0);
251
+ return res;
252
+ })();
248
253
  },
249
254
  websocket: {
250
255
  open: live.open,
@@ -259,6 +264,15 @@ export async function serveConsole(
259
264
  boundHost.includes(":") && !boundHost.startsWith("[") ? `[${boundHost}]` : boundHost;
260
265
  const url = new URL(`http://${hostForUrl}:${boundPort}/`);
261
266
 
267
+ const closeIdle = (
268
+ server as typeof server & { closeIdleConnections(): void }
269
+ ).closeIdleConnections.bind(server);
270
+ const onPressure = (): void => {
271
+ closeIdle();
272
+ };
273
+ const proc = process as NodeJS.EventEmitter;
274
+ proc.on("memoryPressure", onPressure);
275
+
262
276
  return {
263
277
  console: handle,
264
278
  url,
@@ -266,9 +280,10 @@ export async function serveConsole(
266
280
  hostname: boundHost,
267
281
  fetch: fetchHandler,
268
282
  stop(closeActive = false) {
269
- server.stop(closeActive);
283
+ proc.off("memoryPressure", onPressure);
270
284
  void handle.app.stop();
271
285
  void persistence?.close();
286
+ return server.stop(closeActive);
272
287
  },
273
288
  };
274
289
  }
@@ -61,6 +61,7 @@ describe("serveConsole spaProxy", () => {
61
61
  expect(html.status).toBe(200);
62
62
  expect(html.headers.get("content-security-policy")).toBe(CONSOLE_VITE_DEV_CSP);
63
63
  expect(html.headers.get("content-security-policy")).toContain("unsafe-inline");
64
+ expect(html.headers.get("content-security-policy")).toContain("frame-src 'self' blob:");
64
65
  expect(await html.text()).toContain("vite-spa");
65
66
 
66
67
  const client = await server.fetch(
@@ -31,6 +31,8 @@ import {
31
31
  parseSqlPolicySpec,
32
32
  quotePgIdent,
33
33
  sqlPolicyRowId,
34
+ emitStoreSchemaPolicySource,
35
+ installOkeRlsHelpers,
34
36
  } from "../../drivers/pg-rls.ts";
35
37
  import type { ConsoleStoreChild, ConsoleWillNotFire } from "./store.ts";
36
38
 
@@ -117,18 +119,52 @@ export async function listSqlTableRls(sql: SqlStoreHandle): Promise<ReadonlyMap<
117
119
  }
118
120
 
119
121
  /**
120
- * Copy RLS flags onto SQL table children. Catalog folders are unchanged.
122
+ * Live RLS policy counts (`pg_policies`) keyed by table name.
123
+ *
124
+ * Counts in process — memory SQL has no `GROUP BY` / `COUNT(*)::int`.
125
+ *
126
+ * @param sql - Open SQL handle
127
+ */
128
+ export async function listSqlTablePolicyCounts(
129
+ sql: SqlStoreHandle,
130
+ ): Promise<ReadonlyMap<string, number>> {
131
+ try {
132
+ const rows = await sql.raw(`SELECT * FROM pg_policies`);
133
+ const out = new Map<string, number>();
134
+ for (const row of rows) {
135
+ if (!isAppCatalogSchema(row)) continue;
136
+ const name = stringOrNull(row.tablename ?? row.table);
137
+ if (name === null) continue;
138
+ out.set(name, (out.get(name) ?? 0) + 1);
139
+ }
140
+ return out;
141
+ } catch {
142
+ return new Map();
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Copy RLS flags and policy counts onto SQL table children. Catalog folders
148
+ * are unchanged.
121
149
  *
122
150
  * @param children - Store list children
123
151
  * @param rlsByTable - Table name → RLS enabled
152
+ * @param declared - Manifest RLS when live catalog is empty
153
+ * @param policyCounts - Table name → live + declared policy count
124
154
  */
125
155
  export function applySqlTableRls(
126
156
  children: readonly ConsoleStoreChild[],
127
157
  rlsByTable: ReadonlyMap<string, boolean>,
158
+ declared?: ReadonlyMap<string, boolean>,
159
+ policyCounts?: ReadonlyMap<string, number>,
128
160
  ): ConsoleStoreChild[] {
129
161
  return children.map((child) => {
130
162
  if (child.kind !== "table") return child;
131
- return { ...child, rls: rlsByTable.get(child.name) ?? child.rls === true };
163
+ return {
164
+ ...child,
165
+ rls: rlsByTable.get(child.name) ?? declared?.get(child.name) ?? child.rls === true,
166
+ rlsPolicyCount: policyCounts?.get(child.name) ?? child.rlsPolicyCount ?? 0,
167
+ };
132
168
  });
133
169
  }
134
170
 
@@ -156,6 +192,9 @@ export async function listSqlCatalog(
156
192
  limit: number,
157
193
  ): Promise<readonly Record<string, unknown>[]> {
158
194
  const live = await tryLiveCatalog(sql, kind, limit);
195
+ if (kind === "policy") {
196
+ return overlayDeclaredPolicies(live ?? [], manifest, storeName).slice(0, limit);
197
+ }
159
198
  if (live) return live;
160
199
  if (kind === "index") return manifestIndexes(manifest, storeName).slice(0, limit);
161
200
  if (kind === "extension") return fallbackExtensions().slice(0, limit);
@@ -172,6 +211,11 @@ export async function createSqlPolicy(
172
211
  sql: SqlStoreHandle,
173
212
  patch: Readonly<Record<string, unknown>>,
174
213
  ): Promise<void> {
214
+ try {
215
+ await installOkeRlsHelpers((stmt) => sql.raw(stmt));
216
+ } catch {
217
+ // Memory / engines without CREATE FUNCTION — CREATE POLICY may still run.
218
+ }
175
219
  const spec = parseSqlPolicySpec(patch);
176
220
  if (patch.enableRls === true) {
177
221
  await sql.raw(buildRowSecuritySql(spec.table, true));
@@ -393,11 +437,13 @@ function catalogColumnDescriptions(kind: SqlCatalogKind): Readonly<Record<string
393
437
  return {
394
438
  name: "Policy",
395
439
  table: "Table",
440
+ origin: "Declared or live-only",
396
441
  command: "Command",
397
442
  roles: "Roles",
398
443
  permissive: "Permissive or restrictive",
399
444
  using: "USING expression",
400
445
  with_check: "WITH CHECK expression",
446
+ code: "Copy as code",
401
447
  };
402
448
  }
403
449
  return {
@@ -459,19 +505,113 @@ function isAppCatalogSchema(row: Record<string, unknown>): boolean {
459
505
  function projectPolicyRow(row: Record<string, unknown>): Record<string, unknown> {
460
506
  const name = stringOrNull(row.name ?? row.policyname) ?? "";
461
507
  const table = stringOrNull(row.table ?? row.tablename) ?? "";
508
+ const command = stringOrNull(row.command ?? row.cmd) ?? "ALL";
509
+ const roles = rolesText(row.roles);
510
+ const using = stringOrNull(row.using ?? row.qual);
511
+ const withCheck = stringOrNull(row.with_check);
512
+ const permissive = stringOrNull(row.permissive) ?? "PERMISSIVE";
462
513
  return {
463
514
  id: sqlPolicyRowId(table, name),
464
515
  name,
465
516
  table,
466
517
  schema: stringOrNull(row.schema ?? row.schemaname) ?? "public",
467
- command: stringOrNull(row.command ?? row.cmd) ?? "ALL",
468
- roles: rolesText(row.roles),
469
- permissive: stringOrNull(row.permissive) ?? "PERMISSIVE",
470
- using: stringOrNull(row.using ?? row.qual),
471
- with_check: stringOrNull(row.with_check),
518
+ command,
519
+ roles,
520
+ permissive,
521
+ using,
522
+ with_check: withCheck,
523
+ origin: stringOrNull(row.origin) ?? "live",
524
+ code: policyCopyAsCode({
525
+ name,
526
+ table,
527
+ command,
528
+ roles,
529
+ permissive,
530
+ using,
531
+ withCheck,
532
+ }),
472
533
  };
473
534
  }
474
535
 
536
+ function overlayDeclaredPolicies(
537
+ live: readonly Record<string, unknown>[],
538
+ manifest: Manifest | null,
539
+ storeName: string,
540
+ ): Record<string, unknown>[] {
541
+ const byId = new Map<string, Record<string, unknown>>();
542
+ for (const row of live) {
543
+ byId.set(String(row.id), { ...row, origin: "live" });
544
+ }
545
+ for (const row of declaredPoliciesFromManifest(manifest, storeName)) {
546
+ const existing = byId.get(row.id);
547
+ byId.set(row.id, existing ? { ...existing, ...row, origin: "declared" } : row);
548
+ }
549
+ return [...byId.values()];
550
+ }
551
+
552
+ function declaredPoliciesFromManifest(
553
+ manifest: Manifest | null,
554
+ storeName: string,
555
+ ): Array<Record<string, unknown> & { readonly id: string }> {
556
+ const tables = manifest?.stores?.[storeName]?.tables ?? {};
557
+ const out: Array<Record<string, unknown> & { readonly id: string }> = [];
558
+ for (const [table, spec] of Object.entries(tables)) {
559
+ for (const [name, policy] of Object.entries(spec.policies ?? {})) {
560
+ const command = (policy.for ?? "all").toUpperCase();
561
+ const roles = Array.isArray(policy.to) ? policy.to.join(", ") : (policy.to ?? "public");
562
+ const permissive = (policy.as ?? "permissive").toUpperCase();
563
+ out.push({
564
+ id: sqlPolicyRowId(table, name),
565
+ name,
566
+ table,
567
+ schema: "public",
568
+ command,
569
+ roles,
570
+ permissive,
571
+ using: policy.using ?? null,
572
+ with_check: policy.withCheck ?? null,
573
+ origin: "declared",
574
+ code: policyCopyAsCode({
575
+ name,
576
+ table,
577
+ command,
578
+ roles,
579
+ permissive,
580
+ using: policy.using,
581
+ withCheck: policy.withCheck,
582
+ }),
583
+ });
584
+ }
585
+ }
586
+ return out;
587
+ }
588
+
589
+ function policyCopyAsCode(input: {
590
+ readonly name: string;
591
+ readonly table: string;
592
+ readonly command: string;
593
+ readonly roles: string;
594
+ readonly permissive: string;
595
+ readonly using?: string | null;
596
+ readonly withCheck?: string | null;
597
+ }): string {
598
+ try {
599
+ return emitStoreSchemaPolicySource(
600
+ parseSqlPolicySpec({
601
+ name: input.name,
602
+ table: input.table,
603
+ command: input.command,
604
+ behavior: input.permissive,
605
+ roles: input.roles,
606
+ using: input.using ?? "",
607
+ withCheck: input.withCheck ?? "",
608
+ }),
609
+ );
610
+ } catch {
611
+ return "";
612
+ }
613
+ }
614
+
475
615
  function projectTriggerRow(row: Record<string, unknown>): Record<string, unknown> {
476
616
  const enabled = row.enabled;
477
617
  return {
@@ -41,4 +41,20 @@ describe("applySqlTableRls", () => {
41
41
  const rows = [{ ...child("comments"), rls: false }];
42
42
  expect(applySqlTableRls(rows, new Map())[0]?.rls).toBe(false);
43
43
  });
44
+
45
+ test("Manifest declared RLS wins when live catalog is empty", () => {
46
+ const out = applySqlTableRls([child("bookings")], new Map(), new Map([["bookings", true]]));
47
+ expect(out[0]?.rls).toBe(true);
48
+ });
49
+
50
+ test("stamps policy counts on tables and leaves catalog folders alone", () => {
51
+ const out = applySqlTableRls(
52
+ [child("comments"), child("policies", "policy")],
53
+ new Map([["comments", true]]),
54
+ undefined,
55
+ new Map([["comments", 2]]),
56
+ );
57
+ expect(out.find((row) => row.name === "comments")?.rlsPolicyCount).toBe(2);
58
+ expect(out.find((row) => row.name === "policies")?.rlsPolicyCount).toBeUndefined();
59
+ });
44
60
  });
@@ -267,13 +267,21 @@ export interface ConsoleState {
267
267
  readonly revealPii?: boolean;
268
268
  readonly tenant?: string;
269
269
  readonly asGate?: string;
270
+ readonly asUserId?: string;
271
+ readonly rls?: import("../../drivers/pg-rls.ts").RlsIdentity;
270
272
  },
271
273
  ) => Promise<{
272
274
  readonly rows: readonly Record<string, unknown>[];
273
275
  readonly masked: boolean;
274
276
  readonly routedRole: "primary" | "replica";
275
277
  readonly asGate: string | null;
278
+ readonly asUserId: string | null;
276
279
  readonly gateApplied: boolean;
280
+ readonly rls: {
281
+ readonly gate: string | null;
282
+ readonly userId: string;
283
+ readonly applied: boolean;
284
+ };
277
285
  }>;
278
286
  /** Engine-native pg_stat_statements + KPIs. */
279
287
  queryStoreSqlStats: (ref: ResourceRef) => Promise<import("./store-stats.ts").StoreSqlStatsResult>;
@@ -578,10 +578,11 @@ describe("queryStore SQL catalog", () => {
578
578
  declaredFingerprint: "x",
579
579
  appliedFingerprint: "x",
580
580
  });
581
- expect(
582
- listedOn.stores.find((s) => s.ref === "sql:db")?.children.find((c) => c.name === "bookings")
583
- ?.rls,
584
- ).toBe(true);
581
+ const listedBookings = listedOn.stores
582
+ .find((s) => s.ref === "sql:db")
583
+ ?.children.find((c) => c.name === "bookings");
584
+ expect(listedBookings?.rls).toBe(true);
585
+ expect(listedBookings?.rlsPolicyCount).toBe(1);
585
586
 
586
587
  await editStore(
587
588
  runtime,
@@ -1097,7 +1098,9 @@ describe("runStoreSql", () => {
1097
1098
  asGate: "public",
1098
1099
  });
1099
1100
  expect(result.asGate).toBe("public");
1101
+ expect(result.asUserId).toBeNull();
1100
1102
  expect(result.gateApplied).toBe(false);
1103
+ expect(result.rls.applied).toBe(false);
1101
1104
  expect(result.rows).toHaveLength(1);
1102
1105
  await runtime.close();
1103
1106
  });