@opengeni/db 1.0.1 → 3.2.0-canary.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 (271) hide show
  1. package/dist/attached-browser-devices.d.ts +7 -0
  2. package/dist/automations.d.ts +154 -0
  3. package/dist/canonical-human-identities.js +3 -3
  4. package/dist/capability-integrations.d.ts +13 -0
  5. package/dist/child-lifecycle-notices.d.ts +68 -0
  6. package/dist/{chunk-4SR4HKKA.js → chunk-2EH4WRHL.js} +19 -11
  7. package/dist/chunk-2EH4WRHL.js.map +1 -0
  8. package/dist/{chunk-CUPM3FGB.js → chunk-2PRS4J5Q.js} +3 -3
  9. package/dist/chunk-6HMM2R5T.js +390 -0
  10. package/dist/chunk-6HMM2R5T.js.map +1 -0
  11. package/dist/{chunk-ZWREYEWA.js → chunk-AZHW7R5V.js} +56 -11
  12. package/dist/chunk-AZHW7R5V.js.map +1 -0
  13. package/dist/{chunk-LTG3S6ZY.js → chunk-E6CLMKEE.js} +2 -2
  14. package/dist/{chunk-6ZYKCRFO.js → chunk-KRLPEMLH.js} +7591 -4417
  15. package/dist/chunk-KRLPEMLH.js.map +1 -0
  16. package/dist/chunk-NAB6FKFN.js +516 -0
  17. package/dist/chunk-NAB6FKFN.js.map +1 -0
  18. package/dist/{chunk-SK7YYHBI.js → chunk-SIBYBA2D.js} +46 -1
  19. package/dist/chunk-SIBYBA2D.js.map +1 -0
  20. package/dist/chunk-UZNXG44K.js +4508 -0
  21. package/dist/chunk-UZNXG44K.js.map +1 -0
  22. package/dist/codemode-operations.d.ts +1 -6
  23. package/dist/company-brain-context-selection-schema.d.ts +705 -0
  24. package/dist/company-brain-context-selection.d.ts +94 -0
  25. package/dist/company-brain-governed-writes.d.ts +24 -0
  26. package/dist/company-brain.d.ts +55 -0
  27. package/dist/company-profile-agent-admin.d.ts +15 -0
  28. package/dist/company-profile-schema.d.ts +619 -2
  29. package/dist/company-profile.d.ts +9 -4
  30. package/dist/connection-authority.d.ts +95 -0
  31. package/dist/connection-scopes.d.ts +8 -0
  32. package/dist/connection-tenancy-backfill.d.ts +85 -0
  33. package/dist/connection-token-resolver.d.ts +30 -1
  34. package/dist/database.d.ts +23 -4
  35. package/dist/editable-artifact-durable-export.js +2 -2
  36. package/dist/editable-artifacts.d.ts +2 -2
  37. package/dist/editable-artifacts.js +3 -3
  38. package/dist/governed-learning-activation-schema.d.ts +984 -0
  39. package/dist/governed-learning-activation.d.ts +98 -0
  40. package/dist/governed-learning-evaluator-schema.d.ts +633 -0
  41. package/dist/governed-learning-evaluator.d.ts +28 -0
  42. package/dist/index.d.ts +1606 -81
  43. package/dist/index.js +21898 -8147
  44. package/dist/index.js.map +1 -1
  45. package/dist/insights.d.ts +18 -0
  46. package/dist/integration-bindings.d.ts +1 -0
  47. package/dist/integration-facets.d.ts +30 -0
  48. package/dist/interaction-placement-loss.d.ts +22 -0
  49. package/dist/managed-human-provisioning.d.ts +25 -0
  50. package/dist/memory-domain.d.ts +3 -3
  51. package/dist/migrate.d.ts +11 -1
  52. package/dist/migrate.js +1 -1
  53. package/dist/new-session-drafts.d.ts +43 -5
  54. package/dist/organization-membership-backfill.d.ts +174 -0
  55. package/dist/organization-membership-lifecycle.d.ts +162 -0
  56. package/dist/pr-review.d.ts +107 -0
  57. package/dist/preference-registry-schema.d.ts +2 -0
  58. package/dist/preference-registry.d.ts +2 -0
  59. package/dist/provision-roles.js +1 -1
  60. package/dist/runtime-posture.d.ts +29 -8
  61. package/dist/schema.d.ts +24914 -13703
  62. package/dist/schema.js +99 -1
  63. package/dist/scoped-knowledge-schema.d.ts +52 -1
  64. package/dist/session-background-commands.d.ts +125 -0
  65. package/dist/session-background-commands.js +34 -0
  66. package/dist/session-background-commands.js.map +1 -0
  67. package/dist/session-control.d.ts +156 -3
  68. package/dist/session-goal-pacing.d.ts +68 -0
  69. package/dist/session-queue-commands.d.ts +43 -4
  70. package/dist/session-realtime-ledger.d.ts +2 -0
  71. package/dist/session-tenancy.d.ts +71 -1
  72. package/dist/session-tenancy.js +25 -3
  73. package/dist/session-tool-call-settlement.d.ts +9 -4
  74. package/dist/slack-routing-personal-workspace.d.ts +23 -0
  75. package/dist/slack-routing.d.ts +268 -0
  76. package/dist/slack-task-policy-schema.d.ts +34 -0
  77. package/dist/slack-task-policy.d.ts +8 -0
  78. package/dist/slack-user-link-access.d.ts +28 -0
  79. package/dist/task-notes-schema.d.ts +594 -0
  80. package/dist/task-notes.d.ts +39 -0
  81. package/dist/turn-initiator.d.ts +2 -0
  82. package/dist/user-resource-authority.d.ts +109 -0
  83. package/dist/video-generation.js +3 -3
  84. package/dist/workspace-authority.d.ts +81 -0
  85. package/dist/workspace-instruction-policies-schema.d.ts +279 -0
  86. package/dist/workspace-instruction-policies.d.ts +62 -0
  87. package/dist/workspace-learning-policy.d.ts +1 -0
  88. package/dist/workspace-membership-permissions.d.ts +2 -0
  89. package/drizzle/0240_enrollment_connection_authority.sql +39 -0
  90. package/drizzle/0241_atomic_personal_resource_delegation.sql +1000 -0
  91. package/drizzle/0241_enrollment_agent_runtime.sql +70 -0
  92. package/drizzle/0244_slack_app_home_refresh_queue.sql +120 -0
  93. package/drizzle/0245_model_context_contribution_facts.sql +88 -0
  94. package/drizzle/0246_integration_personal_instance_authority.sql +70 -0
  95. package/drizzle/0247_terraform_stacks_provenance_repair.sql +428 -0
  96. package/drizzle/0248_terraform_stacks_component_resolution_fence.sql +83 -0
  97. package/drizzle/0249_personal_resource_delegation_authority_correction.sql +662 -0
  98. package/drizzle/0250_direct_retained_process_owner_liveness.sql +199 -0
  99. package/drizzle/0251_connected_machine_operation_policy.sql +45 -0
  100. package/drizzle/0252_scheduled_personal_resource_delegation.sql +1867 -0
  101. package/drizzle/0253_common_user_resource_authority_lifecycle.sql +1555 -0
  102. package/drizzle/0254_scoped_variable_set_authority.sql +1255 -0
  103. package/drizzle/0255_company_brain_governed_write_proposals.sql +524 -0
  104. package/drizzle/0256_connection_authority_delegation.sql +703 -0
  105. package/drizzle/0257_goal_revision_decisions_and_root_constraints.sql +436 -0
  106. package/drizzle/0258_three_scope_document_knowledge_authority.sql +1273 -0
  107. package/drizzle/0259_company_brain_context_selection_receipts.sql +869 -0
  108. package/drizzle/0260_task_note_knowledge_promotion.sql +820 -0
  109. package/drizzle/0261_preference_knowledge_proposal_actor_binding.sql +471 -0
  110. package/drizzle/0262_scoped_connected_machines_and_rigs.sql +1700 -0
  111. package/drizzle/0263_organization_membership_lifecycle.sql +3284 -0
  112. package/drizzle/0264_connection_authority_runtime_activation.sql +1084 -0
  113. package/drizzle/0266_company_brain_context_receipt_inspection.sql +156 -0
  114. package/drizzle/0268_governed_learning_decision_receipts.sql +825 -0
  115. package/drizzle/0269_governed_learning_activation_controller.sql +1874 -0
  116. package/drizzle/0270_governed_learning_history_inspection.sql +170 -0
  117. package/drizzle/0271_company_brain_retrieval_only_default.sql +524 -0
  118. package/drizzle/0272_human_confirmed_learning_activation.sql +591 -0
  119. package/drizzle/0273_scheduled_variable_set_materialization.sql +268 -0
  120. package/drizzle/0274_human_confirmed_knowledge_review.sql +310 -0
  121. package/drizzle/0275_scheduled_connection_authority.sql +5527 -0
  122. package/drizzle/0276_onboarding_proposal_initiating_human_guc.sql +198 -0
  123. package/drizzle/0277_workspace_writer_authority_attribution.sql +393 -0
  124. package/drizzle/0278_workspace_membership_removal_fencing.sql +1174 -0
  125. package/drizzle/0279_workspace_connection_use_lane.sql +343 -0
  126. package/drizzle/0280_connection_and_variable_set_audit_attribution.sql +773 -0
  127. package/drizzle/0281_viewer_holder_authority_claims.sql +52 -0
  128. package/drizzle/0282_variable_set_session_attach_attribution.sql +178 -0
  129. package/drizzle/0283_editable_spreadsheet_authored_state.sql +82 -0
  130. package/drizzle/0284_truthful_human_confirmed_review_reason.sql +930 -0
  131. package/drizzle/0285_organization_tenancy_inventory.sql +329 -0
  132. package/drizzle/0286_widen_task_note_expiry_ceiling.sql +391 -0
  133. package/drizzle/0287_open_suffix_pending_tool_calls.sql +18 -0
  134. package/drizzle/0288_attached_browser_reenrollment.sql +64 -0
  135. package/drizzle/0289_session_composer_policy_authority.sql +597 -0
  136. package/drizzle/0290_organization_membership_backfill.sql +268 -0
  137. package/drizzle/0291_resource_authority_classification_assertion.sql +371 -0
  138. package/drizzle/0292_truthful_tenancy_inventory_counters.sql +291 -0
  139. package/drizzle/0293_confirm_time_rule_rebaseline.sql +615 -0
  140. package/drizzle/0294_preference_activation_authority.sql +237 -0
  141. package/drizzle/0295_retire_legacy_standing_memory_mode.sql +476 -0
  142. package/drizzle/0296_force_rls_backfill_noop_repair.sql +215 -0
  143. package/drizzle/0297_session_ownership_classification_and_backfill.sql +752 -0
  144. package/drizzle/0298_organization_tenancy_parity.sql +609 -0
  145. package/drizzle/0299_organization_membership_lock_order.sql +291 -0
  146. package/drizzle/0300_tenancy_backfill_ledger.sql +459 -0
  147. package/drizzle/0301_session_snapshot_and_pin_visibility.sql +181 -0
  148. package/drizzle/0302_personal_workspace_session_ownership.sql +303 -0
  149. package/drizzle/0303_session_tenancy_product_activation.sql +919 -0
  150. package/drizzle/0304_personal_workspace_private_session_reads.sql +97 -0
  151. package/drizzle/0305_personal_resource_grant_management.sql +584 -0
  152. package/drizzle/0306_atomic_personal_resource_attachments.sql +1374 -0
  153. package/drizzle/0307_session_attention_state.sql +51 -0
  154. package/drizzle/0308_session_archives.sql +35 -0
  155. package/drizzle/0309_channel_project_pins.sql +6 -0
  156. package/drizzle/0310_channel_project_order.sql +27 -0
  157. package/drizzle/0311_company_scope_and_private_session_create.sql +656 -0
  158. package/drizzle/0312_quiescent_session_tree_deletion.sql +39 -0
  159. package/drizzle/0313_private_child_session_authority.sql +365 -0
  160. package/drizzle/0314_unregistered_organization_invitations.sql +606 -0
  161. package/drizzle/0315_personal_github_repository_selection.sql +624 -0
  162. package/drizzle/0316_human_confirmed_activation_resumed_generation.sql +869 -0
  163. package/drizzle/0317_session_goal_continuation_hold.sql +64 -0
  164. package/drizzle/0318_session_turn_startup_milestones.sql +115 -0
  165. package/drizzle/0319_event_triggered_automations.sql +254 -0
  166. package/drizzle/0320_pr_review_pack.sql +113 -0
  167. package/drizzle/0321_slack_bot_environment_display_name.sql +15 -0
  168. package/drizzle/0322_session_turns_unclaimed_prompt_trigger_index.sql +5 -0
  169. package/drizzle/0323_organization_private_session_enablement.sql +489 -0
  170. package/drizzle/0324_human_confirmed_company_profile_agent_admin.sql +856 -0
  171. package/drizzle/0325_child_lifecycle_notices.sql +44 -0
  172. package/drizzle/0326_interaction_operation_error_codes.sql +62 -0
  173. package/drizzle/0327_slack_first_task_hint.sql +29 -0
  174. package/drizzle/0328_session_background_commands.sql +590 -0
  175. package/drizzle/0329_slack_orchestration_delivery_events.sql +91 -0
  176. package/drizzle/0330_api_key_descriptions.sql +20 -0
  177. package/drizzle/0331_managed_organization_creation.sql +163 -0
  178. package/drizzle/0332_organization_shared_workspace_control_plane.sql +539 -0
  179. package/drizzle/0333_session_turn_prompt_routing.sql +45 -0
  180. package/drizzle/0334_connected_machine_workspace_root.sql +99 -0
  181. package/drizzle/0335_slack_workspace_routing.sql +460 -0
  182. package/drizzle/0336_atomic_session_fork_visibility.sql +667 -0
  183. package/drizzle/0337_slack_routed_action_handles.sql +189 -0
  184. package/drizzle/0338_atomic_connected_machine_attachments.sql +410 -0
  185. package/drizzle/0339_document_authority_reclassification.sql +1153 -0
  186. package/drizzle/0340_tenancy_backfill_activation_evidence.sql +1347 -0
  187. package/drizzle/0341_slack_routing_probe_organization_fence.sql +109 -0
  188. package/drizzle/0342_slack_route_prompt_single_pending.sql +32 -0
  189. package/drizzle/0343_personal_document_force_rls_lock_repair.sql +146 -0
  190. package/drizzle/0344_private_session_visibility_transition_gate.sql +85 -0
  191. package/drizzle/0345_tenant_scoped_session_tenancy_fence.sql +2839 -0
  192. package/drizzle/0346_document_migration_audit_surface.sql +454 -0
  193. package/drizzle/0347_connection_authority_convergence_evidence.sql +396 -0
  194. package/package.json +10 -6
  195. package/src/attached-browser-devices.ts +233 -5
  196. package/src/automations.ts +911 -0
  197. package/src/browser-sessions.ts +10 -0
  198. package/src/capability-integrations.ts +175 -1
  199. package/src/child-lifecycle-notices.ts +241 -0
  200. package/src/codemode-operations.ts +1 -35
  201. package/src/company-brain-context-selection-schema.ts +100 -0
  202. package/src/company-brain-context-selection.ts +359 -0
  203. package/src/company-brain-governed-writes.ts +1170 -0
  204. package/src/company-brain.ts +309 -0
  205. package/src/company-profile-agent-admin.ts +225 -0
  206. package/src/company-profile-schema.ts +121 -1
  207. package/src/company-profile.ts +21 -0
  208. package/src/computer-sessions.ts +10 -1
  209. package/src/connection-authority.ts +290 -0
  210. package/src/connection-scopes.ts +20 -0
  211. package/src/connection-tenancy-backfill.ts +167 -0
  212. package/src/connection-token-resolver.ts +263 -31
  213. package/src/database.ts +87 -2
  214. package/src/editable-artifacts-schema.ts +10 -5
  215. package/src/editable-artifacts.ts +21 -11
  216. package/src/governed-learning-activation-schema.ts +121 -0
  217. package/src/governed-learning-activation.ts +588 -0
  218. package/src/governed-learning-evaluator-schema.ts +72 -0
  219. package/src/governed-learning-evaluator.ts +217 -0
  220. package/src/index.ts +11676 -1550
  221. package/src/insights.ts +107 -0
  222. package/src/integration-bindings.ts +88 -6
  223. package/src/integration-facets.ts +287 -84
  224. package/src/interaction-placement-loss.ts +234 -0
  225. package/src/managed-human-provisioning.ts +116 -0
  226. package/src/memory-domain.ts +27 -32
  227. package/src/migrate.ts +77 -1
  228. package/src/new-session-drafts.ts +229 -35
  229. package/src/organization-membership-backfill.ts +735 -0
  230. package/src/organization-membership-lifecycle.ts +995 -0
  231. package/src/pr-review.ts +890 -0
  232. package/src/preference-registry.ts +31 -1
  233. package/src/provision-roles.ts +905 -6
  234. package/src/runtime-posture-cli.ts +2 -0
  235. package/src/runtime-posture.ts +1317 -72
  236. package/src/schema.ts +3316 -428
  237. package/src/scoped-knowledge-schema.ts +16 -8
  238. package/src/scoped-knowledge.ts +1 -1
  239. package/src/session-background-commands.ts +759 -0
  240. package/src/session-control.ts +640 -74
  241. package/src/session-goal-pacing.ts +126 -0
  242. package/src/session-queue-commands.ts +441 -85
  243. package/src/session-realtime-context.ts +4 -6
  244. package/src/session-realtime-ledger.ts +28 -14
  245. package/src/session-realtime-mirror.ts +1 -1
  246. package/src/session-tenancy.ts +410 -49
  247. package/src/session-tool-call-settlement.ts +38 -29
  248. package/src/slack-routing-personal-workspace.ts +67 -0
  249. package/src/slack-routing.ts +867 -0
  250. package/src/slack-task-policy-schema.ts +5 -0
  251. package/src/slack-task-policy.ts +18 -0
  252. package/src/slack-user-link-access.ts +138 -11
  253. package/src/task-notes-schema.ts +74 -0
  254. package/src/task-notes.ts +124 -2
  255. package/src/turn-initiator.ts +8 -0
  256. package/src/user-resource-authority.ts +377 -0
  257. package/src/workspace-authority.ts +160 -0
  258. package/src/workspace-instruction-policies-schema.ts +53 -1
  259. package/src/workspace-instruction-policies.ts +528 -77
  260. package/src/workspace-learning-policy.ts +21 -5
  261. package/src/workspace-membership-permissions.ts +9 -0
  262. package/dist/chunk-4SR4HKKA.js.map +0 -1
  263. package/dist/chunk-6ZYKCRFO.js.map +0 -1
  264. package/dist/chunk-NXAFLCQX.js +0 -2468
  265. package/dist/chunk-NXAFLCQX.js.map +0 -1
  266. package/dist/chunk-SK7YYHBI.js.map +0 -1
  267. package/dist/chunk-WD5ROSLL.js +0 -117
  268. package/dist/chunk-WD5ROSLL.js.map +0 -1
  269. package/dist/chunk-ZWREYEWA.js.map +0 -1
  270. /package/dist/{chunk-CUPM3FGB.js.map → chunk-2PRS4J5Q.js.map} +0 -0
  271. /package/dist/{chunk-LTG3S6ZY.js.map → chunk-E6CLMKEE.js.map} +0 -0
