@opengeni/db 5.0.1 → 6.0.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 (212) hide show
  1. package/dist/automations.d.ts +0 -2
  2. package/dist/canonical-human-identities.d.ts +4 -0
  3. package/dist/canonical-human-identities.js +3 -3
  4. package/dist/capability-components.d.ts +5 -13
  5. package/dist/capability-integrations.d.ts +23 -8
  6. package/dist/capability-reconciliation.d.ts +5 -0
  7. package/dist/capability-reconciliation.js +21 -0
  8. package/dist/capability-reconciliation.js.map +1 -0
  9. package/dist/{chunk-6T2GGYWE.js → chunk-4UJCXBR2.js} +4 -5800
  10. package/dist/chunk-4UJCXBR2.js.map +1 -0
  11. package/dist/{chunk-MWMXWHJD.js → chunk-7JCDSMX2.js} +2 -2
  12. package/dist/chunk-AJA2ZIUW.js +219 -0
  13. package/dist/chunk-AJA2ZIUW.js.map +1 -0
  14. package/dist/chunk-CDAT6EKW.js +247 -0
  15. package/dist/chunk-CDAT6EKW.js.map +1 -0
  16. package/dist/{chunk-DPONPYJD.js → chunk-CJI6G254.js} +70 -18
  17. package/dist/chunk-CJI6G254.js.map +1 -0
  18. package/dist/chunk-G32NVMRJ.js +276 -0
  19. package/dist/chunk-G32NVMRJ.js.map +1 -0
  20. package/dist/chunk-GABXCG3N.js +5811 -0
  21. package/dist/chunk-GABXCG3N.js.map +1 -0
  22. package/dist/{chunk-ERKVSPDX.js → chunk-LVY33EWL.js} +3 -3
  23. package/dist/{chunk-45CP2YKY.js → chunk-MAMBI2HR.js} +9 -3
  24. package/dist/{chunk-45CP2YKY.js.map → chunk-MAMBI2HR.js.map} +1 -1
  25. package/dist/{chunk-F73UKJY6.js → chunk-MN5PZMEK.js} +91 -108
  26. package/dist/chunk-MN5PZMEK.js.map +1 -0
  27. package/dist/chunk-PJI4MUZ7.js +131 -0
  28. package/dist/chunk-PJI4MUZ7.js.map +1 -0
  29. package/dist/{chunk-T6BWIGWJ.js → chunk-RIYHP2WU.js} +2 -2
  30. package/dist/{chunk-7HMK3B45.js → chunk-RL2MUXM3.js} +16 -7
  31. package/dist/chunk-RL2MUXM3.js.map +1 -0
  32. package/dist/{chunk-HQDVI2DB.js → chunk-SYKHLPTK.js} +4 -3
  33. package/dist/{chunk-HQDVI2DB.js.map → chunk-SYKHLPTK.js.map} +1 -1
  34. package/dist/{chunk-T6YLHJLS.js → chunk-XIXVVBW2.js} +198 -51
  35. package/dist/chunk-XIXVVBW2.js.map +1 -0
  36. package/dist/chunk-ZPJ6V2VG.js +156 -0
  37. package/dist/chunk-ZPJ6V2VG.js.map +1 -0
  38. package/dist/codex-capacity-recovery.d.ts +12 -0
  39. package/dist/codex-failure-eligibility.d.ts +9 -0
  40. package/dist/company-profile-schema.d.ts +4 -2
  41. package/dist/connect-attempts.d.ts +15 -0
  42. package/dist/connect-attempts.js +25 -0
  43. package/dist/connect-attempts.js.map +1 -0
  44. package/dist/connection-authority.d.ts +7 -63
  45. package/dist/connection-refresh-lock.d.ts +4 -0
  46. package/dist/connection-token-resolver.d.ts +8 -43
  47. package/dist/database-timing.d.ts +10 -0
  48. package/dist/database.d.ts +4 -0
  49. package/dist/editable-artifact-durable-export.js +2 -2
  50. package/dist/editable-artifacts.d.ts +9 -0
  51. package/dist/editable-artifacts.js +7 -5
  52. package/dist/index.d.ts +447 -78
  53. package/dist/index.js +12705 -12872
  54. package/dist/index.js.map +1 -1
  55. package/dist/insights-usage-bundle.d.ts +3 -0
  56. package/dist/integration-bindings.d.ts +1 -1
  57. package/dist/integration-facets.d.ts +18 -1
  58. package/dist/knowledge-entries-schema.d.ts +2 -0
  59. package/dist/knowledge-entries.d.ts +1 -0
  60. package/dist/knowledge-indexing.d.ts +1 -0
  61. package/dist/live-session-attempt.d.ts +35 -137
  62. package/dist/managed-auth-session-sets.js +3 -3
  63. package/dist/mcp-account-bindings.d.ts +4 -0
  64. package/dist/mcp-oauth.d.ts +11 -0
  65. package/dist/mcp-operations.js +2 -2
  66. package/dist/migrate.js +1 -1
  67. package/dist/organization-integration-policy.d.ts +51 -0
  68. package/dist/organization-integration-policy.js +18 -0
  69. package/dist/organization-integration-policy.js.map +1 -0
  70. package/dist/organization-usage.d.ts +20 -0
  71. package/dist/plugin-packages.d.ts +9 -12
  72. package/dist/pr-review.d.ts +0 -11
  73. package/dist/preference-registry-schema.d.ts +17 -0
  74. package/dist/provision-roles.js +1 -1
  75. package/dist/retained-provider-commands.d.ts +124 -6
  76. package/dist/retained-provider-commands.js +29 -97
  77. package/dist/retained-provider-commands.js.map +1 -1
  78. package/dist/retired-session-create-metadata.d.ts +4 -0
  79. package/dist/runtime-posture.d.ts +7 -5
  80. package/dist/schema.d.ts +876 -656
  81. package/dist/schema.js +5 -7
  82. package/dist/session-admission-block.d.ts +32 -0
  83. package/dist/session-attempt-writers.d.ts +4 -0
  84. package/dist/session-background-commands.js +5 -4
  85. package/dist/session-command-output.js +8 -126
  86. package/dist/session-command-output.js.map +1 -1
  87. package/dist/session-control.d.ts +5 -1
  88. package/dist/session-event-slices.d.ts +10 -0
  89. package/dist/session-event-slices.js +8 -222
  90. package/dist/session-event-slices.js.map +1 -1
  91. package/dist/session-file-attachments.d.ts +28 -0
  92. package/dist/session-goal-reports.d.ts +43 -0
  93. package/dist/session-mcp-approval.d.ts +4 -0
  94. package/dist/session-mcp-credential-rotation.js +5 -4
  95. package/dist/session-mcp-credential-rotation.js.map +1 -1
  96. package/dist/session-message-search.d.ts +22 -0
  97. package/dist/session-queue-commands.d.ts +2 -6
  98. package/dist/session-retry.d.ts +36 -0
  99. package/dist/session-tenancy.js +3 -3
  100. package/dist/skill-source-release-impact.d.ts +18 -0
  101. package/dist/skill-source-release.d.ts +9 -0
  102. package/dist/skills.d.ts +7 -1
  103. package/dist/user-resource-authority.d.ts +0 -8
  104. package/dist/video-generation.js +3 -3
  105. package/dist/workspace-tool-defaults.js +5 -4
  106. package/dist/workspace-tool-defaults.js.map +1 -1
  107. package/drizzle/0468_knowledge_relationship_projection.sql +30 -0
  108. package/drizzle/0469_knowledge_source_discovery.sql +85 -0
  109. package/drizzle/0470_organization_integration_policy.sql +151 -0
  110. package/drizzle/0471_usage_events_account_recent_index.sql +4 -0
  111. package/drizzle/0472_usage_events_workspace_recent_index.sql +4 -0
  112. package/drizzle/0473_organization_usage_analytical_capability.sql +237 -0
  113. package/drizzle/0474_goal_report_requirements.sql +156 -0
  114. package/drizzle/0475_session_event_history_statistics.sql +15 -0
  115. package/drizzle/0476_session_event_history_policy_planning.sql +29 -0
  116. package/drizzle/0477_managed_sign_in_methods.sql +303 -0
  117. package/drizzle/0478_sender_owned_connections.sql +2772 -0
  118. package/drizzle/0479_connection_create_idempotency.sql +16 -0
  119. package/drizzle/0480_private_session_organization_key_administration.sql +154 -0
  120. package/drizzle/0481_personal_connection_visibility_cleanup.sql +45 -0
  121. package/drizzle/0482_remove_packs.sql +417 -0
  122. package/drizzle/0483_preclaim_admission_block.sql +85 -0
  123. package/drizzle/0484_insights_usage_projection.sql +211 -0
  124. package/drizzle/0485_inherited_mcp_approval_policies.sql +6 -0
  125. package/drizzle/0486_reasoning_configuration_message_forks.sql +35 -0
  126. package/drizzle/0487_skill_catalog_message_forks.sql +26 -0
  127. package/drizzle/0488_permanent_skill_removal.sql +207 -0
  128. package/drizzle/0489_modal_command_byte_offsets.sql +17 -0
  129. package/drizzle/0490_periodic_capture_attempt_cadence.sql +6 -0
  130. package/drizzle/0491_warm_capture_holder_reclamation.sql +57 -0
  131. package/drizzle/0492_codex_accepted_source_authority.sql +249 -0
  132. package/drizzle/0493_stopping_command_error_containment.sql +58 -0
  133. package/drizzle/0494_mcp_account_bindings.sql +334 -0
  134. package/drizzle/0495_consented_sandbox_recovery.sql +220 -0
  135. package/drizzle/0496_supervised_command_settlement.sql +386 -0
  136. package/drizzle/0497_modal_inventory_owner_read_capability.sql +112 -0
  137. package/drizzle/0498_enrollment_membership_fence.sql +87 -0
  138. package/drizzle/0499_session_attachment_access.sql +224 -0
  139. package/drizzle/0500_session_execution_epoch.sql +37 -0
  140. package/drizzle/0501_session_sharing_execution.sql +591 -0
  141. package/drizzle/0502_active_message_boundary_forks.sql +30 -0
  142. package/package.json +17 -5
  143. package/src/automations.ts +17 -73
  144. package/src/canonical-human-identities.ts +17 -5
  145. package/src/capability-components.ts +7 -24
  146. package/src/capability-integrations.ts +301 -21
  147. package/src/capability-reconciliation.ts +19 -0
  148. package/src/codex-capacity-recovery.ts +47 -0
  149. package/src/codex-failure-eligibility.ts +93 -0
  150. package/src/connect-attempts.ts +49 -18
  151. package/src/connection-authority.ts +12 -158
  152. package/src/connection-refresh-lock.ts +22 -0
  153. package/src/connection-token-resolver.ts +58 -530
  154. package/src/database-timing.ts +43 -0
  155. package/src/database.ts +61 -26
  156. package/src/editable-artifacts.ts +1 -1
  157. package/src/index.ts +3578 -1273
  158. package/src/insights-model-bundle.ts +19 -7
  159. package/src/insights-usage-bundle.ts +16 -19
  160. package/src/integration-bindings.ts +2 -7
  161. package/src/integration-facets.ts +73 -6
  162. package/src/knowledge-entries.ts +8 -1
  163. package/src/live-session-attempt.ts +0 -22
  164. package/src/mcp-account-bindings.ts +12 -0
  165. package/src/mcp-oauth.ts +26 -0
  166. package/src/organization-integration-policy.ts +235 -0
  167. package/src/organization-usage.ts +83 -0
  168. package/src/plugin-packages.ts +226 -42
  169. package/src/pr-review.ts +7 -69
  170. package/src/preference-registry-schema.ts +1 -0
  171. package/src/provision-roles.ts +50 -37
  172. package/src/retained-provider-commands.ts +286 -9
  173. package/src/retired-session-create-metadata.ts +16 -0
  174. package/src/runtime-posture.ts +217 -13
  175. package/src/schema.ts +104 -120
  176. package/src/session-admission-block.ts +29 -0
  177. package/src/session-attempt-writers.ts +48 -0
  178. package/src/session-control.ts +21 -5
  179. package/src/session-event-slices.ts +76 -26
  180. package/src/session-file-attachments.ts +66 -0
  181. package/src/session-goal-reports.ts +255 -0
  182. package/src/session-mcp-approval.ts +30 -0
  183. package/src/session-message-search.ts +348 -0
  184. package/src/session-queue-commands.ts +60 -36
  185. package/src/session-retry.ts +319 -0
  186. package/src/skill-config-migration.ts +11 -2
  187. package/src/skill-source-release-impact.ts +33 -0
  188. package/src/skill-source-release.ts +67 -26
  189. package/src/skills.ts +44 -6
  190. package/src/user-resource-authority.ts +0 -56
  191. package/dist/chunk-6T2GGYWE.js.map +0 -1
  192. package/dist/chunk-7HMK3B45.js.map +0 -1
  193. package/dist/chunk-DPONPYJD.js.map +0 -1
  194. package/dist/chunk-F73UKJY6.js.map +0 -1
  195. package/dist/chunk-T6YLHJLS.js.map +0 -1
  196. package/dist/host-mcp-binding-match.d.ts +0 -5
  197. package/dist/host-mcp-bindings.d.ts +0 -79
  198. package/dist/host-mcp-resolvers.d.ts +0 -47
  199. package/dist/host-mcp-task-authority.d.ts +0 -43
  200. package/dist/host-selection-identity.d.ts +0 -6
  201. package/dist/pack-components.d.ts +0 -83
  202. package/dist/pack-installations.d.ts +0 -86
  203. package/src/host-mcp-binding-match.ts +0 -34
  204. package/src/host-mcp-bindings.ts +0 -777
  205. package/src/host-mcp-resolvers.ts +0 -266
  206. package/src/host-mcp-task-authority.ts +0 -384
  207. package/src/host-selection-identity.ts +0 -29
  208. package/src/pack-components.ts +0 -980
  209. package/src/pack-installations.ts +0 -950
  210. /package/dist/{chunk-MWMXWHJD.js.map → chunk-7JCDSMX2.js.map} +0 -0
  211. /package/dist/{chunk-ERKVSPDX.js.map → chunk-LVY33EWL.js.map} +0 -0
  212. /package/dist/{chunk-T6BWIGWJ.js.map → chunk-RIYHP2WU.js.map} +0 -0
