@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,761 @@
1
+ import { prepareLegacySkillFolder } from "./skill-metadata-migration";
2
+ import { createHash } from "node:crypto";
3
+ import type postgres from "postgres";
4
+ import {
5
+ KnowledgeEntryContent,
6
+ KnowledgeSourceSyncAction,
7
+ knowledgeSourceAgentConfig,
8
+ StoredKnowledgeEntryContent,
9
+ type KnowledgeEntryScope,
10
+ } from "@opengeni/contracts";
11
+ import {
12
+ fromPostgresLosslessText,
13
+ toPostgresLosslessJson,
14
+ toPostgresLosslessText,
15
+ LOSSLESS_CONTENT_CODEC_VERSION,
16
+ } from "./lossless-json";
17
+
18
+ /** Maintenance-only conversion. It is not a runtime fallback or a second writer. */
19
+ export const KNOWLEDGE_MIGRATION_MARKER = "-- opengeni:unified-knowledge-copy-v1";
20
+ export function knowledgeMigrationId(kind: string, id: string): string {
21
+ const hash = createHash("sha256")
22
+ .update(`opengeni:knowledge:0461:${kind}:${id}`)
23
+ .digest("hex")
24
+ .slice(0, 32);
25
+ return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-5${hash.slice(13, 16)}-8${hash.slice(17, 20)}-${hash.slice(20)}`;
26
+ }
27
+ type Owner = {
28
+ accountId: string;
29
+ workspaceId: string;
30
+ scope: KnowledgeEntryScope;
31
+ subjectId: string | null;
32
+ };
33
+ type Imported = Owner & {
34
+ id: string;
35
+ body: KnowledgeEntryContent;
36
+ createdAt: Date;
37
+ status: string;
38
+ sessionId?: string | null;
39
+ memoryId?: string;
40
+ documentId?: string;
41
+ legacySnapshot?: Record<string, unknown>;
42
+ legacyDocumentVersionId?: string;
43
+ legacyClaimId?: string;
44
+ legacyScopeWorkspaceId?: string | null;
45
+ originalActor?: Record<string, unknown>;
46
+ legacyScopeType?: string;
47
+ roleKey?: string | null;
48
+ scopeSessionId?: string | null;
49
+ validFrom?: Date;
50
+ validUntil?: Date | null;
51
+ };
52
+ type LegacyMemory = {
53
+ id: string;
54
+ account_id: string;
55
+ workspace_id: string;
56
+ text: string;
57
+ text_codec_version: number | null;
58
+ kind: string;
59
+ status: string;
60
+ scope_type: string;
61
+ scope_subject_id: string | null;
62
+ scope_role_key: string | null;
63
+ scope_session_id: string | null;
64
+ created_by_session_id: string | null;
65
+ namespace: string;
66
+ labels: string[];
67
+ created_at: Date;
68
+ valid_from: Date;
69
+ valid_until: Date | null;
70
+ snapshot: Record<string, unknown>;
71
+ relationships: Array<{ entryId: string; relation: string }>;
72
+ confirmed_claim_id: string | null;
73
+ };
74
+ type LegacyDocument = {
75
+ id: string;
76
+ account_id: string;
77
+ workspace_id: string;
78
+ file_id: string;
79
+ base_id: string;
80
+ base_name: string;
81
+ title: string;
82
+ authority_kind: KnowledgeEntryScope;
83
+ authority_subject_id: string | null;
84
+ source_uri: string | null;
85
+ source_external_id: string | null;
86
+ source_version: string | null;
87
+ created_at: Date;
88
+ };
89
+
90
+ /** Resolve only identities that still exist; missing evidence stays in the audit snapshot. */
91
+ async function migrateMemoryEvidence(
92
+ tx: postgres.TransactionSql,
93
+ memory: LegacyMemory,
94
+ ): Promise<KnowledgeEntryContent["evidence"]> {
95
+ const evidence: KnowledgeEntryContent["evidence"] = [];
96
+ const references = memory.snapshot.source_refs;
97
+ if (!Array.isArray(references)) return evidence;
98
+ for (const ref of references) {
99
+ if (
100
+ !ref ||
101
+ typeof ref !== "object" ||
102
+ typeof ref.id !== "string" ||
103
+ !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(ref.id)
104
+ )
105
+ continue;
106
+ let entryId: string | undefined;
107
+ let passage: string | undefined;
108
+ if (ref.kind === "memory") {
109
+ const [found] =
110
+ await tx`SELECT id FROM knowledge_memories WHERE account_id=${memory.account_id} AND id=${ref.id}`;
111
+ if (found && found.id !== memory.id) entryId = String(found.id);
112
+ } else if (ref.kind === "document" || ref.kind === "document_chunk") {
113
+ const [doc] =
114
+ ref.kind === "document_chunk"
115
+ ? 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
116
+ WHERE c.account_id=${memory.account_id} AND c.id=${ref.id}`
117
+ : await tx`SELECT id,status FROM documents WHERE account_id=${memory.account_id} AND id=${ref.id}`;
118
+ if (doc) {
119
+ const [version] =
120
+ await tx`SELECT id FROM knowledge_document_versions WHERE account_id=${memory.account_id}
121
+ AND document_id=${doc.id} ORDER BY created_at DESC,id DESC LIMIT 1`;
122
+ // Only these identities are imported below. Failed/queued ordinary
123
+ // documents have no canonical source yet; preserve their exact reference
124
+ // in legacySnapshot instead of creating a dangling evidence edge.
125
+ if (version || doc.status === "ready")
126
+ entryId = knowledgeMigrationId(
127
+ version ? "document-version" : "document",
128
+ String(version?.id ?? doc.id),
129
+ );
130
+ if (ref.kind === "document_chunk") passage = `Legacy passage ${ref.id}`;
131
+ }
132
+ }
133
+ if (
134
+ entryId &&
135
+ !evidence.some((item) => item.entryId === entryId && item.location.passage === passage)
136
+ ) {
137
+ evidence.push({
138
+ entryId,
139
+ revisionId: knowledgeMigrationId("revision", entryId),
140
+ location: passage ? { passage } : {},
141
+ });
142
+ }
143
+ }
144
+ return evidence;
145
+ }
146
+
147
+ async function importEntry(tx: postgres.TransactionSql, input: Imported) {
148
+ const body = StoredKnowledgeEntryContent.parse(input.body);
149
+ const revisionId = knowledgeMigrationId("revision", input.id);
150
+ const outcome = ["active", "approved", "ready"].includes(input.status)
151
+ ? "published"
152
+ : input.status === "proposed"
153
+ ? "pending"
154
+ : "rejected";
155
+ const archived = !["active", "approved", "ready", "proposed"].includes(input.status);
156
+ const migrationActor = { kind: "migration", subjectId: "service:knowledge-migration:0461" };
157
+ const actor = input.originalActor ?? migrationActor;
158
+ const batchId =
159
+ outcome === "pending"
160
+ ? knowledgeMigrationId(
161
+ "review-batch",
162
+ `${input.accountId}:${input.workspaceId}:${input.scope}:${input.subjectId ?? ""}:${input.sessionId ?? input.id}`,
163
+ )
164
+ : null;
165
+ if (batchId)
166
+ await tx`INSERT INTO knowledge_review_batches(id,account_id,origin_workspace_id,owner_key,session_id,created_at)
167
+ VALUES(${batchId},${input.accountId},${input.workspaceId},${input.scope === "personal" ? `personal:${input.subjectId}` : `${input.scope}:${input.workspaceId}`},
168
+ ${input.sessionId ?? null},${input.createdAt}) ON CONFLICT(id) DO NOTHING`;
169
+ await tx`INSERT INTO knowledge_entries(id,account_id,origin_workspace_id,scope,scope_workspace_id,scope_subject_id,
170
+ version,published_revision_id,latest_revision_id,archived,legacy_memory_id,legacy_document_id,
171
+ 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)
172
+ VALUES(${input.id},${input.accountId},${input.workspaceId},${input.scope},${input.scope === "workspace" ? input.workspaceId : null},
173
+ ${input.subjectId},1,${outcome === "published" ? revisionId : null},${revisionId},${archived},${input.memoryId ?? null},${input.documentId ?? null},
174
+ ${input.legacyScopeType ?? null},${input.roleKey ?? null},${input.scopeSessionId ?? null},${input.legacyDocumentVersionId ?? null},${input.legacyClaimId ?? null},${input.legacyScopeWorkspaceId ?? null},${input.validFrom ?? input.createdAt},
175
+ ${input.validUntil ?? null},${input.createdAt},${input.createdAt})`;
176
+ await tx`INSERT INTO knowledge_entry_revisions(id,account_id,entry_id,number,body,body_codec_version,preview,preview_codec_version,
177
+ actor,created_by_session_id,review_batch_id,legacy_snapshot,created_at)
178
+ VALUES(${revisionId},${input.accountId},${input.id},1,${tx.json(toPostgresLosslessJson(body) as postgres.JSONValue)},
179
+ ${LOSSLESS_CONTENT_CODEC_VERSION},${toPostgresLosslessText(body.content.slice(0, 512))},${LOSSLESS_CONTENT_CODEC_VERSION},
180
+ ${tx.json(actor as postgres.JSONValue)},${input.sessionId ?? null},${batchId},${input.legacySnapshot ? tx.json(input.legacySnapshot as postgres.JSONValue) : null},${input.createdAt})`;
181
+ await tx`INSERT INTO knowledge_entry_decisions(account_id,entry_id,revision_id,version,outcome,actor,created_at)
182
+ VALUES(${input.accountId},${input.id},${revisionId},1,${outcome},${tx.json(migrationActor)},${input.createdAt})`;
183
+ await tx`SELECT knowledge_index_revision(${input.accountId},${input.id},${revisionId},
184
+ ${toPostgresLosslessText(`${body.title}\n${body.content}`)})`;
185
+ }
186
+
187
+ async function importGroup(
188
+ tx: postgres.TransactionSql,
189
+ owner: Owner,
190
+ title: string,
191
+ sourceKey: string,
192
+ ) {
193
+ const id = knowledgeMigrationId(
194
+ "group",
195
+ `${owner.accountId}:${owner.scope}:${owner.scope === "workspace" ? owner.workspaceId : (owner.subjectId ?? "")}:${sourceKey}`,
196
+ );
197
+ const [existing] = await tx`SELECT id FROM knowledge_entries WHERE id=${id}`;
198
+ if (!existing)
199
+ await importEntry(tx, {
200
+ ...owner,
201
+ id,
202
+ status: "ready",
203
+ createdAt: new Date(),
204
+ body: { title, kind: "group", content: "", groupIds: [], evidence: [], relationships: [] },
205
+ });
206
+ return id;
207
+ }
208
+
209
+ /** Pending pre-Skill preference proposals stay in their native authority. A new
210
+ * folder revision preserves the exact original body and links to the old revision;
211
+ * conversion never activates a proposal or reuses its old human confirmation. */
212
+ async function migratePendingLegacySkills(tx: postgres.TransactionSql): Promise<void> {
213
+ let after: string | null = null;
214
+ for (;;) {
215
+ const candidates: Array<{
216
+ id: string;
217
+ workspace_id: string;
218
+ account_id: string;
219
+ preference_id: string;
220
+ revision_id: string;
221
+ content: string;
222
+ title: string;
223
+ description: string;
224
+ session_id: string;
225
+ active_revision_id: string | null;
226
+ scope_version: number;
227
+ }> = await tx<
228
+ Array<{
229
+ id: string;
230
+ workspace_id: string;
231
+ account_id: string;
232
+ preference_id: string;
233
+ revision_id: string;
234
+ content: string;
235
+ title: string;
236
+ description: string;
237
+ session_id: string;
238
+ active_revision_id: string | null;
239
+ scope_version: number;
240
+ }>
241
+ >`
242
+ SELECT receipt.id,receipt.workspace_id,receipt.account_id,receipt.preference_id,receipt.revision_id,
243
+ revision.content,revision.title,revision.description,receipt.session_id,head.active_revision_id,head.scope_version
244
+ FROM company_brain_preference_proposal_receipts receipt
245
+ JOIN preference_registry_preferences head ON head.id=receipt.preference_id AND head.account_id=receipt.account_id
246
+ JOIN preference_registry_revisions revision ON revision.id=receipt.revision_id AND revision.preference_id=head.id
247
+ JOIN knowledge_change_proposals proposal ON proposal.id=receipt.knowledge_proposal_id AND proposal.account_id=receipt.account_id
248
+ WHERE (${after}::uuid IS NULL OR receipt.id>${after}::uuid)
249
+ AND revision.skill_files IS NULL AND head.status NOT IN ('rejected','superseded')
250
+ AND proposal.status='proposed' AND proposal.target_kind='preference'
251
+ AND head.scope='workspace' AND head.scope_workspace_id=receipt.workspace_id
252
+ AND proposal.scope_workspace_id=receipt.workspace_id AND proposal.target_scope='workspace'
253
+ AND coalesce((SELECT review.state FROM knowledge_claim_reviews review WHERE review.claim_id=proposal.claim_id
254
+ AND review.account_id=receipt.account_id ORDER BY review.review_revision DESC LIMIT 1),'proposed') IN ('proposed','approved')
255
+ AND NOT EXISTS(SELECT 1 FROM preference_registry_events event WHERE event.preference_id=head.id
256
+ AND event.new_revision_id=revision.id AND event.type IN ('activated','corrected','rejected'))
257
+ AND NOT EXISTS(SELECT 1 FROM preference_registry_revisions newer WHERE newer.preference_id=head.id
258
+ AND newer.revision>revision.revision AND newer.created_by_subject_id<>'service:skill-migration:0433')
259
+ ORDER BY receipt.id LIMIT 100`;
260
+ if (!candidates.length) break;
261
+ for (const candidate of candidates) {
262
+ const folder = prepareLegacySkillFolder([{ path: "SKILL.md", content: candidate.content }], {
263
+ id: candidate.preference_id,
264
+ title: candidate.title,
265
+ description: candidate.description,
266
+ });
267
+ const main = folder.files.find((file) => file.path === "SKILL.md")!.content;
268
+ const revisionId = knowledgeMigrationId("pending-skill-revision", candidate.id);
269
+ const operationId = knowledgeMigrationId("pending-skill-operation", candidate.id);
270
+ await tx`INSERT INTO preference_registry_revisions(id,account_id,preference_id,title,description,content,content_hash,
271
+ precedence_rank,conflict_strategy,conflicts_with,provenance_source,provenance_source_id,trust,expires_at,
272
+ created_by_subject_id,corrects_revision_id,skill_files,skill_activation_mode)
273
+ SELECT ${revisionId}::uuid,account_id,preference_id,${folder.name},${folder.description},${main},
274
+ ${createHash("sha256").update(main, "utf8").digest("hex")},precedence_rank,conflict_strategy,conflicts_with,
275
+ provenance_source,provenance_source_id,trust,expires_at,'service:knowledge-migration:0461',id,
276
+ ${tx.json(folder.files)},'workspace_managed'
277
+ FROM preference_registry_revisions WHERE id=${candidate.revision_id}::uuid`;
278
+ await tx`INSERT INTO skill_write_receipts(account_id,workspace_id,operation_id,fingerprint,actor,receipt)
279
+ VALUES(${candidate.account_id}::uuid,${candidate.workspace_id}::uuid,${operationId}::uuid,
280
+ ${createHash("sha256").update(operationId).digest("hex")},
281
+ ${tx.json({
282
+ kind: "migration",
283
+ subjectId: "service:knowledge-migration:0461",
284
+ legacyProposalReceiptId: candidate.id,
285
+ legacyRevisionId: candidate.revision_id,
286
+ sessionId: candidate.session_id,
287
+ })},
288
+ ${tx.json({
289
+ operationId,
290
+ skillId: candidate.preference_id,
291
+ revisionId,
292
+ outcome: "pending",
293
+ pendingReason: "approval",
294
+ replayed: false,
295
+ })})`;
296
+ }
297
+ after = candidates.at(-1)!.id;
298
+ }
299
+ }
300
+
301
+ /** All source and destination FORCE-RLS windows are explicit in the SQL file. */
302
+ export async function migrateRetainedKnowledge(tx: postgres.TransactionSql): Promise<void> {
303
+ await migratePendingLegacySkills(tx);
304
+ await migrateSourceSchedules(tx);
305
+ let after: string | null = null;
306
+ for (;;) {
307
+ const memories: LegacyMemory[] = await tx<
308
+ LegacyMemory[]
309
+ >`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,
310
+ m.scope_role_key,m.scope_session_id,m.created_by_session_id,
311
+ (SELECT r.claim_id FROM remember_knowledge_memory_materializations map
312
+ JOIN remember_knowledge_confirmation_receipts r ON r.id=map.confirmation_receipt_id
313
+ 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,
314
+ (to_jsonb(m)-'embedding'-'text') || jsonb_build_object('relationships',coalesce((SELECT jsonb_agg(to_jsonb(edge) ORDER BY edge.id)
315
+ FROM knowledge_memory_relationships edge WHERE edge.account_id=m.account_id
316
+ AND (edge.source_memory_id=m.id OR edge.target_memory_id=m.id)),'[]'::jsonb),
317
+ 'lifecycleEvents',coalesce((SELECT jsonb_agg(to_jsonb(event) ORDER BY event.created_at,event.id)
318
+ FROM knowledge_memory_lifecycle_events event WHERE event.account_id=m.account_id AND event.target_memory_id=m.id),'[]'::jsonb)) AS snapshot,
319
+ coalesce((SELECT jsonb_agg(jsonb_build_object('entryId',edge.target_memory_id,'relation',
320
+ CASE edge.relationship_type WHEN 'derived_from' THEN 'depends_on' WHEN 'corrects' THEN 'supersedes' ELSE edge.relationship_type END)
321
+ ORDER BY edge.id) FROM knowledge_memory_relationships edge
322
+ WHERE edge.account_id=m.account_id AND edge.source_memory_id=m.id AND edge.removed_at IS NULL),'[]'::jsonb) AS relationships
323
+ FROM knowledge_memories m WHERE (${after}::uuid IS NULL OR m.id>${after}::uuid) ORDER BY m.id LIMIT 200`;
324
+ if (!memories.length) break;
325
+ for (const memory of memories) {
326
+ const content = fromPostgresLosslessText(memory.text, memory.text_codec_version);
327
+ const owner: Owner = {
328
+ accountId: memory.account_id,
329
+ workspaceId: memory.workspace_id,
330
+ scope: memory.scope_type === "user" ? "personal" : "workspace",
331
+ subjectId: memory.scope_subject_id,
332
+ };
333
+ // Scope-specific legacy records retain their old selectors; do not put
334
+ // their labels or names in a more broadly visible group during migration.
335
+ const groupIds: string[] = [];
336
+ if (memory.scope_type === "workspace") {
337
+ if (memory.namespace && memory.namespace !== "general")
338
+ groupIds.push(
339
+ await importGroup(tx, owner, memory.namespace, `namespace:${memory.namespace}`),
340
+ );
341
+ for (const label of memory.labels)
342
+ groupIds.push(await importGroup(tx, owner, label, `label:${label}`));
343
+ }
344
+ await importEntry(tx, {
345
+ ...owner,
346
+ id: memory.id,
347
+ memoryId: memory.id,
348
+ ...(memory.confirmed_claim_id ? { legacyClaimId: memory.confirmed_claim_id } : {}),
349
+ legacySnapshot: memory.snapshot,
350
+ originalActor: {
351
+ kind: memory.snapshot.created_by_kind,
352
+ subjectId: memory.snapshot.created_by_subject_id,
353
+ context: memory.snapshot.created_by_context,
354
+ },
355
+ status: memory.status,
356
+ legacyScopeType: memory.scope_type,
357
+ roleKey: memory.scope_role_key,
358
+ scopeSessionId: memory.scope_session_id,
359
+ sessionId: memory.created_by_session_id,
360
+ createdAt: memory.created_at,
361
+ validFrom: memory.valid_from,
362
+ validUntil: memory.valid_until,
363
+ body: {
364
+ title: content.split(/\r?\n/u, 1)[0]?.slice(0, 160) || "Imported knowledge",
365
+ content,
366
+ kind:
367
+ memory.kind === "episodic"
368
+ ? "incident"
369
+ : memory.kind === "semantic"
370
+ ? "fact"
371
+ : memory.kind === "decision"
372
+ ? "decision"
373
+ : "note",
374
+ source: { kind: "manual", externalId: `memory:${memory.id}` },
375
+ groupIds: [...new Set(groupIds)],
376
+ evidence: await migrateMemoryEvidence(tx, memory),
377
+ relationships: memory.relationships.map((relationship) => ({
378
+ entryId: relationship.entryId,
379
+ relation:
380
+ relationship.relation as KnowledgeEntryContent["relationships"][number]["relation"],
381
+ })),
382
+ },
383
+ });
384
+ }
385
+ after = memories.at(-1)!.id;
386
+ }
387
+ after = null;
388
+ for (;;) {
389
+ const documents: LegacyDocument[] = await tx<
390
+ LegacyDocument[]
391
+ >`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,
392
+ d.authority_subject_id,d.source_uri,d.source_external_id,d.source_version,d.created_at
393
+ FROM documents d JOIN document_bases b ON b.id=d.base_id AND b.account_id=d.account_id
394
+ 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)
395
+ AND (${after}::uuid IS NULL OR d.id>${after}::uuid) ORDER BY d.id LIMIT 50`;
396
+ if (!documents.length) break;
397
+ for (const document of documents) {
398
+ const chunks = await tx<Array<{ text: string }>>`SELECT text FROM document_chunks
399
+ WHERE account_id=${document.account_id} AND document_id=${document.id} ORDER BY chunk_index`;
400
+ const owner: Owner = {
401
+ accountId: document.account_id,
402
+ workspaceId: document.workspace_id,
403
+ scope: document.authority_kind,
404
+ subjectId: document.authority_subject_id,
405
+ };
406
+ const groupIds =
407
+ document.authority_kind === "workspace" && document.base_name
408
+ ? [await importGroup(tx, owner, document.base_name, `document-base:${document.base_id}`)]
409
+ : [];
410
+ await importEntry(tx, {
411
+ ...owner,
412
+ id: knowledgeMigrationId("document", document.id),
413
+ documentId: document.id,
414
+ status: "ready",
415
+ createdAt: document.created_at,
416
+ body: {
417
+ title: document.title,
418
+ kind: "source",
419
+ // Historical chunks may overlap. Preserve every retained passage;
420
+ // never claim to have reconstructed missing original full text.
421
+ content: chunks.map((chunk) => chunk.text).join("\n\n"),
422
+ groupIds,
423
+ evidence: [],
424
+ relationships: [],
425
+ source: {
426
+ kind: "file",
427
+ fileId: document.file_id,
428
+ documentId: document.id,
429
+ retention: "passages",
430
+ ...(document.source_uri ? { uri: document.source_uri } : {}),
431
+ ...(document.source_external_id ? { externalId: document.source_external_id } : {}),
432
+ ...(document.source_version ? { version: document.source_version } : {}),
433
+ },
434
+ },
435
+ });
436
+ }
437
+ after = documents.at(-1)!.id;
438
+ }
439
+ await importScopedKnowledge(tx);
440
+ // Every endpoint exists before relationship constraints are checked.
441
+ await tx`INSERT INTO knowledge_entry_links(account_id,entry_id,revision_id,ordinal,target_entry_id,relation,target_revision_id)
442
+ SELECT r.account_id,r.entry_id,r.id,(g.ordinality-1)::integer,g.value::uuid,'group',NULL::uuid
443
+ FROM knowledge_entry_revisions r CROSS JOIN LATERAL jsonb_array_elements_text(r.body->'groupIds') WITH ORDINALITY g(value,ordinality)
444
+ UNION ALL
445
+ SELECT r.account_id,r.entry_id,r.id,jsonb_array_length(r.body->'groupIds')+(l.ordinality-1)::integer,
446
+ (l.value->>'entryId')::uuid,l.value->>'relation',NULL::uuid
447
+ FROM knowledge_entry_revisions r CROSS JOIN LATERAL jsonb_array_elements(r.body->'relationships') WITH ORDINALITY l(value,ordinality)
448
+ UNION ALL
449
+ 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,
450
+ (e.value->>'entryId')::uuid,'evidence',(e.value->>'revisionId')::uuid
451
+ FROM knowledge_entry_revisions r CROSS JOIN LATERAL jsonb_array_elements(r.body->'evidence') WITH ORDINALITY e(value,ordinality)`;
452
+ }
453
+
454
+ /** Import the previously separate entity/claim graph into ordinary entries. */
455
+ async function importScopedKnowledge(tx: postgres.TransactionSql) {
456
+ type ScopedRow = {
457
+ id: string;
458
+ account_id: string;
459
+ scope_kind: KnowledgeEntryScope;
460
+ scope_workspace_id: string | null;
461
+ scope_subject_id: string | null;
462
+ created_at: Date;
463
+ actor_kind: string;
464
+ actor_subject_id: string;
465
+ initiating_human_subject_id: string | null;
466
+ };
467
+ function owner(row: ScopedRow): Owner {
468
+ return {
469
+ accountId: row.account_id,
470
+ // Some organization/user records never had a creating workspace. This
471
+ // deterministic import origin is provenance only and grants no access.
472
+ workspaceId: row.scope_workspace_id ?? knowledgeMigrationId("import-origin", row.account_id),
473
+ scope: row.scope_kind,
474
+ subjectId: row.scope_subject_id,
475
+ };
476
+ }
477
+ function actor(row: ScopedRow) {
478
+ return {
479
+ kind: row.actor_kind,
480
+ subjectId: row.actor_subject_id,
481
+ initiatingHumanSubjectId: row.initiating_human_subject_id,
482
+ };
483
+ }
484
+ let after: string | null = null;
485
+ for (;;) {
486
+ const entities: Array<
487
+ ScopedRow & { display_name: string; aliases: string[]; snapshot: Record<string, unknown> }
488
+ > = await tx<
489
+ Array<
490
+ ScopedRow & { display_name: string; aliases: string[]; snapshot: Record<string, unknown> }
491
+ >
492
+ >`
493
+ SELECT e.*,to_jsonb(e) AS snapshot,coalesce((SELECT jsonb_agg(a.alias ORDER BY a.id)
494
+ FROM knowledge_entity_aliases a WHERE a.account_id=e.account_id AND a.entity_id=e.id),'[]'::jsonb) AS aliases
495
+ FROM knowledge_entities e WHERE (${after}::uuid IS NULL OR e.id>${after}::uuid)
496
+ AND (NOT EXISTS(SELECT 1 FROM knowledge_facts f JOIN knowledge_claims c ON c.fact_id=f.id
497
+ WHERE f.account_id=e.account_id AND (f.subject_entity_id=e.id OR f.object_entity_id=e.id))
498
+ OR EXISTS(SELECT 1 FROM knowledge_facts f JOIN knowledge_claims c ON c.fact_id=f.id
499
+ WHERE f.account_id=e.account_id AND (f.subject_entity_id=e.id OR f.object_entity_id=e.id)
500
+ AND NOT EXISTS(SELECT 1 FROM knowledge_change_proposals p WHERE p.claim_id=c.id AND p.account_id=c.account_id)))
501
+ ORDER BY e.id LIMIT 200`;
502
+ if (!entities.length) break;
503
+ for (const entity of entities)
504
+ await importEntry(tx, {
505
+ ...owner(entity),
506
+ id: knowledgeMigrationId("entity", entity.id),
507
+ status: "approved",
508
+ createdAt: entity.created_at,
509
+ legacyScopeWorkspaceId: entity.scope_workspace_id,
510
+ originalActor: actor(entity),
511
+ legacySnapshot: { entity: entity.snapshot, aliases: entity.aliases },
512
+ body: {
513
+ title: entity.display_name,
514
+ kind: "group",
515
+ content: entity.aliases.join("\n"),
516
+ groupIds: [],
517
+ evidence: [],
518
+ relationships: [],
519
+ },
520
+ });
521
+ after = entities.at(-1)!.id;
522
+ }
523
+ after = null;
524
+ for (;;) {
525
+ const versions: Array<
526
+ ScopedRow & {
527
+ document_id: string | null;
528
+ file_id: string | null;
529
+ external_version_id: string;
530
+ title: string;
531
+ snapshot: Record<string, unknown>;
532
+ source_uri: string | null;
533
+ }
534
+ > = await tx<
535
+ Array<
536
+ ScopedRow & {
537
+ document_id: string | null;
538
+ file_id: string | null;
539
+ external_version_id: string;
540
+ title: string;
541
+ snapshot: Record<string, unknown>;
542
+ source_uri: string | null;
543
+ }
544
+ >
545
+ >`
546
+ SELECT v.*,coalesce(d.title,o.external_object_id) AS title,s.source_uri,
547
+ jsonb_build_object('version',to_jsonb(v),'source',to_jsonb(s),'object',to_jsonb(o),'provider',to_jsonb(p)) AS snapshot
548
+ FROM knowledge_document_versions v JOIN knowledge_source_objects o ON o.account_id=v.account_id AND o.id=v.object_id
549
+ JOIN knowledge_sources s ON s.account_id=v.account_id AND s.id=v.source_id
550
+ JOIN knowledge_providers p ON p.account_id=s.account_id AND p.id=s.provider_id
551
+ LEFT JOIN documents d ON d.account_id=v.account_id AND d.id=v.document_id
552
+ WHERE (${after}::uuid IS NULL OR v.id>${after}::uuid) ORDER BY v.id LIMIT 100`;
553
+ if (!versions.length) break;
554
+ for (const version of versions) {
555
+ const chunks = version.document_id
556
+ ? await tx<Array<{ text: string }>>`SELECT text FROM document_chunks
557
+ WHERE account_id=${version.account_id} AND document_id=${version.document_id} ORDER BY chunk_index`
558
+ : [];
559
+ await importEntry(tx, {
560
+ ...owner(version),
561
+ id: knowledgeMigrationId("document-version", version.id),
562
+ legacyDocumentVersionId: version.id,
563
+ legacyScopeWorkspaceId: version.scope_workspace_id,
564
+ status: "ready",
565
+ createdAt: version.created_at,
566
+ originalActor: actor(version),
567
+ legacySnapshot: version.snapshot,
568
+ body: {
569
+ title: version.title,
570
+ kind: "source",
571
+ content: chunks.map((c) => c.text).join("\n\n"),
572
+ groupIds: [],
573
+ evidence: [],
574
+ relationships: [],
575
+ source: {
576
+ kind: version.file_id ? "file" : "connector",
577
+ externalId: `knowledge-document-version:${version.id}`,
578
+ version: version.external_version_id,
579
+ retention: chunks.length ? "passages" : "reference",
580
+ ...(version.file_id ? { fileId: version.file_id } : {}),
581
+ ...(version.document_id ? { documentId: version.document_id } : {}),
582
+ ...(version.source_uri ? { uri: version.source_uri } : {}),
583
+ },
584
+ },
585
+ });
586
+ }
587
+ after = versions.at(-1)!.id;
588
+ }
589
+ after = null;
590
+ for (;;) {
591
+ const claims: Array<
592
+ ScopedRow & {
593
+ subject_entity_id: string;
594
+ object_entity_id: string | null;
595
+ subject_name: string;
596
+ object_name: string | null;
597
+ predicate_key: string;
598
+ object_value: unknown;
599
+ state: string;
600
+ extraction_method: string;
601
+ effective_at: Date;
602
+ expires_at: Date | null;
603
+ snapshot: Record<string, unknown>;
604
+ }
605
+ > = await tx<
606
+ Array<
607
+ ScopedRow & {
608
+ subject_entity_id: string;
609
+ object_entity_id: string | null;
610
+ subject_name: string;
611
+ object_name: string | null;
612
+ predicate_key: string;
613
+ object_value: unknown;
614
+ state: string;
615
+ extraction_method: string;
616
+ effective_at: Date;
617
+ expires_at: Date | null;
618
+ snapshot: Record<string, unknown>;
619
+ }
620
+ >
621
+ >`
622
+ SELECT c.*,f.subject_entity_id,f.object_entity_id,f.predicate_key,f.object_value,
623
+ subject.display_name AS subject_name,object.display_name AS object_name,
624
+ coalesce((SELECT state FROM knowledge_claim_reviews r WHERE r.account_id=c.account_id AND r.claim_id=c.id
625
+ ORDER BY r.review_revision DESC LIMIT 1),'proposed') AS state,
626
+ jsonb_build_object('claim',to_jsonb(c),'fact',to_jsonb(f),
627
+ 'evidence',coalesce((SELECT jsonb_agg(to_jsonb(e) ORDER BY e.id) FROM knowledge_claim_evidence e
628
+ WHERE e.account_id=c.account_id AND e.claim_id=c.id),'[]'::jsonb),
629
+ 'reviews',coalesce((SELECT jsonb_agg(to_jsonb(r) ORDER BY r.review_revision) FROM knowledge_claim_reviews r
630
+ WHERE r.account_id=c.account_id AND r.claim_id=c.id),'[]'::jsonb)) AS snapshot
631
+ FROM knowledge_claims c JOIN knowledge_facts f ON f.account_id=c.account_id AND f.id=c.fact_id
632
+ JOIN knowledge_entities subject ON subject.account_id=f.account_id AND subject.id=f.subject_entity_id
633
+ LEFT JOIN knowledge_entities object ON object.account_id=f.account_id AND object.id=f.object_entity_id
634
+ WHERE (${after}::uuid IS NULL OR c.id>${after}::uuid)
635
+ AND NOT EXISTS(SELECT 1 FROM knowledge_entries imported WHERE imported.legacy_claim_id=c.id)
636
+ AND NOT EXISTS(SELECT 1 FROM knowledge_change_proposals p WHERE p.account_id=c.account_id AND p.claim_id=c.id
637
+ )
638
+ ORDER BY c.id LIMIT 100`;
639
+ if (!claims.length) break;
640
+ for (const claim of claims) {
641
+ const retained = await tx<Array<{ document_version_id: string; locator: string | null }>>`
642
+ SELECT document_version_id,locator FROM knowledge_claim_evidence WHERE account_id=${claim.account_id}
643
+ AND claim_id=${claim.id} AND document_version_id IS NOT NULL ORDER BY id`;
644
+ const relations = await tx<
645
+ Array<{
646
+ to_claim_id: string;
647
+ imported_entry_id: string | null;
648
+ relation_type: "supersedes" | "conflicts_with";
649
+ }>
650
+ >`
651
+ 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
652
+ WHERE r.account_id=${claim.account_id} AND r.from_claim_id=${claim.id}
653
+ 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`;
654
+ const objectText =
655
+ claim.object_name ??
656
+ (typeof claim.object_value === "string"
657
+ ? claim.object_value
658
+ : JSON.stringify(claim.object_value));
659
+ await importEntry(tx, {
660
+ ...owner(claim),
661
+ id: knowledgeMigrationId("claim", claim.id),
662
+ legacyClaimId: claim.id,
663
+ legacyScopeWorkspaceId: claim.scope_workspace_id,
664
+ status: claim.state,
665
+ createdAt: claim.created_at,
666
+ validFrom: claim.effective_at,
667
+ validUntil: claim.expires_at,
668
+ originalActor: actor(claim),
669
+ legacySnapshot: claim.snapshot,
670
+ body: {
671
+ title: `${claim.subject_name}: ${claim.predicate_key}`,
672
+ kind: "fact",
673
+ content:
674
+ claim.extraction_method === "task-note-promotion-v1" &&
675
+ typeof claim.object_value === "string"
676
+ ? claim.object_value
677
+ : `${claim.subject_name} · ${claim.predicate_key}: ${objectText}`,
678
+ source: { kind: "manual", externalId: `knowledge-claim:${claim.id}` },
679
+ groupIds: [knowledgeMigrationId("entity", claim.subject_entity_id)],
680
+ evidence: retained.map((e) => {
681
+ const id = knowledgeMigrationId("document-version", e.document_version_id);
682
+ return {
683
+ entryId: id,
684
+ revisionId: knowledgeMigrationId("revision", id),
685
+ location: e.locator ? { passage: e.locator } : {},
686
+ };
687
+ }),
688
+ relationships: [
689
+ ...relations.map((r) => ({
690
+ entryId: r.imported_entry_id ?? knowledgeMigrationId("claim", r.to_claim_id),
691
+ relation: r.relation_type,
692
+ })),
693
+ ...(claim.object_entity_id
694
+ ? [
695
+ {
696
+ entryId: knowledgeMigrationId("entity", claim.object_entity_id),
697
+ relation: "related_to" as const,
698
+ },
699
+ ]
700
+ : []),
701
+ ],
702
+ },
703
+ });
704
+ }
705
+ after = claims.at(-1)!.id;
706
+ }
707
+ }
708
+
709
+ /** Preserve source selection and schedule ids, but create a new ordinary task
710
+ * revision. Old occurrence/history rows remain exact and never become agents. */
711
+ async function migrateSourceSchedules(tx: postgres.TransactionSql) {
712
+ await tx`ALTER TABLE scheduled_tasks NO FORCE ROW LEVEL SECURITY`;
713
+ await tx`ALTER TABLE scheduled_tasks DISABLE TRIGGER scheduled_task_tombstone_immutable`;
714
+ const tasks =
715
+ await tx`SELECT id,account_id,workspace_id,action,authority_revision FROM scheduled_tasks
716
+ WHERE action->>'kind'='knowledge_source_sync' AND deleted_at IS NULL ORDER BY id`;
717
+ for (const task of tasks) {
718
+ const source = KnowledgeSourceSyncAction.parse(task.action);
719
+ await tx`SELECT set_config('opengeni.account_id',${task.account_id},true),
720
+ set_config('opengeni.workspace_id',${task.workspace_id},true),
721
+ set_config('opengeni.subject_id',${source.initiatingSubjectId},true),
722
+ set_config('opengeni.initiating_human_subject_id',${source.initiatingSubjectId},true)`;
723
+ await tx`SELECT pg_advisory_xact_lock_shared(hashtextextended('session-tenancy:'||${task.workspace_id}::text,0))`;
724
+ await tx`SELECT id FROM workspaces WHERE id=${task.workspace_id} FOR UPDATE`;
725
+ const [authority] = await tx`SELECT id FROM organization_memberships m
726
+ WHERE m.account_id=${task.account_id} AND m.subject_id=${source.initiatingSubjectId}
727
+ AND m.status='active' AND m.revoked_at IS NULL
728
+ AND (m.personal_workspace_id=${task.workspace_id} OR EXISTS(SELECT 1 FROM workspace_memberships w
729
+ WHERE w.account_id=m.account_id AND w.workspace_id=${task.workspace_id} AND w.subject_id=m.subject_id))`;
730
+ // Old deterministic occurrences cannot resume as agent work. Preserve their
731
+ // audit rows and checkpoints, close their wakes, and let the next ordinary
732
+ // scheduled run continue from the retained provider cursor.
733
+ await tx`UPDATE scheduled_task_runs SET status='failed',completed_at=clock_timestamp(),
734
+ error='knowledge_source_agent_cutover',updated_at=clock_timestamp()
735
+ WHERE task_id=${task.id} AND action_kind='knowledge_source_sync' AND status IN ('queued','dispatched')`;
736
+ await tx`UPDATE knowledge_source_sync_wakes SET completed_at=clock_timestamp()
737
+ WHERE scheduled_task_id=${task.id} AND completed_at IS NULL`;
738
+ await tx`UPDATE knowledge_source_sync_states SET lease_id=NULL,lease_until=NULL,
739
+ buffered_wake=false,buffered_scheduled_task_run_id=NULL,pending_wake_count=0,updated_at=clock_timestamp()
740
+ WHERE scheduled_task_id=${task.id}`;
741
+ // A former owner's schedule is still useful configuration, but migration
742
+ // cannot invent authority for it. Re-enabling requires a current owner edit.
743
+ if (!authority)
744
+ await tx`UPDATE scheduled_tasks SET status='paused',
745
+ metadata=jsonb_set(metadata,'{knowledgeSourceSync}',coalesce(metadata->'knowledgeSourceSync','{}'::jsonb)
746
+ || '{"sourceEnabled":false,"authorityRefreshRequired":true}'::jsonb) WHERE id=${task.id}`;
747
+ const [updated] = await tx`UPDATE scheduled_tasks SET action='{"kind":"agent_turn"}',
748
+ agent_config=${JSON.stringify(knowledgeSourceAgentConfig(source))}::jsonb, authority_revision=authority_revision+1,
749
+ updated_at=clock_timestamp() WHERE id=${task.id} RETURNING authority_revision`;
750
+ if (!updated) throw new Error("Source schedule disappeared during maintenance");
751
+ await tx`SELECT freeze_scheduled_task_personal_resources(${task.account_id}::uuid,
752
+ ${task.workspace_id}::uuid,${task.id}::uuid,${updated.authority_revision}::bigint)`;
753
+ if (authority)
754
+ await tx`SELECT record_scheduled_task_revision_authority(${task.account_id}::uuid,
755
+ ${task.workspace_id}::uuid,${task.id}::uuid,${updated.authority_revision}::bigint)`;
756
+ }
757
+ await tx`ALTER TABLE scheduled_tasks ENABLE TRIGGER scheduled_task_tombstone_immutable`;
758
+ await tx`ALTER TABLE scheduled_tasks FORCE ROW LEVEL SECURITY`;
759
+ await tx`SELECT set_config('opengeni.account_id','',true),set_config('opengeni.workspace_id','',true),
760
+ set_config('opengeni.subject_id','',true),set_config('opengeni.initiating_human_subject_id','',true)`;
761
+ }