@@ -13,8 +13,10 @@ import {
13
13
  IntegrationFacetBindingOwnershipConflictError,
14
14
  IntegrationFacetBindingVersionConflictError,
15
15
  listIntegrationFacetBindingOwners,
16
+ listIntegrationFacetBindingOwnersForBindings,
16
17
  removeIntegrationFacetBindingOwner,
17
18
  upsertIntegrationFacetBinding,
19
+ type IntegrationFacetBindingOwner,
18
20
  } from "./integration-bindings";
19
21
  import * as schema from "./schema";
20
22
 
@@ -39,6 +41,8 @@ export type IntegrationFacetBindingSummary = {
39
41
  lastErrorCode: string | null;
40
42
  createdAt: string;
41
43
  updatedAt: string;
44
+ directlyOwned: boolean;
45
+ owners: IntegrationFacetBindingOwner[];
42
46
  };
43
47
 
44
48
  export type IntegrationFacetDefinitionSummary = {
@@ -96,6 +100,72 @@ export class IntegrationFacetOperationIdempotencyError extends Error {
96
100
  readonly name = "IntegrationFacetOperationIdempotencyError";
97
101
  }
98
102
 
103
+ type IntegrationFacetOperationKind = "configure" | "update" | "disconnect";
104
+
105
+ type IntegrationFacetOperationIdentity = {
106
+ accountId: string;
107
+ workspaceId: string;
108
+ subjectId: string;
109
+ capabilityId: string;
110
+ instanceKey: string;
111
+ facetKey: string;
112
+ idempotencyKey: string;
113
+ };
114
+
115
+ export function integrationFacetConfigureRequestDigest(input: {
116
+ capabilityId: string;
117
+ instanceKey: string;
118
+ facetKey: string;
119
+ displayName: string;
120
+ config: Record<string, unknown>;
121
+ expectedVersion?: number;
122
+ }): string {
123
+ return sha256(
124
+ stableJson({
125
+ action: "configure",
126
+ capabilityId: input.capabilityId,
127
+ instanceKey: input.instanceKey,
128
+ facetKey: input.facetKey,
129
+ displayName: input.displayName,
130
+ config: input.config,
131
+ expectedVersion: input.expectedVersion ?? null,
132
+ }),
133
+ );
134
+ }
135
+
136
+ /**
137
+ * Replays only an exact completed Integration Facet receipt. This intentionally
138
+ * runs before mutable instance/Connection/provider validation at HTTP edges.
139
+ */
140
+ export async function replayCompletedIntegrationFacetOperation<T extends Record<string, unknown>>(
141
+ db: Database,
142
+ input: IntegrationFacetOperationIdentity & {
143
+ kind: IntegrationFacetOperationKind;
144
+ expectedRequestDigest: string | ((result: T) => string);
145
+ },
146
+ ): Promise<T | null> {
147
+ return await withRlsContext(
148
+ db,
149
+ { accountId: input.accountId, workspaceId: input.workspaceId },
150
+ async (scopedDb) => {
151
+ await setSubjectRlsContext(scopedDb, input.subjectId);
152
+ return await scopedDb.transaction(async (txRaw) => {
153
+ const tx = txRaw as unknown as Database;
154
+ await lockFacetOperation(tx, input);
155
+ const existing = await loadFacetOperation(tx, input);
156
+ if (!existing) return null;
157
+ const result = replayFacetOperation<T>(
158
+ existing,
159
+ input,
160
+ input.kind,
161
+ input.expectedRequestDigest,
162
+ );
163
+ return await hydrateFacetOperationResult(tx, input, result);
164
+ });
165
+ },
166
+ );
167
+ }
168
+
99
169
  type IntegrationInstanceContext = {
100
170
  accountId: string;
101
171
  integrationFacetInstallationId: string;
@@ -164,6 +234,10 @@ export async function listIntegrationInstanceFacets(
164
234
  asc(schema.integrationFacetDefinitions.kind),
165
235
  asc(schema.integrationFacetDefinitions.facetKey),
166
236
  );
237
+ const ownersByBindingId = await listIntegrationFacetBindingOwnersForBindings(
238
+ scopedDb,
239
+ rows.flatMap((row) => (row.bindingId ? [row.bindingId] : [])),
240
+ );
167
241
  return {
168
242
  capabilityId,
169
243
  instanceKey,
@@ -177,22 +251,26 @@ export async function listIntegrationInstanceFacets(
177
251
  capabilities: row.capabilities,
178
252
  },
179
253
  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
- })
254
+ ? bindingSummary(
255
+ {
256
+ id: row.bindingId,
257
+ facetKey: row.facetKey,
258
+ kind: row.kind,
259
+ bindingKey: row.bindingKey!,
260
+ displayName: row.displayName!,
261
+ connectionId: row.connectionId ?? null,
262
+ status: row.status!,
263
+ config: row.config!,
264
+ cursor: row.cursor!,
265
+ version: row.version!,
266
+ lastSuccessAt: row.lastSuccessAt ?? null,
267
+ lastErrorCode: row.lastErrorCode ?? null,
268
+ createdAt: row.createdAt!,
269
+ updatedAt: row.updatedAt!,
270
+ },
271
+ ownersByBindingId.get(row.bindingId) ?? [],
272
+ { capabilityId, instanceKey, facetKey: row.facetKey },
273
+ )
196
274
  : null,
197
275
  })),