@@ -540,20 +540,32 @@ export async function readWorkspaceInsightsModelBundle(
540
540
  from current_visible fact
541
541
  ), contribution_source_rows as (
542
542
  select
543
- fact.id,
544
- contribution->>'source' as source,
545
- (contribution->>'items')::bigint as items,
546
- (contribution->>'utf8Bytes')::bigint as utf8_bytes,
547
- (contribution->>'estimatedTokens')::bigint as estimated_tokens
543
+ contribution.entry->>'source' as source,
544
+ (contribution.entry->>'items')::bigint as items,
545
+ (contribution.entry->>'utf8Bytes')::bigint as utf8_bytes,
546
+ (contribution.entry->>'estimatedTokens')::bigint as estimated_tokens,
547
+ -- The validated CHECK rejects duplicate named sources. Its SQL NULL
548
+ -- semantics still permit repeated null sources; count those only once
549
+ -- per fact without sorting all contribution rows by raw fact ID.
550
+ case when contribution.entry->>'source' is not null then true
551
+ else not exists (
552
+ select 1
553
+ from jsonb_array_elements(fact.context_contributions)
554
+ with ordinality earlier(entry, ordinal)
555
+ where earlier.ordinal < contribution.ordinal
556
+ and earlier.entry->>'source' is null
557
+ )
558
+ end as first_source
548
559
  from current_visible fact
549
- cross join lateral jsonb_array_elements(fact.context_contributions) contribution
560
+ cross join lateral jsonb_array_elements(fact.context_contributions)
561
+ with ordinality contribution(entry, ordinal)
550
562
  ), contribution_rows as (
551
563
  select
552
564
  source,
553
565
  sum(items)::bigint as items,
554
566
  sum(utf8_bytes)::bigint as utf8_bytes,
555
567
  sum(estimated_tokens)::bigint as estimated_tokens,
556
- count(distinct id)::bigint as calls
568
+ count(*) filter (where first_source)::bigint as calls
557
569
  from contribution_source_rows
558
570
  group by source
559
571
  )
