@opengeni/db 4.4.0-canary.0 → 4.4.0-canary.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/dist/canonical-human-identities.js +4 -3
  2. package/dist/{chunk-2ZMTQVTR.js → chunk-2KOLL33E.js} +2 -2
  3. package/dist/chunk-45CP2YKY.js +1062 -0
  4. package/dist/chunk-45CP2YKY.js.map +1 -0
  5. package/dist/{chunk-6TKL3GQO.js → chunk-5UUWZDAV.js} +6531 -6480
  6. package/dist/chunk-5UUWZDAV.js.map +1 -0
  7. package/dist/{chunk-QLA3UQNX.js → chunk-6IJD3XXF.js} +17 -3
  8. package/dist/chunk-6IJD3XXF.js.map +1 -0
  9. package/dist/{chunk-232VC2LW.js → chunk-6V3HDEMR.js} +2 -2
  10. package/dist/{chunk-M7PSMVFF.js → chunk-CHBLBEU6.js} +2 -2
  11. package/dist/chunk-M4PKBJRS.js +20318 -0
  12. package/dist/chunk-M4PKBJRS.js.map +1 -0
  13. package/dist/chunk-S2TZ3F4L.js +237 -0
  14. package/dist/chunk-S2TZ3F4L.js.map +1 -0
  15. package/dist/{chunk-UK57QA2H.js → chunk-UOXWVP3T.js} +2 -2
  16. package/dist/{chunk-GVZJ7XSR.js → chunk-WFU6PS54.js} +11 -4
  17. package/dist/chunk-WFU6PS54.js.map +1 -0
  18. package/dist/{chunk-THJGMEK5.js → chunk-XJP7KJ6U.js} +121 -66
  19. package/dist/chunk-XJP7KJ6U.js.map +1 -0
  20. package/dist/connection-token-resolver.d.ts +2 -0
  21. package/dist/database.d.ts +2 -0
  22. package/dist/editable-artifact-durable-export.js +3 -2
  23. package/dist/editable-artifact-durable-export.js.map +1 -1
  24. package/dist/editable-artifacts.js +4 -3
  25. package/dist/external-identities.d.ts +1 -0
  26. package/dist/external-membership-operations.d.ts +44 -0
  27. package/dist/governed-learning-evaluator.d.ts +1 -14
  28. package/dist/host-mcp-bindings.d.ts +4 -0
  29. package/dist/host-mcp-resolvers.d.ts +47 -0
  30. package/dist/host-mcp-task-authority.d.ts +2 -6
  31. package/dist/index.d.ts +29 -31
  32. package/dist/index.js +8616 -8321
  33. package/dist/index.js.map +1 -1
  34. package/dist/knowledge-document-preparation.d.ts +56 -0
  35. package/dist/knowledge-entries-schema.d.ts +2479 -0
  36. package/dist/knowledge-entries.d.ts +421 -0
  37. package/dist/knowledge-indexing.d.ts +99 -0
  38. package/dist/knowledge-migration.d.ts +6 -0
  39. package/dist/managed-auth-session-sets.js +4 -3
  40. package/dist/mcp-operations.js +3 -2
  41. package/dist/mcp-operations.js.map +1 -1
  42. package/dist/migrate.js +2 -1
  43. package/dist/provision-roles.js +1 -1
  44. package/dist/retained-provider-commands.js +3 -2
  45. package/dist/retained-provider-commands.js.map +1 -1
  46. package/dist/runtime-posture.d.ts +7 -7
  47. package/dist/schema.d.ts +115 -12
  48. package/dist/schema.js +26 -1
  49. package/dist/session-background-commands.js +5 -4
  50. package/dist/session-command-output.js +8 -6
  51. package/dist/session-command-output.js.map +1 -1
  52. package/dist/session-event-slices.js +6 -4
  53. package/dist/session-event-slices.js.map +1 -1
  54. package/dist/session-mcp-credential-rotation.js +5 -4
  55. package/dist/session-mcp-credential-rotation.js.map +1 -1
  56. package/dist/session-tenancy.js +4 -3
  57. package/dist/video-generation.js +4 -3
  58. package/dist/workspace-artifacts.d.ts +1 -0
  59. package/dist/workspace-instruction-policies-schema.d.ts +21 -2
  60. package/dist/workspace-instruction-policies.d.ts +1 -1
  61. package/dist/workspace-membership-access.d.ts +11 -0
  62. package/dist/workspace-tool-defaults.js +5 -4
  63. package/dist/workspace-tool-defaults.js.map +1 -1
  64. package/drizzle/0461_unified_knowledge.sql +2859 -0
  65. package/drizzle/0462_agent_instruction_non_destructive_edits.sql +212 -0
  66. package/drizzle/0463_host_mcp_resolver_registration.sql +111 -0
  67. package/drizzle/0464_external_workspace_membership_operations.sql +166 -0
  68. package/package.json +6 -6
  69. package/src/connection-token-resolver.ts +75 -6
  70. package/src/database.ts +12 -1
  71. package/src/external-identities.ts +1 -0
  72. package/src/external-membership-operations.ts +134 -0
  73. package/src/governed-learning-evaluator.ts +0 -69
  74. package/src/host-mcp-binding-match.ts +1 -0
  75. package/src/host-mcp-bindings.ts +24 -1
  76. package/src/host-mcp-resolvers.ts +266 -0
  77. package/src/host-mcp-task-authority.ts +11 -10
  78. package/src/index.ts +307 -128
  79. package/src/knowledge-document-preparation.ts +90 -0
  80. package/src/knowledge-entries-schema.ts +264 -0
  81. package/src/knowledge-entries.ts +860 -0
  82. package/src/knowledge-indexing.ts +126 -0
  83. package/src/knowledge-migration.ts +761 -0
  84. package/src/knowledge-source-sync.ts +46 -7
  85. package/src/migrate.ts +19 -0
  86. package/src/provision-roles.ts +23 -57
  87. package/src/runtime-posture.ts +103 -7
  88. package/src/schema.ts +15 -3
  89. package/src/skills.ts +4 -0
  90. package/src/video-generation.ts +14 -0
  91. package/src/workspace-artifacts.ts +2 -0
  92. package/src/workspace-instruction-policies-schema.ts +1 -0
  93. package/src/workspace-membership-access.ts +61 -0
  94. package/dist/chunk-6TKL3GQO.js.map +0 -1
  95. package/dist/chunk-EYF6MH6C.js +0 -4772
  96. package/dist/chunk-EYF6MH6C.js.map +0 -1
  97. package/dist/chunk-GSBONN55.js +0 -533
  98. package/dist/chunk-GSBONN55.js.map +0 -1
  99. package/dist/chunk-GVZJ7XSR.js.map +0 -1
  100. package/dist/chunk-QLA3UQNX.js.map +0 -1
  101. package/dist/chunk-THJGMEK5.js.map +0 -1
  102. /package/dist/{chunk-2ZMTQVTR.js.map → chunk-2KOLL33E.js.map} +0 -0
  103. /package/dist/{chunk-232VC2LW.js.map → chunk-6V3HDEMR.js.map} +0 -0
  104. /package/dist/{chunk-M7PSMVFF.js.map → chunk-CHBLBEU6.js.map} +0 -0
  105. /package/dist/{chunk-UK57QA2H.js.map → chunk-UOXWVP3T.js.map} +0 -0