198
276
  };
@@ -214,17 +292,7 @@ export async function configureIntegrationFacet(
214
292
  idempotencyKey: string;
215
293
  },
216
294
  ): 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
- );
295
+ const requestDigest = integrationFacetConfigureRequestDigest(input);
228
296
  return await withFacetOperation(db, input, requestDigest, "configure", async (tx) => {
229
297
  const context = await requireMutationContext(tx, input);
230
298
  const definition = await requireFacetDefinition(tx, context, input.facetKey);
@@ -244,16 +312,21 @@ export async function configureIntegrationFacet(
244
312
  owner: facetOwner(input.capabilityId, input.instanceKey, input.facetKey),
245
313
  ...(input.expectedVersion !== undefined ? { expectedVersion: input.expectedVersion } : {}),
246
314
  });
315
+ const owners = await listIntegrationFacetBindingOwners(tx, result.row.id);
247
316
  return {
248
317
  capabilityId: input.capabilityId,
249
318
  instanceKey: input.instanceKey,
250
319
  facetKey: input.facetKey,
251
320
  status: "configured",
252
- binding: bindingSummary({
253
- ...result.row,
254
- facetKey: definition.facetKey,
255
- kind: definition.kind,
256
- }),
321
+ binding: bindingSummary(
322
+ {
323
+ ...result.row,
324
+ facetKey: definition.facetKey,
325
+ kind: definition.kind,
326
+ },
327
+ owners,
328
+ input,
329
+ ),
257
330
  };
258
331
  });
