@opengeni/db 0.28.1 → 0.31.1

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 (187) 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 +91 -0
  4. package/dist/browser-identities.d.ts +128 -0
  5. package/dist/browser-sessions.d.ts +272 -0
  6. package/dist/browser-state-artifacts.d.ts +17 -0
  7. package/dist/capability-components.d.ts +37 -0
  8. package/dist/capability-integrations.d.ts +192 -0
  9. package/dist/chunk-F5FT7BEZ.js +3535 -0
  10. package/dist/chunk-F5FT7BEZ.js.map +1 -0
  11. package/dist/chunk-ISI2TIUG.js +639 -0
  12. package/dist/chunk-ISI2TIUG.js.map +1 -0
  13. package/dist/chunk-MWDVXQOL.js +11639 -0
  14. package/dist/chunk-MWDVXQOL.js.map +1 -0
  15. package/dist/chunk-NBE6CHRI.js +955 -0
  16. package/dist/chunk-NBE6CHRI.js.map +1 -0
  17. package/dist/{chunk-DBS5HPEW.js → chunk-YHZXQ7HP.js} +760 -11
  18. package/dist/chunk-YHZXQ7HP.js.map +1 -0
  19. package/dist/codemode-operations.d.ts +117 -0
  20. package/dist/codex-token-resolver.d.ts +2 -2
  21. package/dist/company-profile-schema.d.ts +798 -0
  22. package/dist/company-profile.d.ts +268 -0
  23. package/dist/computer-sessions.d.ts +109 -0
  24. package/dist/connection-token-resolver.d.ts +9 -4
  25. package/dist/database.d.ts +52 -3
  26. package/dist/editable-artifact-durable-export.d.ts +153 -0
  27. package/dist/editable-artifact-durable-export.js +788 -0
  28. package/dist/editable-artifact-durable-export.js.map +1 -0
  29. package/dist/editable-artifact-materialization.d.ts +123 -0
  30. package/dist/editable-artifacts-schema.d.ts +4152 -0
  31. package/dist/editable-artifacts.d.ts +610 -0
  32. package/dist/editable-artifacts.js +26 -0
  33. package/dist/editable-artifacts.js.map +1 -0
  34. package/dist/environment-crypto.d.ts +8 -4
  35. package/dist/generated-images.d.ts +139 -0
  36. package/dist/index.d.ts +958 -126
  37. package/dist/index.js +31204 -12172
  38. package/dist/index.js.map +1 -1
  39. package/dist/insights.d.ts +51 -4
  40. package/dist/integration-bindings.d.ts +70 -0
  41. package/dist/integration-features.d.ts +99 -0
  42. package/dist/interaction-revisions.d.ts +3 -0
  43. package/dist/interaction-schema.d.ts +5372 -0
  44. package/dist/knowledge-source-sync-schema.d.ts +1679 -0
  45. package/dist/knowledge-source-sync.d.ts +274 -0
  46. package/dist/lossless-columns.d.ts +58 -0
  47. package/dist/lossless-json.d.ts +39 -0
  48. package/dist/memory-domain.d.ts +3 -6
  49. package/dist/memory-governance.d.ts +2 -2
  50. package/dist/memory-slack-delivery.d.ts +101 -0
  51. package/dist/new-session-drafts.d.ts +2 -2
  52. package/dist/pack-components.d.ts +76 -0
  53. package/dist/pack-installations.d.ts +86 -0
  54. package/dist/persistence-errors.d.ts +7 -14
  55. package/dist/plugin-packages.d.ts +126 -0
  56. package/dist/preference-registry.d.ts +1 -1
  57. package/dist/provision-roles.d.ts +9 -1
  58. package/dist/provision-roles.js +1 -1
  59. package/dist/runtime-posture.d.ts +18 -6
  60. package/dist/schema.d.ts +19122 -8731
  61. package/dist/schema.js +173 -1
  62. package/dist/scoped-knowledge.d.ts +97 -1
  63. package/dist/session-control.d.ts +4 -4
  64. package/dist/session-queue-commands.d.ts +19 -11
  65. package/dist/session-realtime-context.d.ts +2 -2
  66. package/dist/session-realtime-ledger.d.ts +4 -4
  67. package/dist/session-realtime-mirror.d.ts +2 -2
  68. package/dist/session-realtime-state.d.ts +1 -1
  69. package/dist/session-realtime-terminal.d.ts +8 -1
  70. package/dist/session-realtime.d.ts +7 -7
  71. package/dist/session-tool-call-settlement.d.ts +1 -1
  72. package/dist/slack-user-link-access.d.ts +54 -0
  73. package/dist/transcription-recordings-schema.d.ts +44 -6
  74. package/dist/transcription-recordings.d.ts +3 -3
  75. package/dist/turn-initiator.d.ts +2 -2
  76. package/dist/video-generation.d.ts +216 -0
  77. package/dist/video-generation.js +66 -0
  78. package/dist/video-generation.js.map +1 -0
  79. package/dist/workspace-artifacts.d.ts +1 -1
  80. package/dist/workspace-instruction-policies.d.ts +1 -1
  81. package/dist/workspace-learning-policy-schema.d.ts +932 -0
  82. package/dist/workspace-learning-policy.d.ts +161 -0
  83. package/drizzle/0065_enrollment_credential_generation.sql +10 -0
  84. package/drizzle/0140_retained_screenshot_artifacts.sql +192 -0
  85. package/drizzle/0176_lossless_canonical_json.sql +975 -0
  86. package/drizzle/0177_session_events_workspace_turn_type_index.sql +5 -0
  87. package/drizzle/0178_permissioned_secret_reads.sql +14 -0
  88. package/drizzle/0179_slack_private_shortcut_delivery_gate.sql +85 -0
  89. package/drizzle/0180_retained_screenshot_lifecycle_fences.sql +273 -0
  90. package/drizzle/0181_connected_machine_removal.sql +52 -0
  91. package/drizzle/0182_connected_machine_remove_session_default.sql +77 -0
  92. package/drizzle/0183_model_call_provider_cost_estimates.sql +39 -0
  93. package/drizzle/0184_sandbox_drain_teardown_fence.sql +682 -0
  94. package/drizzle/0185_temporal_schedule_cleanup_outbox.sql +168 -0
  95. package/drizzle/0186_sandbox_capture_provider_contract.sql +400 -0
  96. package/drizzle/0187_generated_image_artifacts.sql +173 -0
  97. package/drizzle/0188_image_generation_retention_failure.sql +25 -0
  98. package/drizzle/0189_retained_session_image_formats.sql +13 -0
  99. package/drizzle/0190_timeline_annotations.sql +31 -0
  100. package/drizzle/0191_editable_artifact_engine.sql +3853 -0
  101. package/drizzle/0192_editable_artifact_live_tickets.sql +338 -0
  102. package/drizzle/0193_editable_artifact_authorization.sql +190 -0
  103. package/drizzle/0194_editable_artifact_durable_exports.sql +160 -0
  104. package/drizzle/0195_editable_artifact_import_authorization.sql +203 -0
  105. package/drizzle/0196_rig_provider_images.sql +152 -0
  106. package/drizzle/0197_knowledge_source_sync_schedules.sql +523 -0
  107. package/drizzle/0198_memory_slack_publication_delivery.sql +393 -0
  108. package/drizzle/0199_workspace_learning_policy.sql +980 -0
  109. package/drizzle/0201_company_profile_authority.sql +849 -0
  110. package/drizzle/0202_document_index_checkpoints.sql +85 -0
  111. package/drizzle/0203_durable_video_generation.sql +353 -0
  112. package/drizzle/0204_video_generation_funding.sql +44 -0
  113. package/drizzle/0205_attempt_tool_catalogs.sql +230 -0
  114. package/drizzle/0206_browser_sessions.sql +646 -0
  115. package/drizzle/0207_browser_identities.sql +379 -0
  116. package/drizzle/0208_attached_browser_devices.sql +165 -0
  117. package/drizzle/0209_computer_sessions.sql +575 -0
  118. package/drizzle/0210_browser_auth_network_interventions.sql +441 -0
  119. package/drizzle/0211_editable_artifact_session_links.sql +58 -0
  120. package/drizzle/0212_slack_installation_bindings.sql +348 -0
  121. package/drizzle/0213_slack_user_link_access_requests.sql +177 -0
  122. package/drizzle/0214_session_activity_commit_gate.sql +262 -0
  123. package/drizzle/0215_capabilities_platform.sql +980 -0
  124. package/drizzle/0216_pack_component_ownership.sql +235 -0
  125. package/drizzle/0217_capability_definition_delete_authority.sql +32 -0
  126. package/package.json +18 -5
  127. package/src/attached-browser-devices.ts +371 -0
  128. package/src/attempt-tool-catalogs.ts +131 -0
  129. package/src/browser-auth.ts +1896 -0
  130. package/src/browser-identities.ts +1168 -0
  131. package/src/browser-sessions.ts +2005 -0
  132. package/src/browser-state-artifacts.ts +59 -0
  133. package/src/capability-components.ts +166 -0
  134. package/src/capability-integrations.ts +1663 -0
  135. package/src/codemode-operations.ts +674 -0
  136. package/src/company-profile-schema.ts +143 -0
  137. package/src/company-profile.ts +968 -0
  138. package/src/computer-sessions.ts +1130 -0
  139. package/src/connection-token-resolver.ts +270 -25
  140. package/src/database.ts +434 -24
  141. package/src/editable-artifact-durable-export.ts +1201 -0
  142. package/src/editable-artifact-materialization.ts +893 -0
  143. package/src/editable-artifacts-schema.ts +879 -0
  144. package/src/editable-artifacts.ts +5404 -0
  145. package/src/environment-crypto.ts +22 -9
  146. package/src/generated-images.ts +740 -0
  147. package/src/index.ts +10117 -2730
  148. package/src/insights.ts +160 -13
  149. package/src/integration-bindings.ts +455 -0
  150. package/src/integration-features.ts +859 -0
  151. package/src/interaction-revisions.ts +40 -0
  152. package/src/interaction-schema.ts +1303 -0
  153. package/src/knowledge-source-sync-schema.ts +192 -0
  154. package/src/knowledge-source-sync.ts +1922 -0
  155. package/src/lossless-columns.ts +35 -0
  156. package/src/lossless-json.ts +322 -0
  157. package/src/memory-domain.ts +5 -44
  158. package/src/memory-slack-delivery.ts +801 -0
  159. package/src/pack-components.ts +954 -0
  160. package/src/pack-installations.ts +929 -0
  161. package/src/persistence-errors.ts +26 -34
  162. package/src/plugin-packages.ts +1186 -0
  163. package/src/provision-roles.ts +302 -1
  164. package/src/runtime-posture-cli.ts +9 -0
  165. package/src/runtime-posture.ts +514 -5
  166. package/src/schema.ts +5177 -2957
  167. package/src/scoped-knowledge-schema.ts +1 -1
  168. package/src/scoped-knowledge.ts +655 -3
  169. package/src/session-control.ts +133 -81
  170. package/src/session-queue-commands.ts +511 -247
  171. package/src/session-realtime-context.ts +20 -7
  172. package/src/session-realtime-ledger.ts +32 -10
  173. package/src/session-realtime-mirror.ts +4 -2
  174. package/src/session-realtime-terminal.ts +89 -21
  175. package/src/session-realtime.ts +10 -9
  176. package/src/session-tool-call-settlement.ts +38 -15
  177. package/src/slack-user-link-access.ts +702 -0
  178. package/src/transcription-recordings-schema.ts +5 -2
  179. package/src/transcription-recordings.ts +70 -40
  180. package/src/video-generation.ts +1452 -0
  181. package/src/workspace-learning-policy-schema.ts +139 -0
  182. package/src/workspace-learning-policy.ts +595 -0
  183. package/dist/chunk-DBS5HPEW.js.map +0 -1
  184. package/dist/chunk-HT5T62CC.js +0 -6803
  185. package/dist/chunk-HT5T62CC.js.map +0 -1
  186. package/dist/event-payload-sanitizer.d.ts +0 -32
  187. package/src/event-payload-sanitizer.ts +0 -377
