@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
@@ -6,23 +6,12 @@ import {
6
6
  import type {
7
7
  ConnectionCredentialPlacement,
8
8
  ConnectionKind,
9
- ConnectionCredentialsPort,
10
9
  ConnectionStatus,
11
10
  McpConnectionResourceScope,
12
11
  McpCredentialAuthNeededReason,
13
- McpCredentialsRequest,
14
- McpCredentialResolution,
15
- McpGatewayCredentialsRequest,
16
- McpGatewayCredentialResolution,
17
- TurnInitiator,
18
- TurnInitiatorContext,
19
12
  } from "@opengeni/contracts";
20
- import { hostMcpCredentialUseGuard } from "@opengeni/contracts";
21
- import {
22
- ConnectionUseAuthoritySnapshot,
23
- type ConnectionUseAttribution,
24
- type ConnectionUseAuthorizationResult,
25
- } from "@opengeni/contracts/connection-authority";
13
+
14
+ import { type ConnectionUseAttribution } from "@opengeni/contracts/connection-authority";
26
15
  import {
27
16
  PERSONAL_GITHUB_REQUESTED_SCOPES,
28
17
  PERSONAL_GITHUB_PROVIDER_DOMAIN,
@@ -41,15 +30,14 @@ import {
41
30
  } from "@opengeni/network";
42
31
  export { isPrivateAddress } from "@opengeni/network";
43
32
  import { Buffer } from "node:buffer";
44
- import { isIP } from "node:net";
45
33
  import { encryptEnvironmentValue } from "./environment-crypto";
46
34
  import type { Database } from "./database";
35
+ import { withConnectionRefreshLock } from "./connection-refresh-lock";
47
36
  import type {
48
37
  AcceptedConnectionUseContext,
49
38
  AcceptedConnectionUseResolution,
50
39
  } from "./connection-authority";
51
40
  import { connectionScopeKey } from "./connection-scopes";
52
- import { hostMcpBindingMatchesSelection } from "@opengeni/contracts/host-mcp-bindings";
53
41
 
54
42
  const MAX_CREDENTIAL_PLACEMENTS = 32;
55
43
  const MAX_CREDENTIAL_NAME_BYTES = 256;
@@ -173,58 +161,12 @@ export type ResolveConnectionCredentialInput = {
173
161
  credentialResolutionMode?: "execution" | "preflight";
174
162
  /** Frozen provider authority generation captured by the calling integration/catalog. */
175
163
  expectedAuthorityGeneration?: number;
176
- /** Exact immutable accepted-work authority; never credential-bearing. */
177
- connectionUseAuthority?: unknown;
164
+ /** Attribution already authorized for forwarding to a host resolver; never authorizes local use. */
165
+ hostConnectionUseAttribution?: ConnectionUseAttribution;
178
166
  /** Exact accepted attempt plus one stable physical-provider request id. */
179
167
  connectionUseContext?: AcceptedConnectionUseContext;
180
168
  };
181
169
 
182
- export type HostMcpCredentialResolverContext = {
183
- accountId: string;
184
- workspaceId: string;
185
- sessionId: string;
186
- rootSessionId: string;
187
- turnId: string;
188
- attemptId: string | null;
189
- executionGeneration: number;
190
- initiator: TurnInitiator;
191
- initiatorContext: TurnInitiatorContext;
192
- surface: McpCredentialsRequest["surface"];
193
- /** Backend-owned attempt liveness, independent of binding delegation. */
194
- authorizeExecution?: (request: McpCredentialsRequest) => Promise<boolean>;
195
- /** Backend-owned live validator. Never populate from caller JSON or a host
196
- * credential response. Omission denies every explicit durable reference. */
197
- authorizeDurableBinding?: (request: McpCredentialsRequest) => Promise<boolean>;
198
- /** Backend-only accepted-work lookup, not a host response or registry fallback. */
199
- resolveAcceptedBinding?: (
200
- request: McpCredentialsRequest,
201
- ) => Promise<McpServerConnectionRef | null>;
202
- };
203
-
204
- export class HostMcpCredentialScopeError extends Error {
205
- constructor(field: "accountId" | "workspaceId" | "sessionId") {
206
- super(`host MCP credential ${field} scope mismatch`);
207
- this.name = "HostMcpCredentialScopeError";
208
- }
209
- }
210
-
211
- export class HostMcpCredentialBindingError extends Error {
212
- constructor(
213
- field:
214
- | "provider"
215
- | "providerDomain"
216
- | "connectionId"
217
- | "scopes"
218
- | "resource"
219
- | "selectedResources"
220
- | "destinationUrl"
221
- | "credentialPlacements",
222
- ) {
223
- super(`host MCP credential ${field} binding mismatch`);
224
- this.name = "HostMcpCredentialBindingError";
225
- }
226
- }
227
-
228
170
  const OFFICIAL_GMAIL_MCP_RESOURCE = "https://gmailmcp.googleapis.com/mcp/v1";
229
171
  const OFFICIAL_GMAIL_REST_HOST = "gmail.googleapis.com";
230
172
 
@@ -261,344 +203,12 @@ function isOfficialGmailRestDestination(
261
203
  * reach a request; the returned object is a fresh copy so a host cannot mutate
262
204
  * headers after resolution.
263
205
  */
264
- export function buildHostConnectionTokenResolver(
265
- resolve: NonNullable<ConnectionCredentialsPort["mcpCredentials"]>,
266
- suppliedContext: HostMcpCredentialResolverContext,
267
- ): (input: ResolveConnectionCredentialInput) => Promise<ResolveConnectionCredentialResult> {
268
- // Bind the accepted execution once. A caller retaining its constructor
269
- // object must not retarget later requests or replace the post-use validator.
270
- const context: HostMcpCredentialResolverContext = {
271
- ...suppliedContext,
272
- initiator: structuredClone(suppliedContext.initiator),
273
- initiatorContext: structuredClone(suppliedContext.initiatorContext),
274
- };
275
- return async (input) => {
276
- if (input.workspaceId !== context.workspaceId) {
277
- throw new HostMcpCredentialScopeError("workspaceId");
278
- }
279
- const destinationUrl = canonicalHttpUrl(input.destinationUrl);
280
- if (
281
- !destinationUrl ||
282
- (!destinationHostMatchesProvider(destinationUrl, input.connectionRef.providerDomain) &&
283
- !isOfficialGmailRestDestination(destinationUrl, input.connectionRef))
284
- ) {
285
- throw new HostMcpCredentialBindingError("destinationUrl");
286
- }
287
- const toolName = input.toolName ?? input.toolId;
288
- const request: McpCredentialsRequest = {
289
- accountId: context.accountId,
290
- workspaceId: context.workspaceId,
291
- sessionId: context.sessionId,
292
- rootSessionId: context.rootSessionId,
293
- turnId: context.turnId,
294
- attemptId: context.attemptId,
295
- executionGeneration: context.executionGeneration,
296
- initiator: context.initiator,
297
- initiatorContext: { ...context.initiatorContext },
298
- surface: context.surface,
299
- destinationUrl,
300
- credentialTarget: input.credentialTarget ?? "mcp",
301
- serverId: input.serverId,
302
- connectionRef: {
303
- providerDomain: input.connectionRef.providerDomain,
304
- ...(input.connectionRef.authoritySource
305
- ? { authoritySource: input.connectionRef.authoritySource }
306
- : {}),
307
- ...(input.connectionRef.hostBinding
308
- ? { hostBinding: { ...input.connectionRef.hostBinding } }
309
- : {}),
310
- ...(input.connectionRef.provider ? { provider: input.connectionRef.provider } : {}),
311
- ...(input.connectionRef.connectionId
312
- ? { connectionId: input.connectionRef.connectionId }
313
- : {}),
314
- ...(input.connectionRef.kind ? { kind: input.connectionRef.kind } : {}),
315
- ...(input.connectionRef.scopes ? { scopes: [...input.connectionRef.scopes] } : {}),
316
- ...(input.connectionRef.resource ? { resource: input.connectionRef.resource } : {}),
317
- ...(input.connectionRef.selectedResources
318
- ? { selectedResources: copySelectedResources(input.connectionRef.selectedResources) }
319
- : {}),
320
- ...(input.connectionRef.subjectScope
321
- ? { subjectScope: input.connectionRef.subjectScope }
322
- : {}),
323
- },
324
- forceRefresh: input.forceRefresh === true,
325
- ...(input.connectionUseAuthority !== undefined
326
- ? { connectionUseAuthority: input.connectionUseAuthority }
327
- : {}),
328
- ...(input.connectionUseContext
329
- ? { connectionUseRequestId: input.connectionUseContext.physicalRequestId }
330
- : {}),
331
- ...(toolName ? { toolName } : {}),
332
- ...(input.subjectId ? { callerSubjectId: input.subjectId } : {}),
333
- };
334
- // Freeze configuration before any asynchronous authority lookup.
335
- let requestedRef = structuredClone(request.connectionRef);
336
- const credentialTarget = input.credentialTarget ?? "mcp";
337
- const durableDenial = (
338
- reason: McpCredentialAuthNeededReason,
339
- ): ResolveConnectionCredentialResult => ({
340
- status: "auth_needed",
341
- reason,
342
- providerDomain: requestedRef.providerDomain,
343
- authoritySource: "host",
344
- ...(requestedRef.connectionId ? { connectionId: requestedRef.connectionId } : {}),
345
- });
346
- if (requestedRef.hostBinding && "selection" in requestedRef.hostBinding) {
347
- if (!context.resolveAcceptedBinding || !context.authorizeDurableBinding)
348
- return durableDenial("unsupported_auth");
349
- try {
350
- const concrete = await context.resolveAcceptedBinding(structuredClone(request));
351
- if (!concrete?.hostBinding || "selection" in concrete.hostBinding)
352
- return durableDenial("personal_authority_unavailable");
353
- const { hostBinding, ...connectionRef } = concrete;
354
- if (
355
- !hostMcpBindingMatchesSelection(
356
- {
357
- id: hostBinding.bindingId,
358
- generation: hostBinding.generation,
359
- definition: { serverId: request.serverId, destinationUrl, connectionRef },
360
- },
361
- request,
362
- )
363
- )
364
- return durableDenial("personal_authority_unavailable");
365
- request.connectionRef = structuredClone(concrete);
366
- requestedRef = structuredClone(concrete);
367
- } catch {
368
- return durableDenial("refresh_failed");
369
- }
370
- }
371
- const snapshot = structuredClone(request);
372
- const authorized = async () => {
373
- try {
374
- if (
375
- context.authorizeExecution &&
376
- !(await context.authorizeExecution(structuredClone(snapshot)))
377
- )
378
- return "resource_scope_unavailable" as const;
379
- if (
380
- requestedRef.hostBinding &&
381
- !(await context.authorizeDurableBinding?.(structuredClone(snapshot)))
382
- )
383
- return "personal_authority_unavailable" as const;
384
- return null;
385
- } catch {
386
- return "refresh_failed" as const;
387
- }
388
- };
389
- if (requestedRef.hostBinding && !context.authorizeDurableBinding)
390
- return durableDenial("unsupported_auth");
391
- const requiresAuthorization = Boolean(requestedRef.hostBinding || context.authorizeExecution);
392
- if (requiresAuthorization) {
393
- const denial = await authorized();
394
- if (denial) return durableDenial(denial);
395
- }
396
- const result = await resolve(structuredClone(snapshot));
397
- const routeGuard = result.status === "ok" ? result[hostMcpCredentialUseGuard] : undefined;
398
- const routeAuthorized = async () => {
399
- try {
400
- return routeGuard ? await routeGuard() : true;
401
- } catch {
402
- return false;
403
- }
404
- };
405
- if (requiresAuthorization) {
406
- const denial = await authorized();
407
- if (denial) return durableDenial(denial);
408
- }
409
- if (routeGuard && !(await routeAuthorized())) return durableDenial("refresh_failed");
410
- assertHostMcpCredentialScope(result, context);
411
- const normalized = normalizeHostCredentialResolution(result, requestedRef, credentialTarget);
412
- return normalized.status === "ok" && (requiresAuthorization || routeGuard)
413
- ? {
414
- ...normalized,
415
- authorizeProviderRequest: async () =>
416
- (await authorized()) === null && (await routeAuthorized()),
417
- }
418
- : normalized;
419
- };
420
- }
421
-
422
- /** Authenticated request-time adapter. No synthetic session/turn authority and
423
- * no durable binding admission; every physical use must retain live API proof. */
424
- export function buildHostGatewayConnectionTokenResolver(
425
- resolve: NonNullable<ConnectionCredentialsPort["mcpGatewayCredentials"]>,
426
- suppliedContext: Pick<McpGatewayCredentialsRequest, "accountId" | "workspaceId" | "authority">,
427
- reauthorize: () => Promise<void>,
428
- ): (input: ResolveConnectionCredentialInput) => Promise<ResolveConnectionCredentialResult> {
429
- const context = structuredClone(suppliedContext);
430
- return async (input) => {
431
- const ref = structuredClone(input.connectionRef);
432
- const deny = (reason: McpCredentialAuthNeededReason): ResolveConnectionCredentialResult => ({
433
- status: "auth_needed",
434
- reason,
435
- authoritySource: "host",
436
- providerDomain: ref.providerDomain,
437
- ...(ref.connectionId ? { connectionId: ref.connectionId } : {}),
438
- });
439
- if (input.workspaceId !== context.workspaceId)
440
- throw new HostMcpCredentialScopeError("workspaceId");
441
- if (ref.authoritySource !== "host" || ref.hostBinding) return deny("unsupported_auth");
442
- const destinationUrl = canonicalHttpUrl(input.destinationUrl);
443
- if (
444
- !destinationUrl ||
445
- (!destinationHostMatchesProvider(destinationUrl, ref.providerDomain) &&
446
- !isOfficialGmailRestDestination(destinationUrl, ref))
447
- )
448
- throw new HostMcpCredentialBindingError("destinationUrl");
449
- const request: McpGatewayCredentialsRequest = {
450
- ...structuredClone(context),
451
- surface: "workspace_gateway",
452
- requestId: crypto.randomUUID(),
453
- destinationUrl,
454
- credentialTarget: input.credentialTarget ?? "mcp",
455
- serverId: input.serverId,
456
- connectionRef: ref,
457
- forceRefresh: input.forceRefresh === true,
458
- ...((input.toolName ?? input.toolId) ? { toolName: input.toolName ?? input.toolId } : {}),
459
- };
460
- await reauthorize();
461
- const result = await resolve(structuredClone(request));
462
- await reauthorize();
463
- const routeGuard = result.status === "ok" ? result[hostMcpCredentialUseGuard] : undefined;
464
- if (routeGuard) {
465
- try {
466
- if (!(await routeGuard())) return deny("refresh_failed");
467
- } catch {
468
- return deny("refresh_failed");
469
- }
470
- }
471
- for (const field of ["accountId", "workspaceId", "requestId"] as const) {
472
- if (result[field] !== request[field])
473
- throw new Error(`host gateway credential ${field} scope mismatch`);
474
- }
475
- const normalized = normalizeHostCredentialResolution(
476
- result,
477
- ref,
478
- request.credentialTarget ?? "mcp",
479
- );
480
- if (normalized.status !== "ok") return normalized;
481
- return {
482
- ...normalized,
483
- authorizeProviderRequest: async () => {
484
- try {
485
- await reauthorize();
486
- return routeGuard ? await routeGuard() : true;
487
- } catch {
488
- return false;
489
- }
490
- },
491
- };
492
- };
493
- }
494
-
495
- function normalizeHostCredentialResolution(
496
- result: McpCredentialResolution | McpGatewayCredentialResolution,
497
- requestedRef: McpServerConnectionRef,
498
- credentialTarget: "mcp" | "http_api",
499
- ): ResolveConnectionCredentialResult {
500
- assertHostMcpCredentialBinding(result, requestedRef);
501
- if (result.status === "auth_needed") {
502
- const authorizationUrl = normalizedAuthorizationUrl(result.authorizationUrl);
503
- return {
504
- status: "auth_needed",
505
- reason: result.reason,
506
- providerDomain: result.providerDomain,
507
- authoritySource: "host",
508
- ...(result.provider ? { provider: result.provider } : {}),
509
- ...(result.connectionId ? { connectionId: result.connectionId } : {}),
510
- ...(result.scopes ? { scopes: [...result.scopes] } : {}),
511
- ...(result.resource ? { resource: result.resource } : {}),
512
- ...(result.selectedResources
513
- ? { selectedResources: copySelectedResources(result.selectedResources) }
514
- : {}),
515
- ...(authorizationUrl ? { authorizationUrl } : {}),
516
- };
517
- }
518
- if (result.connectionId.length === 0) {
519
- throw new Error("host MCP credential returned an empty connectionId");
520
- }
521
- const explicitPlacements =
522
- result.placements === undefined ? undefined : normalizedCredentialPlacements(result.placements);
523
- const headers = normalizedHostCredentialHeaders(result.headers, explicitPlacements !== undefined);
524
- if (explicitPlacements) {
525
- if (!sameHeaderMap(headers, headerMapForPlacements(explicitPlacements))) {
526
- throw new HostMcpCredentialBindingError("credentialPlacements");
527
- }
528
- if (
529
- credentialTarget !== "http_api" &&
530
- explicitPlacements.some((placement) => placement.carrier !== "header")
531
- ) {
532
- throw new HostMcpCredentialBindingError("credentialPlacements");
533
- }
534
- }
535
- const expiresAt = parseHostCredentialExpiry(result.expiresAt);
536
- return {
537
- status: "ok",
538
- headers,
539
- authoritySource: "host",
540
- ...(explicitPlacements ? { placements: explicitPlacements } : {}),
541
- connectionId: result.connectionId,
542
- ...(expiresAt !== undefined ? { expiresAt } : {}),
543
- };
544
- }
545
-
546
- function assertHostMcpCredentialBinding(
547
- result: McpCredentialResolution | McpGatewayCredentialResolution,
548
- requested: McpServerConnectionRef,
549
- ): void {
550
- if (result.providerDomain !== requested.providerDomain) {
551
- throw new HostMcpCredentialBindingError("providerDomain");
552
- }
553
- if (result.provider !== requested.provider) {
554
- throw new HostMcpCredentialBindingError("provider");
555
- }
556
- if (requested.connectionId && result.connectionId !== requested.connectionId) {
557
- throw new HostMcpCredentialBindingError("connectionId");
558
- }
559
- if (!sameSelectedResources(result.selectedResources, requested.selectedResources)) {
560
- throw new HostMcpCredentialBindingError("selectedResources");
561
- }
562
- if (result.status === "ok") {
563
- if (!sameStringSet(result.scopes, requested.scopes)) {
564
- throw new HostMcpCredentialBindingError("scopes");
565
- }
566
- if (result.resource !== requested.resource) {
567
- throw new HostMcpCredentialBindingError("resource");
568
- }
569
- }
570
- }
571
-
572
- function sameStringSet(left: string[] | undefined, right: string[] | undefined): boolean {
573
- if (left === undefined || right === undefined) return left === right;
574
- if (left.length !== right.length) return false;
575
- const sortedLeft = [...left].sort();
576
- const sortedRight = [...right].sort();
577
- return sortedLeft.every((value, index) => value === sortedRight[index]);
578
- }
579
-
580
- function sameSelectedResources(
581
- left: McpConnectionResourceScope[] | undefined,
582
- right: McpConnectionResourceScope[] | undefined,
583
- ): boolean {
584
- if (left === undefined || right === undefined) return left === right;
585
- const leftKeys = copySelectedResources(left)
586
- .map((resource) => `${resource.kind}\0${resource.id}`)
587
- .sort();
588
- const rightKeys = copySelectedResources(right)
589
- .map((resource) => `${resource.kind}\0${resource.id}`)
590
- .sort();
591
- return (
592
- leftKeys.length === rightKeys.length &&
593
- leftKeys.every((value, index) => value === rightKeys[index])
594
- );
595
- }
596
206
 
597
207
  function copySelectedResources(
598
208
  resources: McpConnectionResourceScope[],
599
209
  ): McpConnectionResourceScope[] {
600
210
  if (resources.length === 0 || resources.length > 256) {
601
- throw new Error("host MCP credential returned an invalid selected resource count");
211
+ throw new Error("connection credential returned an invalid selected resource count");
602
212
  }
603
213
  const seen = new Set<string>();
604
214
  return resources.map((resource) => {
@@ -608,35 +218,24 @@ function copySelectedResources(
608
218
  resource.id.length === 0 ||
609
219
  resource.id.length > 512
610
220
  ) {
611
- throw new Error("host MCP credential returned an invalid selected resource");
221
+ throw new Error("connection credential returned an invalid selected resource");
612
222
  }
613
223
  const key = `${resource.kind}\0${resource.id}`;
614
224
  if (seen.has(key)) {
615
- throw new Error("host MCP credential returned duplicate selected resources");
225
+ throw new Error("connection credential returned duplicate selected resources");
616
226
  }
617
227
  seen.add(key);
618
228
  return { kind: resource.kind, id: resource.id };
619
229
  });
620
230
  }
621
231
 
622
- function assertHostMcpCredentialScope(
623
- result: { accountId: string; workspaceId: string; sessionId: string },
624
- context: HostMcpCredentialResolverContext,
625
- ): void {
626
- for (const field of ["accountId", "workspaceId", "sessionId"] as const) {
627
- if (result[field] !== context[field]) {
628
- throw new HostMcpCredentialScopeError(field);
629
- }
630
- }
631
- }
632
-
633
- export function normalizedHostCredentialHeaders(
232
+ export function normalizedCredentialHeaders(
634
233
  headers: Record<string, string>,
635
234
  allowEmpty = false,
636
235
  ): Record<string, string> {
637
236
  const entries = Object.entries(headers);
638
237
  if ((!allowEmpty && entries.length === 0) || entries.length > 32) {
639
- throw new Error("host MCP credential returned an invalid header count");
238
+ throw new Error("connection credential returned an invalid header count");
640
239
  }
641
240
  const normalized: Record<string, string> = {};
642
241
  for (const [name, value] of entries) {
@@ -649,10 +248,10 @@ export function normalizedHostCredentialHeaders(
649
248
  FORBIDDEN_CREDENTIAL_HEADERS.has(name.toLowerCase()) ||
650
249
  name.toLowerCase().startsWith("sec-")
651
250
  ) {
652
- throw new Error("host MCP credential returned an invalid header");
251
+ throw new Error("connection credential returned an invalid header");
653
252
  }
654
253
  if (Object.keys(normalized).some((existing) => existing.toLowerCase() === name.toLowerCase())) {
655
- throw new Error("host MCP credential returned duplicate headers");
254
+ throw new Error("connection credential returned duplicate headers");
656
255
  }
657
256
  normalized[name] = value;
658
257
  }
@@ -734,56 +333,6 @@ function headerMapForPlacements(
734
333
  return headers;
735
334
  }
736
335
 
737
- function sameHeaderMap(
738
- left: Readonly<Record<string, string>>,
739
- right: Readonly<Record<string, string>>,
740
- ): boolean {
741
- const canonical = (headers: Readonly<Record<string, string>>) =>
742
- Object.entries(headers)
743
- .map(([name, value]) => [name.toLowerCase(), value] as const)
744
- .sort(([leftName], [rightName]) => leftName.localeCompare(rightName));
745
- const leftEntries = canonical(left);
746
- const rightEntries = canonical(right);
747
- return (
748
- leftEntries.length === rightEntries.length &&
749
- leftEntries.every(
750
- ([name, value], index) =>
751
- rightEntries[index]?.[0] === name && rightEntries[index]?.[1] === value,
752
- )
753
- );
754
- }
755
-
756
- function normalizedAuthorizationUrl(value: string | undefined): string | undefined {
757
- if (!value) return undefined;
758
- let url: URL;
759
- try {
760
- url = new URL(value);
761
- } catch {
762
- throw new Error("host MCP credential returned an invalid authorizationUrl");
763
- }
764
- if (url.protocol !== "https:" && !(url.protocol === "http:" && isLoopbackHost(url.hostname))) {
765
- throw new Error("host MCP credential returned an invalid authorizationUrl");
766
- }
767
- return url.toString();
768
- }
769
-
770
- function isLoopbackHost(hostname: string): boolean {
771
- if (hostname === "localhost" || hostname === "::1") return true;
772
- if (isIP(hostname) !== 4) return false;
773
- const [first] = hostname.split(".");
774
- return first === "127";
775
- }
776
-
777
- function parseHostCredentialExpiry(value: string | null | undefined): Date | null | undefined {
778
- if (value === undefined) return undefined;
779
- if (value === null) return null;
780
- const parsed = new Date(value);
781
- if (!Number.isFinite(parsed.getTime())) {
782
- throw new Error("host MCP credential returned an invalid expiresAt");
783
- }
784
- return parsed;
785
- }
786
-
787
336
  export type ConnectionBrokerDeps = {
788
337
  loadCredential: (
789
338
  db: Database,
@@ -805,13 +354,11 @@ export type ConnectionBrokerDeps = {
805
354
  subjectId?: string | null,
806
355
  ) => Promise<void>;
807
356
  refresh: typeof refreshOAuthConnectionCredential;
357
+ /** Test/adapter seam; standalone defaults to the database-backed refresh lock. */
358
+ withRefreshLock?: typeof withConnectionRefreshLock;
808
359
  encrypt: typeof encryptEnvironmentValue;
809
360
  keyBytes: typeof environmentsEncryptionKeyBytes;
810
361
  now: () => Date;
811
- authorizeUse?: (
812
- db: Database,
813
- input: { snapshot: unknown },
814
- ) => Promise<ConnectionUseAuthorizationResult>;
815
362
  authorizeAcceptedUse?: (
816
363
  db: Database,
817
364
  input: AcceptedConnectionUseContext & {
@@ -869,6 +416,7 @@ export function buildConnectionTokenResolver(
869
416
  > & { expectedAuthorityGeneration?: number };
870
417
  const load = async (
871
418
  input: CredentialLookupInput,
419
+ lookupDb: Database = db,
872
420
  ): Promise<ConnectionCredentialForBroker | null> => {
873
421
  const subjectOwned = input.connectionRef.subjectScope === "subject";
874
422
  if (subjectOwned && !input.subjectId) {
@@ -889,7 +437,7 @@ export function buildConnectionTokenResolver(
889
437
  if (input.connectionRef.kind !== undefined) {
890
438
  request.kind = input.connectionRef.kind;
891
439
  }
892
- const credential = await deps.loadCredential(db, settings, request);
440
+ const credential = await deps.loadCredential(lookupDb, settings, request);
893
441
  if (!credential) return null;
894
442
  if (
895
443
  input.expectedAuthorityGeneration !== undefined &&
@@ -968,6 +516,7 @@ export function buildConnectionTokenResolver(
968
516
  const performRefresh = async (
969
517
  cred: ConnectionCredentialForBroker,
970
518
  ref: McpServerConnectionRef,
519
+ lockedDb: Database,
971
520
  ): Promise<ConnectionCredentialForBroker> => {
972
521
  const key = deps.keyBytes(settings);
973
522
  if (!key) {
@@ -989,22 +538,28 @@ export function buildConnectionTokenResolver(
989
538
  if (refreshed.metadata !== undefined) {
990
539
  refreshRecord.metadata = refreshed.metadata;
991
540
  }
992
- const persisted = await deps.recordRefresh(db, refreshRecord);
541
+ const persisted = await deps.recordRefresh(lockedDb, refreshRecord);
993
542
  if (persisted) {
994
- const current = await load({
995
- workspaceId: cred.workspaceId,
996
- connectionRef: { ...ref, connectionId: cred.id },
997
- ...(cred.subjectId ? { subjectId: cred.subjectId } : {}),
998
- });
543
+ const current = await load(
544
+ {
545
+ workspaceId: cred.workspaceId,
546
+ connectionRef: { ...ref, connectionId: cred.id },
547
+ ...(cred.subjectId ? { subjectId: cred.subjectId } : {}),
548
+ },
549
+ lockedDb,
550
+ );
999
551
  if (current) {
1000
552
  return current;
1001
553
  }
1002
554
  }
1003
- const winner = await load({
1004
- workspaceId: cred.workspaceId,
1005
- connectionRef: { ...ref, connectionId: cred.id },
1006
- ...(cred.subjectId ? { subjectId: cred.subjectId } : {}),
1007
- });
555
+ const winner = await load(
556
+ {
557
+ workspaceId: cred.workspaceId,
558
+ connectionRef: { ...ref, connectionId: cred.id },
559
+ ...(cred.subjectId ? { subjectId: cred.subjectId } : {}),
560
+ },
561
+ lockedDb,
562
+ );
1008
563
  if (winner?.status === "active") {
1009
564
  return winner;
1010
565
  }
@@ -1020,7 +575,26 @@ export function buildConnectionTokenResolver(
1020
575
  if (existing) {
1021
576
  return existing;
1022
577
  }
1023
- const promise = performRefresh(cred, ref).finally(() => {
578
+ const promise = (deps.withRefreshLock ?? withConnectionRefreshLock)(
579
+ db,
580
+ cred,
581
+ async (lockedDb) => {
582
+ // A different worker may have rotated while this request waited. Never
583
+ // exchange the old token again, or switch to another authority generation.
584
+ const current = await load(
585
+ {
586
+ workspaceId: cred.workspaceId,
587
+ connectionRef: { ...ref, connectionId: cred.id },
588
+ ...(cred.subjectId ? { subjectId: cred.subjectId } : {}),
589
+ },
590
+ lockedDb,
591
+ );
592
+ if (!current || current.authorityGeneration !== cred.authorityGeneration)
593
+ throw new Error("Connection authority changed before token refresh");
594
+ if (current.status !== "active" || current.version !== cred.version) return current;
595
+ return performRefresh(current, ref, lockedDb);
596
+ },
597
+ ).finally(() => {
1024
598
  if (inflight.get(key) === promise) {
1025
599
  inflight.delete(key);
1026
600
  }
@@ -1095,54 +669,6 @@ export function buildConnectionTokenResolver(
1095
669
  kind: authorization.connectionKind,
1096
670
  subjectScope: expectedPersonal ? "subject" : "workspace",
1097
671
  };
1098
- } else if (input.connectionUseAuthority !== undefined) {
1099
- const authority = ConnectionUseAuthoritySnapshot.parse(input.connectionUseAuthority);
1100
- const expectedPersonal = authority.scope === "user";
1101
- if (
1102
- authority.targetWorkspaceId !== input.workspaceId ||
1103
- authority.providerDomain.toLowerCase() !== ref.providerDomain.toLowerCase() ||
1104
- (ref.connectionId !== undefined && ref.connectionId !== authority.connectionId) ||
1105
- (ref.kind !== undefined && ref.kind !== authority.connectionKind) ||
1106
- (ref.subjectScope === "subject") !== expectedPersonal ||
1107
- !deps.authorizeUse
1108
- ) {
1109
- return authNeeded(
1110
- ref,
1111
- expectedPersonal ? "personal_authority_unavailable" : "missing_connection",
1112
- ref.connectionId,
1113
- );
1114
- }
1115
- const authorization = await deps.authorizeUse(db, { snapshot: authority });
1116
- if (authorization.status === "denied") {
1117
- return authNeeded(
1118
- ref,
1119
- expectedPersonal ? "personal_authority_unavailable" : "missing_connection",
1120
- authority.connectionId,
1121
- );
1122
- }
1123
- if (
1124
- expectedAuthorityGeneration !== undefined &&
1125
- authority.connectionGeneration !== expectedAuthorityGeneration
1126
- ) {
1127
- return authNeeded(ref, authorityReasonForScope(expectedPersonal), authority.connectionId);
1128
- }
1129
- connectionUseAttribution = authorization.attribution;
1130
- expectedAuthorityGeneration = authority.connectionGeneration;
1131
- // Personal resources are organization-user owned and may originate in a
1132
- // different workspace from the session using them. Authorization is
1133
- // evaluated against the target workspace above; the exact credential is
1134
- // then loaded from its frozen physical origin, never rediscovered in the
1135
- // target workspace.
1136
- credentialWorkspaceId = expectedPersonal
1137
- ? authority.originWorkspaceId
1138
- : authority.targetWorkspaceId;
1139
- subjectId = authority.ownerSubjectId ?? undefined;
1140
- ref = {
1141
- ...ref,
1142
- connectionId: authority.connectionId,
1143
- kind: authority.connectionKind,
1144
- subjectScope: expectedPersonal ? "subject" : "workspace",
1145
- };
1146
672
  }
1147
673
  if (ref.subjectScope === "subject" && !subjectId) {
1148
674
  return authNeeded(ref, "personal_authority_unavailable", ref.connectionId);
@@ -1207,7 +733,9 @@ export function buildConnectionTokenResolver(
1207
733
  }
1208
734
  if (!handled) {
1209
735
  await deps
1210
- .setStatus(db, input.workspaceId, "needs_reauth", error.message, {
736
+ // A personal credential may originate outside the workspace
737
+ // consuming it. Apply the CAS to the same row we refreshed.
738
+ .setStatus(db, cred.workspaceId, "needs_reauth", error.message, {
1211
739
  id: cred.id,
1212
740
  version: cred.version,
1213
741
  subjectId: cred.subjectId,
@@ -1448,7 +976,7 @@ function credentialMaterialForConnection(
1448
976
  };
1449
977
  }
1450
978
  try {
1451
- const headers = normalizedHostCredentialHeaders(
979
+ const headers = normalizedCredentialHeaders(
1452
980
  stringRecord((cred.credential as { headers?: unknown }).headers) ?? {},
1453
981
  );
1454
982
  return { status: "ok", headers };