@opengeni/db 0.28.9 → 0.36.0

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 (225) hide show
  1. package/dist/attached-browser-devices.d.ts +42 -0
  2. package/dist/attempt-tool-catalogs.d.ts +16 -0
  3. package/dist/browser-auth.d.ts +299 -0
  4. package/dist/browser-downloads.d.ts +51 -0
  5. package/dist/browser-identities.d.ts +137 -0
  6. package/dist/browser-sessions.d.ts +354 -0
  7. package/dist/browser-state-artifacts.d.ts +91 -0
  8. package/dist/canonical-human-identities.d.ts +48 -0
  9. package/dist/canonical-human-identities.js +28 -0
  10. package/dist/canonical-human-identities.js.map +1 -0
  11. package/dist/capability-components.d.ts +37 -0
  12. package/dist/capability-integrations.d.ts +194 -0
  13. package/dist/chunk-5KTIEDX7.js +13200 -0
  14. package/dist/chunk-5KTIEDX7.js.map +1 -0
  15. package/dist/chunk-CQZ4QSP5.js +995 -0
  16. package/dist/chunk-CQZ4QSP5.js.map +1 -0
  17. package/dist/chunk-F32YT7MP.js +117 -0
  18. package/dist/chunk-F32YT7MP.js.map +1 -0
  19. package/dist/chunk-JC5QPLUM.js +666 -0
  20. package/dist/chunk-JC5QPLUM.js.map +1 -0
  21. package/dist/chunk-P3RE4D2B.js +3535 -0
  22. package/dist/chunk-P3RE4D2B.js.map +1 -0
  23. package/dist/chunk-TAUTWOS5.js +259 -0
  24. package/dist/chunk-TAUTWOS5.js.map +1 -0
  25. package/dist/chunk-VKLB2ZGI.js +2389 -0
  26. package/dist/chunk-VKLB2ZGI.js.map +1 -0
  27. package/dist/codemode-operations.d.ts +117 -0
  28. package/dist/codex-token-resolver.d.ts +2 -2
  29. package/dist/company-profile-schema.d.ts +798 -0
  30. package/dist/company-profile.d.ts +268 -0
  31. package/dist/computer-sessions.d.ts +109 -0
  32. package/dist/connection-token-resolver.d.ts +9 -4
  33. package/dist/database.d.ts +57 -3
  34. package/dist/editable-artifact-durable-export.d.ts +153 -0
  35. package/dist/editable-artifact-durable-export.js +788 -0
  36. package/dist/editable-artifact-durable-export.js.map +1 -0
  37. package/dist/editable-artifact-materialization.d.ts +123 -0
  38. package/dist/editable-artifacts-schema.d.ts +4152 -0
  39. package/dist/editable-artifacts.d.ts +610 -0
  40. package/dist/editable-artifacts.js +26 -0
  41. package/dist/editable-artifacts.js.map +1 -0
  42. package/dist/generated-images.d.ts +139 -0
  43. package/dist/index.d.ts +1122 -126
  44. package/dist/index.js +42329 -18407
  45. package/dist/index.js.map +1 -1
  46. package/dist/insights.d.ts +73 -10
  47. package/dist/integration-bindings.d.ts +70 -0
  48. package/dist/integration-facets.d.ts +99 -0
  49. package/dist/interaction-revisions.d.ts +14 -0
  50. package/dist/interaction-schema.d.ts +6036 -0
  51. package/dist/knowledge-source-sync-schema.d.ts +1679 -0
  52. package/dist/knowledge-source-sync.d.ts +274 -0
  53. package/dist/memory-governance.d.ts +2 -2
  54. package/dist/memory-slack-delivery.d.ts +101 -0
  55. package/dist/new-session-drafts.d.ts +2 -2
  56. package/dist/pack-components.d.ts +76 -0
  57. package/dist/pack-installations.d.ts +86 -0
  58. package/dist/persistence-errors.d.ts +3 -3
  59. package/dist/plugin-packages.d.ts +126 -0
  60. package/dist/preference-registry.d.ts +1 -1
  61. package/dist/provision-roles.d.ts +9 -1
  62. package/dist/provision-roles.js +1 -1
  63. package/dist/runtime-posture.d.ts +19 -6
  64. package/dist/schema.d.ts +22654 -9359
  65. package/dist/schema.js +209 -3
  66. package/dist/scoped-knowledge.d.ts +97 -1
  67. package/dist/session-control.d.ts +33 -7
  68. package/dist/session-queue-commands.d.ts +19 -11
  69. package/dist/session-realtime-context.d.ts +2 -2
  70. package/dist/session-realtime-ledger.d.ts +4 -4
  71. package/dist/session-realtime-mirror.d.ts +2 -2
  72. package/dist/session-realtime-state.d.ts +1 -1
  73. package/dist/session-realtime-terminal.d.ts +1 -1
  74. package/dist/session-realtime.d.ts +7 -7
  75. package/dist/session-tenancy.d.ts +44 -0
  76. package/dist/session-tenancy.js +16 -0
  77. package/dist/session-tenancy.js.map +1 -0
  78. package/dist/session-tool-call-settlement.d.ts +1 -1
  79. package/dist/slack-task-policy-schema.d.ts +847 -0
  80. package/dist/slack-task-policy.d.ts +65 -0
  81. package/dist/slack-user-link-access.d.ts +54 -0
  82. package/dist/task-notes-schema.d.ts +924 -0
  83. package/dist/task-notes.d.ts +85 -0
  84. package/dist/transcription-recordings.d.ts +2 -2
  85. package/dist/turn-initiator.d.ts +2 -2
  86. package/dist/video-generation.d.ts +225 -0
  87. package/dist/video-generation.js +68 -0
  88. package/dist/video-generation.js.map +1 -0
  89. package/dist/workspace-artifacts.d.ts +1 -1
  90. package/dist/workspace-instruction-policies.d.ts +1 -1
  91. package/dist/workspace-learning-policy-schema.d.ts +932 -0
  92. package/dist/workspace-learning-policy.d.ts +161 -0
  93. package/dist/xai-subscription.d.ts +326 -0
  94. package/drizzle/0183_model_call_provider_cost_estimates.sql +39 -0
  95. package/drizzle/0184_sandbox_drain_teardown_fence.sql +682 -0
  96. package/drizzle/0185_temporal_schedule_cleanup_outbox.sql +168 -0
  97. package/drizzle/0186_sandbox_capture_provider_contract.sql +400 -0
  98. package/drizzle/0187_generated_image_artifacts.sql +173 -0
  99. package/drizzle/0188_image_generation_retention_failure.sql +25 -0
  100. package/drizzle/0189_retained_session_image_formats.sql +13 -0
  101. package/drizzle/0190_timeline_annotations.sql +31 -0
  102. package/drizzle/0191_editable_artifact_engine.sql +3853 -0
  103. package/drizzle/0192_editable_artifact_live_tickets.sql +338 -0
  104. package/drizzle/0193_editable_artifact_authorization.sql +190 -0
  105. package/drizzle/0194_editable_artifact_durable_exports.sql +160 -0
  106. package/drizzle/0195_editable_artifact_import_authorization.sql +203 -0
  107. package/drizzle/0196_rig_provider_images.sql +152 -0
  108. package/drizzle/0197_knowledge_source_sync_schedules.sql +523 -0
  109. package/drizzle/0198_memory_slack_publication_delivery.sql +393 -0
  110. package/drizzle/0199_workspace_learning_policy.sql +980 -0
  111. package/drizzle/0201_company_profile_authority.sql +849 -0
  112. package/drizzle/0202_document_index_checkpoints.sql +85 -0
  113. package/drizzle/0203_durable_video_generation.sql +353 -0
  114. package/drizzle/0204_video_generation_funding.sql +44 -0
  115. package/drizzle/0205_attempt_tool_catalogs.sql +230 -0
  116. package/drizzle/0206_browser_sessions.sql +646 -0
  117. package/drizzle/0207_browser_identities.sql +379 -0
  118. package/drizzle/0208_attached_browser_devices.sql +165 -0
  119. package/drizzle/0209_computer_sessions.sql +575 -0
  120. package/drizzle/0210_browser_auth_network_interventions.sql +441 -0
  121. package/drizzle/0211_editable_artifact_session_links.sql +58 -0
  122. package/drizzle/0212_browser_state_transfer_hardening.sql +560 -0
  123. package/drizzle/0212_slack_installation_bindings.sql +348 -0
  124. package/drizzle/0213_browser_interaction_authority.sql +67 -0
  125. package/drizzle/0213_slack_user_link_access_requests.sql +177 -0
  126. package/drizzle/0214_browser_download_saves.sql +34 -0
  127. package/drizzle/0214_session_activity_commit_gate.sql +262 -0
  128. package/drizzle/0215_browser_controller_host.sql +385 -0
  129. package/drizzle/0215_capabilities_platform.sql +980 -0
  130. package/drizzle/0216_browser_auth_health_evidence.sql +137 -0
  131. package/drizzle/0216_pack_component_ownership.sql +235 -0
  132. package/drizzle/0217_capability_definition_delete_authority.sql +32 -0
  133. package/drizzle/0217_external_browser_auth_operations.sql +22 -0
  134. package/drizzle/0218_organization_tenancy_foundation.sql +299 -0
  135. package/drizzle/0219_organization_tenancy_managed_human_provisioning.sql +317 -0
  136. package/drizzle/0219_site_auth_maintenance_sessions.sql +234 -0
  137. package/drizzle/0220_memory_slack_append_only_cascade.sql +38 -0
  138. package/drizzle/0220_session_channels.sql +54 -0
  139. package/drizzle/0221_sessions_channel_index.sql +5 -0
  140. package/drizzle/0222_session_visibility_authority_epochs.sql +158 -0
  141. package/drizzle/0222_sessions_channel_fk.sql +89 -0
  142. package/drizzle/0223_pending_tool_event_output.sql +55 -0
  143. package/drizzle/0223_sessions_channel_fk_validate.sql +118 -0
  144. package/drizzle/0224_slack_post_outcome_reconciliation.sql +123 -0
  145. package/drizzle/0225_session_visibility_fork_activation.sql +1363 -0
  146. package/drizzle/0226_personal_codex_authority_foundation.sql +365 -0
  147. package/drizzle/0227_slack_native_actions.sql +145 -0
  148. package/drizzle/0228_interaction_controller_data_plane.sql +14 -0
  149. package/drizzle/0228_slack_task_policy.sql +400 -0
  150. package/drizzle/0229_slack_inbox_file_fact.sql +10 -0
  151. package/drizzle/0230_user_scoped_variable_sets_rigs.sql +84 -0
  152. package/drizzle/0231_integration_definition_identity_cutover.sql +271 -0
  153. package/drizzle/0232_integration_facet_authority_cutover.sql +610 -0
  154. package/drizzle/0233_skill_and_integration_authority_cutover.sql +1162 -0
  155. package/drizzle/0234_xai_subscription_authority.sql +1139 -0
  156. package/drizzle/0235_canonical_human_login_bindings.sql +920 -0
  157. package/drizzle/0236_browser_identity_lifecycle.sql +39 -0
  158. package/drizzle/0236_session_visibility_slack_policy.sql +72 -0
  159. package/drizzle/0237_interaction_transition_reaper.sql +263 -0
  160. package/drizzle/0238_goal_persistence_policy.sql +471 -0
  161. package/drizzle/0238_supergrok_realtime_model.sql +29 -0
  162. package/drizzle/0239_supergrok_video_funding.sql +50 -0
  163. package/drizzle/0239_task_tree_notes.sql +990 -0
  164. package/package.json +26 -5
  165. package/src/attached-browser-devices.ts +371 -0
  166. package/src/attempt-tool-catalogs.ts +131 -0
  167. package/src/browser-auth.ts +4373 -0
  168. package/src/browser-downloads.ts +517 -0
  169. package/src/browser-identities.ts +1385 -0
  170. package/src/browser-sessions.ts +2294 -0
  171. package/src/browser-state-artifacts.ts +449 -0
  172. package/src/canonical-human-identities.ts +317 -0
  173. package/src/capability-components.ts +166 -0
  174. package/src/capability-integrations.ts +1571 -0
  175. package/src/codemode-operations.ts +674 -0
  176. package/src/company-profile-schema.ts +143 -0
  177. package/src/company-profile.ts +968 -0
  178. package/src/computer-sessions.ts +1130 -0
  179. package/src/connection-token-resolver.ts +270 -25
  180. package/src/database.ts +468 -24
  181. package/src/editable-artifact-durable-export.ts +1201 -0
  182. package/src/editable-artifact-materialization.ts +893 -0
  183. package/src/editable-artifacts-schema.ts +879 -0
  184. package/src/editable-artifacts.ts +5404 -0
  185. package/src/generated-images.ts +740 -0
  186. package/src/index.ts +9963 -1465
  187. package/src/insights.ts +251 -23
  188. package/src/integration-bindings.ts +455 -0
  189. package/src/integration-facets.ts +862 -0
  190. package/src/interaction-revisions.ts +70 -0
  191. package/src/interaction-schema.ts +1606 -0
  192. package/src/knowledge-source-sync-schema.ts +192 -0
  193. package/src/knowledge-source-sync.ts +1922 -0
  194. package/src/memory-slack-delivery.ts +801 -0
  195. package/src/pack-components.ts +954 -0
  196. package/src/pack-installations.ts +886 -0
  197. package/src/persistence-errors.ts +4 -7
  198. package/src/plugin-packages.ts +1186 -0
  199. package/src/provision-roles.ts +534 -1
  200. package/src/runtime-posture-cli.ts +9 -0
  201. package/src/runtime-posture.ts +721 -5
  202. package/src/schema.ts +6270 -3159
  203. package/src/scoped-knowledge-schema.ts +1 -1
  204. package/src/scoped-knowledge.ts +655 -3
  205. package/src/session-control.ts +130 -42
  206. package/src/session-queue-commands.ts +265 -15
  207. package/src/session-realtime-context.ts +2 -2
  208. package/src/session-realtime-ledger.ts +3 -3
  209. package/src/session-realtime.ts +7 -7
  210. package/src/session-tenancy.ts +188 -0
  211. package/src/session-tool-call-settlement.ts +18 -3
  212. package/src/slack-task-policy-schema.ts +130 -0
  213. package/src/slack-task-policy.ts +341 -0
  214. package/src/slack-user-link-access.ts +702 -0
  215. package/src/task-notes-schema.ts +135 -0
  216. package/src/task-notes.ts +178 -0
  217. package/src/video-generation.ts +1530 -0
  218. package/src/workspace-artifacts.ts +1 -1
  219. package/src/workspace-learning-policy-schema.ts +139 -0
  220. package/src/workspace-learning-policy.ts +595 -0
  221. package/src/xai-subscription.ts +1887 -0
  222. package/dist/chunk-P6CUHXQZ.js +0 -7248
  223. package/dist/chunk-P6CUHXQZ.js.map +0 -1
  224. package/dist/chunk-VHBFHMPC.js +0 -1219
  225. package/dist/chunk-VHBFHMPC.js.map +0 -1
