@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
@@ -14,6 +14,18 @@ import type {
14
14
  TurnInitiator,
15
15
  TurnInitiatorContext,
16
16
  } from "@opengeni/contracts";
17
+ import {
18
+ ConnectionUseAuthoritySnapshot,
19
+ type ConnectionUseAttribution,
20
+ type ConnectionUseAuthorizationResult,
21
+ } from "@opengeni/contracts/connection-authority";
22
+ import {
23
+ PERSONAL_GITHUB_REQUESTED_SCOPES,
24
+ PERSONAL_GITHUB_PROVIDER_DOMAIN,
25
+ PERSONAL_GITHUB_TOKEN_URL,
26
+ PersonalGitHubConnectionMetadata,
27
+ } from "@opengeni/contracts/personal-github";
28
+ import { createHash } from "node:crypto";
17
29
  import {
18
30
  OAUTH_MAX_RESPONSE_BYTES,
19
31
  pinnedFetch,
@@ -28,6 +40,11 @@ import { Buffer } from "node:buffer";
28
40
  import { isIP } from "node:net";
29
41
  import { encryptEnvironmentValue } from "./environment-crypto";
30
42
  import type { Database } from "./database";
43
+ import type {
44
+ AcceptedConnectionUseContext,
45
+ AcceptedConnectionUseResolution,
46
+ } from "./connection-authority";
47
+ import { connectionScopeKey } from "./connection-scopes";
31
48
 
32
49
  const MAX_CREDENTIAL_PLACEMENTS = 32;
33
50
  const MAX_CREDENTIAL_NAME_BYTES = 256;
@@ -60,6 +77,8 @@ export type ConnectionCredentialForBroker = {
60
77
  expiresAt: Date | null;
61
78
  lastRefreshAt: Date | null;
62
79
  version: number;
80
+ /** Immutable execution authority generation; distinct from token refresh version. */
81
+ authorityGeneration?: number;
63
82
  metadata: Record<string, unknown>;
64
83
  };
65
84
 
@@ -70,6 +89,7 @@ export type ConnectionCredentialLookupInput = {
70
89
  kind?: ConnectionKind;
71
90
  subjectId?: string | null;
72
91
  allowSubjectOwned?: boolean;
92
+ expectedAuthorityGeneration?: number;
73
93
  };
74
94
 
75
95
  export type ConnectionTokenRefreshInput = {
@@ -79,6 +99,7 @@ export type ConnectionTokenRefreshInput = {
79
99
  credentialEncrypted: string;
80
100
  expiresAt: Date | null;
81
101
  grantedScopes?: string[];
102
+ metadata?: Record<string, unknown>;
82
103
  lastRefreshAt: Date;
83
104
  subjectId?: string | null;
84
105
  };
@@ -98,6 +119,14 @@ export type ResolveConnectionCredentialResult =
98
119
  connectionId: string;
99
120
  /** Exact durable version when the credential came from the local connection store. */
100
121
  connectionVersion?: number;
122
+ /** Metadata-only owner attribution from the immediate pre-use fence. */
123
+ connectionUseAttribution?: ConnectionUseAttribution;
124
+ /**
125
+ * Revalidate the exact accepted attempt immediately before one target
126
+ * provider request. Credential lookup/refresh is a separate audited
127
+ * boundary and must not manufacture a provider-request fact by itself.
128
+ */
129
+ authorizeProviderRequest?: () => Promise<boolean>;
101
130
  expiresAt?: Date | null;
102
131
  }
103
132
  | {
@@ -129,6 +158,10 @@ export type ResolveConnectionCredentialInput = {
129
158
  /** Defaults to header-only MCP transport. */
130
159
  credentialTarget?: "mcp" | "http_api";
131
160
  forceRefresh?: boolean;
161
+ /** Exact immutable accepted-work authority; never credential-bearing. */
162
+ connectionUseAuthority?: unknown;
163
+ /** Exact accepted attempt plus one stable physical-provider request id. */
164
+ connectionUseContext?: AcceptedConnectionUseContext;
132
165
  };
133
166
 
134
167
  export type HostMcpCredentialResolverContext = {
@@ -142,7 +175,6 @@ export type HostMcpCredentialResolverContext = {
142
175
  initiator: TurnInitiator;
143
176
  initiatorContext: TurnInitiatorContext;
144
177
  surface: McpCredentialsRequest["surface"];
145
- allowOfficialGmailRestDestination?: boolean;
146
178
  };
147
179
 
148
180
  export class HostMcpCredentialScopeError extends Error {
@@ -173,10 +205,11 @@ const OFFICIAL_GMAIL_MCP_RESOURCE = "https://gmailmcp.googleapis.com/mcp/v1";
173
205
  const OFFICIAL_GMAIL_REST_HOST = "gmail.googleapis.com";
174
206
 
175
207
  /**
176
- * The opt-in Gmail REST adapter reuses the exact OAuth grant created for
177
- * Google's hosted Gmail MCP while the preview endpoint is unavailable. Keep
178
- * this exception narrower than ordinary provider-domain binding: HTTPS only,
179
- * Google's canonical Gmail API host, and the authenticated `users/me` path.
208
+ * The Gmail bridge reuses the exact OAuth grant created for the official
209
+ * hosted Gmail MCP resource, executing every tool against Gmail's REST API
210
+ * instead of Google's Developer Preview MCP endpoint. Keep this exception
211
+ * narrower than ordinary provider-domain binding: HTTPS only, Google's
212
+ * canonical Gmail API host, and the authenticated `users/me` path.
180
213
  */
181
214
  function isOfficialGmailRestDestination(
182
215
  destinationUrl: string,
@@ -216,10 +249,7 @@ export function buildHostConnectionTokenResolver(
216
249
  if (
217
250
  !destinationUrl ||
218
251
  (!destinationHostMatchesProvider(destinationUrl, input.connectionRef.providerDomain) &&
219
- !(
220
- context.allowOfficialGmailRestDestination === true &&
221
- isOfficialGmailRestDestination(destinationUrl, input.connectionRef)
222
- ))
252
+ !isOfficialGmailRestDestination(destinationUrl, input.connectionRef))
223
253
  ) {
224
254
  throw new HostMcpCredentialBindingError("destinationUrl");
225
255
  }
@@ -255,6 +285,12 @@ export function buildHostConnectionTokenResolver(
255
285
  : {}),
256
286
  },
257
287
  forceRefresh: input.forceRefresh === true,
288
+ ...(input.connectionUseAuthority !== undefined
289
+ ? { connectionUseAuthority: input.connectionUseAuthority }
290
+ : {}),
291
+ ...(input.connectionUseContext
292
+ ? { connectionUseRequestId: input.connectionUseContext.physicalRequestId }
293
+ : {}),
258
294
  ...(toolName ? { toolName } : {}),
259
295
  ...(input.subjectId ? { callerSubjectId: input.subjectId } : {}),
260
296
  };
@@ -575,6 +611,21 @@ export type ConnectionBrokerDeps = {
575
611
  encrypt: typeof encryptEnvironmentValue;
576
612
  keyBytes: typeof environmentsEncryptionKeyBytes;
577
613
  now: () => Date;
614
+ authorizeUse?: (
615
+ db: Database,
616
+ input: { snapshot: unknown },
617
+ ) => Promise<ConnectionUseAuthorizationResult>;
618
+ authorizeAcceptedUse?: (
619
+ db: Database,
620
+ input: AcceptedConnectionUseContext & {
621
+ serverId: string;
622
+ connectionId?: string;
623
+ providerDomain: string;
624
+ connectionKind?: ConnectionKind;
625
+ subjectScope?: "workspace" | "subject";
626
+ ownerSubjectId?: string;
627
+ },
628
+ ) => Promise<AcceptedConnectionUseResolution>;
578
629
  };
579
630
 
580
631
  export type PermanentConnectionRefreshFailure = {
@@ -618,7 +669,7 @@ export function buildConnectionTokenResolver(
618
669
  type CredentialLookupInput = Pick<
619
670
  ResolveConnectionCredentialInput,
620
671
  "workspaceId" | "connectionRef" | "subjectId"
621
- >;
672
+ > & { expectedAuthorityGeneration?: number };
622
673
  const load = async (
623
674
  input: CredentialLookupInput,
624
675
  ): Promise<ConnectionCredentialForBroker | null> => {
@@ -631,6 +682,9 @@ export function buildConnectionTokenResolver(
631
682
  providerDomain: input.connectionRef.providerDomain,
632
683
  allowSubjectOwned: subjectOwned,
633
684
  ...(subjectOwned ? { subjectId: input.subjectId! } : {}),
685
+ ...(input.expectedAuthorityGeneration !== undefined
686
+ ? { expectedAuthorityGeneration: input.expectedAuthorityGeneration }
687
+ : {}),
634
688
  };
635
689
  if (input.connectionRef.connectionId !== undefined) {
636
690
  request.connectionId = input.connectionRef.connectionId;
@@ -640,6 +694,12 @@ export function buildConnectionTokenResolver(
640
694
  }
641
695
  const credential = await deps.loadCredential(db, settings, request);
642
696
  if (!credential) return null;
697
+ if (
698
+ input.expectedAuthorityGeneration !== undefined &&
699
+ credential.authorityGeneration !== input.expectedAuthorityGeneration
700
+ ) {
701
+ return null;
702
+ }
643
703
  if (subjectOwned) {
644
704
  return credential.subjectId === input.subjectId ? credential : null;
645
705
  }
@@ -651,14 +711,19 @@ export function buildConnectionTokenResolver(
651
711
  ref: McpServerConnectionRef,
652
712
  destinationUrl: string,
653
713
  inputCredentialTarget: "mcp" | "http_api",
714
+ connectionUseAttribution?: ConnectionUseAttribution,
654
715
  ): Promise<ResolveConnectionCredentialResult> => {
655
716
  if (cred.status !== "active") {
656
717
  return authNeededForStatus(cred, ref);
657
718
  }
658
- if (!connectionBindingMatches(cred, ref, destinationUrl, settings.gmailRestAdapterEnabled)) {
719
+ if (!connectionBindingMatches(cred, ref, destinationUrl)) {
659
720
  return authNeeded(ref, "missing_connection", cred.id);
660
721
  }
661
- const missingScopes = missingRequestedScopes(ref.scopes, cred.grantedScopes);
722
+ const missingScopes = missingRequestedScopes(
723
+ ref.scopes,
724
+ cred.grantedScopes,
725
+ cred.providerDomain,
726
+ );
662
727
  if (missingScopes.length > 0) {
663
728
  return {
664
729
  status: "auth_needed",
@@ -695,6 +760,7 @@ export function buildConnectionTokenResolver(
695
760
  ...(material.placements ? { placements: material.placements } : {}),
696
761
  connectionId: cred.id,
697
762
  connectionVersion: cred.version,
763
+ ...(connectionUseAttribution ? { connectionUseAttribution } : {}),
698
764
  expiresAt: cred.expiresAt,
699
765
  };
700
766
  };
@@ -720,6 +786,9 @@ export function buildConnectionTokenResolver(
720
786
  if (refreshed.grantedScopes !== undefined) {
721
787
  refreshRecord.grantedScopes = refreshed.grantedScopes;
722
788
  }
789
+ if (refreshed.metadata !== undefined) {
790
+ refreshRecord.metadata = refreshed.metadata;
791
+ }
723
792
  const persisted = await deps.recordRefresh(db, refreshRecord);
724
793
  if (persisted) {
725
794
  const current = await load({
@@ -761,10 +830,11 @@ export function buildConnectionTokenResolver(
761
830
  };
762
831
 
763
832
  return async (input) => {
764
- const ref = input.connectionRef;
765
- if (ref.subjectScope === "subject" && !input.subjectId) {
766
- return authNeeded(ref, "personal_authority_unavailable", ref.connectionId);
767
- }
833
+ let ref = input.connectionRef;
834
+ let subjectId = input.subjectId;
835
+ let credentialWorkspaceId = input.workspaceId;
836
+ let expectedAuthorityGeneration: number | undefined;
837
+ let connectionUseAttribution: ConnectionUseAttribution | undefined;
768
838
  // Repository-scoped provider bindings require a broker that can prove the
769
839
  // selected-resource boundary. The generic standalone credential store has
770
840
  // no provider-specific containment adapter, so it must fail closed instead
@@ -772,9 +842,99 @@ export function buildConnectionTokenResolver(
772
842
  if (ref.selectedResources) {
773
843
  return authNeeded(ref, "resource_scope_unavailable", ref.connectionId);
774
844
  }
845
+ if (input.connectionUseContext !== undefined) {
846
+ if (!deps.authorizeAcceptedUse) {
847
+ return authNeeded(
848
+ ref,
849
+ ref.subjectScope === "subject" ? "personal_authority_unavailable" : "missing_connection",
850
+ ref.connectionId,
851
+ );
852
+ }
853
+ const authorization = await deps.authorizeAcceptedUse(db, {
854
+ ...input.connectionUseContext,
855
+ serverId: input.serverId,
856
+ ...(ref.connectionId ? { connectionId: ref.connectionId } : {}),
857
+ providerDomain: ref.providerDomain,
858
+ ...(ref.kind ? { connectionKind: ref.kind } : {}),
859
+ subjectScope: ref.subjectScope === "subject" ? "subject" : "workspace",
860
+ // An owner binding belongs only to the personal lanes. Interactive
861
+ // turns stamp the initiating human's subjectId on every credential
862
+ // request regardless of ref scope; forwarding it for a workspace ref
863
+ // would make the 0279 workspace lane deny the ambient shared row.
864
+ ...(ref.subjectScope === "subject" && subjectId ? { ownerSubjectId: subjectId } : {}),
865
+ });
866
+ if (authorization.status === "denied") {
867
+ return authNeeded(
868
+ ref,
869
+ ref.subjectScope === "subject" ? "personal_authority_unavailable" : "missing_connection",
870
+ ref.connectionId,
871
+ );
872
+ }
873
+ connectionUseAttribution = authorization.attribution;
874
+ expectedAuthorityGeneration = authorization.attribution.connectionGeneration;
875
+ const expectedPersonal = authorization.attribution.scope !== "workspace";
876
+ credentialWorkspaceId = authorization.originWorkspaceId;
877
+ subjectId = authorization.attribution.ownerSubjectId ?? undefined;
878
+ ref = {
879
+ ...ref,
880
+ connectionId: authorization.attribution.connectionId,
881
+ kind: authorization.connectionKind,
882
+ subjectScope: expectedPersonal ? "subject" : "workspace",
883
+ };
884
+ } else if (input.connectionUseAuthority !== undefined) {
885
+ const authority = ConnectionUseAuthoritySnapshot.parse(input.connectionUseAuthority);
886
+ const expectedPersonal = authority.scope === "user";
887
+ if (
888
+ authority.targetWorkspaceId !== input.workspaceId ||
889
+ authority.providerDomain.toLowerCase() !== ref.providerDomain.toLowerCase() ||
890
+ (ref.connectionId !== undefined && ref.connectionId !== authority.connectionId) ||
891
+ (ref.kind !== undefined && ref.kind !== authority.connectionKind) ||
892
+ (ref.subjectScope === "subject") !== expectedPersonal ||
893
+ !deps.authorizeUse
894
+ ) {
895
+ return authNeeded(
896
+ ref,
897
+ expectedPersonal ? "personal_authority_unavailable" : "missing_connection",
898
+ ref.connectionId,
899
+ );
900
+ }
901
+ const authorization = await deps.authorizeUse(db, { snapshot: authority });
902
+ if (authorization.status === "denied") {
903
+ return authNeeded(
904
+ ref,
905
+ expectedPersonal ? "personal_authority_unavailable" : "missing_connection",
906
+ authority.connectionId,
907
+ );
908
+ }
909
+ connectionUseAttribution = authorization.attribution;
910
+ expectedAuthorityGeneration = authority.connectionGeneration;
911
+ // Personal resources are organization-user owned and may originate in a
912
+ // different workspace from the session using them. Authorization is
913
+ // evaluated against the target workspace above; the exact credential is
914
+ // then loaded from its frozen physical origin, never rediscovered in the
915
+ // target workspace.
916
+ credentialWorkspaceId = expectedPersonal
917
+ ? authority.originWorkspaceId
918
+ : authority.targetWorkspaceId;
919
+ subjectId = authority.ownerSubjectId ?? undefined;
920
+ ref = {
921
+ ...ref,
922
+ connectionId: authority.connectionId,
923
+ kind: authority.connectionKind,
924
+ subjectScope: expectedPersonal ? "subject" : "workspace",
925
+ };
926
+ }
927
+ if (ref.subjectScope === "subject" && !subjectId) {
928
+ return authNeeded(ref, "personal_authority_unavailable", ref.connectionId);
929
+ }
775
930
  let cred: ConnectionCredentialForBroker | null;
776
931
  try {
777
- cred = await load(input);
932
+ cred = await load({
933
+ workspaceId: credentialWorkspaceId,
934
+ connectionRef: ref,
935
+ ...(subjectId ? { subjectId } : {}),
936
+ ...(expectedAuthorityGeneration !== undefined ? { expectedAuthorityGeneration } : {}),
937
+ });
778
938
  } catch {
779
939
  return authNeeded(ref, "refresh_failed");
780
940
  }
@@ -787,9 +947,7 @@ export function buildConnectionTokenResolver(
787
947
  // Reject an audience/destination mismatch before any provider-side refresh
788
948
  // or usage update. Refreshing first would still create an unauthorized
789
949
  // external side effect even though the token was never sent to the target.
790
- if (
791
- !connectionBindingMatches(cred, ref, input.destinationUrl, settings.gmailRestAdapterEnabled)
792
- ) {
950
+ if (!connectionBindingMatches(cred, ref, input.destinationUrl)) {
793
951
  return authNeeded(ref, "missing_connection", cred.id);
794
952
  }
795
953
  if (shouldRefresh(cred, input.forceRefresh === true, deps.now())) {
@@ -828,15 +986,30 @@ export function buildConnectionTokenResolver(
828
986
  return authNeeded(ref, "refresh_failed", cred.id);
829
987
  }
830
988
  }
831
- return await snapshot(cred, ref, input.destinationUrl, input.credentialTarget ?? "mcp");
989
+ if (
990
+ expectedAuthorityGeneration !== undefined &&
991
+ cred.authorityGeneration !== expectedAuthorityGeneration
992
+ ) {
993
+ return authNeeded(ref, authorityReasonForScope(ref.subjectScope === "subject"), cred.id);
994
+ }
995
+ return await snapshot(
996
+ cred,
997
+ ref,
998
+ input.destinationUrl,
999
+ input.credentialTarget ?? "mcp",
1000
+ connectionUseAttribution,
1001
+ );
832
1002
  };
833
1003
  }
834
1004
 
1005
+ function authorityReasonForScope(personal: boolean): AuthNeededReason {
1006
+ return personal ? "personal_authority_unavailable" : "missing_connection";
1007
+ }
1008
+
835
1009
  function connectionBindingMatches(
836
1010
  cred: ConnectionCredentialForBroker,
837
1011
  ref: McpServerConnectionRef,
838
1012
  destinationUrl: string,
839
- gmailRestAdapterEnabled: boolean,
840
1013
  ): boolean {
841
1014
  if (cred.providerDomain.toLowerCase() !== ref.providerDomain.toLowerCase()) return false;
842
1015
  if (ref.kind && cred.kind !== ref.kind) return false;
@@ -852,7 +1025,6 @@ function connectionBindingMatches(
852
1025
  !binding ||
853
1026
  (destination !== binding &&
854
1027
  !(
855
- gmailRestAdapterEnabled &&
856
1028
  binding === canonicalHttpUrl(OFFICIAL_GMAIL_MCP_RESOURCE) &&
857
1029
  isOfficialGmailRestDestination(destination, ref)
858
1030
  ))
@@ -979,12 +1151,16 @@ function authNeededForStatus(
979
1151
  );
980
1152
  }
981
1153
 
982
- function missingRequestedScopes(requested: string[] | undefined, granted: string[]): string[] {
1154
+ function missingRequestedScopes(
1155
+ requested: string[] | undefined,
1156
+ granted: string[],
1157
+ providerDomain: string,
1158
+ ): string[] {
983
1159
  if (!requested?.length) {
984
1160
  return [];
985
1161
  }
986
- const grantedSet = new Set(granted);
987
- return requested.filter((scope) => !grantedSet.has(scope));
1162
+ const grantedSet = new Set(granted.map((scope) => connectionScopeKey(providerDomain, scope)));
1163
+ return requested.filter((scope) => !grantedSet.has(connectionScopeKey(providerDomain, scope)));
988
1164
  }
989
1165
 
990
1166
  /**
@@ -1057,6 +1233,7 @@ export async function refreshOAuthConnectionCredential(
1057
1233
  credential: Record<string, unknown>;
1058
1234
  expiresAt: Date | null;
1059
1235
  grantedScopes?: string[];
1236
+ metadata?: Record<string, unknown>;
1060
1237
  }> {
1061
1238
  if (cred.kind !== "oauth2") {
1062
1239
  return {
@@ -1066,13 +1243,21 @@ export async function refreshOAuthConnectionCredential(
1066
1243
  };
1067
1244
  }
1068
1245
  const refreshToken = stringValue((cred.credential as { refresh_token?: unknown }).refresh_token);
1069
- const tokenEndpoint =
1246
+ const personalGitHubMetadata = PersonalGitHubConnectionMetadata.safeParse(cred.metadata);
1247
+ const personalGitHub =
1248
+ cred.providerDomain.toLowerCase() === PERSONAL_GITHUB_PROVIDER_DOMAIN &&
1249
+ personalGitHubMetadata.success;
1250
+ const storedTokenEndpoint =
1070
1251
  stringValue((cred.credential as { token_endpoint?: unknown }).token_endpoint) ??
1071
1252
  stringValue((cred.metadata as { tokenEndpoint?: unknown }).tokenEndpoint) ??
1072
1253
  stringValue((cred.metadata as { token_endpoint?: unknown }).token_endpoint);
1254
+ const tokenEndpoint = personalGitHub ? PERSONAL_GITHUB_TOKEN_URL : storedTokenEndpoint;
1073
1255
  if (!refreshToken || !tokenEndpoint) {
1074
1256
  throw new Error("connection has no refresh token endpoint");
1075
1257
  }
1258
+ if (personalGitHub && storedTokenEndpoint !== PERSONAL_GITHUB_TOKEN_URL) {
1259
+ throw new ConnectionRefreshHttpError(401, "client_unavailable");
1260
+ }
1076
1261
  let validatedTokenEndpoint: string;
1077
1262
  try {
1078
1263
  validatedTokenEndpoint = validateHttpUrl(tokenEndpoint, {
@@ -1092,11 +1277,25 @@ export async function refreshOAuthConnectionCredential(
1092
1277
  stringValue((cred.credential as { client_id?: unknown }).client_id) ??
1093
1278
  stringValue((cred.metadata as { clientId?: unknown }).clientId) ??
1094
1279
  stringValue((cred.metadata as { client_id?: unknown }).client_id);
1095
- const clientSecret = stringValue((cred.credential as { client_secret?: unknown }).client_secret);
1280
+ const clientSecret = personalGitHub
1281
+ ? settings.githubPersonalOauthClientSecret
1282
+ : stringValue((cred.credential as { client_secret?: unknown }).client_secret);
1096
1283
  const authMethod =
1097
1284
  stringValue(
1098
1285
  (cred.credential as { token_endpoint_auth_method?: unknown }).token_endpoint_auth_method,
1099
1286
  ) ?? "none";
1287
+ if (
1288
+ personalGitHub &&
1289
+ (!settings.githubPersonalOauthEnabled ||
1290
+ !clientId ||
1291
+ clientId !== settings.githubPersonalOauthClientId ||
1292
+ personalGitHubMetadata.data.oauthEnvironment !== settings.environment ||
1293
+ personalGitHubMetadata.data.oauthClientMarker !==
1294
+ createHash("sha256").update(clientId).digest("hex").slice(0, 32) ||
1295
+ !clientSecret)
1296
+ ) {
1297
+ throw new ConnectionRefreshHttpError(401, "client_unavailable");
1298
+ }
1100
1299
  if (clientId) {
1101
1300
  body.set("client_id", clientId);
1102
1301
  }
@@ -1123,7 +1322,10 @@ export async function refreshOAuthConnectionCredential(
1123
1322
  if (resource && resourceParameterSupported) {
1124
1323
  body.set("resource", resource);
1125
1324
  }
1126
- if (ref.scopes?.length) {
1325
+ const scopeParameterSupported =
1326
+ (cred.credential as { scope_parameter_supported?: unknown }).scope_parameter_supported !==
1327
+ false;
1328
+ if (ref.scopes?.length && scopeParameterSupported) {
1127
1329
  body.set("scope", ref.scopes.join(" "));
1128
1330
  }
1129
1331
  const requestBody: BodyInit =
@@ -1165,6 +1367,23 @@ export async function refreshOAuthConnectionCredential(
1165
1367
  }
1166
1368
  const expiresAt = expiresAtFromTokenResponse(payload, cred.expiresAt);
1167
1369
  const scopeText = stringValue(payload.scope);
1370
+ const returnedScopes = scopeText?.split(/[\s,]+/).filter(Boolean) ?? [];
1371
+ const refreshTokenExpiresIn =
1372
+ typeof payload.refresh_token_expires_in === "number"
1373
+ ? payload.refresh_token_expires_in
1374
+ : undefined;
1375
+ const refreshTokenExpiresAt =
1376
+ personalGitHub && refreshTokenExpiresIn && refreshTokenExpiresIn > 0
1377
+ ? new Date(Date.now() + refreshTokenExpiresIn * 1000).toISOString()
1378
+ : null;
1379
+ if (
1380
+ personalGitHub &&
1381
+ scopeText &&
1382
+ (returnedScopes.length !== PERSONAL_GITHUB_REQUESTED_SCOPES.length ||
1383
+ returnedScopes.some((scope) => !PERSONAL_GITHUB_REQUESTED_SCOPES.includes(scope as "repo")))
1384
+ ) {
1385
+ throw new ConnectionRefreshHttpError(401, "scope_changed");
1386
+ }
1168
1387
  const nextCredential = {
1169
1388
  ...cred.credential,
1170
1389
  access_token: accessToken,
@@ -1176,14 +1395,27 @@ export async function refreshOAuthConnectionCredential(
1176
1395
  ...(expiresAt ? { expires_at: expiresAt.toISOString() } : {}),
1177
1396
  ...(resource ? { resource } : {}),
1178
1397
  ...(scopeText ? { scope: scopeText } : {}),
1179
- ...(clientSecret
1398
+ ...(refreshTokenExpiresAt ? { refresh_token_expires_at: refreshTokenExpiresAt } : {}),
1399
+ ...(clientSecret && !personalGitHub
1180
1400
  ? { client_secret: clientSecret, token_endpoint_auth_method: authMethod }
1181
1401
  : {}),
1182
1402
  };
1183
1403
  return {
1184
1404
  credential: nextCredential,
1185
1405
  expiresAt,
1186
- ...(scopeText ? { grantedScopes: scopeText.split(/\s+/).filter(Boolean) } : {}),
1406
+ ...(personalGitHub
1407
+ ? { grantedScopes: [...PERSONAL_GITHUB_REQUESTED_SCOPES] }
1408
+ : scopeText
1409
+ ? { grantedScopes: returnedScopes }
1410
+ : {}),
1411
+ ...(refreshTokenExpiresAt
1412
+ ? {
1413
+ metadata: PersonalGitHubConnectionMetadata.parse({
1414
+ ...personalGitHubMetadata.data,
1415
+ refreshTokenExpiresAt,
1416
+ }),
1417
+ }
1418
+ : {}),
1187
1419
  };
1188
1420
  }
1189
1421