okengine 0.14.1 → 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/package.json +3 -4
- package/site/content/docs/elements/ai.mdx +2 -1
- package/site/content/docs/elements/channel.mdx +1 -0
- package/site/content/docs/elements/clock.mdx +3 -0
- package/site/content/docs/elements/flow.mdx +3 -0
- package/site/content/docs/elements/gate.mdx +25 -22
- package/site/content/docs/elements/signal.mdx +4 -4
- package/site/content/docs/elements/store.mdx +86 -27
- 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/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/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/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/extract.test.ts +409 -33
- package/src/compiler/extract.ts +318 -67
- 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/schema-from-ast.ts +609 -0
- package/src/console/server/bind.ts +2 -2
- package/src/console/server/console.test.ts +3 -0
- package/src/console/server/flows.ts +120 -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/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-BanhLsEY.js → cache-glyph-F1FI122b.js} +1 -1
- 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-BEZDzyYh.js → observability-page-HK9-BO8z.js} +2 -2
- 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/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/traces/http-method.ts +19 -0
- package/src/console/ui-next/src/features/flows/traces/trace-detail.test.ts +19 -1
- package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +10 -3
- 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/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 +3 -0
- package/src/docker/recipes/pgdog.ts +2 -0
- 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/types.ts +10 -0
- package/src/elements/clock/declare.ts +26 -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/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/http.ts +20 -3
- package/src/index.ts +10 -1
- package/src/kernel/adopt-barrel-fresh.test.ts +1 -1
- package/src/kernel/adopt-routes.ts +30 -2
- package/src/kernel/app.ts +131 -14
- package/src/kernel/auto-cache.test.ts +8 -8
- package/src/kernel/auto-registry.test.ts +49 -9
- package/src/kernel/boot.test.ts +4 -4
- package/src/kernel/boot.ts +2 -2
- package/src/kernel/effects-stamping.test.ts +3 -3
- package/src/kernel/element-registries.ts +12 -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.ts +23 -0
- 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 +4 -4
- 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/types.ts +13 -0
- package/src/release/measure.ts +2 -2
- 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 +8 -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/collapsible-DYb0xU8C.js +0 -1
- package/src/console/ui-next/dist/assets/flows-page-DxDsOd4f.js +0 -1
- package/src/console/ui-next/dist/assets/http-method-CJCBYL2j.js +0 -1
- package/src/console/ui-next/dist/assets/index-Ce6WKWKM.js +0 -66
- package/src/console/ui-next/dist/assets/index-D4Ldtj79.css +0 -2
- 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-DFLFfUUX.js +0 -2
- package/src/console/ui-next/dist/assets/units-page-C0gW6Kdo.js +0 -1
- package/src/console/ui-next/dist/assets/vault-page-DuKzqwzW.js +0 -2
|
@@ -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
|
});
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
type SqlStoreHandle,
|
|
30
30
|
type StoreRuntime,
|
|
31
31
|
} from "../../elements/store.ts";
|
|
32
|
+
import { RLS_CONTEXT_DRIVERS, type RlsIdentity } from "../../drivers/pg-rls.ts";
|
|
32
33
|
import { DryRunWriteIsolationError, withDryRun } from "../../kernel/dry-run.ts";
|
|
33
34
|
import { addPiiFieldName, piiNameAliases, PII_MASK } from "../../elements/store/classify.ts";
|
|
34
35
|
import { rankIndexHits } from "./index-search.ts";
|
|
@@ -54,6 +55,7 @@ import {
|
|
|
54
55
|
dropSqlPolicy,
|
|
55
56
|
applySqlTableRls,
|
|
56
57
|
listSqlCatalog,
|
|
58
|
+
listSqlTablePolicyCounts,
|
|
57
59
|
listSqlTableRls,
|
|
58
60
|
setSqlExtension,
|
|
59
61
|
setSqlRowSecurity,
|
|
@@ -107,6 +109,8 @@ export interface ConsoleStoreChild {
|
|
|
107
109
|
readonly columnDescriptions: Readonly<Record<string, string>>;
|
|
108
110
|
/** Live RLS (`pg_class.relrowsecurity`) when the engine can report it. */
|
|
109
111
|
readonly rls?: boolean;
|
|
112
|
+
/** Live + declared RLS policy count when the child is a SQL table. */
|
|
113
|
+
readonly rlsPolicyCount?: number;
|
|
110
114
|
}
|
|
111
115
|
|
|
112
116
|
/** One row in `console.store.list`. */
|
|
@@ -183,6 +187,7 @@ export async function projectStoresList(options: ProjectStoresOptions): Promise<
|
|
|
183
187
|
ref,
|
|
184
188
|
facet,
|
|
185
189
|
childrenOf(manifest!, name, facet, store),
|
|
190
|
+
store.tables,
|
|
186
191
|
);
|
|
187
192
|
const replicaLagMs = facet === "sql" ? latestReplicaLag(options.runs ?? [], children) : null;
|
|
188
193
|
|
|
@@ -291,14 +296,54 @@ async function withSqlTableRls(
|
|
|
291
296
|
ref: ResourceRef,
|
|
292
297
|
facet: StoreFacet,
|
|
293
298
|
children: readonly ConsoleStoreChild[],
|
|
299
|
+
tables?: NonNullable<Manifest["stores"]>[string]["tables"],
|
|
294
300
|
): Promise<readonly ConsoleStoreChild[]> {
|
|
295
|
-
|
|
301
|
+
const declared = declaredTableRls(tables);
|
|
302
|
+
const declaredCounts = declaredTablePolicyCounts(tables);
|
|
303
|
+
if (facet !== "sql" || runtime === null) {
|
|
304
|
+
return applySqlTableRls(children, new Map(), declared, declaredCounts);
|
|
305
|
+
}
|
|
296
306
|
try {
|
|
297
307
|
const sql = (await runtime.openRef(ref, { effects: { reads: [ref] } })) as SqlStoreHandle;
|
|
298
|
-
|
|
308
|
+
const policyCounts = mergePolicyCounts(await listSqlTablePolicyCounts(sql), declaredCounts);
|
|
309
|
+
return applySqlTableRls(children, await listSqlTableRls(sql), declared, policyCounts);
|
|
299
310
|
} catch {
|
|
300
|
-
return children;
|
|
311
|
+
return applySqlTableRls(children, new Map(), declared, declaredCounts);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function declaredTableRls(
|
|
316
|
+
tables: NonNullable<Manifest["stores"]>[string]["tables"] | undefined,
|
|
317
|
+
): ReadonlyMap<string, boolean> {
|
|
318
|
+
const out = new Map<string, boolean>();
|
|
319
|
+
for (const [name, table] of Object.entries(tables ?? {})) {
|
|
320
|
+
if (table.rls === true || Object.keys(table.policies ?? {}).length > 0) {
|
|
321
|
+
out.set(name, true);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return out;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function declaredTablePolicyCounts(
|
|
328
|
+
tables: NonNullable<Manifest["stores"]>[string]["tables"] | undefined,
|
|
329
|
+
): ReadonlyMap<string, number> {
|
|
330
|
+
const out = new Map<string, number>();
|
|
331
|
+
for (const [name, table] of Object.entries(tables ?? {})) {
|
|
332
|
+
const n = Object.keys(table.policies ?? {}).length;
|
|
333
|
+
if (n > 0) out.set(name, n);
|
|
301
334
|
}
|
|
335
|
+
return out;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function mergePolicyCounts(
|
|
339
|
+
live: ReadonlyMap<string, number>,
|
|
340
|
+
declared: ReadonlyMap<string, number>,
|
|
341
|
+
): Map<string, number> {
|
|
342
|
+
const out = new Map(declared);
|
|
343
|
+
for (const [name, n] of live) {
|
|
344
|
+
out.set(name, Math.max(n, out.get(name) ?? 0));
|
|
345
|
+
}
|
|
346
|
+
return out;
|
|
302
347
|
}
|
|
303
348
|
|
|
304
349
|
function columnDescriptionsFor(
|
|
@@ -411,6 +456,9 @@ export interface StoreQueryInput {
|
|
|
411
456
|
readonly q?: string;
|
|
412
457
|
readonly topK?: number;
|
|
413
458
|
readonly revealPii?: boolean;
|
|
459
|
+
readonly asGate?: string;
|
|
460
|
+
readonly asUserId?: string;
|
|
461
|
+
readonly rls?: RlsIdentity;
|
|
414
462
|
}
|
|
415
463
|
|
|
416
464
|
/** Query result shape. */
|
|
@@ -436,6 +484,11 @@ export interface StoreQueryResult {
|
|
|
436
484
|
readonly facetDistribution?: Record<string, Record<string, number>>;
|
|
437
485
|
readonly masked: boolean;
|
|
438
486
|
readonly routedRole?: "primary" | "replica";
|
|
487
|
+
readonly rls?: {
|
|
488
|
+
readonly gate: string | null;
|
|
489
|
+
readonly userId: string;
|
|
490
|
+
readonly applied: boolean;
|
|
491
|
+
};
|
|
439
492
|
}
|
|
440
493
|
|
|
441
494
|
const KV_TTL_RE = /^(\d+)(ms|s|m|h|d)$/;
|
|
@@ -508,18 +561,27 @@ export async function queryStore(
|
|
|
508
561
|
): Promise<StoreQueryResult> {
|
|
509
562
|
const [facet] = input.ref.split(":") as [StoreFacet, string];
|
|
510
563
|
const effects: Effects = { reads: [input.ref] };
|
|
564
|
+
const catalog = input.child ? sqlCatalogKind(input.child) : null;
|
|
565
|
+
const rls = catalog ? undefined : input.rls;
|
|
511
566
|
const handle = await runtime.openRef(input.ref, {
|
|
512
567
|
effects,
|
|
513
568
|
revealPii: input.revealPii === true,
|
|
569
|
+
...(rls ? { rls } : {}),
|
|
514
570
|
});
|
|
515
571
|
|
|
516
572
|
if (facet === "sql") {
|
|
517
573
|
const sql = handle as SqlStoreHandle;
|
|
518
574
|
const table = input.child;
|
|
575
|
+
const stamped = rlsStamp(sql.driverId, rls);
|
|
519
576
|
if (!table) {
|
|
520
|
-
return {
|
|
577
|
+
return {
|
|
578
|
+
facet,
|
|
579
|
+
rows: [],
|
|
580
|
+
masked: !input.revealPii,
|
|
581
|
+
routedRole: sql.routedRole,
|
|
582
|
+
...(stamped ? { rls: stamped } : {}),
|
|
583
|
+
};
|
|
521
584
|
}
|
|
522
|
-
const catalog = sqlCatalogKind(table);
|
|
523
585
|
if (catalog) {
|
|
524
586
|
const storeName = input.ref.split(":")[1] ?? "";
|
|
525
587
|
const rows = await listSqlCatalog(sql, catalog, manifest, storeName, input.limit ?? 200);
|
|
@@ -540,6 +602,7 @@ export async function queryStore(
|
|
|
540
602
|
rows,
|
|
541
603
|
masked: !input.revealPii,
|
|
542
604
|
routedRole: sql.routedRole,
|
|
605
|
+
...(stamped ? { rls: stamped } : {}),
|
|
543
606
|
};
|
|
544
607
|
} catch {
|
|
545
608
|
// Table may not exist yet — empty browse.
|
|
@@ -548,6 +611,7 @@ export async function queryStore(
|
|
|
548
611
|
rows: [],
|
|
549
612
|
masked: !input.revealPii,
|
|
550
613
|
routedRole: sql.routedRole,
|
|
614
|
+
...(stamped ? { rls: stamped } : {}),
|
|
551
615
|
};
|
|
552
616
|
}
|
|
553
617
|
}
|
|
@@ -765,6 +829,9 @@ export interface StoreEditInput {
|
|
|
765
829
|
readonly confirmation?: string;
|
|
766
830
|
readonly reason?: string;
|
|
767
831
|
readonly confirmed?: boolean;
|
|
832
|
+
readonly asGate?: string;
|
|
833
|
+
readonly asUserId?: string;
|
|
834
|
+
readonly rls?: RlsIdentity;
|
|
768
835
|
}
|
|
769
836
|
|
|
770
837
|
/** Result of a direct store edit (preview or applied). */
|
|
@@ -777,6 +844,11 @@ export interface StoreEditResult {
|
|
|
777
844
|
readonly kind: "send" | "ask";
|
|
778
845
|
readonly resource: string;
|
|
779
846
|
}>;
|
|
847
|
+
readonly rls?: {
|
|
848
|
+
readonly gate: string | null;
|
|
849
|
+
readonly userId: string;
|
|
850
|
+
readonly applied: boolean;
|
|
851
|
+
};
|
|
780
852
|
}
|
|
781
853
|
|
|
782
854
|
/**
|
|
@@ -818,6 +890,15 @@ export async function editStore(
|
|
|
818
890
|
willNotFire,
|
|
819
891
|
applied: false,
|
|
820
892
|
wouldHaveFired,
|
|
893
|
+
...(input.rls
|
|
894
|
+
? {
|
|
895
|
+
rls: {
|
|
896
|
+
gate: input.rls.gate,
|
|
897
|
+
userId: input.rls.userId,
|
|
898
|
+
applied: false,
|
|
899
|
+
},
|
|
900
|
+
}
|
|
901
|
+
: {}),
|
|
821
902
|
};
|
|
822
903
|
} catch (err) {
|
|
823
904
|
await restoreEditTarget(runtime, snapshot);
|
|
@@ -828,7 +909,12 @@ export async function editStore(
|
|
|
828
909
|
}
|
|
829
910
|
}
|
|
830
911
|
|
|
831
|
-
await applyEdit(
|
|
912
|
+
const appliedRls = await applyEdit(
|
|
913
|
+
runtime,
|
|
914
|
+
input,
|
|
915
|
+
{ revealPii: false, persistMeta: true },
|
|
916
|
+
manifest,
|
|
917
|
+
);
|
|
832
918
|
// Direct edit: invalidate cache for written resource (effects-derived).
|
|
833
919
|
runtime.onWriteEffects({ writes: [effectRef] });
|
|
834
920
|
void options.production;
|
|
@@ -838,6 +924,7 @@ export async function editStore(
|
|
|
838
924
|
willNotFire,
|
|
839
925
|
applied: true,
|
|
840
926
|
wouldHaveFired: [],
|
|
927
|
+
...(appliedRls ? { rls: appliedRls } : {}),
|
|
841
928
|
};
|
|
842
929
|
}
|
|
843
930
|
|
|
@@ -1125,11 +1212,14 @@ async function applyEdit(
|
|
|
1125
1212
|
input: StoreEditInput,
|
|
1126
1213
|
ctx: { readonly revealPii: boolean; readonly persistMeta?: boolean },
|
|
1127
1214
|
manifest?: Manifest | null,
|
|
1128
|
-
): Promise<
|
|
1215
|
+
): Promise<StoreEditResult["rls"] | undefined> {
|
|
1129
1216
|
const [facet] = input.ref.split(":") as [StoreFacet, string];
|
|
1217
|
+
const catalog = input.child ? sqlCatalogKind(input.child) : null;
|
|
1218
|
+
const rls = catalog ? undefined : input.rls;
|
|
1130
1219
|
const handle = await runtime.openRef(input.ref, {
|
|
1131
1220
|
effects: { writes: [input.ref] },
|
|
1132
1221
|
revealPii: ctx.revealPii,
|
|
1222
|
+
...(rls ? { rls } : {}),
|
|
1133
1223
|
});
|
|
1134
1224
|
|
|
1135
1225
|
if (facet === "files") {
|
|
@@ -1242,12 +1332,12 @@ async function applyEdit(
|
|
|
1242
1332
|
`INSERT INTO "${table}" (${columns}) VALUES (${placeholders})`,
|
|
1243
1333
|
sets.map((c) => input.patch[c]),
|
|
1244
1334
|
);
|
|
1245
|
-
return;
|
|
1335
|
+
return rlsStamp(sql.driverId, rls);
|
|
1246
1336
|
}
|
|
1247
1337
|
const assignments = sets.map((c) => `"${c}" = ?`).join(", ");
|
|
1248
1338
|
const params = [...sets.map((c) => input.patch[c]), id];
|
|
1249
1339
|
await sql.raw(`UPDATE "${table}" SET ${assignments} WHERE "id" = ?`, params);
|
|
1250
|
-
return;
|
|
1340
|
+
return rlsStamp(sql.driverId, rls);
|
|
1251
1341
|
}
|
|
1252
1342
|
|
|
1253
1343
|
throw new Error(`direct edit not supported for facet ${facet}`);
|
|
@@ -1383,7 +1473,23 @@ export function isKnownConsoleSqlGate(manifest: Manifest | null, name: string):
|
|
|
1383
1473
|
return gate.kind !== "rate" && !name.startsWith("rate:");
|
|
1384
1474
|
}
|
|
1385
1475
|
|
|
1386
|
-
|
|
1476
|
+
function rlsStamp(
|
|
1477
|
+
driverId: string,
|
|
1478
|
+
identity: RlsIdentity | undefined,
|
|
1479
|
+
):
|
|
1480
|
+
| {
|
|
1481
|
+
readonly gate: string | null;
|
|
1482
|
+
readonly userId: string;
|
|
1483
|
+
readonly applied: boolean;
|
|
1484
|
+
}
|
|
1485
|
+
| undefined {
|
|
1486
|
+
if (!identity) return undefined;
|
|
1487
|
+
return {
|
|
1488
|
+
gate: identity.gate,
|
|
1489
|
+
userId: identity.userId,
|
|
1490
|
+
applied: RLS_CONTEXT_DRIVERS.has(driverId),
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1387
1493
|
|
|
1388
1494
|
/** DML / DDL heads — keep in sync with `isSqlWrite` in the query console. */
|
|
1389
1495
|
const STORE_SQL_WRITE_HEADS = new Set([
|
|
@@ -1434,72 +1540,50 @@ export async function runStoreSql(
|
|
|
1434
1540
|
readonly allowWrite: boolean;
|
|
1435
1541
|
readonly revealPii?: boolean;
|
|
1436
1542
|
readonly tenant?: string;
|
|
1437
|
-
/** View rows as this Gate (`oke.gate`
|
|
1543
|
+
/** View rows as this Gate (`oke.gate` on postgres / pglite). */
|
|
1438
1544
|
readonly asGate?: string;
|
|
1545
|
+
readonly asUserId?: string;
|
|
1546
|
+
readonly rls?: RlsIdentity;
|
|
1439
1547
|
},
|
|
1440
1548
|
): Promise<{
|
|
1441
1549
|
readonly rows: readonly Record<string, unknown>[];
|
|
1442
1550
|
readonly masked: boolean;
|
|
1443
1551
|
readonly routedRole: "primary" | "replica";
|
|
1444
1552
|
readonly asGate: string | null;
|
|
1553
|
+
readonly asUserId: string | null;
|
|
1445
1554
|
readonly gateApplied: boolean;
|
|
1555
|
+
readonly rls: {
|
|
1556
|
+
readonly gate: string | null;
|
|
1557
|
+
readonly userId: string;
|
|
1558
|
+
readonly applied: boolean;
|
|
1559
|
+
};
|
|
1446
1560
|
}> {
|
|
1447
1561
|
const trimmed = sqlText.trim().replace(/;+\s*$/, "");
|
|
1448
1562
|
const isWrite = isStoreSqlWrite(trimmed);
|
|
1449
1563
|
if (isWrite && !options.allowWrite) {
|
|
1450
1564
|
throw new Error("SQL console is read-only by default — write requires console:store.sql:write");
|
|
1451
1565
|
}
|
|
1566
|
+
const rls = options.rls;
|
|
1452
1567
|
const handle = (await runtime.openRef(ref, {
|
|
1453
1568
|
effects: isWrite ? { writes: [ref] } : { reads: [ref] },
|
|
1454
1569
|
revealPii: options.revealPii === true,
|
|
1570
|
+
...(rls ? { rls } : {}),
|
|
1455
1571
|
})) as SqlStoreHandle;
|
|
1456
|
-
const
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
try {
|
|
1472
|
-
await handle.raw("ROLLBACK");
|
|
1473
|
-
} catch {
|
|
1474
|
-
// Connection may already be idle after a failed SET.
|
|
1475
|
-
}
|
|
1476
|
-
}
|
|
1477
|
-
throw err;
|
|
1478
|
-
}
|
|
1479
|
-
}
|
|
1480
|
-
|
|
1481
|
-
/**
|
|
1482
|
-
* Open a transaction and set `oke.gate` + `row_security` for RLS simulation.
|
|
1483
|
-
* No-op on memory SQL.
|
|
1484
|
-
*
|
|
1485
|
-
* @param handle - Open SQL handle
|
|
1486
|
-
* @param gate - Validated Gate name
|
|
1487
|
-
*/
|
|
1488
|
-
async function applySqlGateContext(handle: SqlStoreHandle, gate: string): Promise<boolean> {
|
|
1489
|
-
if (!GATE_CONTEXT_DRIVERS.has(handle.driverId)) return false;
|
|
1490
|
-
try {
|
|
1491
|
-
await handle.raw("BEGIN");
|
|
1492
|
-
await handle.raw("SET LOCAL row_security = on");
|
|
1493
|
-
await handle.raw("SELECT set_config('oke.gate', ?, true)", [gate]);
|
|
1494
|
-
return true;
|
|
1495
|
-
} catch {
|
|
1496
|
-
try {
|
|
1497
|
-
await handle.raw("ROLLBACK");
|
|
1498
|
-
} catch {
|
|
1499
|
-
// ignore
|
|
1500
|
-
}
|
|
1501
|
-
return false;
|
|
1502
|
-
}
|
|
1572
|
+
const stamped = rlsStamp(handle.driverId, rls) ?? {
|
|
1573
|
+
gate: rls?.gate ?? options.asGate?.trim() ?? null,
|
|
1574
|
+
userId: rls?.userId ?? options.asUserId?.trim() ?? "",
|
|
1575
|
+
applied: false,
|
|
1576
|
+
};
|
|
1577
|
+
const rows = await handle.raw(trimmed);
|
|
1578
|
+
return {
|
|
1579
|
+
rows,
|
|
1580
|
+
masked: !options.revealPii,
|
|
1581
|
+
routedRole: handle.routedRole,
|
|
1582
|
+
asGate: stamped.gate,
|
|
1583
|
+
asUserId: stamped.userId.length > 0 ? stamped.userId : options.asUserId?.trim() || null,
|
|
1584
|
+
gateApplied: stamped.applied,
|
|
1585
|
+
rls: stamped,
|
|
1586
|
+
};
|
|
1503
1587
|
}
|
|
1504
1588
|
|
|
1505
1589
|
/**
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{wr as e}from"./shortcut-keys-CoqwEIi0.js";import{A as t}from"./xyflow-D7n4g6go.js";import{V as n,n as r,t as i}from"./react-D8E3mtu1.js";var a=t();function o({open:t,openHeight:o=`auto`,className:s,style:c,transition:l,...u}){let d=r()??!1;return(0,a.jsx)(i.div,{...u,"aria-hidden":!t,inert:!t||void 0,initial:!1,animate:d?{opacity:+!!t}:{opacity:+!!t,clipPath:t?`inset(0 0 0% 0)`:`inset(0 0 100% 0)`,y:t?0:-4},transition:l??{duration:d?0:t?.22:.14,ease:n},className:e(`overflow-hidden`,s),style:{...c,height:t?o:0,pointerEvents:t?void 0:`none`,transformOrigin:`top`}})}export{o as t};
|