259
332
  }
@@ -312,16 +385,21 @@ export async function setIntegrationFacetLifecycle(
312
385
  if (!updated) throw new Error("Failed to update Integration facet lifecycle");
313
386
  row = updated;
314
387
  }
388
+ const owners = await listIntegrationFacetBindingOwners(tx, row.id);
315
389
  return {
316
390
  capabilityId: input.capabilityId,
317
391
  instanceKey: input.instanceKey,
318
392
  facetKey: input.facetKey,
319
393
  status: input.action === "pause" ? "paused" : "active",
320
- binding: bindingSummary({
321
- ...row,
322
- facetKey: definition.facetKey,
323
- kind: definition.kind,
324
- }),
394
+ binding: bindingSummary(
395
+ {
396
+ ...row,
397
+ facetKey: definition.facetKey,
398
+ kind: definition.kind,
399
+ },
400
+ owners,
401
+ input,
402
+ ),
325
403
  };
326
404
  });
327
405
  }
@@ -377,11 +455,15 @@ export async function removeIntegrationFacet(
377
455
  instanceKey: input.instanceKey,
378
456
  facetKey: input.facetKey,
379
457
  status: "not_configured",
380
- binding: bindingSummary({
381
- ...binding,
382
- facetKey: definition.facetKey,
383
- kind: definition.kind,
384
- }),
458
+ binding: bindingSummary(
459
+ {
460
+ ...binding,
461
+ facetKey: definition.facetKey,
462
+ kind: definition.kind,
463
+ },
464
+ owners,
465
+ input,
466
+ ),
385
467
  remainingOwners: owners,
386
468
  };
