@opengeni/db 0.7.3 → 0.9.3

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 (59) hide show
  1. package/dist/{chunk-B22X3IEZ.js → chunk-4LG5NBTC.js} +512 -32
  2. package/dist/chunk-4LG5NBTC.js.map +1 -0
  3. package/dist/{chunk-YFQ7SGE4.js → chunk-BMFDXFPA.js} +23 -1
  4. package/dist/chunk-BMFDXFPA.js.map +1 -0
  5. package/dist/chunk-KW526IJA.js +127 -0
  6. package/dist/chunk-KW526IJA.js.map +1 -0
  7. package/dist/index.d.ts +3 -3
  8. package/dist/index.js +6068 -2090
  9. package/dist/index.js.map +1 -1
  10. package/dist/migrate.d.ts +29 -4
  11. package/dist/migrate.js +3 -1
  12. package/dist/provision-roles.d.ts +721 -46
  13. package/dist/provision-roles.js +1 -1
  14. package/dist/{schema-BN5mB9xZ.d.ts → schema-CdPGTHlD.d.ts} +2118 -119
  15. package/dist/schema.d.ts +1 -1
  16. package/dist/schema.js +17 -1
  17. package/drizzle/0064_rotation_strategy_sharded_backfill.sql +15 -0
  18. package/drizzle/0065_session_attempt_quiescence.sql +26 -0
  19. package/drizzle/0066_session_interruption_attempt_lookup.sql +4 -0
  20. package/drizzle/0067_session_event_payload_bounds.sql +209 -0
  21. package/drizzle/0068_workspace_control_event_bounds.sql +134 -0
  22. package/drizzle/0069_session_event_history_backfill.sql +32 -0
  23. package/drizzle/0070_session_event_type_sequence_lookup.sql +4 -0
  24. package/drizzle/0071_session_event_monitoring_tail.sql +10 -0
  25. package/drizzle/0072_sessions_workspace_created_id_idx.sql +4 -0
  26. package/drizzle/0073_sessions_workspace_updated_id_idx.sql +4 -0
  27. package/drizzle/0074_session_activity_revisions.sql +72 -0
  28. package/drizzle/0075_sessions_workspace_activity_revision_idx.sql +4 -0
  29. package/drizzle/0076_session_workflow_wake_acl.sql +13 -0
  30. package/drizzle/0077_session_attempt_latest_lookup.sql +4 -0
  31. package/drizzle/0094_quarantine_credential_bearing_catalog_urls.sql +51 -0
  32. package/drizzle/0095_github_existing_installations.sql +84 -0
  33. package/drizzle/0096_session_turn_initiators.sql +97 -0
  34. package/drizzle/0097_host_export_outbox.sql +1220 -0
  35. package/drizzle/0098_usage_events_workspace_session_idx.sql +4 -0
  36. package/drizzle/0099_session_human_input_attempt_owner_index.sql +6 -0
  37. package/drizzle/0100_session_human_input_requests.sql +89 -0
  38. package/drizzle/0101_session_mcp_connection_refs.sql +30 -0
  39. package/drizzle/0102_session_command_receipt_service_actor.sql +16 -0
  40. package/drizzle/0103_host_export_root_session.sql +166 -0
  41. package/drizzle/0104_host_export_root_session_backfill.sql +27 -0
  42. package/drizzle/0105_session_turn_instructions.sql +9 -0
  43. package/package.json +4 -4
  44. package/src/connection-token-resolver.ts +287 -1
  45. package/src/event-payload-sanitizer.ts +57 -19
  46. package/src/index.ts +5377 -1125
  47. package/src/memory-domain.ts +1 -1
  48. package/src/migrate.ts +86 -23
  49. package/src/persistence-errors.ts +252 -0
  50. package/src/provision-roles.ts +42 -0
  51. package/src/schema.ts +552 -34
  52. package/src/session-control.ts +518 -38
  53. package/src/session-queue-commands.ts +308 -57
  54. package/src/session-tool-call-settlement.ts +58 -7
  55. package/src/turn-initiator.ts +155 -0
  56. package/dist/chunk-7LDU7F5P.js +0 -80
  57. package/dist/chunk-7LDU7F5P.js.map +0 -1
  58. package/dist/chunk-B22X3IEZ.js.map +0 -1
  59. package/dist/chunk-YFQ7SGE4.js.map +0 -1