@@ -0,0 +1,455 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ import { stableJson } from "@opengeni/contracts";
4
+ import { and, asc, eq, or, sql } from "drizzle-orm";
5
+
6
+ import type { Database } from "./database";
7
+ import { effectiveCapabilityOwnerSql } from "./capability-components";
8
+ import * as schema from "./schema";
9
+
10
+ export type IntegrationFeatureBindingOwner = {
11
+ kind: "direct" | "plugin" | "pack" | "migration";
12
+ id: string;
13
+ removable: boolean;
14
+ };
15
+
16
+ export type UpsertIntegrationFeatureBindingInput = {
17
+ accountId: string;
18
+ workspaceId: string;
19
+ integrationFacetInstallationId: string;
20
+ featureFacetId: string;
21
+ bindingKey: string;
22
+ displayName: string;
23
+ runtimeKey?: string | null;
24
+ connectionId?: string | null;
25
+ config: Record<string, unknown>;
26
+ createdBySubjectId: string;
27
+ owner: IntegrationFeatureBindingOwner;
28
+ expectedVersion?: number;
29
+ };
30
+
31
+ export class IntegrationFeatureBindingVersionConflictError extends Error {
32
+ readonly name = "IntegrationFeatureBindingVersionConflictError";
33
+
34
+ constructor(
35
+ readonly bindingKey: string,
36
+ readonly expectedVersion: number,
37
+ readonly actualVersion: number,
38
+ ) {
39
+ super(
40
+ `Integration instance ${bindingKey} changed: expected version ${expectedVersion}, current version ${actualVersion}`,
41
+ );
42
+ }
43
+ }
44
+
45
+ export class IntegrationFeatureBindingVersionRequiredError extends Error {
46
+ readonly name = "IntegrationFeatureBindingVersionRequiredError";
47
+ }
48
+
49
+ export class IntegrationFeatureBindingOwnershipConflictError extends Error {
50
+ readonly name = "IntegrationFeatureBindingOwnershipConflictError";
51
+ }
52
+
53
+ export function integrationBindingKey(
54
+ connectionId: string | null | undefined,
55
+ requested?: string | null,
56
+ ): string {
57
+ const explicit = requested?.trim().toLowerCase();
58
+ if (explicit) {
59
+ assertBindingKey(explicit);
60
+ return explicit;
61
+ }
62
+ if (!connectionId) return "default";
63
+ return `account-${sha256(`connection\0${connectionId}`).slice(0, 24)}`;
64
+ }
65
+
66
+ export function integrationRuntimeKey(baseServerId: string, bindingKey: string): string {
67
+ assertBindingKey(bindingKey);
68
+ const suffix = sha256(`integration-instance\0${baseServerId}\0${bindingKey}`).slice(0, 16);
69
+ const head = baseServerId.slice(0, 110).replace(/[^A-Za-z0-9._-]/g, "_");
70
+ const runtimeKey = `${head}_${suffix}`;
71
+ if (!/^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/.test(runtimeKey)) {
72
+ throw new Error("Integration instance runtime key is invalid");
73
+ }
74
+ return runtimeKey;
75
+ }
76
+
77
+ export async function ensureIntegrationFeatureFacet(
78
+ db: Database,
79
+ input: {
80
+ integrationFacetId: string;
81
+ featureKey: string;
82
+ kind:
83
+ | "tools"
84
+ | "knowledge_source"
85
+ | "inbound_trigger"
86
+ | "delivery_destination"
87
+ | "identity_link";
88
+ configSchema?: Record<string, unknown>;
89
+ capabilities?: Record<string, unknown>;
90
+ },
91
+ ): Promise<typeof schema.integrationFeatureFacets.$inferSelect> {
92
+ const [existing] = await db
93
+ .select()
94
+ .from(schema.integrationFeatureFacets)
95
+ .where(
96
+ and(
97
+ eq(schema.integrationFeatureFacets.integrationFacetId, input.integrationFacetId),
98
+ eq(schema.integrationFeatureFacets.featureKey, input.featureKey),
99
+ ),
100
+ )
101
+ .limit(1);
102
+ const configSchema = input.configSchema ?? {};
103
+ const capabilities = input.capabilities ?? {};
104
+ if (existing) {
105
+ if (
106
+ existing.kind !== input.kind ||
107
+ stableJson(existing.configSchema) !== stableJson(configSchema) ||
108
+ stableJson(existing.capabilities) !== stableJson(capabilities)
109
+ ) {
110
+ throw new Error(`Integration feature ${input.featureKey} conflicts with stored definition`);
111
+ }
112
+ return existing;
113
+ }
114
+ const [created] = await db
115
+ .insert(schema.integrationFeatureFacets)
116
+ .values({
117
+ integrationFacetId: input.integrationFacetId,
118
+ featureKey: input.featureKey,
119
+ kind: input.kind,
120
+ configSchema,
121
+ capabilities,
122
+ })
123
+ .returning();
124
+ if (!created) throw new Error(`Failed to create Integration feature ${input.featureKey}`);
125
+ return created;
126
+ }
127
+
128
+ export async function upsertIntegrationFeatureBinding(
129
+ db: Database,
130
+ input: UpsertIntegrationFeatureBindingInput,
131
+ ): Promise<{ row: typeof schema.integrationFeatureBindings.$inferSelect; changed: boolean }> {
132
+ assertBindingKey(input.bindingKey);
133
+ if (input.displayName.trim().length === 0 || input.displayName.trim().length > 200) {
134
+ throw new Error("Integration instance display name must contain 1-200 characters");
135
+ }
136
+ await db.execute(
137
+ sql`select pg_advisory_xact_lock(hashtextextended(${`integration-binding:${input.workspaceId}:${input.runtimeKey ?? `${input.featureFacetId}:${input.bindingKey}`}`}, 0))`,
138
+ );
139
+ const [existing] = await db
140
+ .select()
141
+ .from(schema.integrationFeatureBindings)
142
+ .where(
143
+ and(
144
+ eq(schema.integrationFeatureBindings.workspaceId, input.workspaceId),
145
+ input.runtimeKey
146
+ ? eq(schema.integrationFeatureBindings.runtimeKey, input.runtimeKey)
147
+ : and(
148
+ eq(
149
+ schema.integrationFeatureBindings.integrationFacetInstallationId,
150
+ input.integrationFacetInstallationId,
151
+ ),
152
+ eq(schema.integrationFeatureBindings.featureFacetId, input.featureFacetId),
153
+ eq(schema.integrationFeatureBindings.bindingKey, input.bindingKey),
154
+ ),
155
+ ),
156
+ )
157
+ .for("update")
158
+ .limit(1);
159
+ if (!existing) {
160
+ if (input.expectedVersion !== undefined) {
161
+ throw new IntegrationFeatureBindingVersionConflictError(
162
+ input.bindingKey,
163
+ input.expectedVersion,
164
+ 0,
165
+ );
166
+ }
167
+ const [created] = await db
168
+ .insert(schema.integrationFeatureBindings)
169
+ .values({
170
+ accountId: input.accountId,
171
+ workspaceId: input.workspaceId,
172
+ integrationFacetInstallationId: input.integrationFacetInstallationId,
173
+ featureFacetId: input.featureFacetId,
174
+ bindingKey: input.bindingKey,
175
+ displayName: input.displayName.trim(),
176
+ runtimeKey: input.runtimeKey ?? null,
177
+ connectionId: input.connectionId ?? null,
178
+ status: "active",
179
+ config: input.config,
180
+ createdBySubjectId: input.createdBySubjectId,
181
+ })
182
+ .returning();
183
+ if (!created) throw new Error("Failed to create Integration instance");
184
+ await addIntegrationFeatureBindingOwner(db, created.id, input);
185
+ return { row: created, changed: true };
186
+ }
187
+ if (existing.bindingKey !== input.bindingKey) {
188
+ throw new IntegrationFeatureBindingOwnershipConflictError(
189
+ "Integration runtime identity is already assigned to another instance",
190
+ );
191
+ }
192
+ const changed =
193
+ existing.integrationFacetInstallationId !== input.integrationFacetInstallationId ||
194
+ existing.featureFacetId !== input.featureFacetId ||
195
+ existing.displayName !== input.displayName.trim() ||
196
+ existing.runtimeKey !== (input.runtimeKey ?? null) ||
197
+ existing.connectionId !== (input.connectionId ?? null) ||
198
+ existing.status !== "active" ||
199
+ stableJson(existing.config) !== stableJson(input.config);
200
+ let row = existing;
201
+ if (changed) {
202
+ if (input.expectedVersion !== undefined && existing.version !== input.expectedVersion) {
203
+ throw new IntegrationFeatureBindingVersionConflictError(
204
+ input.bindingKey,
205
+ input.expectedVersion,
206
+ existing.version,
207
+ );
208
+ }
209
+ if (input.owner.kind === "direct" && input.expectedVersion === undefined) {
210
+ throw new IntegrationFeatureBindingVersionRequiredError(
211
+ "Updating an Integration instance requires its previewed version",
212
+ );
213
+ }
214
+ const owners = await listIntegrationFeatureBindingOwners(db, existing.id);
215
+ const otherOwners = owners.filter(
216
+ (owner) => owner.kind !== input.owner.kind || owner.id !== input.owner.id,
217
+ );
218
+ if (otherOwners.length > 0) {
219
+ throw new IntegrationFeatureBindingOwnershipConflictError(
220
+ "Integration instance is shared by another owner and cannot be changed in place",
221
+ );
222
+ }
223
+ const [updated] = await db
224
+ .update(schema.integrationFeatureBindings)
225
+ .set({
226
+ integrationFacetInstallationId: input.integrationFacetInstallationId,
227
+ featureFacetId: input.featureFacetId,
228
+ displayName: input.displayName.trim(),
229
+ runtimeKey: input.runtimeKey ?? null,
230
+ connectionId: input.connectionId ?? null,
231
+ status: "active",
232
+ config: input.config,
233
+ cursor: {},
234
+ version: existing.version + 1,
235
+ lastErrorCode: null,
236
+ updatedAt: new Date(),
237
+ })
238
+ .where(eq(schema.integrationFeatureBindings.id, existing.id))
239
+ .returning();
240
+ if (!updated) throw new Error("Failed to update Integration instance");
241
+ row = updated;
242
+ }
243
+ await addIntegrationFeatureBindingOwner(db, row.id, input);
244
+ return { row, changed };
245
+ }
246
+
247
+ export async function listIntegrationFeatureBindingOwners(
248
+ db: Database,
249
+ bindingId: string,
250
+ ): Promise<IntegrationFeatureBindingOwner[]> {
251
+ return await loadIntegrationFeatureBindingOwners(db, bindingId, true);
252
+ }
253
+
254
+ async function loadIntegrationFeatureBindingOwners(
255
+ db: Database,
256
+ bindingId: string,
257
+ effectiveOnly: boolean,
258
+ ): Promise<IntegrationFeatureBindingOwner[]> {
259
+ const rows = await db
260
+ .select({
261
+ kind: schema.integrationFeatureBindingOwners.ownerKind,
262
+ id: schema.integrationFeatureBindingOwners.ownerId,
263
+ removable: schema.integrationFeatureBindingOwners.removable,
264
+ })
265
+ .from(schema.integrationFeatureBindingOwners)
266
+ .where(
267
+ and(
268
+ eq(schema.integrationFeatureBindingOwners.bindingId, bindingId),
269
+ effectiveOnly
270
+ ? effectiveCapabilityOwnerSql(
271
+ schema.integrationFeatureBindingOwners.ownerKind,
272
+ schema.integrationFeatureBindingOwners.ownerId,
273
+ )
274
+ : sql`true`,
275
+ ),
276
+ )
277
+ .orderBy(
278
+ asc(schema.integrationFeatureBindingOwners.ownerKind),
279
+ asc(schema.integrationFeatureBindingOwners.ownerId),
280
+ );
281
+ return rows.map((row) => {
282
+ if (
283
+ row.kind !== "direct" &&
284
+ row.kind !== "plugin" &&
285
+ row.kind !== "pack" &&
286
+ row.kind !== "migration"
287
+ ) {
288
+ throw new Error(`Unknown Integration instance owner kind: ${row.kind}`);
289
+ }
290
+ return { kind: row.kind, id: row.id, removable: row.removable };
291
+ });
292
+ }
293
+
294
+ export async function removeIntegrationFeatureBindingOwner(
295
+ db: Database,
296
+ input: {
297
+ workspaceId: string;
298
+ bindingId: string;
299
+ owner: Pick<IntegrationFeatureBindingOwner, "kind" | "id">;
300
+ expectedVersion?: number;
301
+ },
302
+ ): Promise<{
303
+ binding: typeof schema.integrationFeatureBindings.$inferSelect | null;
304
+ remainingOwners: IntegrationFeatureBindingOwner[];
305
+ }> {
306
+ const [binding] = await db
307
+ .select()
308
+ .from(schema.integrationFeatureBindings)
309
+ .where(
310
+ and(
311
+ eq(schema.integrationFeatureBindings.workspaceId, input.workspaceId),
312
+ eq(schema.integrationFeatureBindings.id, input.bindingId),
313
+ ),
314
+ )
315
+ .for("update")
316
+ .limit(1);
317
+ if (!binding) return { binding: null, remainingOwners: [] };
318
+ if (input.expectedVersion !== undefined && binding.version !== input.expectedVersion) {
319
+ throw new IntegrationFeatureBindingVersionConflictError(
320
+ binding.bindingKey,
321
+ input.expectedVersion,
322
+ binding.version,
323
+ );
324
+ }
325
+ await db
326
+ .delete(schema.integrationFeatureBindingOwners)
327
+ .where(
328
+ and(
329
+ eq(schema.integrationFeatureBindingOwners.workspaceId, input.workspaceId),
330
+ eq(schema.integrationFeatureBindingOwners.bindingId, binding.id),
331
+ eq(schema.integrationFeatureBindingOwners.ownerKind, input.owner.kind),
332
+ eq(schema.integrationFeatureBindingOwners.ownerId, input.owner.id),
333
+ ),
334
+ );
335
+ // Pending or repairing owners are not runtime-effective yet, but they still
336
+ // preserve the shared binding so their operation can resume safely.
337
+ const remainingOwners = await loadIntegrationFeatureBindingOwners(db, binding.id, false);
338
+ if (remainingOwners.length > 0) return { binding, remainingOwners };
339
+ const [disabled] = await db
340
+ .update(schema.integrationFeatureBindings)
341
+ .set({ status: "disabled", version: binding.version + 1, updatedAt: new Date() })
342
+ .where(eq(schema.integrationFeatureBindings.id, binding.id))
343
+ .returning();
344
+ return { binding: disabled ?? binding, remainingOwners: [] };
345
+ }
346
+
347
+ export async function removeIntegrationFeatureBindingOwnersForOwner(
348
+ db: Database,
349
+ input: {
350
+ workspaceId: string;
351
+ owner: Pick<IntegrationFeatureBindingOwner, "kind" | "id">;
352
+ },
353
+ ): Promise<{ disabledBindingIds: string[]; retainedBindingIds: string[] }> {
354
+ const deleted = await db
355
+ .delete(schema.integrationFeatureBindingOwners)
356
+ .where(
357
+ and(
358
+ eq(schema.integrationFeatureBindingOwners.workspaceId, input.workspaceId),
359
+ eq(schema.integrationFeatureBindingOwners.ownerKind, input.owner.kind),
360
+ eq(schema.integrationFeatureBindingOwners.ownerId, input.owner.id),
361
+ ),
362
+ )
363
+ .returning({ bindingId: schema.integrationFeatureBindingOwners.bindingId });
364
+ const disabledBindingIds: string[] = [];
365
+ const retainedBindingIds: string[] = [];
366
+ for (const bindingId of [...new Set(deleted.map((row) => row.bindingId))]) {
367
+ const owners = await loadIntegrationFeatureBindingOwners(db, bindingId, false);
368
+ if (owners.length > 0) {
369
+ retainedBindingIds.push(bindingId);
370
+ continue;
371
+ }
372
+ await db
373
+ .update(schema.integrationFeatureBindings)
374
+ .set({
375
+ status: "disabled",
376
+ version: sql`${schema.integrationFeatureBindings.version} + 1`,
377
+ updatedAt: new Date(),
378
+ })
379
+ .where(
380
+ and(
381
+ eq(schema.integrationFeatureBindings.workspaceId, input.workspaceId),
382
+ eq(schema.integrationFeatureBindings.id, bindingId),
383
+ ),
384
+ );
385
+ disabledBindingIds.push(bindingId);
386
+ }
387
+ return { disabledBindingIds, retainedBindingIds };
388
+ }
389
+
390
+ export async function integrationDefinitionHasBindingOwner(
391
+ db: Database,
392
+ input: {
393
+ workspaceId: string;
394
+ pluginInstallationId: string;
395
+ owner: Pick<IntegrationFeatureBindingOwner, "kind" | "id">;
396
+ },
397
+ ): Promise<boolean> {
398
+ const [row] = await db
399
+ .select({ id: schema.integrationFeatureBindingOwners.id })
400
+ .from(schema.integrationFeatureBindingOwners)
401
+ .innerJoin(
402
+ schema.integrationFeatureBindings,
403
+ eq(schema.integrationFeatureBindings.id, schema.integrationFeatureBindingOwners.bindingId),
404
+ )
405
+ .innerJoin(
406
+ schema.capabilityFacetInstallations,
407
+ eq(
408
+ schema.capabilityFacetInstallations.id,
409
+ schema.integrationFeatureBindings.integrationFacetInstallationId,
410
+ ),
411
+ )
412
+ .where(
413
+ and(
414
+ eq(schema.integrationFeatureBindingOwners.workspaceId, input.workspaceId),
415
+ eq(schema.integrationFeatureBindingOwners.ownerKind, input.owner.kind),
416
+ eq(schema.integrationFeatureBindingOwners.ownerId, input.owner.id),
417
+ eq(schema.capabilityFacetInstallations.pluginInstallationId, input.pluginInstallationId),
418
+ or(
419
+ eq(schema.integrationFeatureBindings.status, "active"),
420
+ eq(schema.integrationFeatureBindings.status, "needs_attention"),
421
+ eq(schema.integrationFeatureBindings.status, "paused"),
422
+ ),
423
+ ),
424
+ )
425
+ .limit(1);
426
+ return Boolean(row);
427
+ }
428
+
429
+ export async function addIntegrationFeatureBindingOwner(
430
+ db: Database,
431
+ bindingId: string,
432
+ input: Pick<UpsertIntegrationFeatureBindingInput, "accountId" | "workspaceId" | "owner">,
433
+ ): Promise<void> {
434
+ await db
435
+ .insert(schema.integrationFeatureBindingOwners)
436
+ .values({
437
+ accountId: input.accountId,
438
+ workspaceId: input.workspaceId,
439
+ bindingId,
440
+ ownerKind: input.owner.kind,
441
+ ownerId: input.owner.id,
442
+ removable: input.owner.removable,
443
+ })
444
+ .onConflictDoNothing();
445
+ }
446
+
447
+ function assertBindingKey(value: string): void {
448
+ if (!/^[a-z0-9](?:[a-z0-9._/-]*[a-z0-9])?$/.test(value) || value.length > 128) {
449
+ throw new Error("Integration instance key is invalid");
450
+ }
451
+ }
452
+
453
+ function sha256(value: string): string {
454
+ return createHash("sha256").update(value).digest("hex");
455
+ }