387
469
  }
@@ -391,17 +473,24 @@ export async function removeIntegrationFacet(
391
473
  owner,
392
474
  expectedVersion: input.expectedVersion,
393
475
  });
476
+ const effectiveRemainingOwners = removed.binding
477
+ ? await listIntegrationFacetBindingOwners(tx, removed.binding.id)
478
+ : [];
394
479
  return {
395
480
  capabilityId: input.capabilityId,
396
481
  instanceKey: input.instanceKey,
397
482
  facetKey: input.facetKey,
398
483
  status: removed.remainingOwners.length > 0 ? "retained_by_other_owners" : "removed",
399
484
  binding: removed.binding
400
- ? bindingSummary({
401
- ...removed.binding,
402
- facetKey: definition.facetKey,
403
- kind: definition.kind,
404
- })
485
+ ? bindingSummary(
486
+ {
487
+ ...removed.binding,
488
+ facetKey: definition.facetKey,
489
+ kind: definition.kind,
490
+ },
491
+ effectiveRemainingOwners,
492
+ input,
493
+ )
405
494
  : null,
406
495
  remainingOwners: removed.remainingOwners,
407
496
  };
@@ -410,17 +499,9 @@ export async function removeIntegrationFacet(
410
499
 
411
500
  async function withFacetOperation<T extends Record<string, unknown>>(
412
501
  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
- },
502
+ input: IntegrationFacetOperationIdentity,
422
503
  requestDigest: string,
423
- kind: string,
504
+ kind: IntegrationFacetOperationKind,
424
505
  execute: (tx: Database) => Promise<T>,
425
506
  ): Promise<T> {
426
507
  return await withRlsContext(
@@ -430,30 +511,11 @@ async function withFacetOperation<T extends Record<string, unknown>>(
430
511
  await setSubjectRlsContext(scopedDb, input.subjectId);
431
512
  return await scopedDb.transaction(async (txRaw) => {
432
513
  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);
514
+ await lockFacetOperation(tx, input);
515
+ const existing = await loadFacetOperation(tx, input);
447
516
  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
- );
517
+ const result = replayFacetOperation<T>(existing, input, kind, requestDigest);
518
+ return await hydrateFacetOperationResult(tx, input, result);
457
519
  }
458
520
  const result = await execute(tx);
459
521
  await tx.insert(schema.capabilityOperations).values({
@@ -463,9 +525,7 @@ async function withFacetOperation<T extends Record<string, unknown>>(
463
525
  requestDigest,
464
526
  kind,
465
527
  targetKind: "facet_binding",
466
- targetId: `facet:${sha256(
467
- `${input.capabilityId}\0${input.instanceKey}\0${input.facetKey}`,
468
- )}`,
528
+ targetId: facetOperationTargetId(input),
469
529
  status: "completed",
470
530
  phase: "completed",
471
531
  result,
@@ -478,6 +538,140 @@ async function withFacetOperation<T extends Record<string, unknown>>(
478
538
  );
479
539
  }
480
540
 
541
+ async function lockFacetOperation(
542
+ db: Database,
543
+ input: Pick<IntegrationFacetOperationIdentity, "workspaceId" | "idempotencyKey">,
544
+ ): Promise<void> {
545
+ await db.execute(
546
+ sql`select pg_advisory_xact_lock(hashtextextended(${`capability-operation:${input.workspaceId}:${input.idempotencyKey}`}, 0))`,
547
+ );
548
+ }
549
+
550
+ async function loadFacetOperation(
551
+ db: Database,
552
+ input: Pick<IntegrationFacetOperationIdentity, "accountId" | "workspaceId" | "idempotencyKey">,
553
+ ) {
554
+ const [existing] = await db
555
+ .select()
556
+ .from(schema.capabilityOperations)
557
+ .where(
558
+ and(
559
+ eq(schema.capabilityOperations.accountId, input.accountId),
560
+ eq(schema.capabilityOperations.workspaceId, input.workspaceId),
561
+ eq(schema.capabilityOperations.idempotencyKey, input.idempotencyKey),
562
+ ),
563
+ )
564
+ .for("update")
565
+ .limit(1);
566
+ return existing;
567
+ }
568
+
569
+ function replayFacetOperation<T extends Record<string, unknown>>(
570
+ existing: typeof schema.capabilityOperations.$inferSelect,
571
+ input: IntegrationFacetOperationIdentity,
572
+ kind: IntegrationFacetOperationKind,
573
+ expectedRequestDigest: string | ((result: T) => string),
574
+ ): T {
575
+ if (
576
+ existing.createdBySubjectId !== input.subjectId ||
577
+ existing.kind !== kind ||
578
+ existing.targetKind !== "facet_binding" ||
579
+ existing.targetId !== facetOperationTargetId(input)
580
+ ) {
581
+ throw new IntegrationFacetOperationIdempotencyError(
582
+ "Integration facet idempotency key was reused",
583
+ );
584
+ }
585
+ if (
586
+ typeof expectedRequestDigest === "string" &&
587
+ existing.requestDigest !== expectedRequestDigest
588
+ ) {
589
+ throw new IntegrationFacetOperationIdempotencyError(
590
+ "Integration facet idempotency key was reused",
591
+ );
592
+ }
593
+ if (existing.status !== "completed" || !existing.result) {
594
+ throw new IntegrationFacetOperationIdempotencyError(
595
+ "Integration facet operation is already in progress",
596
+ );
597
+ }
598
+ const result = existing.result as T;
599
+ const requestDigest =
600
+ typeof expectedRequestDigest === "string"
601
+ ? expectedRequestDigest
602
+ : expectedRequestDigest(result);
603
+ if (existing.requestDigest !== requestDigest) {
604
+ throw new IntegrationFacetOperationIdempotencyError(
605
+ "Integration facet idempotency key was reused",
606
+ );
607
+ }
608
+ return result;
609
+ }
610
+
611
+ function facetOperationTargetId(
612
+ input: Pick<IntegrationFacetOperationIdentity, "capabilityId" | "instanceKey" | "facetKey">,
613
+ ): string {
614
+ return `facet:${sha256(`${input.capabilityId}\0${input.instanceKey}\0${input.facetKey}`)}`;
615
+ }
616
+
617
+ async function hydrateFacetOperationResult<T extends Record<string, unknown>>(
618
+ db: Database,
619
+ input: { capabilityId: string; instanceKey: string; facetKey: string },
620
+ result: T,
621
+ ): Promise<T> {
622
+ const binding = recordValue(result.binding);
623
+ if (!binding) return result;
624
+ if (typeof binding.directlyOwned === "boolean" && Array.isArray(binding.owners)) return result;
625
+
626
+ const expectedOwner = facetOwner(input.capabilityId, input.instanceKey, input.facetKey);
627
+ let owners =
628
+ typeof binding.id === "string" ? await listIntegrationFacetBindingOwners(db, binding.id) : [];
629
+ if (owners.length === 0) {
630
+ owners = capabilityComponentOwners(result.remainingOwners);
631
+ }
632
+ if (
633
+ owners.length === 0 &&
634
+ (result.status === "configured" || result.status === "paused" || result.status === "active")
635
+ ) {
636
+ owners = [expectedOwner];
637
+ }
638
+ return {
639
+ ...result,
640
+ binding: {
641
+ ...binding,
642
+ directlyOwned: owners.some(
643
+ (owner) => owner.kind === expectedOwner.kind && owner.id === expectedOwner.id,
644
+ ),
645
+ owners,
646
+ },
647
+ } as T;
648
+ }
649
+
650
+ function capabilityComponentOwners(value: unknown): IntegrationFacetBindingOwner[] {
651
+ if (!Array.isArray(value)) return [];
652
+ return value.flatMap((entry) => {
653
+ const owner = recordValue(entry);
654
+ if (
655
+ !owner ||
656
+ (owner.kind !== "direct" &&
657
+ owner.kind !== "plugin" &&
658
+ owner.kind !== "pack" &&
659
+ owner.kind !== "migration") ||
660
+ typeof owner.id !== "string" ||
661
+ typeof owner.removable !== "boolean"
662
+ ) {
663
+ return [];
664
+ }
665
+ return [{ kind: owner.kind, id: owner.id, removable: owner.removable }];
666
+ });
667
+ }
668
+
669
+ function recordValue(value: unknown): Record<string, unknown> | null {
670
+ return value && typeof value === "object" && !Array.isArray(value)
671
+ ? (value as Record<string, unknown>)
672
+ : null;
673
+ }
674
+
481
675
  async function requireMutationContext(
482
676
  db: Database,
483
677
  input: {
@@ -705,7 +899,12 @@ type BindingSummaryRow = Pick<
705
899
  kind: string;
706
900
  };
707
901
 
708
- function bindingSummary(row: BindingSummaryRow): IntegrationFacetBindingSummary {
902
+ function bindingSummary(
903
+ row: BindingSummaryRow,
904
+ owners: IntegrationFacetBindingOwner[],
905
+ identity: { capabilityId: string; instanceKey: string; facetKey: string },
906
+ ): IntegrationFacetBindingSummary {
907
+ const expectedOwner = facetOwner(identity.capabilityId, identity.instanceKey, identity.facetKey);
709
908
  return {
710
909
  id: row.id,
711
910
  facetKey: row.facetKey,
@@ -721,6 +920,10 @@ function bindingSummary(row: BindingSummaryRow): IntegrationFacetBindingSummary
721
920
  lastErrorCode: row.lastErrorCode,
722
921
  createdAt: row.createdAt.toISOString(),
723
922
  updatedAt: row.updatedAt.toISOString(),
923
+ directlyOwned: owners.some(
924
+ (owner) => owner.kind === expectedOwner.kind && owner.id === expectedOwner.id,
925
+ ),
926
+ owners,
724
927
  };
725
928
  }
726
929