@@ -1,4 +1,5 @@
1
1
  import type { SessionEvent } from "@opengeni/contracts";
2
+ import { boundModelToolOutputItem } from "@opengeni/codex";
2
3
  import { and, asc, eq, sql } from "drizzle-orm";
3
4
  import type { Database } from "./index";
4
5
  import { sanitizeEventPayload, sanitizeModelPayload } from "./event-payload-sanitizer";
@@ -129,14 +130,17 @@ export async function closePendingSessionToolCallsInTransaction(
129
130
  if (pending.length === 0) return { sequence: input.sequence, events: [], closed: 0 };
130
131
 
131
132
  const history = await tx
132
- .select({ item: schema.sessionHistoryItems.item })
133
+ .select({
134
+ position: schema.sessionHistoryItems.position,
135
+ item: schema.sessionHistoryItems.item,
136
+ active: schema.sessionHistoryItems.active,
137
+ })
133
138
  .from(schema.sessionHistoryItems)
134
139
  .where(
135
140
  and(
136
141
  eq(schema.sessionHistoryItems.workspaceId, input.workspaceId),
137
142
  eq(schema.sessionHistoryItems.sessionId, input.sessionId),
138
143
  eq(schema.sessionHistoryItems.turnId, input.turnId),
139
- eq(schema.sessionHistoryItems.active, true),
140
144
  ),
141
145
  )
142
146
  .orderBy(asc(schema.sessionHistoryItems.position));
@@ -149,6 +153,20 @@ export async function closePendingSessionToolCallsInTransaction(
149
153
  eq(schema.sessionHistoryItems.sessionId, input.sessionId),
150
154
  ),
151
155
  );
156
+ const existingOutputEvents = await tx
157
+ .select({ callId: sql<string | null>`${schema.sessionEvents.payload} ->> 'id'` })
158
+ .from(schema.sessionEvents)
159
+ .where(
160
+ and(
161
+ eq(schema.sessionEvents.workspaceId, input.workspaceId),
162
+ eq(schema.sessionEvents.sessionId, input.sessionId),
163
+ eq(schema.sessionEvents.turnId, input.turnId),
164
+ eq(schema.sessionEvents.type, "agent.toolCall.output"),
165
+ ),
166
+ );
167
+ const projectedCallIds = new Set(
168
+ existingOutputEvents.flatMap(({ callId }) => (callId ? [callId] : [])),
169
+ );
152
170
  let nextPosition = Math.floor(Number(maxPosition)) + 1;
153
171
  let sequence = input.sequence;
154
172
  const historyValues: Array<typeof schema.sessionHistoryItems.$inferInsert> = [];
@@ -160,7 +178,23 @@ export async function closePendingSessionToolCallsInTransaction(
160
178
  );
161
179
  const existingResult = resultType
162
180
  ? history.find(
163
- ({ item }) => historyItemType(item) === resultType && historyCallId(item) === call.callId,
181
+ ({ item, position }) =>
182
+ position > (existingCall?.position ?? Number.MAX_SAFE_INTEGER) &&
183
+ historyItemType(item) === resultType &&
184
+ historyCallId(item) === call.callId,
185
+ )
186
+ : undefined;
187
+ const activeCall = history.find(
188
+ ({ item, active }) =>
189
+ active && historyItemType(item) === call.callType && historyCallId(item) === call.callId,
190
+ );
191
+ const activeResult = resultType
192
+ ? history.find(
193
+ ({ item, active, position }) =>
194
+ active &&
195
+ position > (activeCall?.position ?? Number.MAX_SAFE_INTEGER) &&
196
+ historyItemType(item) === resultType &&
197
+ historyCallId(item) === call.callId,
164
198
  )
165
199
  : undefined;