@@ -86,6 +86,9 @@ function stringValue(row: JsonRecord, key: string): string {
86
86
  * One read for the three existing usage windows. The event-type arrays are
87
87
  * deliberately closed so the authority function can keep selective predicates
88
88
  * inside its scan while all rollup math remains outside the billing path.
89
+ * Only the reused current window needs a second materialization: aggregate the
90
+ * single-use prior/month SRFs directly, since their volatile calls prevent CTE
91
+ * inlining even without an explicit MATERIALIZED keyword.
89
92
  */
90
93
  export async function readWorkspaceInsightsUsageBundle(
91
94
  db: Database,
@@ -103,28 +106,12 @@ export async function readWorkspaceInsightsUsageBundle(
103
106
  with current_visible as materialized (
104
107
  select usage_row.event_type, usage_row.quantity, usage_row.occurred_at,
105
108
  usage_row.source_resource_id
106
- from opengeni_private.visible_workspace_insights_usage_events(
109
+ from opengeni_private.visible_workspace_insights_usage_projection(
107
110
  ${input.workspaceId},
108
111
  ${input.since.toISOString()}::timestamp with time zone,
109
112
  ${input.until.toISOString()}::timestamp with time zone,
110
113
  array['model.cost', 'sandbox.warm_seconds']::text[]
111
114
  ) usage_row
112
- ), prior_visible as materialized (
113
- select usage_row.event_type, usage_row.quantity
114
- from opengeni_private.visible_workspace_insights_usage_events(
115
- ${input.workspaceId},
116
- ${input.priorSince.toISOString()}::timestamp with time zone,
117
- ${input.priorUntil.toISOString()}::timestamp with time zone,
118
- array['model.cost', 'sandbox.warm_seconds']::text[]
119
- ) usage_row
120
- ), month_visible as materialized (
121
- select usage_row.event_type, usage_row.quantity, usage_row.occurred_at
122
- from opengeni_private.visible_workspace_insights_usage_events(
123
- ${input.workspaceId},
124
- ${input.monthSince.toISOString()}::timestamp with time zone,
125
- 'infinity'::timestamp with time zone,
126
- array['model.tokens', 'agent_run.created']::text[]
127
- ) usage_row
128
115
  ), current_totals as (
129
116
  select
130
117
  coalesce(sum(usage_row.quantity) filter (
@@ -142,7 +129,12 @@ export async function readWorkspaceInsightsUsageBundle(
142
129
  coalesce(sum(usage_row.quantity) filter (
143
130
  where usage_row.event_type = 'sandbox.warm_seconds'
144
131
  ), 0) as prior_warm_seconds
145
- from prior_visible usage_row
132
+ from opengeni_private.visible_workspace_insights_usage_projection(
133
+ ${input.workspaceId},
134
+ ${input.priorSince.toISOString()}::timestamp with time zone,
135
+ ${input.priorUntil.toISOString()}::timestamp with time zone,
136
+ array['model.cost', 'sandbox.warm_seconds']::text[]
137
+ ) usage_row
146
138
  ), month_totals as (
147
139
  select
148
140
  coalesce(sum(usage_row.quantity) filter (
@@ -153,7 +145,12 @@ export async function readWorkspaceInsightsUsageBundle(
153
145
  where usage_row.event_type = 'agent_run.created'
154
146
  and usage_row.occurred_at > ${input.monthSince.toISOString()}::timestamp with time zone
155
147
  ), 0) as agent_runs_used
156
- from month_visible usage_row
148
+ from opengeni_private.visible_workspace_insights_usage_projection(
149
+ ${input.workspaceId},
150
+ ${input.monthSince.toISOString()}::timestamp with time zone,
151
+ 'infinity'::timestamp with time zone,
152
+ array['model.tokens', 'agent_run.created']::text[]
153
+ ) usage_row
157
154
  ), bucket_rows as (
158
155
  select
159
156
  ${bucket} as bucket,
@@ -8,7 +8,7 @@ import { effectiveCapabilityOwnerSql } from "./capability-components";
8
8
  import * as schema from "./schema";
9
9
 
10
10
  export type IntegrationFacetBindingOwner = {
11
- kind: "direct" | "plugin" | "pack" | "migration";
11
+ kind: "direct" | "plugin" | "migration";
12
12
  id: string;
13
13
  removable: boolean;
14
14
  };
@@ -358,12 +358,7 @@ async function loadIntegrationFacetBindingOwnersForBindings(
358
358
  asc(schema.integrationFacetBindingOwners.ownerId),
359
359
  );
360
360
  for (const row of rows) {
361
- if (
362
- row.kind !== "direct" &&
363
- row.kind !== "plugin" &&
364
- row.kind !== "pack" &&
365
- row.kind !== "migration"
366
- ) {
361
+ if (row.kind !== "direct" && row.kind !== "plugin" && row.kind !== "migration") {
367
362
  throw new Error(`Unknown Integration instance owner kind: ${row.kind}`);
368
363
  }
369
364
  ownersByBindingId
@@ -78,7 +78,7 @@ export type IntegrationFacetRemovalResult = {
78
78
  status: "not_configured" | "removed" | "retained_by_other_owners";
79
79
  binding: IntegrationFacetBindingSummary | null;
80
80
  remainingOwners: {
81
- kind: "direct" | "plugin" | "pack" | "migration";
81
+ kind: "direct" | "plugin" | "migration";
82
82
  id: string;
83
83
  removable: boolean;
84
84
  }[];
@@ -172,8 +172,20 @@ type IntegrationInstanceContext = {
172
172
  integrationFacetId: string;
173
173
  providerDomain: string;
174
174
  connectionId: string | null;
175
+ apiProtocol: string | null;
176
+ definitionId: string | null;
177
+ definitionProvenance: string | null;
175
178
  };
176
179
 
180
+ /** Trusted server callback, never part of a serialized mutation request. The
181
+ * caller must acquire its policy fence before invoking the facet operation.
182
+ * IDs/domains in this context identify installed records, not curated provenance.
183
+ */
184
+ export type IntegrationFacetAcquisitionAuthorizer = (
185
+ tx: Database,
186
+ context: Readonly<IntegrationInstanceContext>,
187
+ ) => Promise<void>;
188
+
177
189
  export async function listIntegrationInstanceFacets(
178
190
  db: Database,
179
191
  workspaceId: string,
@@ -290,6 +302,7 @@ export async function configureIntegrationFacet(
290
302
  config: Record<string, unknown>;
291
303
  expectedVersion?: number;
292
304
  idempotencyKey: string;
305
+ beforeAcquire?: IntegrationFacetAcquisitionAuthorizer;
293
306
  },
294
307
  ): Promise<IntegrationFacetMutationResult> {
295
308
  const requestDigest = integrationFacetConfigureRequestDigest(input);
@@ -298,6 +311,26 @@ export async function configureIntegrationFacet(
298
311
  const definition = await requireFacetDefinition(tx, context, input.facetKey);
299
312
  assertFacetConfig(input.config, definition.configSchema);
300
313
  await requireFacetConnection(tx, input, context, definition.capabilities);
314
+ if (input.beforeAcquire) {
315
+ // Match upsertIntegrationFacetBinding's advisory -> row prefix before
316
+ // deciding whether this request adds authority. The upsert reuses both
317
+ // transaction locks, including the absent-row insertion fence.
318
+ await tx.execute(
319
+ sql`select pg_advisory_xact_lock(hashtextextended(${`integration-binding:${input.workspaceId}:${definition.id}:${input.instanceKey}`}, 0))`,
320
+ );
321
+ const existing = await loadFacetBinding(tx, context, input.instanceKey, definition.id, true);
322
+ const owner = facetOwner(input.capabilityId, input.instanceKey, input.facetKey);
323
+ const owners = existing ? await listIntegrationFacetBindingOwners(tx, existing.id) : [];
324
+ if (
325
+ !existing ||
326
+ existing.status !== "active" ||
327
+ existing.connectionId !== context.connectionId ||
328
+ stableJson(existing.config) !== stableJson(input.config) ||
329
+ !owners.some((current) => current.kind === owner.kind && current.id === owner.id)
330
+ ) {
331
+ await input.beforeAcquire(tx, context);
332
+ }
333
+ }
301
334
  const result = await upsertIntegrationFacetBinding(tx, {
302
335
  accountId: input.accountId,
303
336
  workspaceId: input.workspaceId,
@@ -343,6 +376,7 @@ export async function setIntegrationFacetLifecycle(
343
376
  action: "pause" | "resume";
344
377
  expectedVersion: number;
345
378
  idempotencyKey: string;
379
+ beforeAcquire?: IntegrationFacetAcquisitionAuthorizer;
346
380
  },
347
381
  ): Promise<IntegrationFacetMutationResult> {
348
382
  const requestDigest = sha256(
@@ -352,6 +386,7 @@ export async function setIntegrationFacetLifecycle(
352
386
  workspaceId: undefined,
353
387
  subjectId: undefined,
354
388
  idempotencyKey: undefined,
389
+ beforeAcquire: undefined,
355
390
  }),
356
391
  );
357
392
  return await withFacetOperation(db, input, requestDigest, "update", async (tx) => {
@@ -370,6 +405,9 @@ export async function setIntegrationFacetLifecycle(
370
405
  }
371
406
  await assertDirectFacetOwnership(tx, binding.id, input);
372
407
  const targetStatus = input.action === "pause" ? "paused" : "active";
408
+ if (input.action === "resume" && binding.status !== "active") {
409
+ await input.beforeAcquire?.(tx, context);
410
+ }
373
411
  let row = binding;
374
412
  if (binding.status !== targetStatus) {
375
413
  const [updated] = await tx
@@ -653,10 +691,7 @@ function capabilityComponentOwners(value: unknown): IntegrationFacetBindingOwner
653
691
  const owner = recordValue(entry);
654
692
  if (
655
693
  !owner ||
656
- (owner.kind !== "direct" &&
657
- owner.kind !== "plugin" &&
658
- owner.kind !== "pack" &&
659
- owner.kind !== "migration") ||
694
+ (owner.kind !== "direct" && owner.kind !== "plugin" && owner.kind !== "migration") ||
660
695
  typeof owner.id !== "string" ||
661
696
  typeof owner.removable !== "boolean"
662
697
  ) {
@@ -709,6 +744,13 @@ async function loadIntegrationInstanceContext(
709
744
  integrationFacetId: schema.integrationFacetDefinitions.integrationFacetId,
710
745
  providerDomain: schema.capabilityIntegrationFacets.providerDomain,
711
746
  connectionId: schema.integrationFacetBindings.connectionId,
747
+ apiProtocol: schema.capabilityApiFacets.protocol,
748
+ definitionId: sql<
749
+ string | null
750
+ >`${schema.capabilityPluginVersions.manifest} ->> 'definitionId'`,
751
+ definitionProvenance: sql<
752
+ string | null
753
+ >`${schema.capabilityPluginVersions.manifest} ->> 'definitionProvenance'`,
712
754
  })
713
755
  .from(schema.integrationFacetBindings)
714
756
  .innerJoin(
@@ -740,6 +782,21 @@ async function loadIntegrationInstanceContext(
740
782
  schema.capabilityPluginVersions,
741
783
  eq(schema.capabilityPluginVersions.id, schema.capabilityPluginInstallations.pluginVersionId),
742
784
  )
785
+ .leftJoin(
786
+ schema.capabilityApiFacets,
787
+ and(
788
+ eq(
789
+ schema.capabilityApiFacets.integrationFacetId,
790
+ schema.capabilityIntegrationFacets.facetId,
791
+ ),
792
+ sql`exists (
793
+ select 1 from ${schema.capabilityFacetInstallations} api_installation
794
+ where api_installation.plugin_installation_id = ${schema.capabilityPluginInstallations.id}
795
+ and api_installation.facet_id = ${schema.capabilityApiFacets.facetId}
796
+ and api_installation.status = 'active'
797
+ )`,
798
+ ),
799
+ )
743
800
  .where(
744
801
  and(
745
802
  eq(schema.integrationFacetBindings.workspaceId, workspaceId),
@@ -759,7 +816,17 @@ async function loadIntegrationInstanceContext(
759
816
  ),
760
817
  )
761
818
  .limit(2);
762
- if (lock) query = query.for("update") as typeof query;
819
+ if (lock)
820
+ query = query.for("update", {
821
+ of: [
822
+ schema.integrationFacetBindings,
823
+ schema.integrationFacetDefinitions,
824
+ schema.capabilityIntegrationFacets,
825
+ schema.capabilityFacetInstallations,
826
+ schema.capabilityPluginInstallations,
827
+ schema.capabilityPluginVersions,
828
+ ],
829
+ }) as typeof query;
763
830
  const rows = await query;
764
831
  if (rows.length === 0) return null;
765
832
  if (rows.length !== 1) throw new Error(`API Integration ${capabilityId} instance is ambiguous`);
@@ -644,12 +644,19 @@ export async function inspectKnowledgeFilePreparation(
644
644
  export async function completeKnowledgeFilePreparation(
645
645
  db: Database,
646
646
  context: KnowledgeContext,
647
- input: { fileId: string; title: string; content: string; sourceVersion?: string },
647
+ input: {
648
+ fileId: string;
649
+ title: string;
650
+ content: string;
651
+ sourceVersion?: string;
652
+ purpose?: "evidence" | "reference";
653
+ },
648
654
  ) {
649
655
  return KnowledgeFilePreparationResult.parse(
650
656
  await knowledgeFilePreparationOperation(db, context, {
651
657
  operation: "complete",
652
658
  fileId: z.uuid().parse(input.fileId),
659
+ purpose: z.enum(["evidence", "reference"]).parse(input.purpose ?? "evidence"),
653
660
  ...(input.sourceVersion
654
661
  ? {
655
662
  sourceVersion: z
@@ -2,7 +2,6 @@ import { and, eq, inArray, sql } from "drizzle-orm";
2
2
  import { withWorkspaceRls, type Database } from "./database";
3
3
  import * as schema from "./schema";
4
4
  import { getExternalLinkTurnAuthorization } from "./external-link-work";
5
- import { initiatorFromStorage } from "./turn-initiator";
6
5
 
7
6
  /** Shared physical-attempt fence. Does not mutate goal/execution snapshots. */
8
7
  export async function getLiveSessionAttemptTurn(
@@ -62,24 +61,3 @@ export async function getLiveSessionAttemptTurn(
62
61
  return row.turn;
63
62
  });
64
63
  }
65
-
66
- /** Host credential reads need provenance and live authority, not a runnable
67
- * execution projection or a write to the turn's frozen goal snapshot. */
68
- export async function getHostMcpLiveAttempt(
69
- db: Database,
70
- workspaceId: string,
71
- sessionId: string,
72
- attemptId: string,
73
- ) {
74
- const row = await getLiveSessionAttemptTurn(db, workspaceId, sessionId, attemptId);
75
- if (!row) return null;
76
- return {
77
- ...row,
78
- initiator: initiatorFromStorage(
79
- row.initiatorKind,
80
- row.initiatorSubjectId,
81
- row.initiatorContext ?? {},
82
- ),
83
- initiatorContext: row.initiatorContext ?? {},
84
- };
85
- }
@@ -0,0 +1,12 @@
1
+ import {
2
+ McpConnectionAccountBindings,
3
+ type McpConnectionAccountBinding,
4
+ } from "@opengeni/contracts";
5
+
6
+ /** NULL is a pre-account-routing receipt, not an empty accepted selection.
7
+ * Never default it to [] (or default [] to NULL) during replay/inheritance. */
8
+ export function parseAcceptedMcpAccountBindings(
9
+ value: unknown,
10
+ ): McpConnectionAccountBinding[] | null {
11
+ return value == null ? null : McpConnectionAccountBindings.parse(value);
12
+ }
package/src/mcp-oauth.ts CHANGED
@@ -177,6 +177,32 @@ export async function deleteMcpOAuthAuthorizationRequest(
177
177
  );
178
178
  }
179
179
 
180
+ export async function rebindMcpOAuthAuthorizationRequest(
181
+ db: Database,
182
+ input: {
183
+ requestHash: string;
184
+ subjectId: string;
185
+ accountId: string;
186
+ workspaceId: string;
187
+ permissions: AccessGrant["permissions"];
188
+ toolIdentities: Array<{ serverId: string; toolName: string }>;
189
+ },
190
+ ): Promise<boolean> {
191
+ const [row] = await rawRows<{ request_hash: string }>(
192
+ db,
193
+ sql`update mcp_oauth_authorization_requests
194
+ set account_id = ${input.accountId},
195
+ workspace_id = ${input.workspaceId},
196
+ permissions = ${JSON.stringify(input.permissions)}::jsonb,
197
+ tool_identities = ${JSON.stringify(input.toolIdentities)}::jsonb
198
+ where request_hash = ${input.requestHash}
199
+ and subject_id = ${input.subjectId}
200
+ and expires_at > clock_timestamp()
201
+ returning request_hash`,
202
+ );
203
+ return Boolean(row);
204
+ }
205
+
180
206
  export async function consumeMcpOAuthAuthorizationRequest(
181
207
  db: Database,
182
208
  input: {
@@ -0,0 +1,235 @@
1
+ import {
2
+ OrganizationIntegrationPolicy,
3
+ UpdateOrganizationIntegrationPolicyRequest,
4
+ assertOrganizationIntegrationAllowed,
5
+ } from "@opengeni/contracts";
6
+ import { sql } from "drizzle-orm";
7
+ import { z } from "zod";
8
+ import {
9
+ rawRows,
10
+ rlsContextForWorkspace,
11
+ setSubjectRlsContext,
12
+ withRlsContext,
13
+ type Database,
14
+ } from "./database";
15
+
16
+ export type OrganizationIntegrationPolicyScope = { accountId: string };
17
+ export type OrganizationIntegrationAcquisitionScope = { accountId: string; workspaceId: string };
18
+ const policyScope = z.object({
19
+ accountId: z
20
+ .string()
21
+ .uuid()
22
+ .transform((value) => value.toLowerCase()),
23
+ });
24
+ const acquisitionScope = policyScope.extend({
25
+ workspaceId: z
26
+ .string()
27
+ .uuid()
28
+ .transform((value) => value.toLowerCase()),
29
+ });
30
+ /** Trusted application adapter MUST verify canonical human/local organization-admin
31
+ * provenance or accountScopedApiKeyWorkspaceAuthority (exact account + workspace:admin).
32
+ * This callback is a server dependency, never deserialized request data. The returned
33
+ * subject alone is NOT authentication. DB checks below are live authority fences only.
34
+ */
35
+ export type AuthorizeOrganizationIntegrationPolicyAdministration = () => Promise<{
36
+ accountId: string;
37
+ subjectId: string;
38
+ }>;
39
+
40
+ async function requireReadCommitted(tx: Database): Promise<void> {
41
+ const [isolation] = await rawRows<{ level: string }>(
42
+ tx,
43
+ sql`select current_setting('transaction_isolation') as level`,
44
+ );
45
+ if (isolation?.level !== "read committed") {
46
+ throw new Error("Organization integration policy requires read committed isolation");
47
+ }
48
+ }
49
+
50
+ async function read(
51
+ tx: Database,
52
+ scope: OrganizationIntegrationPolicyScope,
53
+ ): Promise<OrganizationIntegrationPolicy> {
54
+ const [row] = await rawRows<{ policy: unknown }>(
55
+ tx,
56
+ sql`select jsonb_build_object('mode', mode, 'allowedIntegrationKeys', allowed_integration_keys, 'revision', revision) as policy from organization_integration_policies where account_id = ${scope.accountId}::uuid`,
57
+ );
58
+ return OrganizationIntegrationPolicy.parse(row?.policy ?? {});
59
+ }
60
+
61
+ /** Own only the subject setting this policy operation changes. The enclosing
62
+ * withRlsContext transaction/savepoint restores all settings on failure; on
63
+ * success it restores account/workspace only, so restore our subject explicitly.
64
+ * Do not run restoration after a failed SQL statement in an aborted transaction.
65
+ */
66
+ async function withAdministratorSubject<T>(
67
+ tx: Database,
68
+ subjectId: string,
69
+ use: () => Promise<T>,
70
+ ): Promise<T> {
71
+ const [previous] = await rawRows<{ subject: string | null }>(
72
+ tx,
73
+ sql`select current_setting('opengeni.subject_id', true) as subject`,
74
+ );
75
+ await setSubjectRlsContext(tx, subjectId);
76
+ const result = await use();
77
+ const expected = previous?.subject ?? "";
78
+ const [restored] = await rawRows<{ subject: string }>(
79
+ tx,
80
+ sql`select set_config('opengeni.subject_id', ${expected}, true) as subject`,
81
+ );
82
+ if (restored?.subject !== expected)
83
+ throw new Error("Integration policy subject scope was not restored");
84
+ return result;
85
+ }
86
+
87
+ export async function getOrganizationIntegrationPolicy(
88
+ db: Database,
89
+ inputScope: OrganizationIntegrationPolicyScope,
90
+ authorize: AuthorizeOrganizationIntegrationPolicyAdministration,
91
+ ): Promise<OrganizationIntegrationPolicy> {
92
+ const scope = policyScope.parse(inputScope);
93
+ const actor = await authorize();
94
+ if (actor.accountId.toLowerCase() !== scope.accountId || !actor.subjectId)
95
+ throw new Error("Canonical organization administrator required");
96
+ return withRlsContext(
97
+ db,
98
+ { ...scope, workspaceId: null },
99
+ async (tx) => {
100
+ await requireReadCommitted(tx);
101
+ return withAdministratorSubject(tx, actor.subjectId, async () => {
102
+ // Live authority only: no policy or organization advisory lock on reads.
103
+ await tx.execute(
104
+ sql`select opengeni_private.assert_organization_integration_policy_administrator(${scope.accountId}::uuid, ${actor.subjectId})`,
105
+ );
106
+ return read(tx, scope);
107
+ });
108
+ },
109
+ undefined,
110
+ "none",
111
+ );
112
+ }
113
+
114
+ /** Call before acquiring membership/workspace/credential locks in any enclosing
115
+ * transaction. The SQL operation owns policy-exclusive -> membership -> admin
116
+ * row order; it never locks a workspace and rejects acquisition-lock upgrades.
117
+ */
118
+ export async function updateOrganizationIntegrationPolicy(
119
+ db: Database,
120
+ inputScope: OrganizationIntegrationPolicyScope,
121
+ input: UpdateOrganizationIntegrationPolicyRequest,
122
+ authorize: AuthorizeOrganizationIntegrationPolicyAdministration,
123
+ ): Promise<OrganizationIntegrationPolicy> {
124
+ const scope = policyScope.parse(inputScope);
125
+ const request = UpdateOrganizationIntegrationPolicyRequest.parse(input);
126
+ const actor = await authorize();
127
+ if (actor.accountId.toLowerCase() !== scope.accountId || !actor.subjectId)
128
+ throw new Error("Canonical organization administrator required");
129
+ return withRlsContext(
130
+ db,
131
+ { ...scope, workspaceId: null },
132
+ async (tx) => {
133
+ await requireReadCommitted(tx);
134
+ return withAdministratorSubject(tx, actor.subjectId, async () => {
135
+ const [row] = await rawRows<{ result: unknown }>(
136
+ tx,
137
+ sql`select opengeni_private.update_organization_integration_policy(${scope.accountId}::uuid, ${actor.subjectId}, ${JSON.stringify(request)}::jsonb) as result`,
138
+ );
139
+ return OrganizationIntegrationPolicy.parse(row?.result);
140
+ });
141
+ },
142
+ undefined,
143
+ "none",
144
+ );
145
+ }
146
+
147
+ /** Internal serialization primitive, NOT acquisition authorization. Authenticate
148
+ * the caller normally. A callback may first return an exact completed receipt;
149
+ * before any new claim, uncommitted progress, or commit it MUST assert the trusted
150
+ * integration classification against the supplied runtime-frozen policy snapshot.
151
+ * Do not put network operations or retries inside this transaction.
152
+ *
153
+ * Requires read committed isolation. The shared policy fence precedes EVERY
154
+ * organization-membership, workspace/tenancy, and credential lock. In particular,
155
+ * persistProviderOAuthConnection may take the membership fence inside acquire.
156
+ * Policy writers take exclusive policy -> membership -> live administrator rows,
157
+ * and never workspace rows. Ordinary connection lifecycle writers do not take
158
+ * the policy fence. A caller passing an existing transaction must honor this
159
+ * prefix. Never administer policy inside or after an acquisition in that transaction:
160
+ * the SQL writer rejects shared-to-exclusive upgrades rather than risking deadlock.
161
+ * All final persistence must use tx, never a separate connection. No automatic
162
+ * retries: external effects cannot be rolled back. For slow external preparation,
163
+ * preflight separately and use this guard for final persistence. Existing execution,
164
+ * refresh, credential lifecycle, and cleanup do not belong behind this guard.
165
+ */
166
+ export async function withOrganizationIntegrationPolicyFence<T>(
167
+ db: Database,
168
+ inputScope: OrganizationIntegrationAcquisitionScope,
169
+ run: (tx: Database, policy: OrganizationIntegrationPolicy) => Promise<T>,
170
+ ): Promise<T> {
171
+ const requested = acquisitionScope.parse(inputScope);
172
+ // Non-locking authoritative resolution before any new lock; repeat under the
173
+ // policy fence below so a moved/deleted workspace cannot retain stale scope.
174
+ const resolved = await rlsContextForWorkspace(db, requested.workspaceId);
175
+ if (resolved.accountId !== requested.accountId)
176
+ throw new Error("Organization integration policy workspace scope invalid");
177
+ const scope = { accountId: resolved.accountId, workspaceId: requested.workspaceId };
178
+ return withRlsContext(
179
+ db,
180
+ scope,
181
+ async (tx) => {
182
+ await requireReadCommitted(tx);
183
+ await tx.execute(
184
+ sql`select pg_advisory_xact_lock_shared(hashtextextended(${`organization-integration-policy:${scope.accountId}`}, 0))`,
185
+ );
186
+ // No row lock here: the existing writer owns its canonical membership ->
187
+ // workspace prefix. All policy checks and callback writes share this tx.
188
+ const [workspace] = await rawRows(
189
+ tx,
190
+ sql`select id from workspaces where id = ${scope.workspaceId}::uuid and account_id = ${scope.accountId}::uuid`,
191
+ );
192
+ if (!workspace) throw new Error("Organization integration policy workspace scope invalid");
193
+ const policy = await read(tx, scope);
194
+ Object.freeze(policy.allowedIntegrationKeys);
195
+ Object.freeze(policy);
196
+ const result = await run(tx, policy);
197
+ const [finalWorkspace] = await rawRows(
198
+ tx,
199
+ sql`select id from workspaces where id = ${scope.workspaceId}::uuid and account_id = ${scope.accountId}::uuid for key share`,
200
+ );
201
+ if (!finalWorkspace)
202
+ throw new Error("Organization integration policy workspace scope changed before commit");
203
+ return result;
204
+ },
205
+ undefined,
206
+ "none",
207
+ );
208
+ }
209
+
210
+ /** New acquisitions always assert before effects and before returning to commit.
211
+ * The immutable snapshot stays authoritative through commit: the shared fence
212
+ * excludes policy writers and the SQL writer rejects same-transaction upgrades.
213
+ * Receipt-aware callers instead use withOrganizationIntegrationPolicyFence and
214
+ * assert only after distinguishing completed replay from new workflow progress.
215
+ */
216
+ export async function withOrganizationIntegrationAcquisition<T>(
217
+ db: Database,
218
+ inputScope: OrganizationIntegrationAcquisitionScope,
219
+ integrationKeys: readonly (string | null)[],
220
+ acquire: (tx: Database) => Promise<T>,
221
+ ): Promise<T> {
222
+ // Snapshot classifications before the first await, including workspace lookup.
223
+ const keys = [...integrationKeys];
224
+ if (!keys.length)
225
+ throw new Error("Acquisition requires at least one trusted integration classification");
226
+ return withOrganizationIntegrationPolicyFence(db, inputScope, async (tx, policy) => {
227
+ const assert = () => {
228
+ for (const key of keys) assertOrganizationIntegrationAllowed(policy, key);
229
+ };
230
+ assert();
231
+ const result = await acquire(tx);
232
+ assert();
233
+ return result;
234
+ });
235
+ }