@@ -0,0 +1,862 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ import { stableJson } from "@opengeni/contracts";
4
+ import { and, asc, eq, ne, sql } from "drizzle-orm";
5
+
6
+ import {
7
+ setSubjectRlsContext,
8
+ withRlsContext,
9
+ withWorkspaceSubjectRls,
10
+ type Database,
11
+ } from "./database";
12
+ import {
13
+ IntegrationFacetBindingOwnershipConflictError,
14
+ IntegrationFacetBindingVersionConflictError,
15
+ listIntegrationFacetBindingOwners,
16
+ removeIntegrationFacetBindingOwner,
17
+ upsertIntegrationFacetBinding,
18
+ } from "./integration-bindings";
19
+ import * as schema from "./schema";
20
+
21
+ export type IntegrationFacetKind =
22
+ | "knowledge_source"
23
+ | "inbound_trigger"
24
+ | "delivery_destination"
25
+ | "identity_link";
26
+
27
+ export type IntegrationFacetBindingSummary = {
28
+ id: string;
29
+ facetKey: string;
30
+ kind: IntegrationFacetKind;
31
+ bindingKey: string;
32
+ displayName: string;
33
+ connectionId: string | null;
34
+ status: "active" | "paused" | "needs_attention" | "disabled";
35
+ config: Record<string, unknown>;
36
+ version: number;
37
+ hasCursor: boolean;
38
+ lastSuccessAt: string | null;
39
+ lastErrorCode: string | null;
40
+ createdAt: string;
41
+ updatedAt: string;
42
+ };
43
+
44
+ export type IntegrationFacetDefinitionSummary = {
45
+ facetKey: string;
46
+ kind: IntegrationFacetKind;
47
+ configSchema: Record<string, unknown>;
48
+ capabilities: Record<string, unknown>;
49
+ };
50
+
51
+ export type IntegrationInstanceFacets = {
52
+ capabilityId: string;
53
+ instanceKey: string;
54
+ providerDomain: string;
55
+ connectionId: string | null;
56
+ facets: {
57
+ definition: IntegrationFacetDefinitionSummary;
58
+ binding: IntegrationFacetBindingSummary | null;
59
+ }[];
60
+ };
61
+
62
+ export type IntegrationFacetMutationResult = {
63
+ capabilityId: string;
64
+ instanceKey: string;
65
+ facetKey: string;
66
+ status: "configured" | "paused" | "active";
67
+ binding: IntegrationFacetBindingSummary;
68
+ };
69
+
70
+ export type IntegrationFacetRemovalResult = {
71
+ capabilityId: string;
72
+ instanceKey: string;
73
+ facetKey: string;
74
+ status: "not_configured" | "removed" | "retained_by_other_owners";
75
+ binding: IntegrationFacetBindingSummary | null;
76
+ remainingOwners: {
77
+ kind: "direct" | "plugin" | "pack" | "migration";
78
+ id: string;
79
+ removable: boolean;
80
+ }[];
81
+ };
82
+
83
+ export class IntegrationFacetNotFoundError extends Error {
84
+ readonly name = "IntegrationFacetNotFoundError";
85
+ }
86
+
87
+ export class IntegrationFacetConnectionError extends Error {
88
+ readonly name = "IntegrationFacetConnectionError";
89
+ }
90
+
91
+ export class IntegrationFacetConfigError extends Error {
92
+ readonly name = "IntegrationFacetConfigError";
93
+ }
94
+
95
+ export class IntegrationFacetOperationIdempotencyError extends Error {
96
+ readonly name = "IntegrationFacetOperationIdempotencyError";
97
+ }
98
+
99
+ type IntegrationInstanceContext = {
100
+ accountId: string;
101
+ integrationFacetInstallationId: string;
102
+ integrationFacetId: string;
103
+ providerDomain: string;
104
+ connectionId: string | null;
105
+ };
106
+
107
+ export async function listIntegrationInstanceFacets(
108
+ db: Database,
109
+ workspaceId: string,
110
+ subjectId: string,
111
+ capabilityId: string,
112
+ instanceKey: string,
113
+ ): Promise<IntegrationInstanceFacets> {
114
+ return await withWorkspaceSubjectRls(db, workspaceId, subjectId, async (scopedDb) => {
115
+ const context = await loadIntegrationInstanceContext(
116
+ scopedDb,
117
+ workspaceId,
118
+ subjectId,
119
+ capabilityId,
120
+ instanceKey,
121
+ );
122
+ if (!context) throw new IntegrationFacetNotFoundError("Integration instance not found");
123
+ const rows = await scopedDb
124
+ .select({
125
+ facetKey: schema.integrationFacetDefinitions.facetKey,
126
+ kind: schema.integrationFacetDefinitions.kind,
127
+ configSchema: schema.integrationFacetDefinitions.configSchema,
128
+ capabilities: schema.integrationFacetDefinitions.capabilities,
129
+ bindingId: schema.integrationFacetBindings.id,
130
+ bindingKey: schema.integrationFacetBindings.bindingKey,
131
+ displayName: schema.integrationFacetBindings.displayName,
132
+ connectionId: schema.integrationFacetBindings.connectionId,
133
+ status: schema.integrationFacetBindings.status,
134
+ config: schema.integrationFacetBindings.config,
135
+ cursor: schema.integrationFacetBindings.cursor,
136
+ version: schema.integrationFacetBindings.version,
137
+ lastSuccessAt: schema.integrationFacetBindings.lastSuccessAt,
138
+ lastErrorCode: schema.integrationFacetBindings.lastErrorCode,
139
+ createdAt: schema.integrationFacetBindings.createdAt,
140
+ updatedAt: schema.integrationFacetBindings.updatedAt,
141
+ })
142
+ .from(schema.integrationFacetDefinitions)
143
+ .leftJoin(
144
+ schema.integrationFacetBindings,
145
+ and(
146
+ eq(
147
+ schema.integrationFacetBindings.facetDefinitionId,
148
+ schema.integrationFacetDefinitions.id,
149
+ ),
150
+ eq(
151
+ schema.integrationFacetBindings.integrationFacetInstallationId,
152
+ context.integrationFacetInstallationId,
153
+ ),
154
+ eq(schema.integrationFacetBindings.bindingKey, instanceKey),
155
+ ),
156
+ )
157
+ .where(
158
+ and(
159
+ eq(schema.integrationFacetDefinitions.integrationFacetId, context.integrationFacetId),
160
+ ne(schema.integrationFacetDefinitions.kind, "tools"),
161
+ ),
162
+ )
163
+ .orderBy(
164
+ asc(schema.integrationFacetDefinitions.kind),
165
+ asc(schema.integrationFacetDefinitions.facetKey),
166
+ );
167
+ return {
168
+ capabilityId,
169
+ instanceKey,
170
+ providerDomain: context.providerDomain,
171
+ connectionId: context.connectionId,
172
+ facets: rows.map((row) => ({
173
+ definition: {
174
+ facetKey: row.facetKey,
175
+ kind: facetKind(row.kind),
176
+ configSchema: row.configSchema,
177
+ capabilities: row.capabilities,
178
+ },
179
+ binding: row.bindingId
180
+ ? bindingSummary({
181
+ id: row.bindingId,
182
+ facetKey: row.facetKey,
183
+ kind: row.kind,
184
+ bindingKey: row.bindingKey!,
185
+ displayName: row.displayName!,
186
+ connectionId: row.connectionId ?? null,
187
+ status: row.status!,
188
+ config: row.config!,
189
+ cursor: row.cursor!,
190
+ version: row.version!,
191
+ lastSuccessAt: row.lastSuccessAt ?? null,
192
+ lastErrorCode: row.lastErrorCode ?? null,
193
+ createdAt: row.createdAt!,
194
+ updatedAt: row.updatedAt!,
195
+ })
196
+ : null,
197
+ })),
198
+ };
199
+ });
200
+ }
201
+
202
+ export async function configureIntegrationFacet(
203
+ db: Database,
204
+ input: {
205
+ accountId: string;
206
+ workspaceId: string;
207
+ subjectId: string;
208
+ capabilityId: string;
209
+ instanceKey: string;
210
+ facetKey: string;
211
+ displayName: string;
212
+ config: Record<string, unknown>;
213
+ expectedVersion?: number;
214
+ idempotencyKey: string;
215
+ },
216
+ ): Promise<IntegrationFacetMutationResult> {
217
+ const requestDigest = sha256(
218
+ stableJson({
219
+ action: "configure",
220
+ capabilityId: input.capabilityId,
221
+ instanceKey: input.instanceKey,
222
+ facetKey: input.facetKey,
223
+ displayName: input.displayName,
224
+ config: input.config,
225
+ expectedVersion: input.expectedVersion ?? null,
226
+ }),
227
+ );
228
+ return await withFacetOperation(db, input, requestDigest, "configure", async (tx) => {
229
+ const context = await requireMutationContext(tx, input);
230
+ const definition = await requireFacetDefinition(tx, context, input.facetKey);
231
+ assertFacetConfig(input.config, definition.configSchema);
232
+ await requireFacetConnection(tx, input, context, definition.capabilities);
233
+ const result = await upsertIntegrationFacetBinding(tx, {
234
+ accountId: input.accountId,
235
+ workspaceId: input.workspaceId,
236
+ integrationFacetInstallationId: context.integrationFacetInstallationId,
237
+ facetDefinitionId: definition.id,
238
+ bindingKey: input.instanceKey,
239
+ displayName: input.displayName,
240
+ runtimeKey: null,
241
+ connectionId: context.connectionId,
242
+ config: input.config,
243
+ createdBySubjectId: input.subjectId,
244
+ owner: facetOwner(input.capabilityId, input.instanceKey, input.facetKey),
245
+ ...(input.expectedVersion !== undefined ? { expectedVersion: input.expectedVersion } : {}),
246
+ });
247
+ return {
248
+ capabilityId: input.capabilityId,
249
+ instanceKey: input.instanceKey,
250
+ facetKey: input.facetKey,
251
+ status: "configured",
252
+ binding: bindingSummary({
253
+ ...result.row,
254
+ facetKey: definition.facetKey,
255
+ kind: definition.kind,
256
+ }),
257
+ };
258
+ });
259
+ }
260
+
261
+ export async function setIntegrationFacetLifecycle(
262
+ db: Database,
263
+ input: {
264
+ accountId: string;
265
+ workspaceId: string;
266
+ subjectId: string;
267
+ capabilityId: string;
268
+ instanceKey: string;
269
+ facetKey: string;
270
+ action: "pause" | "resume";
271
+ expectedVersion: number;
272
+ idempotencyKey: string;
273
+ },
274
+ ): Promise<IntegrationFacetMutationResult> {
275
+ const requestDigest = sha256(
276
+ stableJson({
277
+ ...input,
278
+ accountId: undefined,
279
+ workspaceId: undefined,
280
+ subjectId: undefined,
281
+ idempotencyKey: undefined,
282
+ }),
283
+ );
284
+ return await withFacetOperation(db, input, requestDigest, "update", async (tx) => {
285
+ const context = await requireMutationContext(tx, input);
286
+ const definition = await requireFacetDefinition(tx, context, input.facetKey);
287
+ const binding = await loadFacetBinding(tx, context, input.instanceKey, definition.id, true);
288
+ if (!binding || binding.status === "disabled") {
289
+ throw new IntegrationFacetNotFoundError("Integration facet is not configured");
290
+ }
291
+ if (binding.version !== input.expectedVersion) {
292
+ throw new IntegrationFacetBindingVersionConflictError(
293
+ input.instanceKey,
294
+ input.expectedVersion,
295
+ binding.version,
296
+ );
297
+ }
298
+ await assertDirectFacetOwnership(tx, binding.id, input);
299
+ const targetStatus = input.action === "pause" ? "paused" : "active";
300
+ let row = binding;
301
+ if (binding.status !== targetStatus) {
302
+ const [updated] = await tx
303
+ .update(schema.integrationFacetBindings)
304
+ .set({
305
+ status: targetStatus,
306
+ version: binding.version + 1,
307
+ ...(input.action === "resume" ? { lastErrorCode: null } : {}),
308
+ updatedAt: new Date(),
309
+ })
310
+ .where(eq(schema.integrationFacetBindings.id, binding.id))
311
+ .returning();
312
+ if (!updated) throw new Error("Failed to update Integration facet lifecycle");
313
+ row = updated;
314
+ }
315
+ return {
316
+ capabilityId: input.capabilityId,
317
+ instanceKey: input.instanceKey,
318
+ facetKey: input.facetKey,
319
+ status: input.action === "pause" ? "paused" : "active",
320
+ binding: bindingSummary({
321
+ ...row,
322
+ facetKey: definition.facetKey,
323
+ kind: definition.kind,
324
+ }),
325
+ };
326
+ });
327
+ }
328
+
329
+ export async function removeIntegrationFacet(
330
+ db: Database,
331
+ input: {
332
+ accountId: string;
333
+ workspaceId: string;
334
+ subjectId: string;
335
+ capabilityId: string;
336
+ instanceKey: string;
337
+ facetKey: string;
338
+ expectedVersion: number;
339
+ idempotencyKey: string;
340
+ },
341
+ ): Promise<IntegrationFacetRemovalResult> {
342
+ const requestDigest = sha256(
343
+ stableJson({
344
+ action: "remove",
345
+ capabilityId: input.capabilityId,
346
+ instanceKey: input.instanceKey,
347
+ facetKey: input.facetKey,
348
+ expectedVersion: input.expectedVersion,
349
+ }),
350
+ );
351
+ return await withFacetOperation(db, input, requestDigest, "disconnect", async (tx) => {
352
+ const context = await requireMutationContext(tx, input);
353
+ const definition = await requireFacetDefinition(tx, context, input.facetKey);
354
+ const binding = await loadFacetBinding(tx, context, input.instanceKey, definition.id, true);
355
+ const owner = facetOwner(input.capabilityId, input.instanceKey, input.facetKey);
356
+ if (!binding) {
357
+ return {
358
+ capabilityId: input.capabilityId,
359
+ instanceKey: input.instanceKey,
360
+ facetKey: input.facetKey,
361
+ status: "not_configured",
362
+ binding: null,
363
+ remainingOwners: [],
364
+ };
365
+ }
366
+ if (binding.version !== input.expectedVersion) {
367
+ throw new IntegrationFacetBindingVersionConflictError(
368
+ input.instanceKey,
369
+ input.expectedVersion,
370
+ binding.version,
371
+ );
372
+ }
373
+ const owners = await listIntegrationFacetBindingOwners(tx, binding.id);
374
+ if (!owners.some((candidate) => candidate.kind === owner.kind && candidate.id === owner.id)) {
375
+ return {
376
+ capabilityId: input.capabilityId,
377
+ instanceKey: input.instanceKey,
378
+ facetKey: input.facetKey,
379
+ status: "not_configured",
380
+ binding: bindingSummary({
381
+ ...binding,
382
+ facetKey: definition.facetKey,
383
+ kind: definition.kind,
384
+ }),
385
+ remainingOwners: owners,
386
+ };
387
+ }
388
+ const removed = await removeIntegrationFacetBindingOwner(tx, {
389
+ workspaceId: input.workspaceId,
390
+ bindingId: binding.id,
391
+ owner,
392
+ expectedVersion: input.expectedVersion,
393
+ });
394
+ return {
395
+ capabilityId: input.capabilityId,
396
+ instanceKey: input.instanceKey,
397
+ facetKey: input.facetKey,
398
+ status: removed.remainingOwners.length > 0 ? "retained_by_other_owners" : "removed",
399
+ binding: removed.binding
400
+ ? bindingSummary({
401
+ ...removed.binding,
402
+ facetKey: definition.facetKey,
403
+ kind: definition.kind,
404
+ })
405
+ : null,
406
+ remainingOwners: removed.remainingOwners,
407
+ };
408
+ });
409
+ }
410
+
411
+ async function withFacetOperation<T extends Record<string, unknown>>(
412
+ db: Database,
413
+ input: {
414
+ accountId: string;
415
+ workspaceId: string;
416
+ subjectId: string;
417
+ capabilityId: string;
418
+ instanceKey: string;
419
+ facetKey: string;
420
+ idempotencyKey: string;
421
+ },
422
+ requestDigest: string,
423
+ kind: string,
424
+ execute: (tx: Database) => Promise<T>,
425
+ ): Promise<T> {
426
+ return await withRlsContext(
427
+ db,
428
+ { accountId: input.accountId, workspaceId: input.workspaceId },
429
+ async (scopedDb) => {
430
+ await setSubjectRlsContext(scopedDb, input.subjectId);
431
+ return await scopedDb.transaction(async (txRaw) => {
432
+ const tx = txRaw as unknown as Database;
433
+ await tx.execute(
434
+ sql`select pg_advisory_xact_lock(hashtextextended(${`capability-operation:${input.workspaceId}:${input.idempotencyKey}`}, 0))`,
435
+ );
436
+ const [existing] = await tx
437
+ .select()
438
+ .from(schema.capabilityOperations)
439
+ .where(
440
+ and(
441
+ eq(schema.capabilityOperations.workspaceId, input.workspaceId),
442
+ eq(schema.capabilityOperations.idempotencyKey, input.idempotencyKey),
443
+ ),
444
+ )
445
+ .for("update")
446
+ .limit(1);
447
+ if (existing) {
448
+ if (existing.requestDigest !== requestDigest) {
449
+ throw new IntegrationFacetOperationIdempotencyError(
450
+ "Integration facet idempotency key was reused",
451
+ );
452
+ }
453
+ if (existing.status === "completed" && existing.result) return existing.result as T;
454
+ throw new IntegrationFacetOperationIdempotencyError(
455
+ "Integration facet operation is already in progress",
456
+ );
457
+ }
458
+ const result = await execute(tx);
459
+ await tx.insert(schema.capabilityOperations).values({
460
+ accountId: input.accountId,
461
+ workspaceId: input.workspaceId,
462
+ idempotencyKey: input.idempotencyKey,
463
+ requestDigest,
464
+ kind,
465
+ targetKind: "facet_binding",
466
+ targetId: `facet:${sha256(
467
+ `${input.capabilityId}\0${input.instanceKey}\0${input.facetKey}`,
468
+ )}`,
469
+ status: "completed",
470
+ phase: "completed",
471
+ result,
472
+ createdBySubjectId: input.subjectId,
473
+ completedAt: new Date(),
474
+ });
475
+ return result;
476
+ });
477
+ },
478
+ );
479
+ }
480
+
481
+ async function requireMutationContext(
482
+ db: Database,
483
+ input: {
484
+ workspaceId: string;
485
+ subjectId: string;
486
+ capabilityId: string;
487
+ instanceKey: string;
488
+ },
489
+ ): Promise<IntegrationInstanceContext> {
490
+ const context = await loadIntegrationInstanceContext(
491
+ db,
492
+ input.workspaceId,
493
+ input.subjectId,
494
+ input.capabilityId,
495
+ input.instanceKey,
496
+ true,
497
+ );
498
+ if (!context) throw new IntegrationFacetNotFoundError("Integration instance not found");
499
+ return context;
500
+ }
501
+
502
+ async function loadIntegrationInstanceContext(
503
+ db: Database,
504
+ workspaceId: string,
505
+ subjectId: string,
506
+ capabilityId: string,
507
+ instanceKey: string,
508
+ lock = false,
509
+ ): Promise<IntegrationInstanceContext | null> {
510
+ let query = db
511
+ .select({
512
+ accountId: schema.integrationFacetBindings.accountId,
513
+ integrationFacetInstallationId:
514
+ schema.integrationFacetBindings.integrationFacetInstallationId,
515
+ integrationFacetId: schema.integrationFacetDefinitions.integrationFacetId,
516
+ providerDomain: schema.capabilityIntegrationFacets.providerDomain,
517
+ connectionId: schema.integrationFacetBindings.connectionId,
518
+ })
519
+ .from(schema.integrationFacetBindings)
520
+ .innerJoin(
521
+ schema.integrationFacetDefinitions,
522
+ eq(schema.integrationFacetDefinitions.id, schema.integrationFacetBindings.facetDefinitionId),
523
+ )
524
+ .innerJoin(
525
+ schema.capabilityIntegrationFacets,
526
+ eq(
527
+ schema.capabilityIntegrationFacets.facetId,
528
+ schema.integrationFacetDefinitions.integrationFacetId,
529
+ ),
530
+ )
531
+ .innerJoin(
532
+ schema.capabilityFacetInstallations,
533
+ eq(
534
+ schema.capabilityFacetInstallations.id,
535
+ schema.integrationFacetBindings.integrationFacetInstallationId,
536
+ ),
537
+ )
538
+ .innerJoin(
539
+ schema.capabilityPluginInstallations,
540
+ eq(
541
+ schema.capabilityPluginInstallations.id,
542
+ schema.capabilityFacetInstallations.pluginInstallationId,
543
+ ),
544
+ )
545
+ .innerJoin(
546
+ schema.capabilityPluginVersions,
547
+ eq(schema.capabilityPluginVersions.id, schema.capabilityPluginInstallations.pluginVersionId),
548
+ )
549
+ .where(
550
+ and(
551
+ eq(schema.integrationFacetBindings.workspaceId, workspaceId),
552
+ eq(schema.integrationFacetBindings.bindingKey, instanceKey),
553
+ eq(schema.integrationFacetBindings.status, "active"),
554
+ eq(schema.integrationFacetDefinitions.kind, "tools"),
555
+ eq(schema.capabilityPluginInstallations.status, "active"),
556
+ sql`${schema.capabilityPluginVersions.manifest} ->> 'capabilityId' = ${capabilityId}`,
557
+ sql`(
558
+ ${schema.integrationFacetBindings.connectionId} is null
559
+ or exists (
560
+ select 1 from ${schema.connections} connection
561
+ where connection.id = ${schema.integrationFacetBindings.connectionId}
562
+ and (connection.subject_id is null or connection.subject_id = ${subjectId})
563
+ )
564
+ )`,
565
+ ),
566
+ )
567
+ .limit(2);
568
+ if (lock) query = query.for("update") as typeof query;
569
+ const rows = await query;
570
+ if (rows.length === 0) return null;
571
+ if (rows.length !== 1) throw new Error(`API Integration ${capabilityId} instance is ambiguous`);
572
+ return rows[0]!;
573
+ }
574
+
575
+ async function requireFacetDefinition(
576
+ db: Database,
577
+ context: IntegrationInstanceContext,
578
+ facetKey: string,
579
+ ): Promise<typeof schema.integrationFacetDefinitions.$inferSelect> {
580
+ const [definition] = await db
581
+ .select()
582
+ .from(schema.integrationFacetDefinitions)
583
+ .where(
584
+ and(
585
+ eq(schema.integrationFacetDefinitions.integrationFacetId, context.integrationFacetId),
586
+ eq(schema.integrationFacetDefinitions.facetKey, facetKey),
587
+ ne(schema.integrationFacetDefinitions.kind, "tools"),
588
+ ),
589
+ )
590
+ .limit(1);
591
+ if (!definition) throw new IntegrationFacetNotFoundError("Integration facet not found");
592
+ facetKind(definition.kind);
593
+ return definition;
594
+ }
595
+
596
+ async function requireFacetConnection(
597
+ db: Database,
598
+ input: { workspaceId: string; subjectId: string },
599
+ context: IntegrationInstanceContext,
600
+ capabilities: Record<string, unknown>,
601
+ ): Promise<void> {
602
+ if (!context.connectionId) {
603
+ if (capabilities.connectionRequired === true) {
604
+ throw new IntegrationFacetConnectionError(
605
+ "Integration facet requires the instance Connection",
606
+ );
607
+ }
608
+ return;
609
+ }
610
+ const [connection] = await db
611
+ .select({
612
+ providerDomain: schema.connections.providerDomain,
613
+ subjectId: schema.connections.subjectId,
614
+ status: schema.connections.status,
615
+ })
616
+ .from(schema.connections)
617
+ .where(
618
+ and(
619
+ eq(schema.connections.workspaceId, input.workspaceId),
620
+ eq(schema.connections.id, context.connectionId),
621
+ ),
622
+ )
623
+ .limit(1);
624
+ if (
625
+ !connection ||
626
+ connection.status !== "active" ||
627
+ (connection.subjectId !== null && connection.subjectId !== input.subjectId) ||
628
+ connection.providerDomain.toLowerCase() !== context.providerDomain.toLowerCase()
629
+ ) {
630
+ throw new IntegrationFacetConnectionError(
631
+ "Integration facet Connection is unavailable or does not match the provider",
632
+ );
633
+ }
634
+ }
635
+
636
+ async function loadFacetBinding(
637
+ db: Database,
638
+ context: IntegrationInstanceContext,
639
+ instanceKey: string,
640
+ facetDefinitionId: string,
641
+ lock = false,
642
+ ): Promise<typeof schema.integrationFacetBindings.$inferSelect | null> {
643
+ let query = db
644
+ .select()
645
+ .from(schema.integrationFacetBindings)
646
+ .where(
647
+ and(
648
+ eq(
649
+ schema.integrationFacetBindings.integrationFacetInstallationId,
650
+ context.integrationFacetInstallationId,
651
+ ),
652
+ eq(schema.integrationFacetBindings.facetDefinitionId, facetDefinitionId),
653
+ eq(schema.integrationFacetBindings.bindingKey, instanceKey),
654
+ ),
655
+ )
656
+ .limit(1);
657
+ if (lock) query = query.for("update") as typeof query;
658
+ const [binding] = await query;
659
+ return binding ?? null;
660
+ }
661
+
662
+ async function assertDirectFacetOwnership(
663
+ db: Database,
664
+ bindingId: string,
665
+ input: { capabilityId: string; instanceKey: string; facetKey: string },
666
+ ): Promise<void> {
667
+ const expected = facetOwner(input.capabilityId, input.instanceKey, input.facetKey);
668
+ const owners = await listIntegrationFacetBindingOwners(db, bindingId);
669
+ if (!owners.some((owner) => owner.kind === expected.kind && owner.id === expected.id)) {
670
+ throw new IntegrationFacetBindingOwnershipConflictError(
671
+ "Integration facet is not directly owned",
672
+ );
673
+ }
674
+ if (owners.some((owner) => owner.kind !== expected.kind || owner.id !== expected.id)) {
675
+ throw new IntegrationFacetBindingOwnershipConflictError(
676
+ "Integration facet is shared by another owner and cannot change lifecycle in place",
677
+ );
678
+ }
679
+ }
680
+
681
+ function facetOwner(capabilityId: string, instanceKey: string, facetKey: string) {
682
+ return {
683
+ kind: "direct" as const,
684
+ id: `facet:${sha256(`${capabilityId}\0${instanceKey}\0${facetKey}`)}`,
685
+ removable: true,
686
+ };
687
+ }
688
+
689
+ type BindingSummaryRow = Pick<
690
+ typeof schema.integrationFacetBindings.$inferSelect,
691
+ | "id"
692
+ | "bindingKey"
693
+ | "displayName"
694
+ | "connectionId"
695
+ | "status"
696
+ | "config"
697
+ | "cursor"
698
+ | "version"
699
+ | "lastSuccessAt"
700
+ | "lastErrorCode"
701
+ | "createdAt"
702
+ | "updatedAt"
703
+ > & {
704
+ facetKey: string;
705
+ kind: string;
706
+ };
707
+
708
+ function bindingSummary(row: BindingSummaryRow): IntegrationFacetBindingSummary {
709
+ return {
710
+ id: row.id,
711
+ facetKey: row.facetKey,
712
+ kind: facetKind(row.kind),
713
+ bindingKey: row.bindingKey,
714
+ displayName: row.displayName,
715
+ connectionId: row.connectionId,
716
+ status: bindingStatus(row.status),
717
+ config: row.config,
718
+ version: row.version,
719
+ hasCursor: Object.keys(row.cursor).length > 0,
720
+ lastSuccessAt: row.lastSuccessAt?.toISOString() ?? null,
721
+ lastErrorCode: row.lastErrorCode,
722
+ createdAt: row.createdAt.toISOString(),
723
+ updatedAt: row.updatedAt.toISOString(),
724
+ };
725
+ }
726
+
727
+ function facetKind(value: string): IntegrationFacetKind {
728
+ if (
729
+ value !== "knowledge_source" &&
730
+ value !== "inbound_trigger" &&
731
+ value !== "delivery_destination" &&
732
+ value !== "identity_link"
733
+ ) {
734
+ throw new Error(`Unknown Integration facet kind: ${value}`);
735
+ }
736
+ return value;
737
+ }
738
+
739
+ function bindingStatus(value: string): "active" | "paused" | "needs_attention" | "disabled" {
740
+ if (
741
+ value !== "active" &&
742
+ value !== "paused" &&
743
+ value !== "needs_attention" &&
744
+ value !== "disabled"
745
+ ) {
746
+ throw new Error(`Unknown Integration facet status: ${value}`);
747
+ }
748
+ return value;
749
+ }
750
+
751
+ function assertFacetConfig(
752
+ config: Record<string, unknown>,
753
+ schemaValue: Record<string, unknown>,
754
+ ): void {
755
+ const serialized = stableJson(config);
756
+ if (Buffer.byteLength(serialized, "utf8") > 131_072) {
757
+ throw new IntegrationFacetConfigError("Integration facet config exceeds 131072 bytes");
758
+ }
759
+ validateSchemaValue(config, schemaValue, "config", 0);
760
+ }
761
+
762
+ function validateSchemaValue(
763
+ value: unknown,
764
+ schemaValue: Record<string, unknown>,
765
+ path: string,
766
+ depth: number,
767
+ ): void {
768
+ if (depth > 8) throw new IntegrationFacetConfigError(`${path} exceeds supported depth`);
769
+ const type = schemaValue.type;
770
+ if (
771
+ Array.isArray(schemaValue.enum) &&
772
+ !schemaValue.enum.some((item) => stableJson(item) === stableJson(value))
773
+ ) {
774
+ throw new IntegrationFacetConfigError(`${path} is not an allowed value`);
775
+ }
776
+ if (type === "object") {
777
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
778
+ throw new IntegrationFacetConfigError(`${path} must be an object`);
779
+ }
780
+ const object = value as Record<string, unknown>;
781
+ const properties = objectValue(schemaValue.properties);
782
+ const required = Array.isArray(schemaValue.required)
783
+ ? schemaValue.required.filter((entry): entry is string => typeof entry === "string")
784
+ : [];
785
+ for (const key of required) {
786
+ if (!(key in object)) throw new IntegrationFacetConfigError(`${path}.${key} is required`);
787
+ }
788
+ if (schemaValue.additionalProperties === false) {
789
+ const unknown = Object.keys(object).find((key) => !(key in properties));
790
+ if (unknown) throw new IntegrationFacetConfigError(`${path}.${unknown} is not supported`);
791
+ }
792
+ for (const [key, child] of Object.entries(object)) {
793
+ const childSchema = objectValue(properties[key]);
794
+ if (Object.keys(childSchema).length > 0) {
795
+ validateSchemaValue(child, childSchema, `${path}.${key}`, depth + 1);
796
+ }
797
+ }
798
+ return;
799
+ }
800
+ if (type === "array") {
801
+ if (!Array.isArray(value)) {
802
+ throw new IntegrationFacetConfigError(`${path} must be an array`);
803
+ }
804
+ if (typeof schemaValue.minItems === "number" && value.length < schemaValue.minItems) {
805
+ throw new IntegrationFacetConfigError(`${path} has too few items`);
806
+ }
807
+ if (typeof schemaValue.maxItems === "number" && value.length > schemaValue.maxItems) {
808
+ throw new IntegrationFacetConfigError(`${path} has too many items`);
809
+ }
810
+ const itemSchema = objectValue(schemaValue.items);
811
+ if (Object.keys(itemSchema).length > 0) {
812
+ value.forEach((item, index) =>
813
+ validateSchemaValue(item, itemSchema, `${path}[${index}]`, depth + 1),
814
+ );
815
+ }
816
+ return;
817
+ }
818
+ if (type === "string") {
819
+ if (typeof value !== "string")
820
+ throw new IntegrationFacetConfigError(`${path} must be a string`);
821
+ if (typeof schemaValue.minLength === "number" && value.length < schemaValue.minLength) {
822
+ throw new IntegrationFacetConfigError(`${path} is too short`);
823
+ }
824
+ if (typeof schemaValue.maxLength === "number" && value.length > schemaValue.maxLength) {
825
+ throw new IntegrationFacetConfigError(`${path} is too long`);
826
+ }
827
+ return;
828
+ }
829
+ if (type === "boolean") {
830
+ if (typeof value !== "boolean")
831
+ throw new IntegrationFacetConfigError(`${path} must be a boolean`);
832
+ return;
833
+ }
834
+ if (type === "integer" || type === "number") {
835
+ if (
836
+ typeof value !== "number" ||
837
+ !Number.isFinite(value) ||
838
+ (type === "integer" && !Number.isInteger(value))
839
+ ) {
840
+ throw new IntegrationFacetConfigError(
841
+ `${path} must be ${type === "integer" ? "an integer" : "a number"}`,
842
+ );
843
+ }
844
+ if (typeof schemaValue.minimum === "number" && value < schemaValue.minimum) {
845
+ throw new IntegrationFacetConfigError(`${path} is below the minimum`);
846
+ }
847
+ if (typeof schemaValue.maximum === "number" && value > schemaValue.maximum) {
848
+ throw new IntegrationFacetConfigError(`${path} exceeds the maximum`);
849
+ }
850
+ return;
851
+ }
852
+ }
853
+
854
+ function objectValue(value: unknown): Record<string, unknown> {
855
+ return value && typeof value === "object" && !Array.isArray(value)
856
+ ? (value as Record<string, unknown>)
857
+ : {};
858
+ }
859
+
860
+ function sha256(value: string): string {
861
+ return createHash("sha256").update(value).digest("hex");
862
+ }