166
200
  const interruptedResult = interruptedToolCallResult({
@@ -173,6 +207,12 @@ export async function closePendingSessionToolCallsInTransaction(
173
207
  call,
174
208
  existingCall,
175
209
  existingResult,
210
+ activeCall,
211
+ activeResult,
212
+ completeDurablePair: Boolean(existingCall && existingResult),
213
+ supersededDurablePair: Boolean(
214
+ existingCall && existingResult && (!existingCall.active || !existingResult.active),
215
+ ),
176
216
  rawCallIsValid: historyItemType(call.callItem) === call.callType,
177
217
  result: existingResult?.item ?? call.resultItem ?? interruptedResult,
178
218
  interrupted: !existingResult && !call.resultItem,
@@ -181,8 +221,8 @@ export async function closePendingSessionToolCallsInTransaction(
181
221
 
182
222
  for (const resolution of resolutions) {
183
223
  if (
184
- !resolution.existingResult &&
185
- !resolution.existingCall &&
224
+ !resolution.completeDurablePair &&
225
+ !resolution.activeCall &&
186
226
  resolution.result &&
187
227
  resolution.rawCallIsValid
188
228
  ) {
@@ -203,17 +243,28 @@ export async function closePendingSessionToolCallsInTransaction(
203
243
  (right.call.resultRecordedAt?.getTime() ?? Number.MAX_SAFE_INTEGER),
204
244
  );
205
245
  for (const resolution of orderedResults) {
206
- if (!resolution.existingResult && resolution.result && resolution.rawCallIsValid) {
246
+ if (
247
+ !resolution.completeDurablePair &&
248
+ !resolution.activeResult &&
249
+ resolution.result &&
250
+ resolution.rawCallIsValid
251
+ ) {
207
252
  historyValues.push({
208
253
  accountId: input.accountId,
209
254
  workspaceId: input.workspaceId,
210
255
  sessionId: input.sessionId,
211
256
  turnId: input.turnId,
212
257
  position: nextPosition++,
213
- item: sanitizeModelPayload(resolution.result),
258
+ item: sanitizeModelPayload(boundModelToolOutputItem(resolution.result)),
214
259
  active: true,
215
260
  });
216
261
  }
262
+ // A pair superseded by compaction was already projected during its live
263
+ // response. Reactivating or re-emitting it would undo the checkpoint and
264
+ // duplicate the UI output. An active complete pair is different: it can be
265
+ // the crash point after model-memory persistence but before event publish,
266
+ // so it keeps the existing durable recovery projection below.
267
+ if (resolution.supersededDurablePair || projectedCallIds.has(resolution.call.callId)) continue;
217
268
  eventValues.push({
218
269
  accountId: input.accountId,
219
270
  workspaceId: input.workspaceId,
@@ -0,0 +1,155 @@
1
+ import {
2
+ UNATTRIBUTED_LEGACY_INITIATOR_SUBJECT_ID,
3
+ type TurnInitiator,
4
+ type TurnInitiatorContext,
5
+ } from "@opengeni/contracts";
6
+ import { and, eq } from "drizzle-orm";
7
+ import type { Database } from "./index";
8
+ import type { SessionCommandActor } from "./session-control";
9
+ import * as schema from "./schema";
10
+
11
+ export const UNATTRIBUTED_LEGACY_INITIATOR: TurnInitiator = {
12
+ kind: "service",
13
+ subjectId: UNATTRIBUTED_LEGACY_INITIATOR_SUBJECT_ID,
14
+ };
15
+
16
+ export type FrozenTurnInitiator = {
17
+ initiator: TurnInitiator;
18
+ context: TurnInitiatorContext;
19
+ };
20
+
21
+ const MAX_AGENT_PROVENANCE_HOPS = 32;
22
+
23
+ /**
24
+ * Bound agent provenance without discarding its causal authority. Once the
25
+ * chain exceeds the cap, retain the first hop and the newest hops in order;
26
+ * consumers can still identify the root attempt while recent diagnostics stay
27
+ * useful. The omitted middle is signalled separately by `viaTruncated`.
28
+ */
29
+ export function clipAgentProvenanceHops(
30
+ hops: Array<Record<string, unknown>>,
31
+ ): Array<Record<string, unknown>> {
32
+ if (hops.length <= MAX_AGENT_PROVENANCE_HOPS) return hops;
33
+ return [hops[0]!, ...hops.slice(-(MAX_AGENT_PROVENANCE_HOPS - 1))];
34
+ }
35
+
36
+ export function initiatorContextForStorage(
37
+ initiator: TurnInitiator,
38
+ context: TurnInitiatorContext = {},
39
+ ): TurnInitiatorContext {
40
+ return initiator.label ? { ...context, label: initiator.label } : { ...context };
41
+ }
42
+
43
+ export function initiatorFromStorage(
44
+ kind: string,
45
+ subjectId: string,
46
+ context: TurnInitiatorContext,
47
+ ): TurnInitiator {
48
+ const label =
49
+ typeof context.label === "string" && context.label.length > 0 ? context.label : null;
50
+ return {
51
+ kind: kind === "subject" ? "subject" : "service",
52
+ subjectId,
53
+ ...(label ? { label } : {}),
54
+ };
55
+ }
56
+
57
+ export function initiatorColumns(value: FrozenTurnInitiator): {
58
+ initiatorKind: TurnInitiator["kind"];
59
+ initiatorSubjectId: string;
60
+ initiatorContext: TurnInitiatorContext;
61
+ } {
62
+ return {
63
+ initiatorKind: value.initiator.kind,
64
+ initiatorSubjectId: value.initiator.subjectId,
65
+ initiatorContext: initiatorContextForStorage(value.initiator, value.context),
66
+ };
67
+ }
68
+
69
+ export function creatorColumns(value: FrozenTurnInitiator): {
70
+ createdByKind: TurnInitiator["kind"];
71
+ createdBySubjectId: string;
72
+ createdByContext: TurnInitiatorContext;
73
+ } {
74
+ return {
75
+ createdByKind: value.initiator.kind,
76
+ createdBySubjectId: value.initiator.subjectId,
77
+ createdByContext: initiatorContextForStorage(value.initiator, value.context),
78
+ };
79
+ }
80
+
81
+ function validAgentHops(value: unknown): Array<Record<string, unknown>> {
82
+ if (!Array.isArray(value)) return [];
83
+ return value.filter(
84
+ (hop): hop is Record<string, unknown> =>
85
+ typeof hop === "object" && hop !== null && !Array.isArray(hop),
86
+ );
87
+ }
88
+
89
+ export async function frozenInitiatorForCommandActor(
90
+ db: Database,
91
+ workspaceId: string,
92
+ actor: SessionCommandActor,
93
+ subjectLabel?: string,
94
+ ): Promise<FrozenTurnInitiator> {
95
+ if (actor.type === "service") {
96
+ return {
97
+ initiator: {
98
+ kind: "service",
99
+ subjectId: actor.subjectId,
100
+ ...(actor.subjectLabel ? { label: actor.subjectLabel } : {}),
101
+ },
102
+ context: { ...(actor.context ?? {}) },
103
+ };
104
+ }
105
+ if (actor.type !== "agent_attempt") {
106
+ return {
107
+ initiator: {
108
+ kind: "subject",
109
+ subjectId: actor.subjectId,
110
+ ...(subjectLabel ? { label: subjectLabel } : {}),
111
+ },
112
+ context: {},
113
+ };
114
+ }
115
+
116
+ const [turn] = await db
117
+ .select({
118
+ initiatorKind: schema.sessionTurns.initiatorKind,
119
+ initiatorSubjectId: schema.sessionTurns.initiatorSubjectId,
120
+ initiatorContext: schema.sessionTurns.initiatorContext,
121
+ })
122
+ .from(schema.sessionTurns)
123
+ .where(
124
+ and(
125
+ eq(schema.sessionTurns.workspaceId, workspaceId),
126
+ eq(schema.sessionTurns.sessionId, actor.sessionId),
127
+ eq(schema.sessionTurns.id, actor.turnId),
128
+ ),
129
+ )
130
+ .limit(1);
131
+ if (!turn) {
132
+ throw new Error(`Agent initiator turn not found: ${actor.turnId}`);
133
+ }
134
+ const storedContext = turn.initiatorContext ?? {};
135
+ const inheritedHops = validAgentHops(storedContext.via);
136
+ const hops = [
137
+ ...inheritedHops,
138
+ {
139
+ kind: "agent",
140
+ sessionId: actor.sessionId,
141
+ turnId: actor.turnId,
142
+ attemptId: actor.attemptId,
143
+ executionGeneration: actor.executionGeneration,
144
+ },
145
+ ];
146
+ const clipped = clipAgentProvenanceHops(hops);
147
+ return {
148
+ initiator: initiatorFromStorage(turn.initiatorKind, turn.initiatorSubjectId, storedContext),
149
+ context: {
150
+ ...storedContext,
151
+ via: clipped,
152
+ ...(hops.length > clipped.length ? { viaTruncated: true } : {}),
153
+ },
154
+ };
155
+ }
@@ -1,80 +0,0 @@
1
- // src/migrate.ts
2
- import { readdir, readFile } from "fs/promises";
3
- import { dirname, join } from "path";
4
- import { fileURLToPath } from "url";
5
- import postgres from "postgres";
6
- var DEFAULT_DATABASE_URL = "postgres://opengeni:opengeni@127.0.0.1:5432/opengeni";
7
- var concurrentIndexDirective = /^-- opengeni:concurrent-index lock-timeout=(\d+(?:ms|s|min))$/;
8
- function assertIdentifier(name, value) {
9
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
10
- throw new Error(`${name} is not a valid Postgres identifier: ${value}`);
11
- }
12
- return value;
13
- }
14
- async function executeMigrationFile(sql, file, sqlText) {
15
- const [firstLine = "", ...remainingLines] = sqlText.replaceAll("\r\n", "\n").split("\n");
16
- const directive = concurrentIndexDirective.exec(firstLine.trim());
17
- if (!directive) {
18
- if (firstLine.trim().startsWith("-- opengeni:")) {
19
- throw new Error(`Unsupported OpenGeni migration directive in ${file}`);
20
- }
21
- await sql.unsafe(sqlText);
22
- return;
23
- }
24
- const lockTimeout = directive[1];
25
- const statement = remainingLines.join("\n").trim();
26
- const withoutTrailingSemicolon = statement.endsWith(";") ? statement.slice(0, -1).trimEnd() : statement;
27
- if (!/^CREATE\s+(?:UNIQUE\s+)?INDEX\s+CONCURRENTLY\b/is.test(withoutTrailingSemicolon) || withoutTrailingSemicolon.includes(";")) {
28
- throw new Error(
29
- `${file}: opengeni:concurrent-index requires exactly one CREATE [UNIQUE] INDEX CONCURRENTLY statement`
30
- );
31
- }
32
- await sql`select set_config('lock_timeout', ${lockTimeout}, false)`;
33
- try {
34
- await sql.unsafe(statement);
35
- } finally {
36
- await sql`select set_config('lock_timeout', '0', false)`;
37
- }
38
- }
39
- async function migrate(databaseUrl = process.env.OPENGENI_MIGRATIONS_DATABASE_URL ?? process.env.OPENGENI_DATABASE_URL ?? DEFAULT_DATABASE_URL, schema = process.env.OPENGENI_DB_SCHEMA?.trim() || void 0) {
40
- const migrationsDir = join(dirname(fileURLToPath(import.meta.url)), "../drizzle");
41
- const files = (await readdir(migrationsDir)).filter((file) => file.endsWith(".sql")).sort();
42
- const sql = postgres(databaseUrl, { max: 1 });
43
- try {
44
- await sql`SELECT pg_advisory_lock(727458)`;
45
- if (schema) {
46
- assertIdentifier("OPENGENI_DB_SCHEMA", schema);
47
- await sql.unsafe(`CREATE SCHEMA IF NOT EXISTS "${schema}"`);
48
- await sql.unsafe(`CREATE SCHEMA IF NOT EXISTS "opengeni_private"`);
49
- await sql.unsafe(`SET search_path = "${schema}", "opengeni_private", "public"`);
50
- }
51
- await sql.unsafe(
52
- `CREATE TABLE IF NOT EXISTS "schema_migrations" ("name" text PRIMARY KEY, "applied_at" timestamptz NOT NULL DEFAULT now())`
53
- );
54
- const appliedRows = await sql`SELECT "name" FROM "schema_migrations"`;
55
- const applied = new Set(appliedRows.map((row) => row.name));
56
- for (const file of files) {
57
- if (applied.has(file)) {
58
- continue;
59
- }
60
- const sqlText = await readFile(join(migrationsDir, file), "utf8");
61
- await executeMigrationFile(sql, file, sqlText);
62
- await sql`INSERT INTO "schema_migrations" ("name") VALUES (${file}) ON CONFLICT DO NOTHING`;
63
- }
64
- } finally {
65
- await sql.end();
66
- }
67
- }
68
- async function runMigrations(adminConnection, targetSchema) {
69
- await migrate(adminConnection, targetSchema);
70
- }
71
- if (import.meta.main) {
72
- await migrate();
73
- console.log("Applied Drizzle SQL migrations.");
74
- }
75
-
76
- export {
77
- migrate,
78
- runMigrations
79
- };
80
- //# sourceMappingURL=chunk-7LDU7F5P.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/migrate.ts"],"sourcesContent":["import { readdir, readFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport postgres from \"postgres\";\n\nconst DEFAULT_DATABASE_URL = \"postgres://opengeni:opengeni@127.0.0.1:5432/opengeni\";\nconst concurrentIndexDirective = /^-- opengeni:concurrent-index lock-timeout=(\\d+(?:ms|s|min))$/;\n\n/** A bare Postgres identifier (schema/role name) safe to interpolate into DDL. */\nfunction assertIdentifier(name: string, value: string): string {\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {\n throw new Error(`${name} is not a valid Postgres identifier: ${value}`);\n }\n return value;\n}\n\n/**\n * Most migration files intentionally execute as one implicit transaction.\n * PostgreSQL forbids CREATE INDEX CONCURRENTLY there, so a migration may opt\n * into one narrowly validated transactionless statement with:\n *\n * -- opengeni:concurrent-index lock-timeout=5s\n * CREATE [UNIQUE] INDEX CONCURRENTLY ...;\n *\n * The directive is deliberately not a generic \"no transaction\" escape hatch:\n * only one concurrent-index statement is accepted, and lock acquisition is\n * always bounded. This keeps additive large-table indexes online without making\n * arbitrary partially-applied migration scripts possible.\n */\nasync function executeMigrationFile(\n sql: postgres.Sql,\n file: string,\n sqlText: string,\n): Promise<void> {\n const [firstLine = \"\", ...remainingLines] = sqlText.replaceAll(\"\\r\\n\", \"\\n\").split(\"\\n\");\n const directive = concurrentIndexDirective.exec(firstLine.trim());\n if (!directive) {\n if (firstLine.trim().startsWith(\"-- opengeni:\")) {\n throw new Error(`Unsupported OpenGeni migration directive in ${file}`);\n }\n await sql.unsafe(sqlText);\n return;\n }\n\n const lockTimeout = directive[1]!;\n const statement = remainingLines.join(\"\\n\").trim();\n const withoutTrailingSemicolon = statement.endsWith(\";\")\n ? statement.slice(0, -1).trimEnd()\n : statement;\n if (\n !/^CREATE\\s+(?:UNIQUE\\s+)?INDEX\\s+CONCURRENTLY\\b/is.test(withoutTrailingSemicolon) ||\n withoutTrailingSemicolon.includes(\";\")\n ) {\n throw new Error(\n `${file}: opengeni:concurrent-index requires exactly one CREATE [UNIQUE] INDEX CONCURRENTLY statement`,\n );\n }\n\n await sql`select set_config('lock_timeout', ${lockTimeout}, false)`;\n try {\n await sql.unsafe(statement);\n } finally {\n await sql`select set_config('lock_timeout', '0', false)`;\n }\n}\n\n/**\n * Apply the OpenGeni SQL migration chain.\n *\n * STANDALONE (default, unchanged): `migrate()` / `migrate(databaseUrl)` runs the\n * whole chain with NO search_path manipulation, so every unqualified\n * table/index/policy lands in the server default schema (`public`). This is the\n * byte-for-byte historical behavior — the migration test suite calls\n * `migrate(DB_URL)` and is unaffected.\n *\n * EMBEDDED (Step I, §7.8 runtime/SDK half): pass a `schema` (or set\n * `OPENGENI_DB_SCHEMA`). The migrate session then `CREATE SCHEMA IF NOT EXISTS`\n * for both `<schema>` and `opengeni_private`, and sets\n * `search_path = \"<schema>\", \"opengeni_private\", \"public\"`, so EVERY unqualified\n * DDL statement lands in the dedicated schema with NO per-statement SQL rewrite\n * (the SPIKE-1 F1 result). Two things make this work and stay idempotent:\n * 1. The policy-existence guards in the migration SQL use `current_schema()`\n * (not a hardcoded `'public'`) — so a re-run finds the policy it already\n * created in `<schema>` and DROP/CREATEs idempotently instead of failing\n * with \"policy already exists\". (This guard substitution is the migrate-\n * time enabler for the runtime search_path approach; without it the SDK\n * entry point silently fails on re-run — the Fork-6 hazard.)\n * 2. `public` stays LAST on the path so `gen_random_uuid()` (pgcrypto) and the\n * `vector` type — both installed into `public` by 0000 — still resolve. The\n * `opengeni_private.*` helpers are always called with an absolute prefix.\n *\n * `OPENGENI_DB_SCHEMA` defaults UNSET → `public` → standalone, so the default\n * binding never regresses.\n */\nexport async function migrate(\n databaseUrl = process.env.OPENGENI_MIGRATIONS_DATABASE_URL ??\n process.env.OPENGENI_DATABASE_URL ??\n DEFAULT_DATABASE_URL,\n schema: string | undefined = process.env.OPENGENI_DB_SCHEMA?.trim() || undefined,\n): Promise<void> {\n const migrationsDir = join(dirname(fileURLToPath(import.meta.url)), \"../drizzle\");\n const files = (await readdir(migrationsDir)).filter((file) => file.endsWith(\".sql\")).sort();\n const sql = postgres(databaseUrl, { max: 1 });\n try {\n // Serialize concurrent migrate() runs; the session-level lock is released\n // when the connection closes.\n await sql`SELECT pg_advisory_lock(727458)`;\n if (schema) {\n assertIdentifier(\"OPENGENI_DB_SCHEMA\", schema);\n await sql.unsafe(`CREATE SCHEMA IF NOT EXISTS \"${schema}\"`);\n // opengeni_private is also created by 0001 with an absolute prefix, but the\n // session search_path must already resolve it for the policy predicates\n // and the SECURITY DEFINER functions that inherit the caller's path.\n await sql.unsafe(`CREATE SCHEMA IF NOT EXISTS \"opengeni_private\"`);\n await sql.unsafe(`SET search_path = \"${schema}\", \"opengeni_private\", \"public\"`);\n }\n await sql.unsafe(\n `CREATE TABLE IF NOT EXISTS \"schema_migrations\" (\"name\" text PRIMARY KEY, \"applied_at\" timestamptz NOT NULL DEFAULT now())`,\n );\n const appliedRows = await sql`SELECT \"name\" FROM \"schema_migrations\"`;\n const applied = new Set(appliedRows.map((row) => row.name as string));\n for (const file of files) {\n if (applied.has(file)) {\n continue;\n }\n const sqlText = await readFile(join(migrationsDir, file), \"utf8\");\n await executeMigrationFile(sql, file, sqlText);\n await sql`INSERT INTO \"schema_migrations\" (\"name\") VALUES (${file}) ON CONFLICT DO NOTHING`;\n }\n } finally {\n await sql.end();\n }\n}\n\n/**\n * SDK entry point (Step I): run the migration chain over a host-supplied admin\n * connection string against an explicit target schema. This is the embedded\n * topology's named entry — a host calls `runMigrations(adminConnection,\n * targetSchema)` from its own provisioning code instead of relying on env vars.\n * `targetSchema` undefined → `public` → standalone behavior. Thin wrapper over\n * `migrate` so there is one migration engine.\n */\nexport async function runMigrations(adminConnection: string, targetSchema?: string): Promise<void> {\n await migrate(adminConnection, targetSchema);\n}\n\nif (import.meta.main) {\n await migrate();\n console.log(\"Applied Drizzle SQL migrations.\");\n}\n"],"mappings":";AAAA,SAAS,SAAS,gBAAgB;AAClC,SAAS,SAAS,YAAY;AAC9B,SAAS,qBAAqB;AAC9B,OAAO,cAAc;AAErB,IAAM,uBAAuB;AAC7B,IAAM,2BAA2B;AAGjC,SAAS,iBAAiB,MAAc,OAAuB;AAC7D,MAAI,CAAC,2BAA2B,KAAK,KAAK,GAAG;AAC3C,UAAM,IAAI,MAAM,GAAG,IAAI,wCAAwC,KAAK,EAAE;AAAA,EACxE;AACA,SAAO;AACT;AAeA,eAAe,qBACb,KACA,MACA,SACe;AACf,QAAM,CAAC,YAAY,IAAI,GAAG,cAAc,IAAI,QAAQ,WAAW,QAAQ,IAAI,EAAE,MAAM,IAAI;AACvF,QAAM,YAAY,yBAAyB,KAAK,UAAU,KAAK,CAAC;AAChE,MAAI,CAAC,WAAW;AACd,QAAI,UAAU,KAAK,EAAE,WAAW,cAAc,GAAG;AAC/C,YAAM,IAAI,MAAM,+CAA+C,IAAI,EAAE;AAAA,IACvE;AACA,UAAM,IAAI,OAAO,OAAO;AACxB;AAAA,EACF;AAEA,QAAM,cAAc,UAAU,CAAC;AAC/B,QAAM,YAAY,eAAe,KAAK,IAAI,EAAE,KAAK;AACjD,QAAM,2BAA2B,UAAU,SAAS,GAAG,IACnD,UAAU,MAAM,GAAG,EAAE,EAAE,QAAQ,IAC/B;AACJ,MACE,CAAC,mDAAmD,KAAK,wBAAwB,KACjF,yBAAyB,SAAS,GAAG,GACrC;AACA,UAAM,IAAI;AAAA,MACR,GAAG,IAAI;AAAA,IACT;AAAA,EACF;AAEA,QAAM,wCAAwC,WAAW;AACzD,MAAI;AACF,UAAM,IAAI,OAAO,SAAS;AAAA,EAC5B,UAAE;AACA,UAAM;AAAA,EACR;AACF;AA8BA,eAAsB,QACpB,cAAc,QAAQ,IAAI,oCACxB,QAAQ,IAAI,yBACZ,sBACF,SAA6B,QAAQ,IAAI,oBAAoB,KAAK,KAAK,QACxD;AACf,QAAM,gBAAgB,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC,GAAG,YAAY;AAChF,QAAM,SAAS,MAAM,QAAQ,aAAa,GAAG,OAAO,CAAC,SAAS,KAAK,SAAS,MAAM,CAAC,EAAE,KAAK;AAC1F,QAAM,MAAM,SAAS,aAAa,EAAE,KAAK,EAAE,CAAC;AAC5C,MAAI;AAGF,UAAM;AACN,QAAI,QAAQ;AACV,uBAAiB,sBAAsB,MAAM;AAC7C,YAAM,IAAI,OAAO,gCAAgC,MAAM,GAAG;AAI1D,YAAM,IAAI,OAAO,gDAAgD;AACjE,YAAM,IAAI,OAAO,sBAAsB,MAAM,iCAAiC;AAAA,IAChF;AACA,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AACA,UAAM,cAAc,MAAM;AAC1B,UAAM,UAAU,IAAI,IAAI,YAAY,IAAI,CAAC,QAAQ,IAAI,IAAc,CAAC;AACpE,eAAW,QAAQ,OAAO;AACxB,UAAI,QAAQ,IAAI,IAAI,GAAG;AACrB;AAAA,MACF;AACA,YAAM,UAAU,MAAM,SAAS,KAAK,eAAe,IAAI,GAAG,MAAM;AAChE,YAAM,qBAAqB,KAAK,MAAM,OAAO;AAC7C,YAAM,uDAAuD,IAAI;AAAA,IACnE;AAAA,EACF,UAAE;AACA,UAAM,IAAI,IAAI;AAAA,EAChB;AACF;AAUA,eAAsB,cAAc,iBAAyB,cAAsC;AACjG,QAAM,QAAQ,iBAAiB,YAAY;AAC7C;AAEA,IAAI,YAAY,MAAM;AACpB,QAAM,QAAQ;AACd,UAAQ,IAAI,iCAAiC;AAC/C;","names":[]}