@@ -0,0 +1,1062 @@
1
+ import {
2
+ LOSSLESS_CONTENT_CODEC_VERSION,
3
+ fromPostgresLosslessText,
4
+ toPostgresLosslessJson,
5
+ toPostgresLosslessText
6
+ } from "./chunk-S2TZ3F4L.js";
7
+
8
+ // src/migrate.ts
9
+ import { readdir, readFile } from "fs/promises";
10
+ import { dirname, join } from "path";
11
+ import { fileURLToPath } from "url";
12
+ import postgres from "postgres";
13
+
14
+ // src/skill-metadata-migration.ts
15
+ import { readSkillMetadata } from "@opengeni/contracts";
16
+ var SKILL_METADATA_MIGRATION_MARKER = "-- opengeni:skill-metadata-stage-v1";
17
+ function prepareLegacySkillFolder(files, legacy) {
18
+ const main = files.find((file) => file.path === "SKILL.md");
19
+ if (!main) throw new Error(`Skill ${legacy.id} requires SKILL.md before migration`);
20
+ let content = main.content;
21
+ if (!/^\s*---(?:\r?\n|$)/u.test(content)) {
22
+ const slug = legacy.title.normalize("NFKD").toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-|-$/gu, "");
23
+ const name = slug && slug.length <= 64 ? slug : `legacy-${legacy.id.replaceAll("-", "")}`;
24
+ content = `---
25
+ name: ${JSON.stringify(name)}
26
+ description: ${JSON.stringify(legacy.description)}
27
+ ---
28
+ ${content}`;
29
+ }
30
+ const metadata = readSkillMetadata(content);
31
+ return {
32
+ files: files.map((file) => file.path === "SKILL.md" ? { ...file, content } : file),
33
+ ...metadata
34
+ };
35
+ }
36
+ async function createSkillMetadataMigrationStage(tx) {
37
+ await tx`CREATE TEMP TABLE skill_metadata_0426 (
38
+ source_kind text NOT NULL, source_id uuid NOT NULL, account_id uuid NOT NULL,
39
+ workspace_id uuid, files jsonb NOT NULL, name text NOT NULL, description text NOT NULL
40
+ ) ON COMMIT DROP`;
41
+ }
42
+ async function stageSkillMetadataMigration(tx) {
43
+ const sources = await tx`
44
+ SELECT 'installed' AS source_kind,sf.facet_id AS source_id,i.account_id,i.workspace_id,
45
+ sf.name AS title,sf.description,
46
+ coalesce(jsonb_agg(jsonb_build_object('path',ff.path,'content',ff.content) ORDER BY ff.path COLLATE "C")
47
+ FILTER (WHERE ff.id IS NOT NULL),'[]'::jsonb) AS files
48
+ FROM capability_plugin_installations i JOIN capability_facets f ON f.plugin_version_id=i.plugin_version_id
49
+ JOIN capability_skill_facets sf ON sf.facet_id=f.id LEFT JOIN capability_skill_files ff ON ff.skill_facet_id=sf.facet_id
50
+ WHERE i.status='active'
51
+ GROUP BY i.account_id,i.workspace_id,sf.facet_id
52
+ UNION ALL
53
+ SELECT 'authored',h.id,h.account_id,h.scope_workspace_id,r.title,r.description,
54
+ coalesce(r.skill_files,jsonb_build_array(jsonb_build_object('path','SKILL.md','content',r.content)))
55
+ FROM preference_registry_preferences h JOIN preference_registry_revisions r
56
+ ON r.id=h.active_revision_id AND r.preference_id=h.id AND r.account_id=h.account_id
57
+ WHERE h.status='active'
58
+ `;
59
+ for (const source of sources) {
60
+ let folder;
61
+ try {
62
+ folder = prepareLegacySkillFolder(source.files, {
63
+ id: source.source_id,
64
+ title: source.title,
65
+ description: source.description
66
+ });
67
+ } catch (cause) {
68
+ throw new Error(
69
+ `Skill ${source.source_kind}:${source.source_id} needs explicit frontmatter repair before 0433`,
70
+ { cause }
71
+ );
72
+ }
73
+ const [valid] = await tx`SELECT skill_files_valid(${tx.json(folder.files)}) AS valid`;
74
+ if (!valid?.valid)
75
+ throw new Error(
76
+ `Skill ${source.source_id} exceeds the valid folder limits after metadata migration`
77
+ );
78
+ await tx`INSERT INTO pg_temp.skill_metadata_0426(source_kind,source_id,account_id,workspace_id,files,name,description)
79
+ VALUES(${source.source_kind},${source.source_id},${source.account_id},${source.workspace_id},${tx.json(folder.files)},${folder.name},${folder.description})`;
80
+ }
81
+ }
82
+
83
+ // src/knowledge-migration.ts
84
+ import { createHash } from "crypto";
85
+ import {
86
+ KnowledgeSourceSyncAction,
87
+ knowledgeSourceAgentConfig,
88
+ StoredKnowledgeEntryContent
89
+ } from "@opengeni/contracts";
90
+ var KNOWLEDGE_MIGRATION_MARKER = "-- opengeni:unified-knowledge-copy-v1";
91
+ function knowledgeMigrationId(kind, id) {
92
+ const hash = createHash("sha256").update(`opengeni:knowledge:0461:${kind}:${id}`).digest("hex").slice(0, 32);
93
+ return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-5${hash.slice(13, 16)}-8${hash.slice(17, 20)}-${hash.slice(20)}`;
94
+ }
95
+ async function migrateMemoryEvidence(tx, memory) {
96
+ const evidence = [];
97
+ const references = memory.snapshot.source_refs;
98
+ if (!Array.isArray(references)) return evidence;
99
+ for (const ref of references) {
100
+ if (!ref || typeof ref !== "object" || typeof ref.id !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(ref.id))
101
+ continue;
102
+ let entryId;
103
+ let passage;
104
+ if (ref.kind === "memory") {
105
+ const [found] = await tx`SELECT id FROM knowledge_memories WHERE account_id=${memory.account_id} AND id=${ref.id}`;
106
+ if (found && found.id !== memory.id) entryId = String(found.id);
107
+ } else if (ref.kind === "document" || ref.kind === "document_chunk") {
108
+ const [doc] = ref.kind === "document_chunk" ? await tx`SELECT d.id,d.status FROM document_chunks c JOIN documents d ON d.id=c.document_id AND d.account_id=c.account_id
109
+ WHERE c.account_id=${memory.account_id} AND c.id=${ref.id}` : await tx`SELECT id,status FROM documents WHERE account_id=${memory.account_id} AND id=${ref.id}`;
110
+ if (doc) {
111
+ const [version] = await tx`SELECT id FROM knowledge_document_versions WHERE account_id=${memory.account_id}
112
+ AND document_id=${doc.id} ORDER BY created_at DESC,id DESC LIMIT 1`;
113
+ if (version || doc.status === "ready")
114
+ entryId = knowledgeMigrationId(
115
+ version ? "document-version" : "document",
116
+ String(version?.id ?? doc.id)
117
+ );
118
+ if (ref.kind === "document_chunk") passage = `Legacy passage ${ref.id}`;
119
+ }
120
+ }
121
+ if (entryId && !evidence.some((item) => item.entryId === entryId && item.location.passage === passage)) {
122
+ evidence.push({
123
+ entryId,
124
+ revisionId: knowledgeMigrationId("revision", entryId),
125
+ location: passage ? { passage } : {}
126
+ });
127
+ }
128
+ }
129
+ return evidence;
130
+ }
131
+ async function importEntry(tx, input) {
132
+ const body = StoredKnowledgeEntryContent.parse(input.body);
133
+ const revisionId = knowledgeMigrationId("revision", input.id);
134
+ const outcome = ["active", "approved", "ready"].includes(input.status) ? "published" : input.status === "proposed" ? "pending" : "rejected";
135
+ const archived = !["active", "approved", "ready", "proposed"].includes(input.status);
136
+ const migrationActor = { kind: "migration", subjectId: "service:knowledge-migration:0461" };
137
+ const actor = input.originalActor ?? migrationActor;
138
+ const batchId = outcome === "pending" ? knowledgeMigrationId(
139
+ "review-batch",
140
+ `${input.accountId}:${input.workspaceId}:${input.scope}:${input.subjectId ?? ""}:${input.sessionId ?? input.id}`
141
+ ) : null;
142
+ if (batchId)
143
+ await tx`INSERT INTO knowledge_review_batches(id,account_id,origin_workspace_id,owner_key,session_id,created_at)
144
+ VALUES(${batchId},${input.accountId},${input.workspaceId},${input.scope === "personal" ? `personal:${input.subjectId}` : `${input.scope}:${input.workspaceId}`},
145
+ ${input.sessionId ?? null},${input.createdAt}) ON CONFLICT(id) DO NOTHING`;
146
+ await tx`INSERT INTO knowledge_entries(id,account_id,origin_workspace_id,scope,scope_workspace_id,scope_subject_id,
147
+ version,published_revision_id,latest_revision_id,archived,legacy_memory_id,legacy_document_id,
148
+ legacy_scope_type,legacy_scope_role_key,legacy_scope_session_id,legacy_document_version_id,legacy_claim_id,legacy_scope_workspace_id,valid_from,valid_until,created_at,updated_at)
149
+ VALUES(${input.id},${input.accountId},${input.workspaceId},${input.scope},${input.scope === "workspace" ? input.workspaceId : null},
150
+ ${input.subjectId},1,${outcome === "published" ? revisionId : null},${revisionId},${archived},${input.memoryId ?? null},${input.documentId ?? null},
151
+ ${input.legacyScopeType ?? null},${input.roleKey ?? null},${input.scopeSessionId ?? null},${input.legacyDocumentVersionId ?? null},${input.legacyClaimId ?? null},${input.legacyScopeWorkspaceId ?? null},${input.validFrom ?? input.createdAt},
152
+ ${input.validUntil ?? null},${input.createdAt},${input.createdAt})`;
153
+ await tx`INSERT INTO knowledge_entry_revisions(id,account_id,entry_id,number,body,body_codec_version,preview,preview_codec_version,
154
+ actor,created_by_session_id,review_batch_id,legacy_snapshot,created_at)
155
+ VALUES(${revisionId},${input.accountId},${input.id},1,${tx.json(toPostgresLosslessJson(body))},
156
+ ${LOSSLESS_CONTENT_CODEC_VERSION},${toPostgresLosslessText(body.content.slice(0, 512))},${LOSSLESS_CONTENT_CODEC_VERSION},
157
+ ${tx.json(actor)},${input.sessionId ?? null},${batchId},${input.legacySnapshot ? tx.json(input.legacySnapshot) : null},${input.createdAt})`;
158
+ await tx`INSERT INTO knowledge_entry_decisions(account_id,entry_id,revision_id,version,outcome,actor,created_at)
159
+ VALUES(${input.accountId},${input.id},${revisionId},1,${outcome},${tx.json(migrationActor)},${input.createdAt})`;
160
+ await tx`SELECT knowledge_index_revision(${input.accountId},${input.id},${revisionId},
161
+ ${toPostgresLosslessText(`${body.title}
162
+ ${body.content}`)})`;
163
+ }
164
+ async function importGroup(tx, owner, title, sourceKey) {
165
+ const id = knowledgeMigrationId(
166
+ "group",
167
+ `${owner.accountId}:${owner.scope}:${owner.scope === "workspace" ? owner.workspaceId : owner.subjectId ?? ""}:${sourceKey}`
168
+ );
169
+ const [existing] = await tx`SELECT id FROM knowledge_entries WHERE id=${id}`;
170
+ if (!existing)
171
+ await importEntry(tx, {
172
+ ...owner,
173
+ id,
174
+ status: "ready",
175
+ createdAt: /* @__PURE__ */ new Date(),
176
+ body: { title, kind: "group", content: "", groupIds: [], evidence: [], relationships: [] }
177
+ });
178
+ return id;
179
+ }
180
+ async function migratePendingLegacySkills(tx) {
181
+ let after = null;
182
+ for (; ; ) {
183
+ const candidates = await tx`
184
+ SELECT receipt.id,receipt.workspace_id,receipt.account_id,receipt.preference_id,receipt.revision_id,
185
+ revision.content,revision.title,revision.description,receipt.session_id,head.active_revision_id,head.scope_version
186
+ FROM company_brain_preference_proposal_receipts receipt
187
+ JOIN preference_registry_preferences head ON head.id=receipt.preference_id AND head.account_id=receipt.account_id
188
+ JOIN preference_registry_revisions revision ON revision.id=receipt.revision_id AND revision.preference_id=head.id
189
+ JOIN knowledge_change_proposals proposal ON proposal.id=receipt.knowledge_proposal_id AND proposal.account_id=receipt.account_id
190
+ WHERE (${after}::uuid IS NULL OR receipt.id>${after}::uuid)
191
+ AND revision.skill_files IS NULL AND head.status NOT IN ('rejected','superseded')
192
+ AND proposal.status='proposed' AND proposal.target_kind='preference'
193
+ AND head.scope='workspace' AND head.scope_workspace_id=receipt.workspace_id
194
+ AND proposal.scope_workspace_id=receipt.workspace_id AND proposal.target_scope='workspace'
195
+ AND coalesce((SELECT review.state FROM knowledge_claim_reviews review WHERE review.claim_id=proposal.claim_id
196
+ AND review.account_id=receipt.account_id ORDER BY review.review_revision DESC LIMIT 1),'proposed') IN ('proposed','approved')
197
+ AND NOT EXISTS(SELECT 1 FROM preference_registry_events event WHERE event.preference_id=head.id
198
+ AND event.new_revision_id=revision.id AND event.type IN ('activated','corrected','rejected'))
199
+ AND NOT EXISTS(SELECT 1 FROM preference_registry_revisions newer WHERE newer.preference_id=head.id
200
+ AND newer.revision>revision.revision AND newer.created_by_subject_id<>'service:skill-migration:0433')
201
+ ORDER BY receipt.id LIMIT 100`;
202
+ if (!candidates.length) break;
203
+ for (const candidate of candidates) {
204
+ const folder = prepareLegacySkillFolder([{ path: "SKILL.md", content: candidate.content }], {
205
+ id: candidate.preference_id,
206
+ title: candidate.title,
207
+ description: candidate.description
208
+ });
209
+ const main = folder.files.find((file) => file.path === "SKILL.md").content;
210
+ const revisionId = knowledgeMigrationId("pending-skill-revision", candidate.id);
211
+ const operationId = knowledgeMigrationId("pending-skill-operation", candidate.id);
212
+ await tx`INSERT INTO preference_registry_revisions(id,account_id,preference_id,title,description,content,content_hash,
213
+ precedence_rank,conflict_strategy,conflicts_with,provenance_source,provenance_source_id,trust,expires_at,
214
+ created_by_subject_id,corrects_revision_id,skill_files,skill_activation_mode)
215
+ SELECT ${revisionId}::uuid,account_id,preference_id,${folder.name},${folder.description},${main},
216
+ ${createHash("sha256").update(main, "utf8").digest("hex")},precedence_rank,conflict_strategy,conflicts_with,
217
+ provenance_source,provenance_source_id,trust,expires_at,'service:knowledge-migration:0461',id,
218
+ ${tx.json(folder.files)},'workspace_managed'
219
+ FROM preference_registry_revisions WHERE id=${candidate.revision_id}::uuid`;
220
+ await tx`INSERT INTO skill_write_receipts(account_id,workspace_id,operation_id,fingerprint,actor,receipt)
221
+ VALUES(${candidate.account_id}::uuid,${candidate.workspace_id}::uuid,${operationId}::uuid,
222
+ ${createHash("sha256").update(operationId).digest("hex")},
223
+ ${tx.json({
224
+ kind: "migration",
225
+ subjectId: "service:knowledge-migration:0461",
226
+ legacyProposalReceiptId: candidate.id,
227
+ legacyRevisionId: candidate.revision_id,
228
+ sessionId: candidate.session_id
229
+ })},
230
+ ${tx.json({
231
+ operationId,
232
+ skillId: candidate.preference_id,
233
+ revisionId,
234
+ outcome: "pending",
235
+ pendingReason: "approval",
236
+ replayed: false
237
+ })})`;
238
+ }
239
+ after = candidates.at(-1).id;
240
+ }
241
+ }
242
+ async function migrateRetainedKnowledge(tx) {
243
+ await migratePendingLegacySkills(tx);
244
+ await migrateSourceSchedules(tx);
245
+ let after = null;
246
+ for (; ; ) {
247
+ const memories = await tx`SELECT m.id,m.account_id,m.workspace_id,m.text,m.text_codec_version,m.kind,m.status,m.scope_type,m.scope_subject_id,
248
+ m.scope_role_key,m.scope_session_id,m.created_by_session_id,
249
+ (SELECT r.claim_id FROM remember_knowledge_memory_materializations map
250
+ JOIN remember_knowledge_confirmation_receipts r ON r.id=map.confirmation_receipt_id
251
+ WHERE map.account_id=m.account_id AND map.memory_id=m.id) AS confirmed_claim_id,m.namespace_key AS namespace,m.labels,m.created_at,m.valid_from,m.valid_until,
252
+ (to_jsonb(m)-'embedding'-'text') || jsonb_build_object('relationships',coalesce((SELECT jsonb_agg(to_jsonb(edge) ORDER BY edge.id)
253
+ FROM knowledge_memory_relationships edge WHERE edge.account_id=m.account_id
254
+ AND (edge.source_memory_id=m.id OR edge.target_memory_id=m.id)),'[]'::jsonb),
255
+ 'lifecycleEvents',coalesce((SELECT jsonb_agg(to_jsonb(event) ORDER BY event.created_at,event.id)
256
+ FROM knowledge_memory_lifecycle_events event WHERE event.account_id=m.account_id AND event.target_memory_id=m.id),'[]'::jsonb)) AS snapshot,
257
+ coalesce((SELECT jsonb_agg(jsonb_build_object('entryId',edge.target_memory_id,'relation',
258
+ CASE edge.relationship_type WHEN 'derived_from' THEN 'depends_on' WHEN 'corrects' THEN 'supersedes' ELSE edge.relationship_type END)
259
+ ORDER BY edge.id) FROM knowledge_memory_relationships edge
260
+ WHERE edge.account_id=m.account_id AND edge.source_memory_id=m.id AND edge.removed_at IS NULL),'[]'::jsonb) AS relationships
261
+ FROM knowledge_memories m WHERE (${after}::uuid IS NULL OR m.id>${after}::uuid) ORDER BY m.id LIMIT 200`;
262
+ if (!memories.length) break;
263
+ for (const memory of memories) {
264
+ const content = fromPostgresLosslessText(memory.text, memory.text_codec_version);
265
+ const owner = {
266
+ accountId: memory.account_id,
267
+ workspaceId: memory.workspace_id,
268
+ scope: memory.scope_type === "user" ? "personal" : "workspace",
269
+ subjectId: memory.scope_subject_id
270
+ };
271
+ const groupIds = [];
272
+ if (memory.scope_type === "workspace") {
273
+ if (memory.namespace && memory.namespace !== "general")
274
+ groupIds.push(
275
+ await importGroup(tx, owner, memory.namespace, `namespace:${memory.namespace}`)
276
+ );
277
+ for (const label of memory.labels)
278
+ groupIds.push(await importGroup(tx, owner, label, `label:${label}`));
279
+ }
280
+ await importEntry(tx, {
281
+ ...owner,
282
+ id: memory.id,
283
+ memoryId: memory.id,
284
+ ...memory.confirmed_claim_id ? { legacyClaimId: memory.confirmed_claim_id } : {},
285
+ legacySnapshot: memory.snapshot,
286
+ originalActor: {
287
+ kind: memory.snapshot.created_by_kind,
288
+ subjectId: memory.snapshot.created_by_subject_id,
289
+ context: memory.snapshot.created_by_context
290
+ },
291
+ status: memory.status,
292
+ legacyScopeType: memory.scope_type,
293
+ roleKey: memory.scope_role_key,
294
+ scopeSessionId: memory.scope_session_id,
295
+ sessionId: memory.created_by_session_id,
296
+ createdAt: memory.created_at,
297
+ validFrom: memory.valid_from,
298
+ validUntil: memory.valid_until,
299
+ body: {
300
+ title: content.split(/\r?\n/u, 1)[0]?.slice(0, 160) || "Imported knowledge",
301
+ content,
302
+ kind: memory.kind === "episodic" ? "incident" : memory.kind === "semantic" ? "fact" : memory.kind === "decision" ? "decision" : "note",
303
+ source: { kind: "manual", externalId: `memory:${memory.id}` },
304
+ groupIds: [...new Set(groupIds)],
305
+ evidence: await migrateMemoryEvidence(tx, memory),
306
+ relationships: memory.relationships.map((relationship) => ({
307
+ entryId: relationship.entryId,
308
+ relation: relationship.relation
309
+ }))
310
+ }
311
+ });
312
+ }
313
+ after = memories.at(-1).id;
314
+ }
315
+ after = null;
316
+ for (; ; ) {
317
+ const documents = await tx`SELECT d.id,d.account_id,d.workspace_id,d.file_id,d.base_id,b.name AS base_name,d.title,d.authority_kind,
318
+ d.authority_subject_id,d.source_uri,d.source_external_id,d.source_version,d.created_at
319
+ FROM documents d JOIN document_bases b ON b.id=d.base_id AND b.account_id=d.account_id
320
+ WHERE d.status='ready' AND NOT EXISTS(SELECT 1 FROM knowledge_document_versions v WHERE v.account_id=d.account_id AND v.document_id=d.id)
321
+ AND (${after}::uuid IS NULL OR d.id>${after}::uuid) ORDER BY d.id LIMIT 50`;
322
+ if (!documents.length) break;
323
+ for (const document of documents) {
324
+ const chunks = await tx`SELECT text FROM document_chunks
325
+ WHERE account_id=${document.account_id} AND document_id=${document.id} ORDER BY chunk_index`;
326
+ const owner = {
327
+ accountId: document.account_id,
328
+ workspaceId: document.workspace_id,
329
+ scope: document.authority_kind,
330
+ subjectId: document.authority_subject_id
331
+ };
332
+ const groupIds = document.authority_kind === "workspace" && document.base_name ? [await importGroup(tx, owner, document.base_name, `document-base:${document.base_id}`)] : [];
333
+ await importEntry(tx, {
334
+ ...owner,
335
+ id: knowledgeMigrationId("document", document.id),
336
+ documentId: document.id,
337
+ status: "ready",
338
+ createdAt: document.created_at,
339
+ body: {
340
+ title: document.title,
341
+ kind: "source",
342
+ // Historical chunks may overlap. Preserve every retained passage;
343
+ // never claim to have reconstructed missing original full text.
344
+ content: chunks.map((chunk) => chunk.text).join("\n\n"),
345
+ groupIds,
346
+ evidence: [],
347
+ relationships: [],
348
+ source: {
349
+ kind: "file",
350
+ fileId: document.file_id,
351
+ documentId: document.id,
352
+ retention: "passages",
353
+ ...document.source_uri ? { uri: document.source_uri } : {},
354
+ ...document.source_external_id ? { externalId: document.source_external_id } : {},
355
+ ...document.source_version ? { version: document.source_version } : {}
356
+ }
357
+ }
358
+ });
359
+ }
360
+ after = documents.at(-1).id;
361
+ }
362
+ await importScopedKnowledge(tx);
363
+ await tx`INSERT INTO knowledge_entry_links(account_id,entry_id,revision_id,ordinal,target_entry_id,relation,target_revision_id)
364
+ SELECT r.account_id,r.entry_id,r.id,(g.ordinality-1)::integer,g.value::uuid,'group',NULL::uuid
365
+ FROM knowledge_entry_revisions r CROSS JOIN LATERAL jsonb_array_elements_text(r.body->'groupIds') WITH ORDINALITY g(value,ordinality)
366
+ UNION ALL
367
+ SELECT r.account_id,r.entry_id,r.id,jsonb_array_length(r.body->'groupIds')+(l.ordinality-1)::integer,
368
+ (l.value->>'entryId')::uuid,l.value->>'relation',NULL::uuid
369
+ FROM knowledge_entry_revisions r CROSS JOIN LATERAL jsonb_array_elements(r.body->'relationships') WITH ORDINALITY l(value,ordinality)
370
+ UNION ALL
371
+ SELECT r.account_id,r.entry_id,r.id,jsonb_array_length(r.body->'groupIds')+jsonb_array_length(r.body->'relationships')+(e.ordinality-1)::integer,
372
+ (e.value->>'entryId')::uuid,'evidence',(e.value->>'revisionId')::uuid
373
+ FROM knowledge_entry_revisions r CROSS JOIN LATERAL jsonb_array_elements(r.body->'evidence') WITH ORDINALITY e(value,ordinality)`;
374
+ }
375
+ async function importScopedKnowledge(tx) {
376
+ function owner(row) {
377
+ return {
378
+ accountId: row.account_id,
379
+ // Some organization/user records never had a creating workspace. This
380
+ // deterministic import origin is provenance only and grants no access.
381
+ workspaceId: row.scope_workspace_id ?? knowledgeMigrationId("import-origin", row.account_id),
382
+ scope: row.scope_kind,
383
+ subjectId: row.scope_subject_id
384
+ };
385
+ }
386
+ function actor(row) {
387
+ return {
388
+ kind: row.actor_kind,
389
+ subjectId: row.actor_subject_id,
390
+ initiatingHumanSubjectId: row.initiating_human_subject_id
391
+ };
392
+ }
393
+ let after = null;
394
+ for (; ; ) {
395
+ const entities = await tx`
396
+ SELECT e.*,to_jsonb(e) AS snapshot,coalesce((SELECT jsonb_agg(a.alias ORDER BY a.id)
397
+ FROM knowledge_entity_aliases a WHERE a.account_id=e.account_id AND a.entity_id=e.id),'[]'::jsonb) AS aliases
398
+ FROM knowledge_entities e WHERE (${after}::uuid IS NULL OR e.id>${after}::uuid)
399
+ AND (NOT EXISTS(SELECT 1 FROM knowledge_facts f JOIN knowledge_claims c ON c.fact_id=f.id
400
+ WHERE f.account_id=e.account_id AND (f.subject_entity_id=e.id OR f.object_entity_id=e.id))
401
+ OR EXISTS(SELECT 1 FROM knowledge_facts f JOIN knowledge_claims c ON c.fact_id=f.id
402
+ WHERE f.account_id=e.account_id AND (f.subject_entity_id=e.id OR f.object_entity_id=e.id)
403
+ AND NOT EXISTS(SELECT 1 FROM knowledge_change_proposals p WHERE p.claim_id=c.id AND p.account_id=c.account_id)))
404
+ ORDER BY e.id LIMIT 200`;
405
+ if (!entities.length) break;
406
+ for (const entity of entities)
407
+ await importEntry(tx, {
408
+ ...owner(entity),
409
+ id: knowledgeMigrationId("entity", entity.id),
410
+ status: "approved",
411
+ createdAt: entity.created_at,
412
+ legacyScopeWorkspaceId: entity.scope_workspace_id,
413
+ originalActor: actor(entity),
414
+ legacySnapshot: { entity: entity.snapshot, aliases: entity.aliases },
415
+ body: {
416
+ title: entity.display_name,
417
+ kind: "group",
418
+ content: entity.aliases.join("\n"),
419
+ groupIds: [],
420
+ evidence: [],
421
+ relationships: []
422
+ }
423
+ });
424
+ after = entities.at(-1).id;
425
+ }
426
+ after = null;
427
+ for (; ; ) {
428
+ const versions = await tx`
429
+ SELECT v.*,coalesce(d.title,o.external_object_id) AS title,s.source_uri,
430
+ jsonb_build_object('version',to_jsonb(v),'source',to_jsonb(s),'object',to_jsonb(o),'provider',to_jsonb(p)) AS snapshot
431
+ FROM knowledge_document_versions v JOIN knowledge_source_objects o ON o.account_id=v.account_id AND o.id=v.object_id
432
+ JOIN knowledge_sources s ON s.account_id=v.account_id AND s.id=v.source_id
433
+ JOIN knowledge_providers p ON p.account_id=s.account_id AND p.id=s.provider_id
434
+ LEFT JOIN documents d ON d.account_id=v.account_id AND d.id=v.document_id
435
+ WHERE (${after}::uuid IS NULL OR v.id>${after}::uuid) ORDER BY v.id LIMIT 100`;
436
+ if (!versions.length) break;
437
+ for (const version of versions) {
438
+ const chunks = version.document_id ? await tx`SELECT text FROM document_chunks
439
+ WHERE account_id=${version.account_id} AND document_id=${version.document_id} ORDER BY chunk_index` : [];
440
+ await importEntry(tx, {
441
+ ...owner(version),
442
+ id: knowledgeMigrationId("document-version", version.id),
443
+ legacyDocumentVersionId: version.id,
444
+ legacyScopeWorkspaceId: version.scope_workspace_id,
445
+ status: "ready",
446
+ createdAt: version.created_at,
447
+ originalActor: actor(version),
448
+ legacySnapshot: version.snapshot,
449
+ body: {
450
+ title: version.title,
451
+ kind: "source",
452
+ content: chunks.map((c) => c.text).join("\n\n"),
453
+ groupIds: [],
454
+ evidence: [],
455
+ relationships: [],
456
+ source: {
457
+ kind: version.file_id ? "file" : "connector",
458
+ externalId: `knowledge-document-version:${version.id}`,
459
+ version: version.external_version_id,
460
+ retention: chunks.length ? "passages" : "reference",
461
+ ...version.file_id ? { fileId: version.file_id } : {},
462
+ ...version.document_id ? { documentId: version.document_id } : {},
463
+ ...version.source_uri ? { uri: version.source_uri } : {}
464
+ }
465
+ }
466
+ });
467
+ }
468
+ after = versions.at(-1).id;
469
+ }
470
+ after = null;
471
+ for (; ; ) {
472
+ const claims = await tx`
473
+ SELECT c.*,f.subject_entity_id,f.object_entity_id,f.predicate_key,f.object_value,
474
+ subject.display_name AS subject_name,object.display_name AS object_name,
475
+ coalesce((SELECT state FROM knowledge_claim_reviews r WHERE r.account_id=c.account_id AND r.claim_id=c.id
476
+ ORDER BY r.review_revision DESC LIMIT 1),'proposed') AS state,
477
+ jsonb_build_object('claim',to_jsonb(c),'fact',to_jsonb(f),
478
+ 'evidence',coalesce((SELECT jsonb_agg(to_jsonb(e) ORDER BY e.id) FROM knowledge_claim_evidence e
479
+ WHERE e.account_id=c.account_id AND e.claim_id=c.id),'[]'::jsonb),
480
+ 'reviews',coalesce((SELECT jsonb_agg(to_jsonb(r) ORDER BY r.review_revision) FROM knowledge_claim_reviews r
481
+ WHERE r.account_id=c.account_id AND r.claim_id=c.id),'[]'::jsonb)) AS snapshot
482
+ FROM knowledge_claims c JOIN knowledge_facts f ON f.account_id=c.account_id AND f.id=c.fact_id
483
+ JOIN knowledge_entities subject ON subject.account_id=f.account_id AND subject.id=f.subject_entity_id
484
+ LEFT JOIN knowledge_entities object ON object.account_id=f.account_id AND object.id=f.object_entity_id
485
+ WHERE (${after}::uuid IS NULL OR c.id>${after}::uuid)
486
+ AND NOT EXISTS(SELECT 1 FROM knowledge_entries imported WHERE imported.legacy_claim_id=c.id)
487
+ AND NOT EXISTS(SELECT 1 FROM knowledge_change_proposals p WHERE p.account_id=c.account_id AND p.claim_id=c.id
488
+ )
489
+ ORDER BY c.id LIMIT 100`;
490
+ if (!claims.length) break;
491
+ for (const claim of claims) {
492
+ const retained = await tx`
493
+ SELECT document_version_id,locator FROM knowledge_claim_evidence WHERE account_id=${claim.account_id}
494
+ AND claim_id=${claim.id} AND document_version_id IS NOT NULL ORDER BY id`;
495
+ const relations = await tx`
496
+ SELECT r.to_claim_id,r.relation_type,(SELECT e.id FROM knowledge_entries e WHERE e.legacy_claim_id=r.to_claim_id) AS imported_entry_id FROM knowledge_claim_relations r
497
+ WHERE r.account_id=${claim.account_id} AND r.from_claim_id=${claim.id}
498
+ AND NOT EXISTS(SELECT 1 FROM knowledge_change_proposals p WHERE p.account_id=r.account_id AND p.claim_id=r.to_claim_id ) ORDER BY r.id`;
499
+ const objectText = claim.object_name ?? (typeof claim.object_value === "string" ? claim.object_value : JSON.stringify(claim.object_value));
500
+ await importEntry(tx, {
501
+ ...owner(claim),
502
+ id: knowledgeMigrationId("claim", claim.id),
503
+ legacyClaimId: claim.id,
504
+ legacyScopeWorkspaceId: claim.scope_workspace_id,
505
+ status: claim.state,
506
+ createdAt: claim.created_at,
507
+ validFrom: claim.effective_at,
508
+ validUntil: claim.expires_at,
509
+ originalActor: actor(claim),
510
+ legacySnapshot: claim.snapshot,
511
+ body: {
512
+ title: `${claim.subject_name}: ${claim.predicate_key}`,
513
+ kind: "fact",
514
+ content: claim.extraction_method === "task-note-promotion-v1" && typeof claim.object_value === "string" ? claim.object_value : `${claim.subject_name} \xB7 ${claim.predicate_key}: ${objectText}`,
515
+ source: { kind: "manual", externalId: `knowledge-claim:${claim.id}` },
516
+ groupIds: [knowledgeMigrationId("entity", claim.subject_entity_id)],
517
+ evidence: retained.map((e) => {
518
+ const id = knowledgeMigrationId("document-version", e.document_version_id);
519
+ return {
520
+ entryId: id,
521
+ revisionId: knowledgeMigrationId("revision", id),
522
+ location: e.locator ? { passage: e.locator } : {}
523
+ };
524
+ }),
525
+ relationships: [
526
+ ...relations.map((r) => ({
527
+ entryId: r.imported_entry_id ?? knowledgeMigrationId("claim", r.to_claim_id),
528
+ relation: r.relation_type
529
+ })),
530
+ ...claim.object_entity_id ? [
531
+ {
532
+ entryId: knowledgeMigrationId("entity", claim.object_entity_id),
533
+ relation: "related_to"
534
+ }
535
+ ] : []
536
+ ]
537
+ }
538
+ });
539
+ }
540
+ after = claims.at(-1).id;
541
+ }
542
+ }
543
+ async function migrateSourceSchedules(tx) {
544
+ await tx`ALTER TABLE scheduled_tasks NO FORCE ROW LEVEL SECURITY`;
545
+ await tx`ALTER TABLE scheduled_tasks DISABLE TRIGGER scheduled_task_tombstone_immutable`;
546
+ const tasks = await tx`SELECT id,account_id,workspace_id,action,authority_revision FROM scheduled_tasks
547
+ WHERE action->>'kind'='knowledge_source_sync' AND deleted_at IS NULL ORDER BY id`;
548
+ for (const task of tasks) {
549
+ const source = KnowledgeSourceSyncAction.parse(task.action);
550
+ await tx`SELECT set_config('opengeni.account_id',${task.account_id},true),
551
+ set_config('opengeni.workspace_id',${task.workspace_id},true),
552
+ set_config('opengeni.subject_id',${source.initiatingSubjectId},true),
553
+ set_config('opengeni.initiating_human_subject_id',${source.initiatingSubjectId},true)`;
554
+ await tx`SELECT pg_advisory_xact_lock_shared(hashtextextended('session-tenancy:'||${task.workspace_id}::text,0))`;
555
+ await tx`SELECT id FROM workspaces WHERE id=${task.workspace_id} FOR UPDATE`;
556
+ const [authority] = await tx`SELECT id FROM organization_memberships m
557
+ WHERE m.account_id=${task.account_id} AND m.subject_id=${source.initiatingSubjectId}
558
+ AND m.status='active' AND m.revoked_at IS NULL
559
+ AND (m.personal_workspace_id=${task.workspace_id} OR EXISTS(SELECT 1 FROM workspace_memberships w
560
+ WHERE w.account_id=m.account_id AND w.workspace_id=${task.workspace_id} AND w.subject_id=m.subject_id))`;
561
+ await tx`UPDATE scheduled_task_runs SET status='failed',completed_at=clock_timestamp(),
562
+ error='knowledge_source_agent_cutover',updated_at=clock_timestamp()
563
+ WHERE task_id=${task.id} AND action_kind='knowledge_source_sync' AND status IN ('queued','dispatched')`;
564
+ await tx`UPDATE knowledge_source_sync_wakes SET completed_at=clock_timestamp()
565
+ WHERE scheduled_task_id=${task.id} AND completed_at IS NULL`;
566
+ await tx`UPDATE knowledge_source_sync_states SET lease_id=NULL,lease_until=NULL,
567
+ buffered_wake=false,buffered_scheduled_task_run_id=NULL,pending_wake_count=0,updated_at=clock_timestamp()
568
+ WHERE scheduled_task_id=${task.id}`;
569
+ if (!authority)
570
+ await tx`UPDATE scheduled_tasks SET status='paused',
571
+ metadata=jsonb_set(metadata,'{knowledgeSourceSync}',coalesce(metadata->'knowledgeSourceSync','{}'::jsonb)
572
+ || '{"sourceEnabled":false,"authorityRefreshRequired":true}'::jsonb) WHERE id=${task.id}`;
573
+ const [updated] = await tx`UPDATE scheduled_tasks SET action='{"kind":"agent_turn"}',
574
+ agent_config=${JSON.stringify(knowledgeSourceAgentConfig(source))}::jsonb, authority_revision=authority_revision+1,
575
+ updated_at=clock_timestamp() WHERE id=${task.id} RETURNING authority_revision`;
576
+ if (!updated) throw new Error("Source schedule disappeared during maintenance");
577
+ await tx`SELECT freeze_scheduled_task_personal_resources(${task.account_id}::uuid,
578
+ ${task.workspace_id}::uuid,${task.id}::uuid,${updated.authority_revision}::bigint)`;
579
+ if (authority)
580
+ await tx`SELECT record_scheduled_task_revision_authority(${task.account_id}::uuid,
581
+ ${task.workspace_id}::uuid,${task.id}::uuid,${updated.authority_revision}::bigint)`;
582
+ }
583
+ await tx`ALTER TABLE scheduled_tasks ENABLE TRIGGER scheduled_task_tombstone_immutable`;
584
+ await tx`ALTER TABLE scheduled_tasks FORCE ROW LEVEL SECURITY`;
585
+ await tx`SELECT set_config('opengeni.account_id','',true),set_config('opengeni.workspace_id','',true),
586
+ set_config('opengeni.subject_id','',true),set_config('opengeni.initiating_human_subject_id','',true)`;
587
+ }
588
+
589
+ // src/skill-config-migration.ts
590
+ import { createHash as createHash2 } from "crypto";
591
+ import {
592
+ StoredCapabilityPack,
593
+ StoredSessionSkills,
594
+ validateSkillTextFiles
595
+ } from "@opengeni/contracts";
596
+ function object(value) {
597
+ if (!value || typeof value !== "object" || Array.isArray(value))
598
+ throw new Error("Expected stored configuration object");
599
+ return value;
600
+ }
601
+ function convertLegacyConfigSkills(value, identity) {
602
+ if (!Array.isArray(value)) throw new Error("Expected stored Skill array");
603
+ const names = /* @__PURE__ */ new Set();
604
+ const converted = value.map((entry, index) => {
605
+ const skill = object(entry);
606
+ if (!Array.isArray(skill.files)) throw new Error("Expected stored Skill folder");
607
+ const files = skill.files.map((fileEntry) => {
608
+ const file = object(fileEntry);
609
+ if (typeof file.path !== "string" || typeof file.content !== "string")
610
+ throw new Error("Invalid stored Skill text file");
611
+ if (Object.keys(file).some((key) => key !== "path" && key !== "content"))
612
+ throw new Error("Unexpected stored Skill file fields");
613
+ return { path: file.path, content: file.content };
614
+ });
615
+ const main = files.find((file) => file.path === "SKILL.md");
616
+ const plain = main && !/^\s*---(?:\r?\n|$)/u.test(main.content);
617
+ if (plain && (typeof skill.name !== "string" || typeof skill.description !== "string"))
618
+ throw new Error("Plain Skill requires historical name and description");
619
+ const folder = prepareLegacySkillFolder(files, {
620
+ id: createHash2("sha256").update(`${identity}:${index}`).digest("hex").slice(0, 32),
621
+ title: typeof skill.name === "string" ? skill.name : "",
622
+ description: typeof skill.description === "string" ? skill.description : ""
623
+ });
624
+ validateSkillTextFiles(folder.files);
625
+ if (names.has(folder.name)) throw new Error(`Canonical Skill name collision: ${folder.name}`);
626
+ names.add(folder.name);
627
+ return plain ? { ...skill, ...folder } : entry;
628
+ });
629
+ StoredSessionSkills.parse(converted);
630
+ return converted;
631
+ }
632
+ function convertLegacyPackConfig(value, identity) {
633
+ const pack = object(value);
634
+ const converted = { ...pack };
635
+ if (pack.skills !== void 0)
636
+ converted.skills = convertLegacyConfigSkills(pack.skills, identity);
637
+ if (Array.isArray(pack.automationTemplates))
638
+ converted.automationTemplates = pack.automationTemplates.map((entry, index) => {
639
+ const template = object(entry);
640
+ const sessionTemplate = object(template.sessionTemplate);
641
+ return {
642
+ ...template,
643
+ sessionTemplate: {
644
+ ...sessionTemplate,
645
+ ...sessionTemplate.skills !== void 0 ? {
646
+ skills: convertLegacyConfigSkills(
647
+ sessionTemplate.skills,
648
+ `${identity}:template:${index}`
649
+ )
650
+ } : {}
651
+ }
652
+ };
653
+ });
654
+ StoredCapabilityPack.parse(converted);
655
+ return converted;
656
+ }
657
+ async function assertSkillConfigurationMaintenanceWindow(tx) {
658
+ const rows = await tx`SELECT c.oid FROM pg_class c
659
+ WHERE c.oid='sessions'::regclass AND pg_get_userbyid(c.relowner)=current_user
660
+ AND c.relrowsecurity AND NOT c.relforcerowsecurity
661
+ AND to_regclass('pg_temp.skill_metadata_0426') IS NOT NULL`;
662
+ if (rows.length !== 1)
663
+ throw new Error(
664
+ "Skill configuration conversion requires the parser-backed maintenance owner window"
665
+ );
666
+ }
667
+ async function migrateLegacySkillConfigurations(tx) {
668
+ await assertSkillConfigurationMaintenanceWindow(tx);
669
+ await tx`SELECT pg_advisory_xact_lock(hashtextextended('session-tenancy:'||workspace_id::text,0))
670
+ FROM (SELECT DISTINCT workspace_id FROM sessions ORDER BY workspace_id) workspaces`;
671
+ await tx`LOCK TABLE sessions, workspace_packs, session_turns, automation_triggers,
672
+ automation_trigger_revisions, automation_runs, automation_trigger_events, automation_run_event_links, pack_installations IN SHARE ROW EXCLUSIVE MODE`;
673
+ const pinned = await tx`
674
+ SELECT 'pack-installation' AS kind,id::text,manifest_snapshot AS value FROM pack_installations
675
+ WHERE status <> 'disabled' AND manifest_snapshot IS NOT NULL
676
+ UNION ALL SELECT 'automation-current',t.id::text,jsonb_build_object('skills',r.session_template->'skills')
677
+ FROM automation_triggers t JOIN automation_trigger_revisions r ON r.trigger_id=t.id AND r.revision=t.current_revision
678
+ WHERE t.status IN ('active','paused') AND r.session_template ? 'skills'
679
+ UNION ALL SELECT 'automation-run',id::text,jsonb_build_object('skills',accepted_execution->'sessionTemplate'->'skills')
680
+ FROM automation_runs WHERE status IN ('queued','dispatching') AND accepted_execution->'sessionTemplate' ? 'skills'
681
+ UNION ALL SELECT 'automation-event',e.id::text,jsonb_build_object('skills',r.session_template->'skills')
682
+ FROM automation_trigger_events e CROSS JOIN LATERAL jsonb_array_elements(e.matched_trigger_revisions) matched
683
+ JOIN automation_trigger_revisions r ON r.trigger_id=(matched->>'triggerId')::uuid AND r.revision=(matched->>'revision')::integer
684
+ WHERE e.status='accepted' AND r.session_template ? 'skills'
685
+ AND NOT EXISTS (SELECT 1 FROM automation_runs run WHERE run.event_id=e.id AND run.trigger_id=r.trigger_id AND run.trigger_revision=r.revision)
686
+ AND NOT EXISTS (SELECT 1 FROM automation_run_event_links link WHERE link.event_id=e.id AND link.trigger_id=r.trigger_id)
687
+ `;
688
+ const blockers = [];
689
+ for (const source of pinned) {
690
+ try {
691
+ const original = object(source.value);
692
+ const converted = source.kind === "pack-installation" ? convertLegacyPackConfig(original, source.id) : { ...original, skills: convertLegacyConfigSkills(original.skills, source.id) };
693
+ if (JSON.stringify(converted) !== JSON.stringify(original))
694
+ blockers.push(`${source.kind}:${source.id} (plain content)`);
695
+ } catch {
696
+ blockers.push(`${source.kind}:${source.id} (invalid content)`);
697
+ }
698
+ }
699
+ const candidates = await tx`
700
+ SELECT 'session' AS kind,s.id::text,s.account_id,s.workspace_id,s.skills AS original,
701
+ (s.active_turn_id IS NOT NULL OR EXISTS(SELECT 1 FROM session_turns t WHERE t.session_id=s.id AND t.status NOT IN ('completed','failed','cancelled'))) AS pinned
702
+ FROM sessions s WHERE s.skills <> '[]'::jsonb
703
+ UNION ALL SELECT 'workspace-pack',id::text,account_id,workspace_id,manifest,false FROM workspace_packs
704
+ `;
705
+ const changes = [];
706
+ for (const source of candidates) {
707
+ try {
708
+ const converted = source.kind === "session" ? convertLegacyConfigSkills(source.original, source.id) : convertLegacyPackConfig(source.original, source.id);
709
+ if (source.kind === "session") StoredSessionSkills.parse(converted);
710
+ if (JSON.stringify(source.original) !== JSON.stringify(converted)) {
711
+ if (source.pinned) blockers.push(`${source.kind}:${source.id} (runnable accepted turn)`);
712
+ else changes.push({ source, converted });
713
+ }
714
+ } catch {
715
+ blockers.push(`${source.kind}:${source.id} (invalid content or canonical collision)`);
716
+ }
717
+ }
718
+ if (blockers.length)
719
+ throw new Error(
720
+ `0433 Skill configuration preflight: ${blockers.length} repair-required sources; drain or explicitly replace pinned sources before retry: ${blockers.slice(0, 30).join(", ")}`
721
+ );
722
+ for (const { source, converted } of changes) {
723
+ const original = tx.json(source.original);
724
+ const replacement = tx.json(converted);
725
+ await tx`INSERT INTO skill_config_conversion_receipts(account_id,workspace_id,source_kind,source_id,original_configuration,original_hash,replacement_hash)
726
+ VALUES(${source.account_id},${source.workspace_id},${source.kind},${source.id},${original},
727
+ encode(sha256(convert_to(${original}::jsonb::text,'UTF8')),'hex'),encode(sha256(convert_to(${replacement}::jsonb::text,'UTF8')),'hex'))`;
728
+ const updated = source.kind === "session" ? await tx`UPDATE sessions SET skills=${replacement} WHERE id=${source.id} AND account_id=${source.account_id} AND workspace_id=${source.workspace_id} AND skills=${original} RETURNING id` : await tx`UPDATE workspace_packs SET manifest=${replacement} WHERE id=${source.id} AND account_id=${source.account_id} AND workspace_id=${source.workspace_id} AND manifest=${original} RETURNING id`;
729
+ if (updated.length !== 1)
730
+ throw new Error(`0433 Skill configuration CAS conflict: ${source.kind}:${source.id}`);
731
+ }
732
+ }
733
+
734
+ // src/migration-runner-settings.ts
735
+ function batchedBackfillTransactionLocalSetting(file) {
736
+ if (file === "0411_new_session_draft_project_provenance_backfill.sql") {
737
+ return {
738
+ guc: "opengeni.new_session_draft_project_provenance_backfill_v1",
739
+ value: "1"
740
+ };
741
+ }
742
+ return null;
743
+ }
744
+
745
+ // src/migrate.ts
746
+ var DEFAULT_DATABASE_URL = "postgres://opengeni:opengeni@127.0.0.1:5432/opengeni";
747
+ var DEFAULT_MAX_NESTED_AGENT_DEPTH = 3;
748
+ var MAX_NESTED_AGENT_DEPTH = 2147483647;
749
+ var DEFAULT_APPLICATION_DATABASE_ROLE = "opengeni_app";
750
+ var MIGRATION_APPLICATION_ROLES_SETTING = "opengeni.migration_application_roles";
751
+ var MAX_MIGRATION_APPLICATION_ROLES = 16;
752
+ var batchedBackfillDirective = /^-- opengeni:batched-backfill batch-size=(\d+) lock-timeout=(\d+(?:ms|s|min)) statement-timeout=(\d+(?:ms|s|min))$/;
753
+ var concurrentIndexDirective = /^-- opengeni:concurrent-index lock-timeout=(\d+(?:ms|s|min))$/;
754
+ var concurrentIndexStatement = /^CREATE\s+(?:UNIQUE\s+)?INDEX\s+CONCURRENTLY\s+(?:(IF\s+NOT\s+EXISTS)\s+)?(?:"((?:[^"]|"")+)"|([A-Za-z_][A-Za-z0-9_]*))\s+ON\b/is;
755
+ var governedLegacyConcurrentIndexMigrations = /* @__PURE__ */ new Set([
756
+ "0066_session_interruption_attempt_lookup.sql",
757
+ "0070_session_event_type_sequence_lookup.sql",
758
+ "0071_session_event_monitoring_tail.sql",
759
+ "0072_sessions_workspace_created_id_idx.sql",
760
+ "0073_sessions_workspace_updated_id_idx.sql",
761
+ "0075_sessions_workspace_activity_revision_idx.sql",
762
+ "0077_session_attempt_latest_lookup.sql"
763
+ ]);
764
+ function assertIdentifier(name, value) {
765
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
766
+ throw new Error(`${name} is not a valid Postgres identifier: ${value}`);
767
+ }
768
+ return value;
769
+ }
770
+ function parseConcurrentIndexMigration(file, sqlText) {
771
+ const lines = sqlText.replaceAll("\r\n", "\n").split("\n");
772
+ const firstLine = lines[0]?.trim() ?? "";
773
+ const deploymentPrefixed = /^-- deployment-mode: (?:rolling|maintenance)$/.test(firstLine);
774
+ const directiveIndex = deploymentPrefixed ? 1 : 0;
775
+ const directiveLine = lines[directiveIndex]?.trim() ?? "";
776
+ const directive = concurrentIndexDirective.exec(directiveLine);
777
+ if (!directive) {
778
+ if (directiveLine.startsWith("-- opengeni:")) {
779
+ throw new Error(`Unsupported OpenGeni migration directive in ${file}`);
780
+ }
781
+ return null;
782
+ }
783
+ const lockTimeout = directive[1];
784
+ const statement = lines.slice(directiveIndex + 1).join("\n").trim();
785
+ const withoutTrailingSemicolon = statement.endsWith(";") ? statement.slice(0, -1).trimEnd() : statement;
786
+ const parsedStatement = concurrentIndexStatement.exec(withoutTrailingSemicolon);
787
+ if (!parsedStatement || withoutTrailingSemicolon.includes(";")) {
788
+ throw new Error(
789
+ `${file}: opengeni:concurrent-index requires exactly one CREATE [UNIQUE] INDEX CONCURRENTLY IF NOT EXISTS statement with an unqualified index name`
790
+ );
791
+ }
792
+ const idempotentInSql = parsedStatement[1] !== void 0;
793
+ if (!idempotentInSql && !governedLegacyConcurrentIndexMigrations.has(file)) {
794
+ throw new Error(
795
+ `${file}: opengeni:concurrent-index requires IF NOT EXISTS; bare statements are supported only for governed historical migrations`
796
+ );
797
+ }
798
+ return {
799
+ indexName: (parsedStatement[2] ?? parsedStatement[3]).replaceAll('""', '"'),
800
+ lockTimeout,
801
+ skipWhenValid: !idempotentInSql,
802
+ statement
803
+ };
804
+ }
805
+ function parseBatchedBackfillMigration(file, sqlText) {
806
+ const lines = sqlText.replaceAll("\r\n", "\n").split("\n");
807
+ const directiveIndex = /^-- deployment-mode: (?:rolling|maintenance)$/.test(
808
+ lines[0]?.trim() ?? ""
809
+ ) ? 1 : 0;
810
+ const directive = batchedBackfillDirective.exec(lines[directiveIndex]?.trim() ?? "");
811
+ if (!directive) return null;
812
+ const statement = lines.slice(directiveIndex + 1).filter((line) => !line.trim().startsWith("--")).join("\n").trim();
813
+ const withoutTrailingSemicolon = statement.endsWith(";") ? statement.slice(0, -1).trimEnd() : statement;
814
+ const batchSize = Number(directive[1]);
815
+ if (!Number.isSafeInteger(batchSize) || batchSize < 1 || batchSize > 1e4 || !/^WITH\b/is.test(withoutTrailingSemicolon) || !/\bUPDATE\b/is.test(withoutTrailingSemicolon) || !/\bRETURNING\b/is.test(withoutTrailingSemicolon) || !new RegExp(`\\bLIMIT\\s+${batchSize}\\b`, "i").test(withoutTrailingSemicolon) || withoutTrailingSemicolon.includes(";")) {
816
+ throw new Error(
817
+ `${file}: opengeni:batched-backfill requires one bounded WITH ... UPDATE ... RETURNING statement whose LIMIT matches batch-size`
818
+ );
819
+ }
820
+ return {
821
+ batchSize,
822
+ lockTimeout: directive[2],
823
+ statementTimeout: directive[3],
824
+ statement
825
+ };
826
+ }
827
+ async function executeMigrationFile(sql, file, sqlText) {
828
+ if (sqlText.includes(SKILL_METADATA_MIGRATION_MARKER)) {
829
+ if (file !== "0433_unified_skill_lifecycle.sql")
830
+ throw new Error("Skill metadata stage is restricted to migration 0433");
831
+ const parts = sqlText.split(SKILL_METADATA_MIGRATION_MARKER);
832
+ if (parts.length !== 2) throw new Error("0433 requires exactly one Skill metadata stage");
833
+ await sql.begin(async (transaction) => {
834
+ await createSkillMetadataMigrationStage(transaction);
835
+ await transaction`SELECT
836
+ pg_catalog.set_config('opengeni.sandbox_recovery_protocol_v2','1',true),
837
+ pg_catalog.set_config('opengeni.session_variable_set_attachments_v1','1',true)`;
838
+ await transaction.unsafe(parts[0]);
839
+ await stageSkillMetadataMigration(transaction);
840
+ await migrateLegacySkillConfigurations(transaction);
841
+ await transaction.unsafe(parts[1]);
842
+ await transaction`INSERT INTO schema_migrations(name) VALUES(${file}) ON CONFLICT DO NOTHING`;
843
+ });
844
+ return;
845
+ }
846
+ if (sqlText.includes(KNOWLEDGE_MIGRATION_MARKER)) {
847
+ if (file !== "0461_unified_knowledge.sql")
848
+ throw new Error("Knowledge conversion is restricted to migration 0461");
849
+ const parts = sqlText.split(KNOWLEDGE_MIGRATION_MARKER);
850
+ if (parts.length !== 2) throw new Error("0461 requires exactly one Knowledge conversion stage");
851
+ await sql.begin(async (transaction) => {
852
+ await transaction`CREATE TEMP TABLE knowledge_conversion_0461(completed boolean NOT NULL) ON COMMIT DROP`;
853
+ await transaction`SELECT
854
+ pg_catalog.set_config('opengeni.sandbox_recovery_protocol_v2','1',true),
855
+ pg_catalog.set_config('opengeni.session_variable_set_attachments_v1','1',true)`;
856
+ await transaction.unsafe(parts[0]);
857
+ await migrateRetainedKnowledge(transaction);
858
+ await transaction`INSERT INTO pg_temp.knowledge_conversion_0461 VALUES(true)`;
859
+ await transaction.unsafe(parts[1]);
860
+ await transaction`INSERT INTO schema_migrations(name) VALUES(${file}) ON CONFLICT DO NOTHING`;
861
+ });
862
+ return;
863
+ }
864
+ const batchedBackfill = parseBatchedBackfillMigration(file, sqlText);
865
+ if (batchedBackfill) {
866
+ await sql`select set_config('lock_timeout', ${batchedBackfill.lockTimeout}, false)`;
867
+ await sql`select set_config('statement_timeout', ${batchedBackfill.statementTimeout}, false)`;
868
+ try {
869
+ for (; ; ) {
870
+ const result = await sql.begin(async (transaction) => {
871
+ const transactionLocalSetting = batchedBackfillTransactionLocalSetting(file);
872
+ await transaction`select
873
+ pg_catalog.set_config('opengeni.sandbox_recovery_protocol_v2', '1', true),
874
+ pg_catalog.set_config(
875
+ 'opengeni.session_variable_set_attachments_v1',
876
+ '1',
877
+ true
878
+ )`;
879
+ if (transactionLocalSetting) {
880
+ await transaction`select pg_catalog.set_config(
881
+ ${transactionLocalSetting.guc},
882
+ ${transactionLocalSetting.value},
883
+ true
884
+ )`;
885
+ }
886
+ return await transaction.unsafe(batchedBackfill.statement);
887
+ });
888
+ if (result.length === 0) break;
889
+ }
890
+ } finally {
891
+ await sql`select set_config('statement_timeout', '0', false)`;
892
+ await sql`select set_config('lock_timeout', '0', false)`;
893
+ }
894
+ return;
895
+ }
896
+ const concurrentIndex = parseConcurrentIndexMigration(file, sqlText);
897
+ if (!concurrentIndex) {
898
+ await sql.unsafe(
899
+ `SELECT
900
+ pg_catalog.set_config('opengeni.sandbox_recovery_protocol_v2', '1', true),
901
+ pg_catalog.set_config('opengeni.session_variable_set_attachments_v1', '1', true);
902
+ ${file === "0434_ordered_model_history.sql" ? "SET CONSTRAINTS ALL IMMEDIATE;\n" : ""}${sqlText}`
903
+ );
904
+ return;
905
+ }
906
+ await sql`select set_config('lock_timeout', ${concurrentIndex.lockTimeout}, false)`;
907
+ try {
908
+ const [existing] = await sql`
909
+ select i.indisvalid as valid, i.indisready as ready
910
+ from pg_catalog.pg_class c
911
+ join pg_catalog.pg_namespace n on n.oid = c.relnamespace
912
+ join pg_catalog.pg_index i on i.indexrelid = c.oid
913
+ where n.nspname = current_schema() and c.relname = ${concurrentIndex.indexName}
914
+ `;
915
+ if (existing && (!existing.valid || !existing.ready)) {
916
+ const quotedIndexName = `"${concurrentIndex.indexName.replaceAll('"', '""')}"`;
917
+ await sql.unsafe(`DROP INDEX CONCURRENTLY ${quotedIndexName}`);
918
+ } else if (existing && concurrentIndex.skipWhenValid) {
919
+ return;
920
+ }
921
+ await sql.unsafe(concurrentIndex.statement);
922
+ } finally {
923
+ await sql`select set_config('lock_timeout', '0', false)`;
924
+ }
925
+ }
926
+ function deploymentDepthPolicy(options) {
927
+ const raw = options === void 0 ? process.env.OPENGENI_MAX_NESTED_AGENT_DEPTH?.trim() || void 0 : options.maxNestedAgentDepth;
928
+ if (raw === void 0) {
929
+ return { maxNestedAgentDepth: DEFAULT_MAX_NESTED_AGENT_DEPTH, source: "default" };
930
+ }
931
+ const value = typeof raw === "number" ? raw : Number(raw);
932
+ if (!Number.isSafeInteger(value) || value < 0 || value > MAX_NESTED_AGENT_DEPTH || typeof raw === "string" && !/^(0|[1-9][0-9]*)$/.test(raw)) {
933
+ throw new Error(
934
+ `OPENGENI_MAX_NESTED_AGENT_DEPTH must be a non-negative 32-bit integer: ${raw}`
935
+ );
936
+ }
937
+ return { maxNestedAgentDepth: value, source: "deployment" };
938
+ }
939
+ function migrationApplicationRoles(schema, options) {
940
+ const configured = options?.applicationDatabaseRoles;
941
+ const environment = process.env.OPENGENI_MIGRATION_APPLICATION_DATABASE_ROLES?.trim();
942
+ const explicit = configured !== void 0 ? configured : environment ? environment.split(",").map((role) => role.trim()) : void 0;
943
+ if (!explicit) {
944
+ const configuredRuntimeRoles = [
945
+ process.env.OPENGENI_APP_DATABASE_USER?.trim(),
946
+ process.env.OPENGENI_RUNTIME_DATABASE_ROLE?.trim()
947
+ ].filter((role) => Boolean(role) && role !== DEFAULT_APPLICATION_DATABASE_ROLE);
948
+ if (schema || configuredRuntimeRoles.length > 0) {
949
+ throw new Error(
950
+ "Pending migrations require the exact application database roles via MigrationRuntimeOptions.applicationDatabaseRoles or OPENGENI_MIGRATION_APPLICATION_DATABASE_ROLES for dedicated-schema or custom-role deployments"
951
+ );
952
+ }
953
+ return [DEFAULT_APPLICATION_DATABASE_ROLE];
954
+ }
955
+ if (configured !== void 0 && configured.some((role) => typeof role !== "string" || role !== role.trim())) {
956
+ throw new Error(
957
+ "MigrationRuntimeOptions.applicationDatabaseRoles must contain canonical Postgres role names without surrounding whitespace"
958
+ );
959
+ }
960
+ if (explicit.length < 1 || explicit.length > MAX_MIGRATION_APPLICATION_ROLES) {
961
+ throw new Error(
962
+ `Migration application database roles must contain 1-${MAX_MIGRATION_APPLICATION_ROLES} entries`
963
+ );
964
+ }
965
+ const normalized = [...new Set(explicit)].sort(
966
+ (left, right) => Buffer.compare(Buffer.from(left, "utf8"), Buffer.from(right, "utf8"))
967
+ );
968
+ if (normalized.length !== explicit.length || normalized.some((role) => role.length === 0 || Buffer.byteLength(role, "utf8") > 63)) {
969
+ throw new Error(
970
+ "Migration application database roles must be unique, non-empty Postgres role names of at most 63 UTF-8 bytes"
971
+ );
972
+ }
973
+ return normalized;
974
+ }
975
+ async function persistDeploymentDepthPolicy(sql, policy) {
976
+ const [relation] = await sql`
977
+ select to_regclass('nested_agent_depth_configuration') is not null as exists
978
+ `;
979
+ if (!relation?.exists) return;
980
+ await sql`
981
+ insert into "nested_agent_depth_configuration" (
982
+ "singleton", "max_nested_agent_depth", "policy_source", "updated_at"
983
+ ) values (true, ${policy.maxNestedAgentDepth}, ${policy.source}, now())
984
+ on conflict ("singleton") do update
985
+ set "max_nested_agent_depth" = excluded."max_nested_agent_depth",
986
+ "policy_source" = excluded."policy_source",
987
+ "updated_at" = now()
988
+ where "nested_agent_depth_configuration"."max_nested_agent_depth"
989
+ is distinct from excluded."max_nested_agent_depth"
990
+ or "nested_agent_depth_configuration"."policy_source"
991
+ is distinct from excluded."policy_source"
992
+ `;
993
+ }
994
+ 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, runtimeOptions) {
995
+ const migrationsDir = join(dirname(fileURLToPath(import.meta.url)), "../drizzle");
996
+ const files = (await readdir(migrationsDir)).filter((file) => file.endsWith(".sql")).sort();
997
+ const depthPolicy = deploymentDepthPolicy(runtimeOptions);
998
+ const sql = postgres(databaseUrl, { max: 1 });
999
+ try {
1000
+ await sql`SELECT pg_advisory_lock(727458)`;
1001
+ if (schema) {
1002
+ assertIdentifier("OPENGENI_DB_SCHEMA", schema);
1003
+ await sql.unsafe(`CREATE SCHEMA IF NOT EXISTS "${schema}"`);
1004
+ await sql.unsafe(`CREATE SCHEMA IF NOT EXISTS "opengeni_private"`);
1005
+ await sql.unsafe(`SET search_path = "${schema}", "opengeni_private", "public"`);
1006
+ }
1007
+ await sql`select set_config('opengeni.max_nested_agent_depth', ${String(depthPolicy.maxNestedAgentDepth)}, false)`;
1008
+ await sql`select set_config('opengeni.nested_agent_depth_policy_source', ${depthPolicy.source}, false)`;
1009
+ await sql.unsafe(
1010
+ `CREATE TABLE IF NOT EXISTS "schema_migrations" ("name" text PRIMARY KEY, "applied_at" timestamptz NOT NULL DEFAULT now())`
1011
+ );
1012
+ const appliedRows = await sql`SELECT "name" FROM "schema_migrations"`;
1013
+ const applied = new Set(appliedRows.map((row) => row.name));
1014
+ const pendingMigrationSources = /* @__PURE__ */ new Map();
1015
+ for (const file of files) {
1016
+ if (!applied.has(file)) {
1017
+ pendingMigrationSources.set(file, await readFile(join(migrationsDir, file), "utf8"));
1018
+ }
1019
+ }
1020
+ if (Array.from(pendingMigrationSources.values()).some(
1021
+ (source) => source.includes(MIGRATION_APPLICATION_ROLES_SETTING)
1022
+ )) {
1023
+ const applicationRoles = migrationApplicationRoles(schema, runtimeOptions);
1024
+ await sql`select set_config(
1025
+ ${MIGRATION_APPLICATION_ROLES_SETTING},
1026
+ ${JSON.stringify(applicationRoles)},
1027
+ false
1028
+ )`;
1029
+ }
1030
+ for (const file of files) {
1031
+ if (applied.has(file)) {
1032
+ continue;
1033
+ }
1034
+ const sqlText = pendingMigrationSources.get(file);
1035
+ if (sqlText === void 0) {
1036
+ throw new Error(`Pending migration source was not loaded: ${file}`);
1037
+ }
1038
+ await executeMigrationFile(sql, file, sqlText);
1039
+ await sql`INSERT INTO "schema_migrations" ("name") VALUES (${file}) ON CONFLICT DO NOTHING`;
1040
+ }
1041
+ await persistDeploymentDepthPolicy(sql, depthPolicy);
1042
+ } finally {
1043
+ await sql.end();
1044
+ }
1045
+ }
1046
+ async function runMigrations(adminConnection, targetSchema, runtimeOptions) {
1047
+ await migrate(adminConnection, targetSchema, runtimeOptions);
1048
+ }
1049
+ if (import.meta.main) {
1050
+ await migrate();
1051
+ console.log("Applied Drizzle SQL migrations.");
1052
+ }
1053
+
1054
+ export {
1055
+ parseConcurrentIndexMigration,
1056
+ parseBatchedBackfillMigration,
1057
+ executeMigrationFile,
1058
+ migrationApplicationRoles,
1059
+ migrate,
1060
+ runMigrations
1061
+ };
1062
+ //# sourceMappingURL=chunk-45CP2YKY.js.map