@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
@@ -1,777 +0,0 @@
1
- import { createHash, randomUUID } from "node:crypto";
2
- import { sql } from "drizzle-orm";
3
- import { stableJson, type McpCredentialsRequest } from "@opengeni/contracts";
4
- import { getHostMcpLiveAttempt } from "./live-session-attempt";
5
- import { hostMcpBindingMatchesRequest } from "./host-mcp-binding-match";
6
- import {
7
- HostMcpBinding,
8
- CreateHostMcpBindingRequest,
9
- HostMcpDelegation,
10
- IssueHostMcpDelegationRequest,
11
- HostMcpAcceptedAuthority,
12
- HostMcpOwnerSubject,
13
- hostMcpBindingMatchesSelection,
14
- } from "@opengeni/contracts/host-mcp-bindings";
15
- import { rawRows, withWorkspaceSubjectRls, type Database } from "./database";
16
- import { listSelfOrganizationMemberships } from "./organization-membership-lifecycle";
17
- import { lockExternalWorkspaceMembershipLifecycle } from "./external-identities";
18
- import { subjectHasLiveWorkspaceAuthorityInScope } from "./workspace-authority";
19
-
20
- export type HostMcpBindingOwner = {
21
- accountId: string;
22
- workspaceId: string;
23
- subjectId: string;
24
- authorizationRevision: number;
25
- };
26
-
27
- /** Resolve the effective member's revision, not the authenticating external
28
- * identity revision. Caller must separately prove its authority to act as them. */
29
- export async function resolveHostMcpBindingOwner(
30
- db: Database,
31
- input: Omit<HostMcpBindingOwner, "authorizationRevision">,
32
- ): Promise<HostMcpBindingOwner> {
33
- if (!HostMcpOwnerSubject.safeParse(input.subjectId).success)
34
- throw new HostMcpDelegationAuthorityError("Host owner unavailable");
35
- return withWorkspaceSubjectRls(db, input.workspaceId, input.subjectId, async (tx) => {
36
- await lockExternalWorkspaceMembershipLifecycle(tx, input.accountId);
37
- const membership = (await listSelfOrganizationMemberships(tx, input.subjectId)).find(
38
- (value) => value.organizationId === input.accountId,
39
- );
40
- if (
41
- !membership ||
42
- membership.status !== "active" ||
43
- !(await subjectHasLiveWorkspaceAuthorityInScope(tx, input))
44
- )
45
- throw new HostMcpDelegationAuthorityError("Host owner unavailable");
46
- return { ...input, authorizationRevision: membership.authorizationRevision };
47
- });
48
- }
49
- export class HostMcpBindingConflictError extends Error {
50
- constructor() {
51
- super("Host binding changed or operation conflicts");
52
- this.name = "HostMcpBindingConflictError";
53
- }
54
- }
55
- export class HostMcpDelegationAuthorityError extends Error {}
56
- type Row = {
57
- id: string;
58
- account_id: string;
59
- workspace_id: string;
60
- owner_subject_id: string;
61
- authorization_revision: number | string;
62
- generation: number | string;
63
- status: string;
64
- definition: unknown;
65
- created_at: Date | string;
66
- revoked_at: Date | string | null;
67
- request_digest: string;
68
- };
69
- const timestamp = (value: Date | string) => new Date(value).toISOString();
70
- function project(row: Row): HostMcpBinding {
71
- return HostMcpBinding.parse({
72
- id: row.id,
73
- accountId: row.account_id,
74
- workspaceId: row.workspace_id,
75
- ownerSubjectId: row.owner_subject_id,
76
- authorizationRevision: Number(row.authorization_revision),
77
- generation: Number(row.generation),
78
- status: row.status,
79
- definition: row.definition,
80
- createdAt: timestamp(row.created_at),
81
- revokedAt: row.revoked_at === null ? null : timestamp(row.revoked_at),
82
- });
83
- }
84
-
85
- async function scoped<T>(
86
- db: Database,
87
- owner: HostMcpBindingOwner,
88
- operation: (tx: Database) => Promise<T>,
89
- ): Promise<T> {
90
- return withWorkspaceSubjectRls(db, owner.workspaceId, owner.subjectId, async (tx) => {
91
- await lockExternalWorkspaceMembershipLifecycle(tx, owner.accountId);
92
- const memberships = await listSelfOrganizationMemberships(tx, owner.subjectId);
93
- const membership = memberships.find((item) => item.organizationId === owner.accountId);
94
- if (
95
- !HostMcpOwnerSubject.safeParse(owner.subjectId).success ||
96
- !membership ||
97
- membership.status !== "active" ||
98
- membership.authorizationRevision !== owner.authorizationRevision
99
- )
100
- throw new Error("Host binding owner authority unavailable");
101
- return operation(tx);
102
- });
103
- }
104
-
105
- /** Low-level persistence, not HTTP authentication. Callers must establish the
106
- * exact external owner and workspace grant before entering this scoped seam. */
107
- export async function createHostMcpBinding(
108
- db: Database,
109
- owner: HostMcpBindingOwner,
110
- raw: unknown,
111
- ): Promise<HostMcpBinding> {
112
- const input = CreateHostMcpBindingRequest.parse(raw);
113
- const digest = createHash("sha256").update(stableJson(input.definition)).digest("hex");
114
- return scoped(db, owner, async (tx) => {
115
- await tx.execute(sql`insert into host_mcp_bindings (id, account_id, workspace_id, owner_subject_id, authorization_revision, operation_id, request_digest, definition)
116
- values (${randomUUID()}::uuid, ${owner.accountId}::uuid, ${owner.workspaceId}::uuid, ${owner.subjectId}, ${owner.authorizationRevision}, ${input.operationId}::uuid, ${digest}, ${JSON.stringify(input.definition)}::jsonb)
117
- on conflict (workspace_id, owner_subject_id, operation_id) do nothing`);
118
- const [row] = await rawRows<Row>(
119
- tx,
120
- sql`select * from host_mcp_bindings where workspace_id = ${owner.workspaceId}::uuid and owner_subject_id = ${owner.subjectId} and operation_id = ${input.operationId}::uuid`,
121
- );
122
- if (!row || row.request_digest !== digest) throw new HostMcpBindingConflictError();
123
- return project(row);
124
- });
125
- }
126
-
127
- export async function getHostMcpBinding(
128
- db: Database,
129
- owner: HostMcpBindingOwner,
130
- id: string,
131
- ): Promise<HostMcpBinding | null> {
132
- return scoped(db, owner, async (tx) => {
133
- const [row] = await rawRows<Row>(
134
- tx,
135
- sql`select * from host_mcp_bindings where id = ${id}::uuid and account_id = ${owner.accountId}::uuid and workspace_id = ${owner.workspaceId}::uuid and owner_subject_id = ${owner.subjectId}`,
136
- );
137
- return row ? project(row) : null;
138
- });
139
- }
140
-
141
- export async function revokeHostMcpBinding(
142
- db: Database,
143
- owner: HostMcpBindingOwner,
144
- id: string,
145
- expectedGeneration: number,
146
- ): Promise<HostMcpBinding> {
147
- if (!Number.isSafeInteger(expectedGeneration) || expectedGeneration < 1)
148
- throw new Error("Invalid binding generation");
149
- return scoped(db, owner, async (tx) => {
150
- const [row] = await rawRows<Row>(
151
- tx,
152
- sql`select * from host_mcp_bindings where id = ${id}::uuid and account_id = ${owner.accountId}::uuid and workspace_id = ${owner.workspaceId}::uuid and owner_subject_id = ${owner.subjectId} for update`,
153
- );
154
- if (!row) throw new Error("Host binding unavailable");
155
- const current = project(row);
156
- if (current.status === "revoked" && current.generation === expectedGeneration + 1)
157
- return current;
158
- if (current.status !== "active" || current.generation !== expectedGeneration)
159
- throw new HostMcpBindingConflictError();
160
- const [updated] = await rawRows<Row>(
161
- tx,
162
- sql`update host_mcp_bindings set status = 'revoked', generation = generation + 1, revoked_at = clock_timestamp() where id = ${id}::uuid returning *`,
163
- );
164
- if (!updated) throw new Error("Host binding unavailable");
165
- return project(updated);
166
- });
167
- }
168
-
169
- type DelegationRow = {
170
- id: string;
171
- account_id: string;
172
- workspace_id: string;
173
- owner_subject_id: string;
174
- owner_authorization_revision: number | string;
175
- binding_id: string;
176
- binding_generation: number | string;
177
- grant_definition: unknown;
178
- generation: number | string;
179
- status: string;
180
- created_at: Date | string;
181
- revoked_at: Date | string | null;
182
- request_digest: string;
183
- };
184
- function projectDelegation(row: DelegationRow): HostMcpDelegation {
185
- return HostMcpDelegation.parse({
186
- id: row.id,
187
- accountId: row.account_id,
188
- workspaceId: row.workspace_id,
189
- ownerSubjectId: row.owner_subject_id,
190
- ownerAuthorizationRevision: Number(row.owner_authorization_revision),
191
- bindingId: row.binding_id,
192
- bindingGeneration: Number(row.binding_generation),
193
- grant: row.grant_definition,
194
- generation: Number(row.generation),
195
- status: row.status,
196
- createdAt: timestamp(row.created_at),
197
- revokedAt: row.revoked_at === null ? null : timestamp(row.revoked_at),
198
- });
199
- }
200
-
201
- /** Internal persistence only. Caller must first verify owner identity and grant
202
- * issuance permission. No worker consumes these rows without accepted-work capture. */
203
- export async function issueHostMcpDelegation(
204
- db: Database,
205
- owner: HostMcpBindingOwner,
206
- raw: unknown,
207
- ): Promise<HostMcpDelegation> {
208
- const input = IssueHostMcpDelegationRequest.parse(raw);
209
- const digest = createHash("sha256").update(stableJson(input)).digest("hex");
210
- return scoped(db, owner, async (tx) => {
211
- if (!(await subjectHasLiveWorkspaceAuthorityInScope(tx, owner)))
212
- throw new HostMcpDelegationAuthorityError("Host delegation workspace unavailable");
213
- // Serialize operation replays without re-running INSERT guards on a revoked binding.
214
- await tx.execute(
215
- sql`select pg_advisory_xact_lock(hashtextextended(${`host-delegation:${owner.workspaceId}:${owner.subjectId}:${input.operationId}`}, 0))`,
216
- );
217
- const [prior] = await rawRows<DelegationRow>(
218
- tx,
219
- sql`select * from host_mcp_delegations where workspace_id = ${owner.workspaceId}::uuid and owner_subject_id = ${owner.subjectId} and operation_id = ${input.operationId}::uuid`,
220
- );
221
- if (prior) {
222
- if (prior.request_digest !== digest) throw new HostMcpBindingConflictError();
223
- return projectDelegation(prior);
224
- }
225
- const [binding] = await rawRows<Row>(
226
- tx,
227
- sql`select * from host_mcp_bindings where id = ${input.bindingId}::uuid and account_id = ${owner.accountId}::uuid and workspace_id = ${owner.workspaceId}::uuid and owner_subject_id = ${owner.subjectId} for share`,
228
- );
229
- if (
230
- !binding ||
231
- binding.status !== "active" ||
232
- Number(binding.generation) !== input.expectedBindingGeneration ||
233
- Number(binding.authorization_revision) !== owner.authorizationRevision
234
- )
235
- throw new HostMcpBindingConflictError();
236
- if (input.grant.sessionId) {
237
- const [session] = await rawRows<{
238
- visibility: string;
239
- authority_epoch: number;
240
- owner_subject_id: string | null;
241
- }>(
242
- tx,
243
- sql`select visibility, authority_epoch, owner_subject_id from sessions where id = ${input.grant.sessionId}::uuid and workspace_id = ${owner.workspaceId}::uuid and account_id = ${owner.accountId}::uuid for share`,
244
- );
245
- if (
246
- !session ||
247
- session.visibility !== input.grant.context ||
248
- session.authority_epoch !== input.grant.expectedAuthorityEpoch ||
249
- (session.visibility === "user_private" && session.owner_subject_id !== owner.subjectId)
250
- )
251
- throw new HostMcpDelegationAuthorityError("Host delegation session authority unavailable");
252
- } else if (input.grant.expectedAuthorityEpoch != null)
253
- throw new HostMcpDelegationAuthorityError("Host delegation epoch requires a session");
254
- const [row] = await rawRows<DelegationRow>(
255
- tx,
256
- sql`insert into host_mcp_delegations (id, account_id, workspace_id, owner_subject_id, owner_authorization_revision, binding_id, binding_generation, operation_id, request_digest, grant_definition)
257
- values (${randomUUID()}::uuid, ${owner.accountId}::uuid, ${owner.workspaceId}::uuid, ${owner.subjectId}, ${owner.authorizationRevision}, ${input.bindingId}::uuid, ${input.expectedBindingGeneration}, ${input.operationId}::uuid, ${digest}, ${JSON.stringify(input.grant)}::jsonb) returning *`,
258
- );
259
- if (!row) throw new Error("Host delegation unavailable");
260
- return projectDelegation(row);
261
- });
262
- }
263
-
264
- export async function getHostMcpDelegation(
265
- db: Database,
266
- owner: HostMcpBindingOwner,
267
- id: string,
268
- ): Promise<HostMcpDelegation | null> {
269
- return scoped(db, owner, async (tx) => {
270
- const [row] = await rawRows<DelegationRow>(
271
- tx,
272
- sql`select * from host_mcp_delegations where id = ${id}::uuid and account_id = ${owner.accountId}::uuid and workspace_id = ${owner.workspaceId}::uuid and owner_subject_id = ${owner.subjectId}`,
273
- );
274
- return row ? projectDelegation(row) : null;
275
- });
276
- }
277
-
278
- export async function revokeHostMcpDelegation(
279
- db: Database,
280
- owner: HostMcpBindingOwner,
281
- id: string,
282
- expectedGeneration: number,
283
- ): Promise<HostMcpDelegation> {
284
- if (!Number.isSafeInteger(expectedGeneration) || expectedGeneration < 1)
285
- throw new Error("Invalid delegation generation");
286
- return scoped(db, owner, async (tx) => {
287
- const [row] = await rawRows<DelegationRow>(
288
- tx,
289
- sql`select * from host_mcp_delegations where id = ${id}::uuid and account_id = ${owner.accountId}::uuid and workspace_id = ${owner.workspaceId}::uuid and owner_subject_id = ${owner.subjectId} for update`,
290
- );
291
- if (!row) throw new Error("Host delegation unavailable");
292
- const current = projectDelegation(row);
293
- if (current.status === "revoked" && current.generation === expectedGeneration + 1)
294
- return current;
295
- if (current.status !== "active" || current.generation !== expectedGeneration)
296
- throw new HostMcpBindingConflictError();
297
- const [updated] = await rawRows<DelegationRow>(
298
- tx,
299
- sql`update host_mcp_delegations set status = 'revoked', generation = generation + 1, revoked_at = clock_timestamp() where id = ${id}::uuid returning *`,
300
- );
301
- if (!updated) throw new Error("Host delegation unavailable");
302
- return projectDelegation(updated);
303
- });
304
- }
305
-
306
- /** Internal direct-admission boundary, NOT authentication and NOT persistence.
307
- * The caller must establish a verified external owner and explicit selection,
308
- * and capture the returned authority with accepted work INSIDE `capture`.
309
- * Locks remain held through that callback. Historical initiator metadata only
310
- * narrows the verified caller's selection; it never establishes who called.
311
- * Schedules, continuations and inherited turns need their own frozen causal
312
- * authority path and deliberately cannot use this direct-only boundary.
313
- */
314
- export async function withDirectHostMcpAdmission<T>(
315
- db: Database,
316
- owner: HostMcpBindingOwner,
317
- input: {
318
- sessionId: string;
319
- turnId: string;
320
- delegationId: string;
321
- expectedDelegationGeneration: number;
322
- },
323
- capture: (tx: Database, authority: HostMcpAcceptedAuthority) => Promise<T>,
324
- ): Promise<T> {
325
- if (
326
- !Number.isSafeInteger(input.expectedDelegationGeneration) ||
327
- input.expectedDelegationGeneration < 1
328
- )
329
- throw new HostMcpDelegationAuthorityError("Invalid host admission generation");
330
- return scoped(db, owner, async (tx) => {
331
- const deny = (): never => {
332
- throw new HostMcpDelegationAuthorityError("Host direct admission unavailable");
333
- };
334
- if (!(await subjectHasLiveWorkspaceAuthorityInScope(tx, owner))) deny();
335
- const membership = (await listSelfOrganizationMemberships(tx, owner.subjectId)).find(
336
- (value) => value.organizationId === owner.accountId,
337
- );
338
- if (
339
- !membership ||
340
- membership.status !== "active" ||
341
- membership.authorizationRevision !== owner.authorizationRevision
342
- )
343
- return deny();
344
- const [session] = await rawRows<{
345
- visibility: "user_private" | "workspace_shared";
346
- authority_epoch: number;
347
- owner_subject_id: string | null;
348
- }>(
349
- tx,
350
- sql`select visibility, authority_epoch, owner_subject_id from sessions where id = ${input.sessionId}::uuid and account_id = ${owner.accountId}::uuid and workspace_id = ${owner.workspaceId}::uuid for update`,
351
- );
352
- if (
353
- !session ||
354
- (session.visibility === "user_private" && session.owner_subject_id !== owner.subjectId)
355
- )
356
- return deny();
357
- const [turn] = await rawRows<{ id: string }>(
358
- tx,
359
- sql`
360
- select id from session_turns where id = ${input.turnId}::uuid
361
- and session_id = ${input.sessionId}::uuid and account_id = ${owner.accountId}::uuid and workspace_id = ${owner.workspaceId}::uuid
362
- and status = 'queued' and active_attempt_id is null and source in ('user', 'api')
363
- and initiator_kind = 'subject' and initiator_subject_id = ${owner.subjectId}
364
- and (initiating_human_subject_id is null or initiating_human_subject_id = ${owner.subjectId})
365
- and not (initiator_context ?| array['via','viaTruncated','provenanceError','backfill'])
366
- and not exists (select 1 from session_turn_attempts a where a.turn_id = session_turns.id and a.workspace_id = session_turns.workspace_id)
367
- for update`,
368
- );
369
- if (!turn) return deny();
370
- const [row] = await rawRows<DelegationRow>(
371
- tx,
372
- sql`select * from host_mcp_delegations where id = ${input.delegationId}::uuid and account_id = ${owner.accountId}::uuid and workspace_id = ${owner.workspaceId}::uuid and owner_subject_id = ${owner.subjectId} for share`,
373
- );
374
- if (!row) return deny();
375
- const delegation = projectDelegation(row);
376
- if (
377
- delegation.status !== "active" ||
378
- delegation.revokedAt !== null ||
379
- delegation.generation !== input.expectedDelegationGeneration ||
380
- delegation.ownerAuthorizationRevision !== owner.authorizationRevision ||
381
- delegation.grant.context !== session.visibility
382
- )
383
- return deny();
384
- if (
385
- delegation.grant.mode === "session" &&
386
- (delegation.grant.sessionId !== input.sessionId ||
387
- delegation.grant.expectedAuthorityEpoch !== session.authority_epoch)
388
- )
389
- return deny();
390
- const [bindingRow] = await rawRows<Row>(
391
- tx,
392
- sql`select * from host_mcp_bindings where id = ${delegation.bindingId}::uuid and account_id = ${owner.accountId}::uuid and workspace_id = ${owner.workspaceId}::uuid and owner_subject_id = ${owner.subjectId} for share`,
393
- );
394
- if (!bindingRow) return deny();
395
- const binding = project(bindingRow);
396
- if (
397
- binding.status !== "active" ||
398
- binding.revokedAt !== null ||
399
- binding.generation !== delegation.bindingGeneration ||
400
- binding.authorizationRevision !== owner.authorizationRevision
401
- )
402
- return deny();
403
- const authority = HostMcpAcceptedAuthority.parse({
404
- version: 1,
405
- accountId: owner.accountId,
406
- workspaceId: owner.workspaceId,
407
- targetSessionId: input.sessionId,
408
- targetSessionVisibility: session.visibility,
409
- targetSessionAuthorityEpoch: session.authority_epoch,
410
- acceptedWork: { kind: "turn", turnId: turn.id },
411
- bindingId: binding.id,
412
- bindingGeneration: binding.generation,
413
- definition: binding.definition,
414
- ownerSubjectId: owner.subjectId,
415
- ownerOrganizationMembershipId: membership.id,
416
- ownerMembershipAuthorizationRevision: membership.authorizationRevision,
417
- delegationId: delegation.id,
418
- delegationGeneration: delegation.generation,
419
- source: { kind: "direct" },
420
- });
421
- return capture(tx, authority);
422
- });
423
- }
424
-
425
- /** Internal direct-turn persistence. The authenticated acceptance transaction
426
- * must call this before claim; this does not authenticate the supplied owner.
427
- * Exact replay is allowed, but a turn/server selection cannot be replaced. */
428
- export async function captureDirectHostMcpAuthority(
429
- db: Database,
430
- owner: HostMcpBindingOwner,
431
- input: Parameters<typeof withDirectHostMcpAdmission>[2],
432
- ): Promise<HostMcpAcceptedAuthority> {
433
- return withDirectHostMcpAdmission(db, owner, input, async (tx, authority) => {
434
- await tx.execute(sql`insert into host_mcp_turn_authorities
435
- (turn_id, server_id, account_id, workspace_id, session_id, owner_subject_id, binding_id, delegation_id, canonical_snapshot)
436
- values (${input.turnId}::uuid, ${authority.definition.serverId}, ${owner.accountId}::uuid,
437
- ${owner.workspaceId}::uuid, ${input.sessionId}::uuid, ${owner.subjectId},
438
- ${authority.bindingId}::uuid, ${authority.delegationId}::uuid, ${JSON.stringify(authority)}::jsonb)
439
- on conflict (turn_id, server_id) do nothing`);
440
- const [stored] = await rawRows<{ canonical_snapshot: unknown }>(
441
- tx,
442
- sql`
443
- select canonical_snapshot from host_mcp_turn_authorities where turn_id = ${input.turnId}::uuid
444
- and server_id = ${authority.definition.serverId} and account_id = ${owner.accountId}::uuid
445
- and workspace_id = ${owner.workspaceId}::uuid and owner_subject_id = ${owner.subjectId}`,
446
- );
447
- if (!stored || stableJson(stored.canonical_snapshot) !== stableJson(authority))
448
- throw new HostMcpBindingConflictError();
449
- return HostMcpAcceptedAuthority.parse(stored.canonical_snapshot);
450
- });
451
- }
452
-
453
- /** Copy only the authority of an exact causal turn after its canonical machine
454
- * update has been delivered. The insert trigger independently proves delivery,
455
- * live generations and byte-equivalent authority; no creator fallback exists.
456
- * Revoked selections are omitted, allowing a continuation to explain loss of
457
- * access without making its whole session permanently unclaimable. */
458
- export async function inheritCausalHostMcpTurnAuthorities(
459
- db: Database,
460
- input: {
461
- accountId: string;
462
- workspaceId: string;
463
- sessionId: string;
464
- subjectId: string;
465
- sourceTurnId: string;
466
- targetTurnId: string;
467
- },
468
- ): Promise<void> {
469
- if (
470
- !HostMcpOwnerSubject.safeParse(input.subjectId).success ||
471
- input.sourceTurnId === input.targetTurnId
472
- )
473
- return;
474
- await withWorkspaceSubjectRls(db, input.workspaceId, input.subjectId, async (tx) => {
475
- await lockExternalWorkspaceMembershipLifecycle(tx, input.accountId);
476
- const membership = (await listSelfOrganizationMemberships(tx, input.subjectId)).find(
477
- (m) => m.organizationId === input.accountId && m.status === "active",
478
- );
479
- if (!membership || !(await subjectHasLiveWorkspaceAuthorityInScope(tx, input))) return;
480
- const rows = await rawRows<{ canonical_snapshot: unknown }>(
481
- tx,
482
- sql`
483
- select a.canonical_snapshot from host_mcp_turn_authorities a
484
- join host_mcp_delegations d on d.id = a.delegation_id
485
- join host_mcp_bindings b on b.id = a.binding_id
486
- join sessions s on s.id = a.session_id
487
- where a.account_id = ${input.accountId}::uuid and a.workspace_id = ${input.workspaceId}::uuid
488
- and a.session_id = ${input.sessionId}::uuid and a.turn_id = ${input.sourceTurnId}::uuid
489
- and a.owner_subject_id = ${input.subjectId}
490
- and d.status = 'active' and b.status = 'active'
491
- and d.revoked_at is null and b.revoked_at is null
492
- and d.generation::text = a.canonical_snapshot ->> 'delegationGeneration'
493
- and b.generation::text = a.canonical_snapshot ->> 'bindingGeneration'
494
- and s.authority_epoch::text = a.canonical_snapshot ->> 'targetSessionAuthorityEpoch'
495
- and s.visibility = a.canonical_snapshot ->> 'targetSessionVisibility'
496
- and d.owner_authorization_revision = ${membership.authorizationRevision}
497
- and a.canonical_snapshot ->> 'ownerOrganizationMembershipId' = ${membership.id}
498
- for share of d, b`,
499
- );
500
- for (const row of rows) {
501
- const prior = HostMcpAcceptedAuthority.parse(row.canonical_snapshot);
502
- const authority = HostMcpAcceptedAuthority.parse({
503
- ...prior,
504
- acceptedWork: { kind: "turn", turnId: input.targetTurnId },
505
- source: { kind: "inherited_turn", sessionId: input.sessionId, turnId: input.sourceTurnId },
506
- });
507
- await tx.execute(sql`insert into host_mcp_turn_authorities
508
- (turn_id, server_id, account_id, workspace_id, session_id, owner_subject_id, binding_id, delegation_id, canonical_snapshot)
509
- values (${input.targetTurnId}::uuid, ${authority.definition.serverId}, ${input.accountId}::uuid,
510
- ${input.workspaceId}::uuid, ${input.sessionId}::uuid, ${input.subjectId},
511
- ${authority.bindingId}::uuid, ${authority.delegationId}::uuid, ${JSON.stringify(authority)}::jsonb)`);
512
- }
513
- });
514
- }
515
-
516
- /** Initial child turn only. The stored parent-turn pointer was admitted by the
517
- * native signed-attempt creation boundary; caller metadata never chooses it. */
518
- export async function inheritChildHostMcpTurnAuthorities(
519
- db: Database,
520
- input: {
521
- accountId: string;
522
- workspaceId: string;
523
- sessionId: string;
524
- turnId: string;
525
- subjectId: string;
526
- },
527
- ): Promise<void> {
528
- if (!HostMcpOwnerSubject.safeParse(input.subjectId).success) return;
529
- await withWorkspaceSubjectRls(db, input.workspaceId, input.subjectId, async (tx) => {
530
- await lockExternalWorkspaceMembershipLifecycle(tx, input.accountId);
531
- const membership = (await listSelfOrganizationMemberships(tx, input.subjectId)).find(
532
- (m) => m.organizationId === input.accountId && m.status === "active",
533
- );
534
- if (!membership || !(await subjectHasLiveWorkspaceAuthorityInScope(tx, input))) return;
535
- const rows = await rawRows<{
536
- canonical_snapshot: unknown;
537
- authority_epoch: number;
538
- visibility: string;
539
- parent_session_id: string;
540
- parent_turn_id: string;
541
- }>(
542
- tx,
543
- sql`
544
- select a.canonical_snapshot, c.authority_epoch, c.visibility, c.parent_session_id, c.parent_turn_id
545
- from sessions c join sessions p on p.id = c.parent_session_id and p.workspace_id = c.workspace_id
546
- join host_mcp_turn_authorities a on a.turn_id = c.parent_turn_id and a.session_id = p.id
547
- join host_mcp_delegations d on d.id = a.delegation_id
548
- join host_mcp_bindings b on b.id = a.binding_id
549
- where c.id = ${input.sessionId}::uuid and c.workspace_id = ${input.workspaceId}::uuid
550
- and c.account_id = ${input.accountId}::uuid and a.account_id = c.account_id and a.workspace_id = c.workspace_id
551
- and a.owner_subject_id = ${input.subjectId} and c.visibility = p.visibility
552
- and a.canonical_snapshot ->> 'targetSessionAuthorityEpoch' = p.authority_epoch::text
553
- and a.canonical_snapshot ->> 'ownerOrganizationMembershipId' = ${membership.id}
554
- and a.canonical_snapshot ->> 'ownerMembershipAuthorizationRevision' = ${String(membership.authorizationRevision)}
555
- and d.status = 'active' and b.status = 'active' and d.grant_definition ->> 'mode' = 'always'
556
- and d.generation::text = a.canonical_snapshot ->> 'delegationGeneration'
557
- and b.generation::text = a.canonical_snapshot ->> 'bindingGeneration'
558
- and exists (select 1 from jsonb_array_elements(c.tools) tool where tool ->> 'kind' = 'mcp' and tool ->> 'id' = a.server_id)
559
- for share of d, b`,
560
- );
561
- for (const row of rows) {
562
- const parent = HostMcpAcceptedAuthority.parse(row.canonical_snapshot);
563
- const authority = HostMcpAcceptedAuthority.parse({
564
- ...parent,
565
- targetSessionId: input.sessionId,
566
- targetSessionVisibility: row.visibility,
567
- targetSessionAuthorityEpoch: row.authority_epoch,
568
- acceptedWork: { kind: "turn", turnId: input.turnId },
569
- source: {
570
- kind: "child_turn",
571
- sessionId: row.parent_session_id,
572
- turnId: row.parent_turn_id,
573
- },
574
- });
575
- await tx.execute(sql`insert into host_mcp_turn_authorities
576
- (turn_id, server_id, account_id, workspace_id, session_id, owner_subject_id, binding_id, delegation_id, canonical_snapshot)
577
- values (${input.turnId}::uuid, ${authority.definition.serverId}, ${input.accountId}::uuid,
578
- ${input.workspaceId}::uuid, ${input.sessionId}::uuid, ${input.subjectId}, ${authority.bindingId}::uuid,
579
- ${authority.delegationId}::uuid, ${JSON.stringify(authority)}::jsonb)`);
580
- }
581
- });
582
- }
583
-
584
- /** Live consumption of captured direct or same-session causal turn authority.
585
- * Request context must come from the accepted attempt, never caller JSON.
586
- * Missing/revoked authority denies; infrastructure failures propagate so the
587
- * broker reports refresh_failed rather than misclassifying an outage.
588
- * The historical function name is retained for existing internal consumers. */
589
- export async function authorizeDirectHostMcpUse(
590
- db: Database,
591
- request: McpCredentialsRequest,
592
- /** Internal metadata observer; called only after every live authorization fence succeeds. */
593
- onAuthorized?: (snapshot: HostMcpAcceptedAuthority) => void,
594
- ): Promise<boolean> {
595
- const snapshot = await resolveDirectHostMcpUseAuthority(db, request);
596
- if (!snapshot) return false;
597
- onAuthorized?.(snapshot);
598
- return true;
599
- }
600
-
601
- async function resolveDirectHostMcpUseAuthority(
602
- db: Database,
603
- request: McpCredentialsRequest,
604
- ): Promise<HostMcpAcceptedAuthority | false> {
605
- if (!request.attemptId || !request.connectionRef.hostBinding) return false;
606
- const current = await getHostMcpLiveAttempt(
607
- db,
608
- request.workspaceId,
609
- request.sessionId,
610
- request.attemptId,
611
- );
612
- if (
613
- !current ||
614
- current.id !== request.turnId ||
615
- current.executionGeneration !== request.executionGeneration
616
- )
617
- return false;
618
- // The accepted initiator only scopes this read. Permission comes from the
619
- // immutable captured row and live checks below, not from initiator metadata.
620
- const direct =
621
- ["user", "api"].includes(current.source) &&
622
- current.initiator.kind === "subject" &&
623
- !["via", "viaTruncated", "provenanceError", "backfill"].some((key) =>
624
- Object.hasOwn(current.initiatorContext, key),
625
- );
626
- const inherited = ["goal", "system"].includes(current.source) && !current.scheduledTaskRunId;
627
- const scheduled = current.source === "system" && Boolean(current.scheduledTaskRunId);
628
- const child = ["user", "api"].includes(current.source) && !direct;
629
- const subjectId =
630
- direct && current.initiator.kind === "subject"
631
- ? current.initiator.subjectId
632
- : inherited || scheduled || child
633
- ? current.initiatingHumanSubjectId
634
- : null;
635
- if (!subjectId) return false;
636
- return withWorkspaceSubjectRls(db, request.workspaceId, subjectId, async (tx) => {
637
- await lockExternalWorkspaceMembershipLifecycle(tx, request.accountId);
638
- const [stored] = await rawRows<{ canonical_snapshot: unknown }>(
639
- tx,
640
- sql`
641
- select canonical_snapshot from host_mcp_turn_authorities where turn_id = ${request.turnId}::uuid
642
- and server_id = ${request.serverId} and account_id = ${request.accountId}::uuid
643
- and workspace_id = ${request.workspaceId}::uuid and session_id = ${request.sessionId}::uuid
644
- and owner_subject_id = ${subjectId}`,
645
- );
646
- const parsed = HostMcpAcceptedAuthority.safeParse(stored?.canonical_snapshot);
647
- if (!parsed.success) return false;
648
- const a = parsed.data;
649
- if (
650
- !(
651
- (direct && a.source.kind === "direct") ||
652
- (inherited &&
653
- a.source.kind === "inherited_turn" &&
654
- a.source.sessionId === request.sessionId) ||
655
- (scheduled &&
656
- a.source.kind === "scheduled_task" &&
657
- a.acceptedWork.kind === "scheduled_task" &&
658
- a.acceptedWork.runId === current.scheduledTaskRunId) ||
659
- (child && a.source.kind === "child_turn" && a.acceptedWork.kind === "turn")
660
- ) ||
661
- (a.acceptedWork.kind === "turn" && a.acceptedWork.turnId !== request.turnId) ||
662
- a.accountId !== request.accountId ||
663
- a.workspaceId !== request.workspaceId ||
664
- a.targetSessionId !== request.sessionId ||
665
- a.ownerSubjectId !== subjectId
666
- )
667
- return false;
668
- if (a.scheduledOrigin) {
669
- const origin = a.scheduledOrigin;
670
- const [run] = await rawRows<{ denial: string | null }>(
671
- tx,
672
- sql`
673
- select validate_scheduled_agent_run_live_authority(${request.accountId}::uuid,
674
- ${request.workspaceId}::uuid, r.id) as denial from scheduled_task_runs r
675
- where r.id = ${origin.runId}::uuid and r.task_id = ${origin.taskId}::uuid
676
- and r.task_authority_revision = ${origin.taskAuthorityRevision}
677
- and r.account_id = ${request.accountId}::uuid and r.workspace_id = ${request.workspaceId}::uuid
678
- and r.accepted_execution_snapshot ->> 'causalHumanSubjectId' = ${subjectId}`,
679
- );
680
- if (!run || run.denial !== null) return false;
681
- }
682
- const membership = (await listSelfOrganizationMemberships(tx, subjectId)).find(
683
- (m) => m.organizationId === request.accountId,
684
- );
685
- if (
686
- !membership ||
687
- membership.status !== "active" ||
688
- membership.id !== a.ownerOrganizationMembershipId ||
689
- membership.authorizationRevision !== a.ownerMembershipAuthorizationRevision ||
690
- !(await subjectHasLiveWorkspaceAuthorityInScope(tx, {
691
- accountId: request.accountId,
692
- workspaceId: request.workspaceId,
693
- subjectId,
694
- }))
695
- )
696
- return false;
697
- // Migration 0418's self-membership boundary independently checks active
698
- // external identity; runtime has no direct external_identities privileges.
699
- const [session] = await rawRows<{
700
- visibility: string;
701
- authority_epoch: number;
702
- owner_subject_id: string | null;
703
- }>(
704
- tx,
705
- sql`select visibility, authority_epoch, owner_subject_id from sessions where id = ${request.sessionId}::uuid and account_id = ${request.accountId}::uuid and workspace_id = ${request.workspaceId}::uuid`,
706
- );
707
- if (
708
- !session ||
709
- session.visibility !== a.targetSessionVisibility ||
710
- session.authority_epoch !== a.targetSessionAuthorityEpoch ||
711
- (session.visibility === "user_private" && session.owner_subject_id !== subjectId)
712
- )
713
- return false;
714
- const [d] = await rawRows<DelegationRow>(
715
- tx,
716
- sql`select * from host_mcp_delegations where id = ${a.delegationId}::uuid and account_id = ${request.accountId}::uuid and workspace_id = ${request.workspaceId}::uuid and owner_subject_id = ${subjectId}`,
717
- );
718
- const [b] = await rawRows<Row>(
719
- tx,
720
- sql`select * from host_mcp_bindings where id = ${a.bindingId}::uuid and account_id = ${request.accountId}::uuid and workspace_id = ${request.workspaceId}::uuid and owner_subject_id = ${subjectId}`,
721
- );
722
- if (!d || !b) return false;
723
- const delegation = projectDelegation(d),
724
- binding = project(b);
725
- if (
726
- delegation.status !== "active" ||
727
- delegation.revokedAt !== null ||
728
- delegation.generation !== a.delegationGeneration ||
729
- delegation.ownerAuthorizationRevision !== a.ownerMembershipAuthorizationRevision ||
730
- delegation.bindingId !== a.bindingId ||
731
- delegation.bindingGeneration !== a.bindingGeneration ||
732
- delegation.grant.context !== a.targetSessionVisibility ||
733
- (delegation.grant.mode === "session" &&
734
- (delegation.grant.sessionId !== request.sessionId ||
735
- delegation.grant.expectedAuthorityEpoch !== a.targetSessionAuthorityEpoch)) ||
736
- binding.status !== "active" ||
737
- binding.revokedAt !== null ||
738
- binding.authorizationRevision !== a.ownerMembershipAuthorizationRevision ||
739
- binding.generation !== a.bindingGeneration ||
740
- stableJson(binding.definition) !== stableJson(a.definition) ||
741
- !(request.connectionRef.hostBinding && "selection" in request.connectionRef.hostBinding
742
- ? request.credentialTarget === "mcp" && hostMcpBindingMatchesSelection(binding, request)
743
- : hostMcpBindingMatchesRequest(binding, request))
744
- )
745
- return false;
746
- const live = await getHostMcpLiveAttempt(
747
- tx,
748
- request.workspaceId,
749
- request.sessionId,
750
- request.attemptId!,
751
- );
752
- return (
753
- live !== null &&
754
- live.id === request.turnId &&
755
- live.executionGeneration === request.executionGeneration &&
756
- a
757
- );
758
- });
759
- }
760
-
761
- /** Resolve a configuration selector from the exact live accepted turn, never
762
- * registry inventory or session creator metadata. The broker still revalidates
763
- * the resulting concrete reference around resolution and every physical use. */
764
- export async function resolveAcceptedHostMcpBinding(
765
- db: Database,
766
- request: McpCredentialsRequest,
767
- ): Promise<McpCredentialsRequest["connectionRef"] | null> {
768
- if (!request.connectionRef.hostBinding || !("selection" in request.connectionRef.hostBinding))
769
- return null;
770
- const authority = await resolveDirectHostMcpUseAuthority(db, request);
771
- return authority
772
- ? {
773
- ...authority.definition.connectionRef,
774
- hostBinding: { bindingId: authority.bindingId, generation: authority.bindingGeneration },
775
- }
776
- : null;
777
- }