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.
- package/manifest.v1.schema.json +7 -1
- package/package.json +4 -5
- package/site/content/docs/ai/mcp.mdx +27 -2
- package/site/content/docs/elements/ai.mdx +59 -11
- package/site/content/docs/elements/channel.mdx +1 -0
- package/site/content/docs/elements/clock.mdx +30 -12
- package/site/content/docs/elements/flow.mdx +9 -3
- package/site/content/docs/elements/gate.mdx +25 -22
- package/site/content/docs/elements/signal.mdx +71 -25
- package/site/content/docs/elements/store.mdx +87 -28
- package/site/content/docs/elements/vault.mdx +1 -0
- package/site/content/docs/get-started/basic-usage.mdx +22 -13
- package/site/content/docs/get-started/index.mdx +5 -0
- package/site/content/docs/get-started/installation.mdx +2 -2
- package/site/content/docs/get-started/introduction.mdx +11 -3
- package/site/content/docs/get-started/meta.json +1 -1
- package/site/content/docs/get-started/project-structure.mdx +932 -0
- package/site/content/docs/reference/cli.md +13 -10
- package/site/content/docs/reference/client.mdx +25 -21
- package/site/content/docs/reference/fx.mdx +27 -22
- package/site/content/docs/reference/security.md +1 -1
- package/src/auth/bindings.ts +14 -5
- package/src/auth/cross-plane.ts +10 -3
- package/src/cli/ai-setup/ai-setup.test.ts +62 -0
- package/src/cli/ai-setup/apply.ts +32 -1
- package/src/cli/ai-setup/recommend.test.ts +25 -0
- package/src/cli/ai-setup/recommend.ts +8 -3
- package/src/cli/attach-host-console.test.ts +3 -2
- package/src/cli/attach-host-console.ts +3 -0
- package/src/cli/build.test.ts +4 -2
- package/src/cli/db-seed.ts +48 -15
- package/src/cli/db.test.ts +104 -0
- package/src/cli/db.ts +122 -10
- package/src/cli/dev-controls.test.ts +20 -0
- package/src/cli/dev-controls.ts +13 -0
- package/src/cli/dev.test.ts +15 -4
- package/src/cli/dev.ts +102 -29
- package/src/cli/ensure-drizzle-config.ts +2 -1
- package/src/cli/tui/DevLive.tsx +12 -2
- package/src/cli/tui/keys.test.ts +2 -1
- package/src/cli/tui/keys.ts +3 -2
- package/src/compiler/aot.test.ts +1 -1
- package/src/compiler/effects-infer.ts +58 -3
- package/src/compiler/extract.test.ts +458 -29
- package/src/compiler/extract.ts +387 -69
- package/src/compiler/flow-path.test.ts +96 -0
- package/src/compiler/flow-path.ts +173 -0
- package/src/compiler/generate-adopt.test.ts +112 -3
- package/src/compiler/generate-adopt.ts +280 -26
- package/src/compiler/response.ts +45 -1
- package/src/compiler/schema-from-ast.ts +609 -0
- package/src/console/server/ai.ts +5 -2
- package/src/console/server/bind.ts +2 -2
- package/src/console/server/console.test.ts +3 -0
- package/src/console/server/flows.ts +121 -40
- package/src/console/server/invoke-user-flow.test.ts +3 -1
- package/src/console/server/invoke-user-flow.ts +17 -2
- package/src/console/server/iso-at.test.ts +9 -0
- package/src/console/server/iso-at.ts +11 -0
- package/src/console/server/security-headers.ts +6 -3
- package/src/console/server/serve.ts +17 -2
- package/src/console/server/spa-proxy.test.ts +1 -0
- package/src/console/server/sql-catalog.ts +147 -7
- package/src/console/server/sql-rls.test.ts +16 -0
- package/src/console/server/state.ts +8 -0
- package/src/console/server/store.test.ts +7 -4
- package/src/console/server/store.ts +142 -58
- package/src/console/ui-next/dist/assets/agent-disclosure-C0X1fbWF.js +1 -0
- package/src/console/ui-next/dist/assets/cache-glyph-F1FI122b.js +1 -0
- package/src/console/ui-next/dist/assets/{call-pii-button-bqkxMrJH.js → call-pii-button-DEDCl_j3.js} +1 -1
- package/src/console/ui-next/dist/assets/collapsible-LPqGvfoz.js +1 -0
- package/src/console/ui-next/dist/assets/{confirm-sheet-DDCRmG62.js → confirm-sheet-3ptDzXbz.js} +1 -1
- package/src/console/ui-next/dist/assets/{duration-tone-oiRxPVsZ.js → duration-tone-sC3lGABz.js} +1 -1
- package/src/console/ui-next/dist/assets/{explorer-empty-HjTnVQoR.js → explorer-empty-BvYrygyO.js} +1 -1
- package/src/console/ui-next/dist/assets/flows-page-Dluo1Bd7.js +1 -0
- package/src/console/ui-next/dist/assets/{highlighted-json-CvDPvveV.js → highlighted-json-CS_O8L-r.js} +1 -1
- package/src/console/ui-next/dist/assets/http-method--sWDdXSB.js +1 -0
- package/src/console/ui-next/dist/assets/index-Ca3HZMVq.js +66 -0
- package/src/console/ui-next/dist/assets/index-UtTDRQpS.css +2 -0
- package/src/console/ui-next/dist/assets/{link-DF7SZ9Ek.js → link-COeyggt-.js} +1 -1
- package/src/console/ui-next/dist/assets/observability-page-HK9-BO8z.js +4 -0
- package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +1 -0
- package/src/console/ui-next/dist/assets/replica-lag-DBIFFf7d.js +18 -0
- package/src/console/ui-next/dist/assets/sheet-form-Dnwa5oOR.js +1 -0
- package/src/console/ui-next/dist/assets/shortcut-keys-CoqwEIi0.js +1 -0
- package/src/console/ui-next/dist/assets/skeleton-BHcfDYcb.js +1 -0
- package/src/console/ui-next/dist/assets/store-page-BSohH3wM.js +41 -0
- package/src/console/ui-next/dist/assets/trace-detail-sheet-CFIGRnnA.js +2 -0
- package/src/console/ui-next/dist/assets/tree-expand-toggle-BgLl34w7.js +54 -0
- package/src/console/ui-next/dist/assets/units-page-B0cFE76A.js +1 -0
- package/src/console/ui-next/dist/assets/{use-vault-list-Cl79j_ku.js → use-vault-list-BLbZhzyF.js} +1 -1
- package/src/console/ui-next/dist/assets/vault-page-B1SbYe10.js +2 -0
- package/src/console/ui-next/dist/index.html +6 -6
- package/src/console/ui-next/seed-invoke-host.ts +2 -0
- package/src/console/ui-next/src/client.ts +35 -7
- package/src/console/ui-next/src/components/motion/table/index.tsx +16 -0
- package/src/console/ui-next/src/components/motion/table/types.ts +4 -0
- package/src/console/ui-next/src/components/ui/kbd.tsx +2 -2
- package/src/console/ui-next/src/features/flows/graph/build-flow-graph.test.ts +18 -0
- package/src/console/ui-next/src/features/flows/graph/build-flow-graph.ts +14 -1
- package/src/console/ui-next/src/features/flows/graph/neighborhood.test.ts +17 -0
- package/src/console/ui-next/src/features/flows/graph/neighborhood.ts +16 -3
- package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +3 -1
- package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +24 -0
- package/src/console/ui-next/src/features/flows/traces/http-method.ts +19 -0
- package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +9 -3
- package/src/console/ui-next/src/features/flows/traces/trace-detail.test.ts +29 -2
- package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +10 -3
- package/src/console/ui-next/src/features/observability/lib/ask-count.test.ts +25 -0
- package/src/console/ui-next/src/features/observability/lib/ask-count.ts +4 -1
- package/src/console/ui-next/src/features/store/detail/browse-section.tsx +45 -1
- package/src/console/ui-next/src/features/store/detail/resource-panel.tsx +78 -16
- package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +155 -14
- package/src/console/ui-next/src/features/store/explorer/store-tree.tsx +33 -133
- package/src/console/ui-next/src/features/store/files/file-preview.tsx +4 -1
- package/src/console/ui-next/src/features/store/grid/rls-policy-sheet.tsx +326 -165
- package/src/console/ui-next/src/features/store/grid/sql-insert-sheet.tsx +6 -0
- package/src/console/ui-next/src/features/store/grid/store-data-grid.tsx +50 -3
- package/src/console/ui-next/src/features/store/lib/files-meta.test.ts +3 -0
- package/src/console/ui-next/src/features/store/lib/files-meta.ts +12 -0
- package/src/console/ui-next/src/features/store/lib/query-defaults.test.ts +25 -0
- package/src/console/ui-next/src/features/store/lib/query-defaults.ts +85 -3
- package/src/console/ui-next/src/features/store/lib/query-gate.ts +30 -4
- package/src/console/ui-next/src/features/store/lib/rls-policy.test.ts +139 -0
- package/src/console/ui-next/src/features/store/lib/rls-policy.ts +223 -21
- package/src/console/ui-next/src/features/store/lib/schema-graph.ts +4 -0
- package/src/console/ui-next/src/features/store/lib/sql-catalog.test.ts +24 -0
- package/src/console/ui-next/src/features/store/lib/sql-catalog.ts +19 -0
- package/src/console/ui-next/src/features/store/lib/store-tree.test.ts +6 -0
- package/src/console/ui-next/src/features/store/lib/store-tree.ts +13 -0
- package/src/console/ui-next/src/features/store/query/query-console.tsx +48 -14
- package/src/console/ui-next/src/features/store/query/query-gate-menu.tsx +19 -20
- package/src/console/ui-next/src/features/store/query/query-gate-parts.tsx +2 -1
- package/src/console/ui-next/src/features/store/query/query-results.tsx +110 -83
- package/src/console/ui-next/src/features/store/schema/schema-table-node.tsx +17 -0
- package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +112 -72
- package/src/console/ui-next/src/features/units/detail/effects-summary.tsx +12 -4
- package/src/console/ui-next/src/features/units/lib/contract-input.test.ts +206 -1
- package/src/console/ui-next/src/features/units/lib/contract-input.ts +55 -0
- package/src/console/ui-next/src/features/vault/lib/types.ts +1 -1
- package/src/docker/compose-health.test.ts +27 -0
- package/src/docker/compose-health.ts +19 -3
- package/src/docker/docker.test.ts +4 -1
- package/src/docker/dockerfile.ts +1 -1
- package/src/docker/recipes/pgdog.ts +2 -0
- package/src/drivers/ai-anthropic.ts +5 -0
- package/src/drivers/ai-ollama.ts +49 -30
- package/src/drivers/ai-openai-compatible.ts +57 -46
- package/src/drivers/ai-providers.test.ts +3 -0
- package/src/drivers/bun-native-completeness.test.ts +7 -9
- package/src/drivers/clock-postgres.ts +13 -9
- package/src/drivers/instances-postgres.ts +4 -6
- package/src/drivers/journal-postgres.ts +13 -9
- package/src/drivers/pg-rls.test.ts +87 -0
- package/src/drivers/pg-rls.ts +233 -7
- package/src/drivers/pglite.ts +26 -3
- package/src/drivers/postgres.test.ts +23 -0
- package/src/drivers/postgres.ts +133 -9
- package/src/drivers/redis.ts +11 -4
- package/src/drivers/signal-redis.ts +24 -14
- package/src/drivers/signal-types.ts +2 -1
- package/src/drivers/types.ts +10 -0
- package/src/elements/ai/declare.ts +109 -0
- package/src/elements/ai/errors.test.ts +5 -1
- package/src/elements/ai/errors.ts +30 -2
- package/src/elements/ai/eval.ts +4 -6
- package/src/elements/ai/mcp-client.test.ts +206 -0
- package/src/elements/ai/mcp-client.ts +362 -0
- package/src/elements/ai/mcp-http.ts +159 -0
- package/src/elements/ai/mcp-mock.ts +134 -0
- package/src/elements/ai/mcp-protocol.ts +234 -0
- package/src/elements/ai/mcp-stdio.test.ts +50 -0
- package/src/elements/ai/mcp-stdio.ts +212 -0
- package/src/elements/ai/mcp-transport.ts +70 -0
- package/src/elements/ai/runtime.ts +159 -29
- package/src/elements/ai.test.ts +139 -0
- package/src/elements/ai.ts +16 -0
- package/src/elements/clock/declare.ts +26 -1
- package/src/elements/clock/health.test.ts +43 -0
- package/src/elements/clock/runtime.ts +55 -2
- package/src/elements/clock/schedule.ts +71 -142
- package/src/elements/clock.test.ts +9 -40
- package/src/elements/clock.ts +6 -1
- package/src/elements/gate/boot.ts +5 -5
- package/src/elements/gate/declare.ts +36 -4
- package/src/elements/gate/flatten.ts +1 -1
- package/src/elements/gate/runtime.ts +1 -1
- package/src/elements/index.ts +2 -0
- package/src/elements/signal/declare.ts +2 -1
- package/src/elements/signal/runtime.ts +16 -1
- package/src/elements/signal.ts +1 -0
- package/src/elements/store/cache.test.ts +2 -0
- package/src/elements/store/cache.ts +3 -3
- package/src/elements/store/declare.ts +9 -6
- package/src/elements/store/emit-drizzle.ts +44 -8
- package/src/elements/store/resource.test.ts +1 -1
- package/src/elements/store/rls-identity.test.ts +95 -0
- package/src/elements/store/rls-identity.ts +116 -0
- package/src/elements/store/runtime.ts +4 -0
- package/src/elements/store/schema-decl.test.ts +37 -0
- package/src/elements/store/schema-decl.ts +161 -0
- package/src/elements/store/seed.ts +1 -1
- package/src/elements/store/sql-condition.test.ts +12 -0
- package/src/elements/store/sql-condition.ts +26 -2
- package/src/elements/store/sql-rls-isolation.test.ts +165 -0
- package/src/elements/store/sql-rls-stamp.test.ts +117 -0
- package/src/elements/store/sql-session.ts +81 -3
- package/src/elements/store/upsert-app.test.ts +1 -1
- package/src/elements/store.ts +14 -0
- package/src/elements/vault/builtin-adapter.ts +17 -0
- package/src/full.ts +2 -0
- package/src/http.ts +20 -3
- package/src/index.ts +14 -1
- package/src/kernel/adopt-barrel-fresh.test.ts +1 -1
- package/src/kernel/adopt-routes.ts +30 -2
- package/src/kernel/app.ts +227 -77
- package/src/kernel/auto-cache.test.ts +8 -8
- package/src/kernel/auto-registry.test.ts +54 -9
- package/src/kernel/boot-bind/ai.ts +24 -0
- package/src/kernel/boot-bind/clock.ts +2 -0
- package/src/kernel/boot.test.ts +4 -4
- package/src/kernel/boot.ts +5 -3
- package/src/kernel/effects-stamping.test.ts +3 -3
- package/src/kernel/element-registries.ts +15 -5
- package/src/kernel/errors.ts +24 -0
- package/src/kernel/flow-units.ts +64 -0
- package/src/kernel/flow.ts +29 -17
- package/src/kernel/fx-dead-letters.test.ts +77 -0
- package/src/kernel/fx.test.ts +22 -0
- package/src/kernel/fx.ts +135 -6
- package/src/kernel/horizontal-child.ts +4 -4
- package/src/kernel/http-path-pending.ts +20 -0
- package/src/kernel/http-resource.ts +4 -0
- package/src/kernel/http-stream.test.ts +174 -0
- package/src/kernel/index.ts +2 -0
- package/src/kernel/pipeline.test.ts +2 -2
- package/src/kernel/plugin-elements.test.ts +1 -1
- package/src/kernel/plugin-needs.test.ts +1 -1
- package/src/kernel/ready.test.ts +2 -2
- package/src/kernel/resource-mount.test.ts +14 -0
- package/src/kernel/run-duration.test.ts +2 -2
- package/src/kernel/stamp-http.test.ts +79 -0
- package/src/kernel/stamp-http.ts +75 -0
- package/src/kernel/triggers.ts +52 -27
- package/src/manifest/mcp-ref.ts +88 -0
- package/src/manifest/types.ts +41 -2
- package/src/manifest/validate.test.ts +20 -0
- package/src/mcp/docs-server.ts +1 -1
- package/src/mcp/server.ts +1 -1
- package/src/plugins/compression.test.ts +21 -0
- package/src/plugins/compression.ts +1 -0
- package/src/release/measure.ts +2 -2
- package/src/runtime/bun.ts +41 -4
- package/src/runtime/json-code-block.test.ts +2 -2
- package/src/runtime/serve.test.ts +4 -4
- package/src/term.test.ts +52 -4
- package/src/term.ts +125 -17
- package/src/test/reset-element-registries.ts +10 -3
- package/src/cli/ask-seed.test.ts +0 -96
- package/src/cli/ask-seed.ts +0 -82
- package/src/console/ui-next/dist/assets/cache-glyph-CLPBqZeb.js +0 -1
- package/src/console/ui-next/dist/assets/collapsible-DYb0xU8C.js +0 -1
- package/src/console/ui-next/dist/assets/flows-page-Dg8CTE29.js +0 -1
- package/src/console/ui-next/dist/assets/http-method-BJ92Z_ke.js +0 -1
- package/src/console/ui-next/dist/assets/index-Bp-R7jtM.js +0 -66
- package/src/console/ui-next/dist/assets/index-D4Ldtj79.css +0 -2
- package/src/console/ui-next/dist/assets/observability-page-DAnpEaq1.js +0 -4
- package/src/console/ui-next/dist/assets/query-gate-parts-1m8m1iNp.js +0 -2
- package/src/console/ui-next/dist/assets/react-B1ML8gxg.js +0 -1
- package/src/console/ui-next/dist/assets/replica-lag-C8_BRt2x.js +0 -18
- package/src/console/ui-next/dist/assets/sheet-form-D-ata7jy.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-keys-DO4IsVqv.js +0 -1
- package/src/console/ui-next/dist/assets/skeleton-CL_X0GCj.js +0 -1
- package/src/console/ui-next/dist/assets/store-page-v3LXdYpr.js +0 -45
- package/src/console/ui-next/dist/assets/trace-detail-sheet-D16lWQMt.js +0 -2
- package/src/console/ui-next/dist/assets/units-page-BtQ0bqMe.js +0 -1
- 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
|
|
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
|
-
|
|
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
|
-
*
|
|
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 {
|
|
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
|
|
468
|
-
roles
|
|
469
|
-
permissive
|
|
470
|
-
using
|
|
471
|
-
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
|
-
|
|
582
|
-
|
|
583
|
-
|
|
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
|
});
|