@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,266 +0,0 @@
1
- import { createHmac, randomUUID } from "node:crypto";
2
- import { sql } from "drizzle-orm";
3
- import { stableJson } from "@opengeni/contracts";
4
- import {
5
- HostMcpResolver,
6
- HostMcpResolverSource,
7
- PutHostMcpResolverRequest,
8
- RevokeHostMcpResolverRequest,
9
- } from "@opengeni/contracts/host-mcp-resolvers";
10
- import { rawRows, setSubjectRlsContext, withRlsContext, type Database } from "./database";
11
- import { encryptEnvironmentValue, decryptEnvironmentValue } from "./environment-crypto";
12
-
13
- export class HostMcpResolverError extends Error {
14
- constructor(
15
- readonly status: 403 | 404 | 409,
16
- message: string,
17
- ) {
18
- super(message);
19
- }
20
- }
21
- type Actor = { accountId: string; subjectId: string };
22
- type Row = {
23
- id: string;
24
- account_id: string;
25
- external_source: string;
26
- url: string;
27
- secret_encrypted: string;
28
- timeout_ms: number;
29
- generation: number | string;
30
- status: string;
31
- created_at: Date | string;
32
- updated_at: Date | string;
33
- };
34
- const project = (row: Row): HostMcpResolver =>
35
- HostMcpResolver.parse({
36
- id: row.id,
37
- organizationId: row.account_id,
38
- externalSource: row.external_source,
39
- url: row.url,
40
- timeoutMs: row.timeout_ms,
41
- generation: Number(row.generation),
42
- status: row.status,
43
- createdAt: new Date(row.created_at).toISOString(),
44
- updatedAt: new Date(row.updated_at).toISOString(),
45
- });
46
-
47
- /** Caller proves service-key provenance at the public authentication boundary.
48
- * Recheck the exact live key under lock, including receipt replay. */
49
- async function administered<T>(
50
- db: Database,
51
- actor: Actor,
52
- use: (tx: Database) => Promise<T>,
53
- ): Promise<T> {
54
- return withRlsContext(db, { accountId: actor.accountId, workspaceId: null }, async (tx) => {
55
- await setSubjectRlsContext(tx, actor.subjectId);
56
- const [key] = await rawRows(
57
- tx,
58
- sql`select id from api_keys where account_id = ${actor.accountId}::uuid
59
- and 'api_key:' || id::text = ${actor.subjectId} and workspace_id is null and credential_kind = 'organization'
60
- and revoked_at is null and (expires_at is null or expires_at > clock_timestamp())
61
- and permissions ? 'workspace:admin' for share`,
62
- );
63
- if (!key) throw new HostMcpResolverError(403, "organization service administration required");
64
- await tx.execute(
65
- sql`select pg_advisory_xact_lock(hashtextextended(${`host-mcp-resolvers:${actor.accountId}`}, 0))`,
66
- );
67
- return use(tx);
68
- });
69
- }
70
-
71
- export async function getHostMcpResolver(
72
- db: Database,
73
- actor: Actor,
74
- source: string,
75
- ): Promise<HostMcpResolver> {
76
- const externalSource = HostMcpResolverSource.parse(source);
77
- return administered(db, actor, async (tx) => {
78
- const [row] = await rawRows<Row>(
79
- tx,
80
- sql`select * from host_mcp_resolvers where account_id = ${actor.accountId}::uuid and external_source = ${externalSource}`,
81
- );
82
- if (!row) throw new HostMcpResolverError(404, "resolver unavailable");
83
- return project(row);
84
- });
85
- }
86
-
87
- export async function mutateHostMcpResolver(
88
- db: Database,
89
- actor: Actor,
90
- input: {
91
- externalSource: string;
92
- encryptionKey: Uint8Array;
93
- legacyConfigured: boolean;
94
- } & (
95
- | { kind: "put"; request: PutHostMcpResolverRequest }
96
- | { kind: "revoke"; request: RevokeHostMcpResolverRequest }
97
- ),
98
- ): Promise<HostMcpResolver> {
99
- const externalSource = HostMcpResolverSource.parse(input.externalSource);
100
- const putRequest = input.kind === "put" ? PutHostMcpResolverRequest.parse(input.request) : null;
101
- const operation = putRequest ?? RevokeHostMcpResolverRequest.parse(input.request);
102
- const digest = createHmac("sha256", input.encryptionKey)
103
- .update("opengeni:host-mcp-resolver-operation:v1\0")
104
- .update(stableJson({ actor, externalSource, kind: input.kind, request: operation }))
105
- .digest("hex");
106
- return administered(db, actor, async (tx) => {
107
- const [receipt] = await rawRows<{ request_digest: string; result: unknown }>(
108
- tx,
109
- sql`select request_digest, result from host_mcp_resolver_operations
110
- where account_id = ${actor.accountId}::uuid and operation_id = ${operation.operationId}::uuid`,
111
- );
112
- if (receipt) {
113
- if (receipt.request_digest !== digest)
114
- throw new HostMcpResolverError(409, "resolver operation conflicts");
115
- // Return historical metadata only; never restore old configuration or secrets.
116
- return HostMcpResolver.parse(receipt.result);
117
- }
118
- const [current] = await rawRows<Row>(
119
- tx,
120
- sql`select * from host_mcp_resolvers where account_id = ${actor.accountId}::uuid and external_source = ${externalSource} for update`,
121
- );
122
- if ((current ? Number(current.generation) : 0) !== operation.expectedGeneration)
123
- throw new HostMcpResolverError(409, "resolver generation changed");
124
- let row: Row | undefined;
125
- if (putRequest) {
126
- const request = putRequest;
127
- const [existing] = await rawRows(
128
- tx,
129
- sql`select id from host_mcp_resolvers where account_id = ${actor.accountId}::uuid limit 1`,
130
- );
131
- if (!existing && input.legacyConfigured && !request.acknowledgeLegacyRoutingReplacement)
132
- throw new HostMcpResolverError(
133
- 409,
134
- "first registration requires explicit legacy routing replacement acknowledgement",
135
- );
136
- const id = current?.id ?? randomUUID();
137
- const generation = request.expectedGeneration + 1;
138
- const encrypted = encryptEnvironmentValue(
139
- input.encryptionKey,
140
- JSON.stringify({
141
- version: 1,
142
- id,
143
- accountId: actor.accountId,
144
- externalSource,
145
- generation,
146
- url: request.url,
147
- bearerToken: request.bearerToken,
148
- }),
149
- );
150
- [row] = current
151
- ? await rawRows<Row>(
152
- tx,
153
- sql`update host_mcp_resolvers set url = ${request.url}, secret_encrypted = ${encrypted}, timeout_ms = ${request.timeoutMs}, generation = ${generation}, status = 'active', updated_at = clock_timestamp() where id = ${id}::uuid returning *`,
154
- )
155
- : await rawRows<Row>(
156
- tx,
157
- sql`insert into host_mcp_resolvers (id, account_id, external_source, url, secret_encrypted, timeout_ms, generation, status) values (${id}::uuid, ${actor.accountId}::uuid, ${externalSource}, ${request.url}, ${encrypted}, ${request.timeoutMs}, 1, 'active') returning *`,
158
- );
159
- } else {
160
- if (!current) throw new HostMcpResolverError(404, "resolver unavailable");
161
- [row] = await rawRows<Row>(
162
- tx,
163
- sql`update host_mcp_resolvers set status = 'revoked', generation = generation + 1, updated_at = clock_timestamp() where id = ${current.id}::uuid returning *`,
164
- );
165
- }
166
- if (!row) throw new Error("resolver mutation unavailable");
167
- const result = project(row);
168
- await tx.execute(sql`insert into host_mcp_resolver_operations (account_id, operation_id, actor_subject_id, request_digest, result)
169
- values (${actor.accountId}::uuid, ${operation.operationId}::uuid, ${actor.subjectId}, ${digest}, ${JSON.stringify(result)}::jsonb)`);
170
- return result;
171
- });
172
- }
173
-
174
- export type HostMcpResolverRoute =
175
- | { mode: "legacy" }
176
- | { mode: "denied" }
177
- | {
178
- mode: "namespace";
179
- id: string;
180
- generation: number;
181
- accountId: string;
182
- workspaceId: string;
183
- externalSource: string;
184
- url: string;
185
- bearerToken: string;
186
- timeoutMs: number;
187
- };
188
- /** Runtime lookup, not tool authority. Failure must never be interpreted as legacy. */
189
- export async function resolveHostMcpResolverRoute(
190
- db: Database,
191
- input: { accountId: string; workspaceId: string },
192
- encryptionKey: Uint8Array | null,
193
- ): Promise<HostMcpResolverRoute> {
194
- return withRlsContext(
195
- db,
196
- { accountId: input.accountId, workspaceId: input.workspaceId },
197
- async (tx) => {
198
- const [workspace] = await rawRows<{ external_source: string | null }>(
199
- tx,
200
- sql`select external_source from workspaces where id = ${input.workspaceId}::uuid and account_id = ${input.accountId}::uuid`,
201
- );
202
- if (!workspace) return { mode: "denied" };
203
- const [any] = await rawRows(
204
- tx,
205
- sql`select id from host_mcp_resolvers where account_id = ${input.accountId}::uuid limit 1`,
206
- );
207
- if (!any) return { mode: "legacy" };
208
- if (!workspace.external_source || !encryptionKey) return { mode: "denied" };
209
- const [row] = await rawRows<Row>(
210
- tx,
211
- sql`select * from host_mcp_resolvers where account_id = ${input.accountId}::uuid and external_source = ${workspace.external_source} and status = 'active'`,
212
- );
213
- if (!row) return { mode: "denied" };
214
- const bundle = JSON.parse(decryptEnvironmentValue(encryptionKey, row.secret_encrypted));
215
- if (
216
- bundle.version !== 1 ||
217
- bundle.id !== row.id ||
218
- bundle.accountId !== input.accountId ||
219
- bundle.externalSource !== workspace.external_source ||
220
- bundle.generation !== Number(row.generation) ||
221
- bundle.url !== row.url ||
222
- typeof bundle.bearerToken !== "string" ||
223
- !bundle.bearerToken ||
224
- /[\r\n]/u.test(bundle.bearerToken)
225
- )
226
- throw new Error("resolver secret binding invalid");
227
- return {
228
- mode: "namespace",
229
- id: row.id,
230
- generation: Number(row.generation),
231
- accountId: input.accountId,
232
- workspaceId: input.workspaceId,
233
- externalSource: workspace.external_source,
234
- url: row.url,
235
- bearerToken: bundle.bearerToken,
236
- timeoutMs: row.timeout_ms,
237
- };
238
- },
239
- );
240
- }
241
-
242
- export async function hostMcpResolverRouteIsCurrent(
243
- db: Database,
244
- input: { accountId: string; workspaceId: string },
245
- route: HostMcpResolverRoute,
246
- ): Promise<boolean> {
247
- if (route.mode === "denied") return false;
248
- return withRlsContext(
249
- db,
250
- { accountId: input.accountId, workspaceId: input.workspaceId },
251
- async (tx) => {
252
- const [row] =
253
- route.mode === "legacy"
254
- ? await rawRows(
255
- tx,
256
- sql`select w.id from workspaces w where w.id = ${input.workspaceId}::uuid and w.account_id = ${input.accountId}::uuid and not exists (select 1 from host_mcp_resolvers r where r.account_id = w.account_id)`,
257
- )
258
- : await rawRows(
259
- tx,
260
- sql`select r.id from host_mcp_resolvers r join workspaces w on w.account_id = r.account_id and w.external_source = r.external_source
261
- where w.id = ${input.workspaceId}::uuid and r.account_id = ${input.accountId}::uuid and r.external_source = ${route.externalSource} and r.id = ${route.id}::uuid and r.generation = ${route.generation} and r.status = 'active'`,
262
- );
263
- return Boolean(row);
264
- },
265
- );
266
- }
@@ -1,384 +0,0 @@
1
- import { sql } from "drizzle-orm";
2
- import {
3
- stableJson,
4
- ScheduledTaskRunAcceptedExecution,
5
- type ScheduledTask,
6
- } from "@opengeni/contracts";
7
- import {
8
- HostMcpAcceptedAuthority,
9
- HostMcpTaskAuthority,
10
- HostMcpBindingDefinition,
11
- HostMcpOwnerSubject,
12
- hostMcpBindingMatchesSelection,
13
- type HostMcpBindingSelection,
14
- } from "@opengeni/contracts/host-mcp-bindings";
15
- import { getScheduledTaskRevisionAuthority } from "./scheduled-task-revision-authority";
16
- import { getHostMcpLiveAttempt } from "./live-session-attempt";
17
- import { rawRows, withWorkspaceSubjectRls, type Database } from "./database";
18
- import {
19
- getHostMcpBinding,
20
- getHostMcpDelegation,
21
- HostMcpDelegationAuthorityError,
22
- authorizeDirectHostMcpUse,
23
- type HostMcpBindingOwner,
24
- } from "./host-mcp-bindings";
25
-
26
- export type SelectedHostMcpTaskGrant = {
27
- delegationId: string;
28
- generation: number;
29
- bindingId: string;
30
- bindingGeneration: number;
31
- definition: HostMcpBindingDefinition;
32
- };
33
-
34
- /** Internal capture only. The API/core authenticates the external owner and
35
- * rechecks that proof in this same task-write transaction before calling. */
36
- export async function captureHostMcpTaskAuthorities(
37
- db: Database,
38
- owner: HostMcpBindingOwner,
39
- task: ScheduledTask,
40
- selections: SelectedHostMcpTaskGrant[],
41
- ): Promise<void> {
42
- if (task.accountId !== owner.accountId || task.workspaceId !== owner.workspaceId)
43
- throw new HostMcpDelegationAuthorityError("Host task scope mismatch");
44
- const proof = await getScheduledTaskRevisionAuthority(db, {
45
- ...owner,
46
- taskId: task.id,
47
- taskAuthorityRevision: task.authorityRevision,
48
- });
49
- if (
50
- !proof ||
51
- proof.subjectId !== owner.subjectId ||
52
- proof.membershipAuthorizationRevision !== owner.authorizationRevision
53
- )
54
- throw new HostMcpDelegationAuthorityError("Host task revision authority changed");
55
- const target =
56
- task.runMode === "existing_session" && task.targetSessionId
57
- ? ((
58
- await withWorkspaceSubjectRls(db, owner.workspaceId, owner.subjectId, (tx) =>
59
- rawRows<{
60
- id: string;
61
- visibility: "user_private" | "workspace_shared";
62
- authority_epoch: number;
63
- }>(
64
- tx,
65
- sql`select id, visibility, authority_epoch from sessions where id = ${task.targetSessionId}::uuid
66
- and workspace_id = ${owner.workspaceId}::uuid and account_id = ${owner.accountId}::uuid`,
67
- ),
68
- )
69
- )[0] ?? null)
70
- : null;
71
- const context = target?.visibility ?? "workspace_shared";
72
- await withWorkspaceSubjectRls(db, owner.workspaceId, owner.subjectId, async (tx) => {
73
- for (const selection of selections) {
74
- const delegation = await getHostMcpDelegation(tx, owner, selection.delegationId);
75
- const binding = delegation ? await getHostMcpBinding(tx, owner, delegation.bindingId) : null;
76
- const definition = HostMcpBindingDefinition.parse(selection.definition);
77
- if (
78
- !delegation ||
79
- !binding ||
80
- delegation.status !== "active" ||
81
- binding.status !== "active" ||
82
- delegation.generation !== selection.generation ||
83
- binding.generation !== delegation.bindingGeneration ||
84
- binding.id !== selection.bindingId ||
85
- binding.generation !== selection.bindingGeneration ||
86
- stableJson(binding.definition) !== stableJson(definition) ||
87
- delegation.grant.context !== context ||
88
- (delegation.grant.mode === "session" &&
89
- (!target ||
90
- delegation.grant.sessionId !== target.id ||
91
- delegation.grant.expectedAuthorityEpoch !== target.authority_epoch))
92
- )
93
- throw new HostMcpDelegationAuthorityError("Host task delegation changed");
94
- const authority = HostMcpTaskAuthority.parse({
95
- version: 1,
96
- accountId: owner.accountId,
97
- workspaceId: owner.workspaceId,
98
- taskId: task.id,
99
- taskAuthorityRevision: task.authorityRevision,
100
- taskExecutionDigest: task.executionDigest,
101
- ownerSubjectId: owner.subjectId,
102
- ownerOrganizationMembershipId: proof.organizationMembershipId,
103
- ownerMembershipAuthorizationRevision: proof.membershipAuthorizationRevision,
104
- bindingId: binding.id,
105
- bindingGeneration: binding.generation,
106
- delegationId: delegation.id,
107
- delegationGeneration: delegation.generation,
108
- definition,
109
- context,
110
- });
111
- await tx.execute(sql`insert into host_mcp_task_authorities
112
- (task_id, task_authority_revision, server_id, account_id, workspace_id, owner_subject_id, binding_id, delegation_id, canonical_snapshot)
113
- values (${task.id}::uuid, ${task.authorityRevision}, ${definition.serverId}, ${owner.accountId}::uuid,
114
- ${owner.workspaceId}::uuid, ${owner.subjectId}, ${binding.id}::uuid, ${delegation.id}::uuid,
115
- ${JSON.stringify(authority)}::jsonb)`);
116
- }
117
- });
118
- }
119
-
120
- /** Backend-only read; native immutable task-revision proof chooses the owner.
121
- * No caller-supplied subject and no ambient workspace account enumeration. */
122
- export async function getHostMcpTaskAuthorities(
123
- db: Database,
124
- input: {
125
- accountId: string;
126
- workspaceId: string;
127
- taskId: string;
128
- taskAuthorityRevision: number;
129
- },
130
- ): Promise<HostMcpTaskAuthority[]> {
131
- const proof = await getScheduledTaskRevisionAuthority(db, input);
132
- if (!proof || !HostMcpOwnerSubject.safeParse(proof.subjectId).success) return [];
133
- return withWorkspaceSubjectRls(db, input.workspaceId, proof.subjectId, async (tx) => {
134
- const rows = await rawRows<{ canonical_snapshot: unknown }>(
135
- tx,
136
- sql`
137
- select canonical_snapshot from host_mcp_task_authorities where task_id = ${input.taskId}::uuid
138
- and task_authority_revision = ${input.taskAuthorityRevision} and account_id = ${input.accountId}::uuid
139
- and workspace_id = ${input.workspaceId}::uuid and owner_subject_id = ${proof.subjectId}
140
- order by server_id`,
141
- );
142
- return rows.map((row) => HostMcpTaskAuthority.parse(row.canonical_snapshot));
143
- });
144
- }
145
-
146
- /** Native task revisions are the authority/version boundary, including ordinary
147
- * lifecycle edits. Never silently drop or reassign retained host selections. */
148
- export async function cloneHostMcpTaskAuthorities(
149
- db: Database,
150
- task: ScheduledTask,
151
- sourceRevision: number,
152
- ): Promise<void> {
153
- if (sourceRevision === task.authorityRevision) return;
154
- const previous = await getHostMcpTaskAuthorities(db, {
155
- accountId: task.accountId,
156
- workspaceId: task.workspaceId,
157
- taskId: task.id,
158
- taskAuthorityRevision: sourceRevision,
159
- });
160
- if (!previous.length) return;
161
- const existing = await getHostMcpTaskAuthorities(db, {
162
- accountId: task.accountId,
163
- workspaceId: task.workspaceId,
164
- taskId: task.id,
165
- taskAuthorityRevision: task.authorityRevision,
166
- });
167
- if (existing.length) {
168
- const selection = (a: HostMcpTaskAuthority) => ({
169
- definition: a.definition,
170
- bindingId: a.bindingId,
171
- bindingGeneration: a.bindingGeneration,
172
- delegationId: a.delegationId,
173
- delegationGeneration: a.delegationGeneration,
174
- ownerSubjectId: a.ownerSubjectId,
175
- ownerMembershipAuthorizationRevision: a.ownerMembershipAuthorizationRevision,
176
- });
177
- if (stableJson(existing.map(selection)) !== stableJson(previous.map(selection)))
178
- throw new HostMcpDelegationAuthorityError("Host task materialization selection changed");
179
- return;
180
- }
181
- const owner = previous[0]!;
182
- await captureHostMcpTaskAuthorities(
183
- db,
184
- {
185
- accountId: owner.accountId,
186
- workspaceId: owner.workspaceId,
187
- subjectId: owner.ownerSubjectId,
188
- authorizationRevision: owner.ownerMembershipAuthorizationRevision,
189
- },
190
- task,
191
- previous.map((a) => ({
192
- delegationId: a.delegationId,
193
- generation: a.delegationGeneration,
194
- bindingId: a.bindingId,
195
- bindingGeneration: a.bindingGeneration,
196
- definition: a.definition,
197
- })),
198
- );
199
- }
200
-
201
- /** Claim-time attachment to the exact accepted run. No host callback or key is
202
- * used here; credentials are requested only when the accepted attempt uses MCP. */
203
- export async function captureScheduledHostMcpTurnAuthorities(
204
- db: Database,
205
- input: {
206
- accountId: string;
207
- workspaceId: string;
208
- sessionId: string;
209
- turnId: string;
210
- runId: string;
211
- },
212
- ): Promise<void> {
213
- const [run] = await rawRows<{
214
- accepted_execution_snapshot: unknown;
215
- task_id: string;
216
- task_authority_revision: string | number;
217
- task_execution_digest: string;
218
- }>(
219
- db,
220
- sql`
221
- select accepted_execution_snapshot, task_id, task_authority_revision, task_execution_digest from scheduled_task_runs where id = ${input.runId}::uuid
222
- and account_id = ${input.accountId}::uuid and workspace_id = ${input.workspaceId}::uuid
223
- and session_id = ${input.sessionId}::uuid and status = 'dispatched'`,
224
- );
225
- if (!run) throw new HostMcpDelegationAuthorityError("Host scheduled run unavailable");
226
- const accepted = ScheduledTaskRunAcceptedExecution.parse(run.accepted_execution_snapshot);
227
- const authorities = await getHostMcpTaskAuthorities(db, {
228
- accountId: input.accountId,
229
- workspaceId: input.workspaceId,
230
- taskId: run.task_id,
231
- taskAuthorityRevision: Number(run.task_authority_revision),
232
- });
233
- if (!authorities.length) return;
234
- const subjectId = authorities[0]!.ownerSubjectId;
235
- if (accepted.causalHumanSubjectId !== subjectId)
236
- throw new HostMcpDelegationAuthorityError("Host scheduled causal human mismatch");
237
- await withWorkspaceSubjectRls(db, input.workspaceId, subjectId, async (tx) => {
238
- const [session] = await rawRows<{ visibility: string; authority_epoch: number }>(
239
- tx,
240
- sql`
241
- select visibility, authority_epoch from sessions where id = ${input.sessionId}::uuid
242
- and account_id = ${input.accountId}::uuid and workspace_id = ${input.workspaceId}::uuid`,
243
- );
244
- if (!session) throw new HostMcpDelegationAuthorityError("Host scheduled session unavailable");
245
- for (const stored of authorities) {
246
- const owner = {
247
- accountId: input.accountId,
248
- workspaceId: input.workspaceId,
249
- subjectId,
250
- authorizationRevision: stored.ownerMembershipAuthorizationRevision,
251
- };
252
- const grant = await getHostMcpDelegation(tx, owner, stored.delegationId);
253
- const binding = await getHostMcpBinding(tx, owner, stored.bindingId);
254
- if (
255
- !grant ||
256
- !binding ||
257
- grant.status !== "active" ||
258
- binding.status !== "active" ||
259
- grant.generation !== stored.delegationGeneration ||
260
- binding.generation !== stored.bindingGeneration
261
- )
262
- continue;
263
- const { taskId, taskAuthorityRevision, taskExecutionDigest, context, ...base } = stored;
264
- if (taskExecutionDigest !== run.task_execution_digest || context !== session.visibility)
265
- throw new HostMcpDelegationAuthorityError("Host scheduled execution changed");
266
- const scheduledOrigin = { taskId, taskAuthorityRevision, runId: input.runId };
267
- const authority = HostMcpAcceptedAuthority.parse({
268
- ...base,
269
- targetSessionId: input.sessionId,
270
- targetSessionVisibility: session.visibility,
271
- targetSessionAuthorityEpoch: session.authority_epoch,
272
- acceptedWork: { kind: "scheduled_task", ...scheduledOrigin },
273
- scheduledOrigin,
274
- source: { kind: "scheduled_task" },
275
- });
276
- await tx.execute(sql`insert into host_mcp_turn_authorities
277
- (turn_id, server_id, account_id, workspace_id, session_id, owner_subject_id, binding_id, delegation_id, canonical_snapshot)
278
- values (${input.turnId}::uuid, ${stored.definition.serverId}, ${input.accountId}::uuid,
279
- ${input.workspaceId}::uuid, ${input.sessionId}::uuid, ${subjectId}, ${stored.bindingId}::uuid,
280
- ${stored.delegationId}::uuid, ${JSON.stringify(authority)}::jsonb)`);
281
- }
282
- });
283
- }
284
-
285
- /** Agent-created task: derive selections exclusively from its live accepted
286
- * attempt, then re-admit only native successor-eligible grants. No user lookup
287
- * or connection inventory is a fallback for a turn with no captured authority. */
288
- export async function inheritHostMcpTaskAuthoritiesFromAttempt(
289
- db: Database,
290
- task: ScheduledTask,
291
- source: { sessionId: string; turnId: string; attemptId: string; executionGeneration: number },
292
- configured: HostMcpBindingSelection[],
293
- ): Promise<void> {
294
- const proof = await getScheduledTaskRevisionAuthority(db, {
295
- accountId: task.accountId,
296
- workspaceId: task.workspaceId,
297
- taskId: task.id,
298
- taskAuthorityRevision: task.authorityRevision,
299
- });
300
- if (!proof || !HostMcpOwnerSubject.safeParse(proof.subjectId).success) return;
301
- const current = await getHostMcpLiveAttempt(
302
- db,
303
- task.workspaceId,
304
- source.sessionId,
305
- source.attemptId,
306
- );
307
- if (
308
- !current ||
309
- current.id !== source.turnId ||
310
- current.executionGeneration !== source.executionGeneration
311
- )
312
- throw new HostMcpDelegationAuthorityError("Host task source attempt changed");
313
- const owner = {
314
- accountId: task.accountId,
315
- workspaceId: task.workspaceId,
316
- subjectId: proof.subjectId,
317
- authorizationRevision: proof.membershipAuthorizationRevision,
318
- };
319
- const selections = await withWorkspaceSubjectRls(
320
- db,
321
- task.workspaceId,
322
- proof.subjectId,
323
- async (tx) => {
324
- const rows = await rawRows<{ canonical_snapshot: unknown }>(
325
- tx,
326
- sql`
327
- select canonical_snapshot from host_mcp_turn_authorities where account_id = ${task.accountId}::uuid
328
- and workspace_id = ${task.workspaceId}::uuid and session_id = ${source.sessionId}::uuid
329
- and turn_id = ${source.turnId}::uuid and owner_subject_id = ${proof.subjectId}`,
330
- );
331
- const selected: SelectedHostMcpTaskGrant[] = [];
332
- for (const row of rows) {
333
- const a = HostMcpAcceptedAuthority.parse(row.canonical_snapshot);
334
- const server = configured.find((c) => c.serverId === a.definition.serverId);
335
- if (!server) continue;
336
- if (
337
- !hostMcpBindingMatchesSelection(
338
- { id: a.bindingId, generation: a.bindingGeneration, definition: a.definition },
339
- server,
340
- )
341
- )
342
- throw new HostMcpDelegationAuthorityError("Host task source destination changed");
343
- const delegation = await getHostMcpDelegation(tx, owner, a.delegationId);
344
- if (
345
- !delegation ||
346
- (delegation.grant.mode === "session" &&
347
- (task.runMode !== "existing_session" ||
348
- task.targetSessionId !== delegation.grant.sessionId))
349
- )
350
- continue;
351
- const authorized = await authorizeDirectHostMcpUse(tx, {
352
- accountId: task.accountId,
353
- workspaceId: task.workspaceId,
354
- sessionId: source.sessionId,
355
- rootSessionId: source.sessionId,
356
- turnId: source.turnId,
357
- attemptId: source.attemptId,
358
- executionGeneration: source.executionGeneration,
359
- initiator: current.initiator,
360
- initiatorContext: current.initiatorContext,
361
- surface: "model",
362
- serverId: a.definition.serverId,
363
- destinationUrl: a.definition.destinationUrl,
364
- credentialTarget: "mcp",
365
- forceRefresh: false,
366
- connectionRef: {
367
- ...a.definition.connectionRef,
368
- hostBinding: { bindingId: a.bindingId, generation: a.bindingGeneration },
369
- },
370
- });
371
- if (!authorized) continue;
372
- selected.push({
373
- delegationId: a.delegationId,
374
- generation: a.delegationGeneration,
375
- bindingId: a.bindingId,
376
- bindingGeneration: a.bindingGeneration,
377
- definition: a.definition,
378
- });
379
- }
380
- return selected;
381
- },
382
- );
383
- if (selections.length) await captureHostMcpTaskAuthorities(db, owner, task, selections);
384
- }
@@ -1,29 +0,0 @@
1
- import {
2
- HostMcpCreateSelections as selections,
3
- type HostMcpCreateSelection,
4
- } from "@opengeni/contracts/host-mcp-bindings";
5
- export type { HostMcpCreateSelection } from "@opengeni/contracts/host-mcp-bindings";
6
- const key = "_opengeni_session_create_host_delegations_v1";
7
-
8
- /** Replay identity only: neither grant admission nor credential authority. */
9
- export function normalizeHostCreateSelection(value: unknown): HostMcpCreateSelection[] {
10
- return selections
11
- .parse(value ?? [])
12
- .sort((a, b) => (a.serverId < b.serverId ? -1 : a.serverId > b.serverId ? 1 : 0));
13
- }
14
- export function metadataWithHostCreateSelection(
15
- metadata: Record<string, unknown>,
16
- value: unknown,
17
- ): Record<string, unknown> {
18
- const next = { ...metadata };
19
- delete next[key];
20
- const canonical = normalizeHostCreateSelection(value);
21
- if (canonical.length) next[key] = canonical;
22
- return next;
23
- }
24
- export function hostCreateSelectionFromMetadata(
25
- metadata: Record<string, unknown>,
26
- ): HostMcpCreateSelection[] {
27
- // A malformed stored identity fails closed; it must not become an empty grant.
28
- return normalizeHostCreateSelection(metadata[key]);
29
- }