@opengeni/core 3.0.1 → 3.1.2

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 (257) hide show
  1. package/README.md +4 -4
  2. package/dist/access/index.d.ts +131 -7
  3. package/dist/access/service-initiator.d.ts +3 -0
  4. package/dist/application/archived-session-imports.d.ts +6 -0
  5. package/dist/application/composer-submit.d.ts +1 -1
  6. package/dist/application/connect-operation.d.ts +6 -0
  7. package/dist/application/external-workspace-members.d.ts +13 -1
  8. package/dist/application/modal-native-live-declaration-join.d.ts +37 -0
  9. package/dist/application/modal-native-original-configuration.d.ts +79 -0
  10. package/dist/application/modal-native-worker-host-transport.d.ts +297 -0
  11. package/dist/application/new-session-drafts.d.ts +29 -2
  12. package/dist/application/organization-privacy.d.ts +57 -0
  13. package/dist/application/prepared-mcp-connection.d.ts +31 -0
  14. package/dist/application/sandbox-recovery.d.ts +58 -0
  15. package/dist/application/session-commands.d.ts +3 -0
  16. package/dist/application/session-tenancy.d.ts +13 -4
  17. package/dist/billing/agent-run-admission.d.ts +21 -0
  18. package/dist/billing/limits.d.ts +27 -1
  19. package/dist/billing/model-call-admission.d.ts +34 -0
  20. package/dist/billing/model-usage-settlement.d.ts +112 -0
  21. package/dist/canonical-human-identities.js +1 -1
  22. package/dist/{chunk-VCZFFEPB.js → chunk-7T2SZ7BI.js} +16 -2
  23. package/dist/chunk-7T2SZ7BI.js.map +1 -0
  24. package/dist/{chunk-ZYCNLYGH.js → chunk-OMJFIIWK.js} +178 -12
  25. package/dist/chunk-OMJFIIWK.js.map +1 -0
  26. package/dist/{chunk-H7X52RI2.js → chunk-QI2M2B2K.js} +83 -8
  27. package/dist/chunk-QI2M2B2K.js.map +1 -0
  28. package/dist/{chunk-OF65T3PM.js → chunk-TTWTXQB2.js} +94 -6
  29. package/dist/chunk-TTWTXQB2.js.map +1 -0
  30. package/dist/codex-model-availability.d.ts +55 -0
  31. package/dist/default-session-model.d.ts +192 -0
  32. package/dist/dependencies.d.ts +41 -7
  33. package/dist/developer-setup-credential-ceiling.d.ts +10 -0
  34. package/dist/domain/agent-config-resolution.d.ts +49 -0
  35. package/dist/domain/automations.d.ts +8 -1
  36. package/dist/domain/capabilities.d.ts +112 -8
  37. package/dist/domain/connection-account-selection-error.d.ts +8 -0
  38. package/dist/domain/connector-tool-permissions.d.ts +62 -0
  39. package/dist/domain/direct-model-provider.d.ts +3 -0
  40. package/dist/domain/editable-artifacts/agent-application.d.ts +10 -1
  41. package/dist/domain/editable-artifacts/durable-export.d.ts +22 -1
  42. package/dist/domain/editable-artifacts/ports.d.ts +10 -0
  43. package/dist/domain/editable-artifacts/service.d.ts +3 -1
  44. package/dist/domain/editable-artifacts/types.d.ts +1 -1
  45. package/dist/domain/encryption-key.d.ts +12 -0
  46. package/dist/domain/insights-timing.d.ts +16 -0
  47. package/dist/domain/insights-usage.d.ts +27 -0
  48. package/dist/domain/insights.d.ts +21 -3
  49. package/dist/domain/knowledge-files.d.ts +1 -1
  50. package/dist/domain/knowledge-preparation.d.ts +10 -0
  51. package/dist/domain/knowledge-preparation.test.d.ts +1 -0
  52. package/dist/domain/knowledge-search.d.ts +15 -76
  53. package/dist/domain/mcp-account-bindings.d.ts +17 -0
  54. package/dist/domain/mcp-account-routes.d.ts +21 -0
  55. package/dist/domain/native-mcp-connection-admission.d.ts +3 -0
  56. package/dist/domain/organization-integration-catalog.d.ts +15 -0
  57. package/dist/domain/personal-connection-delegations.d.ts +73 -12
  58. package/dist/domain/pr-review.d.ts +121 -6
  59. package/dist/domain/realtime-voice-billing.d.ts +103 -0
  60. package/dist/domain/resources.d.ts +17 -1
  61. package/dist/domain/scheduled-task-access.d.ts +265 -0
  62. package/dist/domain/scheduled-task-account-audit.d.ts +27 -0
  63. package/dist/domain/scheduled-task-audience.d.ts +70 -0
  64. package/dist/domain/scheduled-tasks.d.ts +61 -18
  65. package/dist/domain/session-connection-accounts.d.ts +53 -0
  66. package/dist/domain/session-file-access.d.ts +4 -0
  67. package/dist/domain/session-native-connection-replacement.d.ts +14 -0
  68. package/dist/domain/session-start-timing.d.ts +7 -0
  69. package/dist/domain/session-tool-policy.d.ts +102 -4
  70. package/dist/domain/sessions.d.ts +179 -15
  71. package/dist/domain/site-audience.d.ts +52 -0
  72. package/dist/domain/skills.d.ts +29 -1
  73. package/dist/domain/slack-bot.d.ts +46 -1
  74. package/dist/domain/slack-publication-secret-safety.d.ts +1 -1
  75. package/dist/domain/vaults/index.d.ts +7 -0
  76. package/dist/domain/vaults/mapping.d.ts +25 -0
  77. package/dist/domain/vaults/provider-registry.d.ts +72 -0
  78. package/dist/domain/vaults/provider-sync.d.ts +176 -0
  79. package/dist/domain/vaults/provider-writes.d.ts +57 -0
  80. package/dist/domain/vaults/secret-resolver.d.ts +34 -0
  81. package/dist/domain/vaults/types.d.ts +24 -0
  82. package/dist/domain/vaults/vault-service.d.ts +35 -0
  83. package/dist/domain/voice-input-billing.d.ts +77 -0
  84. package/dist/domain/web-search-billing.d.ts +51 -0
  85. package/dist/domain/workspace-credits.d.ts +7 -0
  86. package/dist/editable-artifact-live/application.d.ts +2 -1
  87. package/dist/editable-artifact-live/server.d.ts +5 -1
  88. package/dist/editable-artifact-live/ticket.d.ts +2 -1
  89. package/dist/editable-artifact-live/types.d.ts +9 -0
  90. package/dist/editable-artifact-live.js +2 -2
  91. package/dist/editable-artifacts.js +10 -2
  92. package/dist/goal-admission.d.ts +627 -0
  93. package/dist/host-session-usage-limit.d.ts +15 -0
  94. package/dist/index.d.ts +43 -8
  95. package/dist/index.js +22682 -11743
  96. package/dist/index.js.map +1 -1
  97. package/dist/managed-session.d.ts +110 -7
  98. package/dist/model-call.d.ts +58 -0
  99. package/dist/model-catalog.d.ts +29 -3
  100. package/dist/model-provider-credentials.d.ts +43 -0
  101. package/dist/product-usage-metrics.d.ts +30 -0
  102. package/dist/rigs/index.d.ts +1 -13
  103. package/dist/sandbox/fleet.d.ts +58 -4
  104. package/dist/sandbox/routing.d.ts +42 -2
  105. package/dist/sandbox/runtime-settings.d.ts +27 -17
  106. package/dist/session-authorization.d.ts +10 -0
  107. package/dist/session-usage-limit-metrics.d.ts +60 -0
  108. package/dist/session-usage-limits.d.ts +89 -0
  109. package/dist/site-agent-clamp.d.ts +71 -0
  110. package/dist/site-session-origin.d.ts +39 -2
  111. package/dist/transcription.d.ts +120 -3
  112. package/dist/turn-surface.d.ts +20 -0
  113. package/dist/user-presence.d.ts +32 -0
  114. package/dist/workflow-wake-contract.d.ts +13 -1
  115. package/package.json +15 -22
  116. package/src/access/index.ts +961 -38
  117. package/src/access/service-initiator.ts +50 -0
  118. package/src/application/api-integration-servers.ts +9 -0
  119. package/src/application/archived-session-imports.ts +161 -0
  120. package/src/application/composer-submit.ts +3 -2
  121. package/src/application/connect-authority.ts +3 -3
  122. package/src/application/connect-operation.ts +37 -3
  123. package/src/application/external-continuation.ts +20 -3
  124. package/src/application/external-link-work-admission.ts +3 -0
  125. package/src/application/external-workspace-members.ts +51 -3
  126. package/src/application/modal-native-live-declaration-join.ts +196 -0
  127. package/src/application/modal-native-original-configuration.ts +261 -0
  128. package/src/application/modal-native-worker-host-transport.ts +263 -0
  129. package/src/application/new-session-drafts.ts +172 -55
  130. package/src/application/organization-privacy.ts +161 -0
  131. package/src/application/prepared-mcp-connection.ts +133 -0
  132. package/src/application/sandbox-recovery.ts +78 -0
  133. package/src/application/session-commands.ts +73 -1
  134. package/src/application/session-mcp-credential-rotation.ts +61 -18
  135. package/src/application/session-tenancy.ts +262 -44
  136. package/src/application/user-resource-grants.ts +1 -29
  137. package/src/billing/agent-run-admission.ts +117 -0
  138. package/src/billing/limits.ts +138 -14
  139. package/src/billing/model-call-admission.ts +118 -0
  140. package/src/billing/model-usage-settlement.ts +472 -0
  141. package/src/codex-model-availability.ts +264 -0
  142. package/src/default-session-model.ts +692 -0
  143. package/src/dependencies.ts +48 -6
  144. package/src/developer-setup-credential-ceiling.ts +60 -0
  145. package/src/domain/agent-config-resolution.ts +148 -0
  146. package/src/domain/automations.ts +76 -2
  147. package/src/domain/capabilities.ts +830 -181
  148. package/src/domain/connection-account-selection-error.ts +19 -0
  149. package/src/domain/connector-tool-permissions.ts +572 -0
  150. package/src/domain/direct-model-provider.ts +62 -0
  151. package/src/domain/editable-artifacts/agent-application.ts +44 -6
  152. package/src/domain/editable-artifacts/durable-export.ts +100 -5
  153. package/src/domain/editable-artifacts/in-memory.ts +6 -0
  154. package/src/domain/editable-artifacts/ports.ts +11 -0
  155. package/src/domain/editable-artifacts/service.ts +20 -0
  156. package/src/domain/editable-artifacts/types.ts +1 -1
  157. package/src/domain/encryption-key.ts +26 -0
  158. package/src/domain/file-owner.ts +16 -1
  159. package/src/domain/github-action-policies.ts +1 -1
  160. package/src/domain/github-skill-source.ts +3 -1
  161. package/src/domain/insights-timing.ts +95 -0
  162. package/src/domain/insights-usage.ts +67 -0
  163. package/src/domain/insights.ts +167 -43
  164. package/src/domain/knowledge-files.ts +10 -3
  165. package/src/domain/knowledge-messages.ts +1 -0
  166. package/src/domain/knowledge-preparation.test.ts +276 -0
  167. package/src/domain/knowledge-preparation.ts +147 -0
  168. package/src/domain/knowledge-search.test.ts +617 -5
  169. package/src/domain/knowledge-search.ts +240 -23
  170. package/src/domain/mcp-account-bindings.ts +169 -0
  171. package/src/domain/mcp-account-routes.ts +152 -0
  172. package/src/domain/native-mcp-connection-admission.ts +14 -0
  173. package/src/domain/organization-integration-catalog.ts +88 -0
  174. package/src/domain/personal-connection-delegations.ts +399 -257
  175. package/src/domain/pr-review.ts +24 -8
  176. package/src/domain/realtime-voice-billing.ts +425 -0
  177. package/src/domain/resources.ts +50 -4
  178. package/src/domain/scheduled-task-access.ts +1089 -0
  179. package/src/domain/scheduled-task-account-audit.ts +90 -0
  180. package/src/domain/scheduled-task-audience.ts +249 -0
  181. package/src/domain/scheduled-tasks.ts +1313 -374
  182. package/src/domain/session-connection-accounts.ts +239 -0
  183. package/src/domain/session-file-access.ts +47 -0
  184. package/src/domain/session-native-connection-replacement.ts +64 -0
  185. package/src/domain/session-start-timing.ts +60 -0
  186. package/src/domain/session-tool-policy.ts +489 -14
  187. package/src/domain/sessions.ts +2254 -717
  188. package/src/domain/site-audience.ts +130 -0
  189. package/src/domain/skills.ts +9 -0
  190. package/src/domain/slack-bot.ts +141 -4
  191. package/src/domain/slack-publication-secret-safety.ts +1 -1
  192. package/src/domain/vaults/index.ts +19 -0
  193. package/src/domain/vaults/mapping.ts +190 -0
  194. package/src/domain/vaults/provider-registry.ts +201 -0
  195. package/src/domain/vaults/provider-sync.ts +531 -0
  196. package/src/domain/vaults/provider-writes.ts +288 -0
  197. package/src/domain/vaults/secret-resolver.ts +464 -0
  198. package/src/domain/vaults/types.ts +26 -0
  199. package/src/domain/vaults/vault-service.ts +1316 -0
  200. package/src/domain/voice-input-billing.ts +330 -0
  201. package/src/domain/web-search-billing.ts +169 -0
  202. package/src/domain/workspace-credits.ts +13 -0
  203. package/src/editable-artifact-live/application.ts +8 -1
  204. package/src/editable-artifact-live/server.ts +154 -6
  205. package/src/editable-artifact-live/ticket.ts +86 -1
  206. package/src/editable-artifact-live/types.ts +10 -0
  207. package/src/goal-admission.ts +220 -0
  208. package/src/host-session-usage-limit.ts +25 -0
  209. package/src/index.ts +75 -11
  210. package/src/managed-session.ts +137 -6
  211. package/src/model-call.ts +79 -0
  212. package/src/model-catalog.ts +345 -91
  213. package/src/model-provider-credentials.ts +394 -0
  214. package/src/product-usage-metrics.ts +101 -0
  215. package/src/rigs/index.ts +23 -111
  216. package/src/sandbox/fleet.ts +253 -28
  217. package/src/sandbox/routing.ts +204 -43
  218. package/src/sandbox/runtime-settings.ts +159 -85
  219. package/src/session-authorization.ts +172 -23
  220. package/src/session-usage-limit-metrics.ts +95 -0
  221. package/src/session-usage-limits.ts +415 -0
  222. package/src/site-agent-clamp.ts +226 -0
  223. package/src/site-session-origin.ts +84 -5
  224. package/src/transcription.ts +123 -4
  225. package/src/turn-surface.ts +47 -0
  226. package/src/user-presence.ts +134 -0
  227. package/src/workflow-wake-contract.ts +14 -1
  228. package/dist/application/host-mcp-owner.d.ts +0 -6
  229. package/dist/application/host-mcp-resolvers.d.ts +0 -13
  230. package/dist/chunk-5WZYFL45.js +0 -266
  231. package/dist/chunk-5WZYFL45.js.map +0 -1
  232. package/dist/chunk-H7X52RI2.js.map +0 -1
  233. package/dist/chunk-OF65T3PM.js.map +0 -1
  234. package/dist/chunk-VCZFFEPB.js.map +0 -1
  235. package/dist/chunk-ZYCNLYGH.js.map +0 -1
  236. package/dist/domain/connection-authority.d.ts +0 -97
  237. package/dist/domain/connection-authority.js +0 -169
  238. package/dist/domain/connection-authority.js.map +0 -1
  239. package/dist/domain/environments.d.ts +0 -33
  240. package/dist/domain/host-mcp-authority-source-admission.d.ts +0 -10
  241. package/dist/domain/host-mcp-task-admission.d.ts +0 -18
  242. package/dist/domain/packs.d.ts +0 -59
  243. package/dist/domain/product-integration-pack.d.ts +0 -44
  244. package/dist/domain/product-integration-skill.gen.d.ts +0 -5
  245. package/dist/remote-mcp-credentials.d.ts +0 -23
  246. package/dist/remote-mcp-credentials.js +0 -11
  247. package/dist/remote-mcp-credentials.js.map +0 -1
  248. package/src/application/host-mcp-owner.ts +0 -41
  249. package/src/application/host-mcp-resolvers.ts +0 -84
  250. package/src/domain/connection-authority.ts +0 -329
  251. package/src/domain/environments.ts +0 -120
  252. package/src/domain/host-mcp-authority-source-admission.ts +0 -25
  253. package/src/domain/host-mcp-task-admission.ts +0 -121
  254. package/src/domain/packs.ts +0 -699
  255. package/src/domain/product-integration-pack.ts +0 -42
  256. package/src/domain/product-integration-skill.gen.ts +0 -52
  257. package/src/remote-mcp-credentials.ts +0 -361
@@ -0,0 +1,50 @@
1
+ import { ServiceTurnInitiatorContext, type AccessGrant } from "@opengeni/contracts";
2
+ import { HTTPException } from "hono/http-exception";
3
+
4
+ /** Validated provenance only. The API key remains the authenticated principal. */
5
+ export function serviceInitiatorFromHeaders(
6
+ headers: Headers,
7
+ ): Pick<AccessGrant, "serviceInitiator" | "serviceInitiatorContext"> | null {
8
+ const name = headers.get("x-opengeni-service-initiator");
9
+ const rawContext = headers.get("x-opengeni-service-context");
10
+ if (name === null && rawContext === null) return null;
11
+ if (headers.has("x-opengeni-external-actor")) {
12
+ throw new HTTPException(422, {
13
+ message: "service initiator and external actor (asUser) are mutually exclusive",
14
+ });
15
+ }
16
+ if (name === null || !/^[a-z0-9][a-z0-9:._-]{0,63}$/.test(name)) {
17
+ throw new HTTPException(422, {
18
+ message: "x-opengeni-service-initiator must match ^[a-z0-9][a-z0-9:._-]{0,63}$",
19
+ });
20
+ }
21
+ let context: Record<string, string | number | boolean> = {};
22
+ if (rawContext !== null) {
23
+ try {
24
+ if (new TextEncoder().encode(rawContext).byteLength > 2048) throw new Error("oversize");
25
+ const parsed: unknown = JSON.parse(rawContext);
26
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("object");
27
+ if (
28
+ !Object.values(parsed).every(
29
+ (value) =>
30
+ typeof value === "string" ||
31
+ typeof value === "boolean" ||
32
+ (typeof value === "number" && Number.isFinite(value)),
33
+ )
34
+ )
35
+ throw new Error("flat");
36
+ context = parsed as typeof context;
37
+ // Keep delegated provenance's reserved, Opengeni-owned lineage fields.
38
+ if (!ServiceTurnInitiatorContext.safeParse(context).success) throw new Error("reserved");
39
+ } catch {
40
+ throw new HTTPException(422, {
41
+ message:
42
+ "x-opengeni-service-context must be a JSON object of flat string/number/boolean values, at most 2048 UTF-8 bytes, without reserved Opengeni provenance fields",
43
+ });
44
+ }
45
+ }
46
+ return {
47
+ serviceInitiator: { kind: "service", subjectId: name },
48
+ serviceInitiatorContext: context,
49
+ };
50
+ }
@@ -15,6 +15,7 @@ import type {
15
15
  ResolveConnectionCredentialInput,
16
16
  ResolveConnectionCredentialResult,
17
17
  } from "@opengeni/db";
18
+ import { apiIntegrationToolEffect } from "@opengeni/db";
18
19
  import type { FetchLike } from "@opengeni/network";
19
20
  import type { LocalMcpServerRegistration } from "@opengeni/runtime";
20
21
 
@@ -79,6 +80,14 @@ export function buildApiIntegrationMcpServers(
79
80
  return {
80
81
  id: integration.serverId,
81
82
  server,
83
+ effectAuthority: (toolName) => ({
84
+ kind: "api_integration",
85
+ instanceId: integration.instanceId,
86
+ baseUrl: integration.baseUrl,
87
+ connectionRef: integration.connectionRef,
88
+ connectionAuthorityGeneration: integration.connectionAuthorityGeneration,
89
+ effect: apiIntegrationToolEffect(integration.revision, toolName),
90
+ }),
82
91
  approvalAuthority: {
83
92
  kind: "api_integration",
84
93
  capabilityId: integration.capabilityId,
@@ -0,0 +1,161 @@
1
+ import {
2
+ AppendArchivedSessionEventsRequest,
3
+ AppendArchivedSessionEventsResponse,
4
+ ImportArchivedSessionRequest,
5
+ ImportArchivedSessionResponse,
6
+ type Permission,
7
+ type SessionEvent,
8
+ } from "@opengeni/contracts";
9
+ import {
10
+ ArchivedSessionImportError,
11
+ appendArchivedSessionEvents,
12
+ getArchivedSessionImportId,
13
+ importArchivedSession,
14
+ withSessionRlsActorContext,
15
+ } from "@opengeni/db";
16
+ import { publishDurableSessionEvents } from "@opengeni/events";
17
+ import { HTTPException } from "hono/http-exception";
18
+ import {
19
+ hasVerifiedOwningUserAuthorization,
20
+ requirePermission,
21
+ requireResolvedAccessGrantAuthorization,
22
+ type AccessGrantAuthorization,
23
+ } from "../access";
24
+ import type { ApiRouteDeps } from "../dependencies";
25
+ import { creationInitiatorForGrant } from "../domain/sessions";
26
+ import { fileOwnerContextForAccess } from "../domain/file-owner";
27
+ import {
28
+ grantHasAgentAttemptAuthority,
29
+ requireSessionAuthorization,
30
+ } from "../session-authorization";
31
+ import { externalContinuationCommitAuthorizer } from "./external-continuation";
32
+ import { requireManagedHumanPrivateSessionCreate } from "./session-tenancy";
33
+
34
+ export { ArchivedSessionImportError } from "@opengeni/db";
35
+
36
+ function requireImportAuthorization(
37
+ authorization: AccessGrantAuthorization,
38
+ workspaceId: string,
39
+ permission: Permission,
40
+ ) {
41
+ const grant = requireResolvedAccessGrantAuthorization(authorization, workspaceId);
42
+ requirePermission(grant, permission);
43
+ if (grantHasAgentAttemptAuthority(grant)) {
44
+ throw new HTTPException(403, {
45
+ message: "Session history imports require a user or service request",
46
+ });
47
+ }
48
+ return grant;
49
+ }
50
+
51
+ function importerScope(
52
+ authorization: AccessGrantAuthorization,
53
+ workspaceId: string,
54
+ permission: Permission,
55
+ fileOwnerSubjectId: string | null,
56
+ ) {
57
+ const grant = authorization.grant;
58
+ const apiKeyId =
59
+ grant.principalKind === "api_key" && grant.subjectId.startsWith("api_key:")
60
+ ? grant.subjectId.slice("api_key:".length)
61
+ : undefined;
62
+ const beforeCommit = externalContinuationCommitAuthorizer(authorization);
63
+ return {
64
+ accountId: grant.accountId,
65
+ workspaceId,
66
+ subjectId: grant.subjectId,
67
+ fileOwnerSubjectId,
68
+ requiredPermission: permission,
69
+ requireLiveSubject:
70
+ hasVerifiedOwningUserAuthorization(authorization) || authorization.canonicalLocalHumanSession,
71
+ ...(apiKeyId ? { apiKeyId } : {}),
72
+ ...(beforeCommit ? { beforeCommit } : {}),
73
+ };
74
+ }
75
+
76
+ /** Committed import truth survives unavailable fanout; SSE catch-up repairs it.
77
+ * Never make a known committed append look failed and invite an unsafe retry. */
78
+ async function publishImportedEvents(
79
+ deps: ApiRouteDeps,
80
+ workspaceId: string,
81
+ sessionId: string,
82
+ events: SessionEvent[],
83
+ ): Promise<void> {
84
+ if (!events.length) return;
85
+ try {
86
+ await publishDurableSessionEvents(deps.bus, workspaceId, sessionId, events);
87
+ } catch {
88
+ console.warn("[session-import] committed timeline fanout deferred to replay", { sessionId });
89
+ }
90
+ }
91
+
92
+ export async function importArchivedSessionForRequest(
93
+ deps: ApiRouteDeps,
94
+ authorization: AccessGrantAuthorization,
95
+ workspaceId: string,
96
+ rawPayload: unknown,
97
+ ): Promise<ImportArchivedSessionResponse> {
98
+ const grant = requireImportAuthorization(authorization, workspaceId, "sessions:create");
99
+ const payload = ImportArchivedSessionRequest.parse(rawPayload);
100
+ if (payload.visibility === "user_private") {
101
+ await requireManagedHumanPrivateSessionCreate(deps, authorization, workspaceId);
102
+ }
103
+ const creator = creationInitiatorForGrant(grant);
104
+ if (!creator.initiator)
105
+ throw new HTTPException(403, { message: "Import creator is unavailable" });
106
+ const actor = await fileOwnerContextForAccess(deps, authorization, "sessions:create");
107
+ const result = await withSessionRlsActorContext(actor, () =>
108
+ importArchivedSession(deps.db, {
109
+ ...importerScope(
110
+ authorization,
111
+ workspaceId,
112
+ "sessions:create",
113
+ actor.privateFileOwnerSubjectId ?? null,
114
+ ),
115
+ payload,
116
+ createdBy: creator.initiator!,
117
+ createdByContext: creator.context ?? {},
118
+ }),
119
+ );
120
+ await publishImportedEvents(deps, workspaceId, result.session.id, result.events);
121
+ return ImportArchivedSessionResponse.parse(result);
122
+ }
123
+
124
+ export async function appendArchivedSessionEventsForRequest(
125
+ deps: ApiRouteDeps,
126
+ authorization: AccessGrantAuthorization,
127
+ workspaceId: string,
128
+ importId: string,
129
+ rawPayload: unknown,
130
+ ): Promise<AppendArchivedSessionEventsResponse> {
131
+ const grant = requireImportAuthorization(authorization, workspaceId, "sessions:control");
132
+ const payload = AppendArchivedSessionEventsRequest.parse(rawPayload);
133
+ const actor = await fileOwnerContextForAccess(deps, authorization, "sessions:control");
134
+ const result = await withSessionRlsActorContext(actor, async () => {
135
+ const sessionId = await getArchivedSessionImportId(
136
+ deps.db,
137
+ workspaceId,
138
+ grant.subjectId,
139
+ importId,
140
+ );
141
+ if (!sessionId) throw new ArchivedSessionImportError("SESSION_IMPORT_NOT_FOUND");
142
+ await requireSessionAuthorization(deps, grant, {
143
+ sessionId,
144
+ operation: "session.append",
145
+ surface: "core",
146
+ });
147
+ return appendArchivedSessionEvents(deps.db, {
148
+ ...importerScope(
149
+ authorization,
150
+ workspaceId,
151
+ "sessions:control",
152
+ actor.privateFileOwnerSubjectId ?? null,
153
+ ),
154
+ importId,
155
+ sessionId,
156
+ payload,
157
+ });
158
+ });
159
+ await publishImportedEvents(deps, workspaceId, result.sessionId, result.events);
160
+ return AppendArchivedSessionEventsResponse.parse(result);
161
+ }
@@ -26,7 +26,7 @@ export type SubmitComposerDraftForRequestOptions = {
26
26
  * This is the application boundary shared by the stock HTTP adapter and an
27
27
  * in-process embedding host. A host may prepare its own durable business state
28
28
  * before calling this function and project the returned receipt afterward, but
29
- * OpenGeni remains the sole authority for draft validation/rotation, event
29
+ * Opengeni remains the sole authority for draft validation/rotation, event
30
30
  * append, turn creation, routing, and idempotent replay.
31
31
  */
32
32
  export async function submitComposerDraftForRequest(
@@ -48,7 +48,8 @@ export async function submitComposerDraftForRequest(
48
48
  reasoningEffort: input.reasoningEffort,
49
49
  latencyMode: input.latencyMode,
50
50
  mcpCredentialUpdates: input.mcpCredentialUpdates ?? [],
51
- connectionAuthorities: input.connectionAuthorities,
51
+ connectionAccounts: input.connectionAccounts,
52
+
52
53
  ...(input.personalResourceAttachment
53
54
  ? { personalResourceAttachment: input.personalResourceAttachment }
54
55
  : {}),
@@ -8,7 +8,7 @@ import {
8
8
  lockExternalWorkspaceMembershipLifecycle,
9
9
  withWorkspaceSubjectRls,
10
10
  type Database,
11
- lockConnectionSetupKey,
11
+ lockConnectionSetupKeyAuthority,
12
12
  nestedPostgresSqlState,
13
13
  } from "@opengeni/db";
14
14
  import { HTTPException } from "hono/http-exception";
@@ -53,8 +53,8 @@ export async function requireConnectOwnerAuthority(
53
53
  if (state.subjectId.startsWith("external_user:"))
54
54
  throw new HTTPException(403, { message: "External Connect continuation required" });
55
55
  if (state.subjectId.startsWith("api_key:")) {
56
- const permissions = await lockConnectionSetupKey(db, state);
57
- if (!permissions || !hasPermission(permissions, permission))
56
+ const authority = await lockConnectionSetupKeyAuthority(db, state);
57
+ if (!authority || !hasPermission(authority.permissions, permission, authority.permissionMode))
58
58
  throw new HTTPException(403, { message: "Connection API key authority changed" });
59
59
  return;
60
60
  }
@@ -1,4 +1,7 @@
1
1
  import type { ConnectAttempt } from "@opengeni/contracts/connect";
2
+ import { assertOrganizationIntegrationAllowed } from "@opengeni/contracts";
3
+ import { withOrganizationIntegrationPolicyFence } from "@opengeni/db/organization-integration-policy";
4
+ import { integrationKeyForConnectProvider } from "../domain/organization-integration-catalog";
2
5
  import {
3
6
  claimConnectOperation,
4
7
  finishConnectOperation,
@@ -31,11 +34,17 @@ export async function executeConnectOperation(input: {
31
34
  operationId: string;
32
35
  /** Caller computes a stable keyed digest if input contains low-entropy secrets. */
33
36
  inputDigest: string;
37
+ /** Trusted server adapter classification, never derived from request data.
38
+ * Cancellation releases pending setup; it acquires no integration authority. */
39
+ purpose?: "acquisition" | "cancellation";
40
+ /** Trusted adapter's exact attempt fence, inside the organization policy
41
+ * prefix and before Connect row locks, for both claim and final commit. */
42
+ beforeOperation?: ((tx: Database) => Promise<void>) | undefined;
34
43
  authorize: ConnectOperationAuthorization;
35
44
  execute: (attempt: ConnectAttempt) => Promise<PreparedConnectOperation>;
36
45
  }): Promise<ConnectAttempt> {
37
46
  // Snapshot caller-controlled identity before the first asynchronous boundary.
38
- const { db, authorize, execute } = input;
47
+ const { db, authorize, execute, beforeOperation, purpose = "acquisition" } = input;
39
48
  const scope = { ...input.scope };
40
49
  const operation = {
41
50
  attemptId: input.attemptId,
@@ -44,8 +53,33 @@ export async function executeConnectOperation(input: {
44
53
  inputDigest: input.inputDigest,
45
54
  authorize,
46
55
  };
47
- const claim = await claimConnectOperation(db, scope, operation);
56
+ const claim = await withOrganizationIntegrationPolicyFence(db, scope, async (tx, policy) => {
57
+ await beforeOperation?.(tx);
58
+ return claimConnectOperation(tx, scope, {
59
+ ...operation,
60
+ authorizeAcquisition: async (_tx, attempt) => {
61
+ if (purpose === "cancellation") return;
62
+ assertOrganizationIntegrationAllowed(
63
+ policy,
64
+ integrationKeyForConnectProvider(attempt.providerId),
65
+ );
66
+ },
67
+ });
68
+ });
48
69
  if (claim.status === "replayed") return claim.attempt;
49
70
  const prepared = await execute(structuredClone(claim.attempt));
50
- return finishConnectOperation(db, scope, { ...operation, commit: prepared.commit });
71
+ return withOrganizationIntegrationPolicyFence(db, scope, async (tx, policy) => {
72
+ await beforeOperation?.(tx);
73
+ return finishConnectOperation(tx, scope, {
74
+ ...operation,
75
+ commit: prepared.commit,
76
+ authorizeAcquisition: async (_tx, attempt) => {
77
+ if (purpose === "cancellation") return;
78
+ assertOrganizationIntegrationAllowed(
79
+ policy,
80
+ integrationKeyForConnectProvider(attempt.providerId),
81
+ );
82
+ },
83
+ });
84
+ });
51
85
  }
@@ -4,7 +4,9 @@ import {
4
4
  ensureExternalIdentity,
5
5
  resolveExternalIdentityLink,
6
6
  getWorkspaceGrant,
7
- lockActiveExternalOrganizationKey,
7
+ lockActiveExternalOrganizationKeyAuthority,
8
+ getOrganizationApiKey,
9
+ requireWorkspace,
8
10
  lockExternalWorkspaceMembershipLifecycle,
9
11
  managedPersonalWorkspacePermissions,
10
12
  nestedPostgresSqlState,
@@ -13,6 +15,7 @@ import {
13
15
  } from "@opengeni/db";
14
16
  import {
15
17
  hasPermission,
18
+ organizationWorkspaceInScope,
16
19
  externalActorContinuationForAuthorization,
17
20
  type AccessGrantAuthorization,
18
21
  } from "../access";
@@ -65,12 +68,26 @@ export async function requireExternalContinuationAuthority(
65
68
  };
66
69
  if (actor.accountId !== scope.accountId || actor.effectiveSubjectId !== scope.subjectId) deny();
67
70
  await lockExternalWorkspaceMembershipLifecycle(tx, scope.accountId);
68
- const permissions = await lockActiveExternalOrganizationKey(
71
+ const authority = await lockActiveExternalOrganizationKeyAuthority(
69
72
  tx,
70
73
  scope.accountId,
71
74
  actor.authenticatingApiKeyId,
72
75
  );
73
- if (!permissions || required.some((value) => !hasPermission(permissions, value))) deny();
76
+ const key = authority
77
+ ? await getOrganizationApiKey(tx, scope.accountId, actor.authenticatingApiKeyId)
78
+ : null;
79
+ if (
80
+ !authority ||
81
+ !key ||
82
+ !organizationWorkspaceInScope(key.workspaceScope ?? { kind: "all" }, scope.workspaceId) ||
83
+ required.some((value) => !hasPermission(authority.permissions, value, authority.permissionMode))
84
+ )
85
+ deny();
86
+ if (
87
+ key?.permissionMode === "explicit" &&
88
+ (await requireWorkspace(tx, scope.workspaceId)).kind !== "shared"
89
+ )
90
+ deny();
74
91
  const identity = await ensureExternalIdentity(tx, { accountId: scope.accountId, ...reference });
75
92
  if (
76
93
  identity.id !== actor.externalIdentityId ||
@@ -22,6 +22,9 @@ function snapshotFor(authorization: AccessGrantAuthorization | undefined) {
22
22
  ? ExternalLinkWorkSnapshot.parse({
23
23
  ...continuation,
24
24
  permissions: [...authorization!.grant.permissions],
25
+ ...(authorization!.grant.permissionMode === "explicit"
26
+ ? { permissionMode: "explicit" }
27
+ : {}),
25
28
  })
26
29
  : null;
27
30
  }
@@ -4,6 +4,7 @@ import {
4
4
  AddExternalWorkspaceMemberRequest,
5
5
  ExternalIdentityReference,
6
6
  CancelExternalWorkspaceMemberGrantRequest,
7
+ UpdateExternalWorkspaceMemberRequest,
7
8
  type ExternalIdentity,
8
9
  } from "@opengeni/contracts/external-identities";
9
10
  import {
@@ -16,6 +17,7 @@ import {
16
17
  withWorkspaceSubjectRls,
17
18
  addExternalWorkspaceMemberOperation,
18
19
  cancelExternalWorkspaceMemberGrant,
20
+ updateExternalWorkspaceMemberOperation,
19
21
  lookupExternalIdentity,
20
22
  nestedPostgresSqlState,
21
23
  } from "@opengeni/db";
@@ -24,11 +26,14 @@ import {
24
26
  accountScopedApiKeyWorkspaceAuthority,
25
27
  hasPermission,
26
28
  requireAccessContext,
29
+ requireApiKeyDelegationContext,
27
30
  requireFreshAccessGrant,
28
31
  type AccessDeps,
29
32
  } from "../access";
30
33
 
31
- /** Explicit host onboarding. Ordinary asUser reads never call this operation.
34
+ /** Explicit host onboarding. Ordinary asUser requests never call this
35
+ * operation; their first-use membership is `provisionExternalMemberOnFirstUse`
36
+ * in `../access`, under the same key authority and lifecycle fence.
32
37
  * Existing memberships are not overwritten, including reduced permissions. */
33
38
  export async function addExternalWorkspaceMemberForRequest(
34
39
  c: Context,
@@ -38,6 +43,7 @@ export async function addExternalWorkspaceMemberForRequest(
38
43
  ): Promise<ExternalIdentity> {
39
44
  const payload = AddExternalWorkspaceMemberRequest.parse(input);
40
45
  const context = await requireAccessContext(c, deps);
46
+ requireApiKeyDelegationContext(context, payload.permissions);
41
47
  const authority = accountScopedApiKeyWorkspaceAuthority(context);
42
48
  if (!authority)
43
49
  throw new HTTPException(403, {
@@ -128,7 +134,12 @@ function rethrowExternalWorkspaceOperation(error: unknown): never {
128
134
  throw error;
129
135
  }
130
136
 
131
- async function externalService(c: Context, deps: AccessDeps, organizationId: string) {
137
+ async function externalService(
138
+ c: Context,
139
+ deps: AccessDeps,
140
+ organizationId: string,
141
+ workspaceId?: string,
142
+ ) {
132
143
  const context = await requireAccessContext(c, deps);
133
144
  const authority = accountScopedApiKeyWorkspaceAuthority(context);
134
145
  if (
@@ -140,6 +151,11 @@ async function externalService(c: Context, deps: AccessDeps, organizationId: str
140
151
  message: "External membership operation requires an organization service key",
141
152
  });
142
153
  }
154
+ if (workspaceId !== undefined) {
155
+ const grant = await requireFreshAccessGrant(c, deps, workspaceId, "members:manage");
156
+ if (grant.accountId !== organizationId)
157
+ throw new HTTPException(403, { message: "External membership workspace authority changed" });
158
+ }
143
159
  return { organizationId, actorSubjectId: context.subjectId };
144
160
  }
145
161
 
@@ -168,7 +184,7 @@ export async function cancelExternalWorkspaceMemberGrantForRequest(
168
184
  membershipId: string,
169
185
  input: unknown,
170
186
  ) {
171
- const service = await externalService(c, deps, organizationId);
187
+ const service = await externalService(c, deps, organizationId, workspaceId);
172
188
  const request = CancelExternalWorkspaceMemberGrantRequest.safeParse(input);
173
189
  if (!request.success)
174
190
  throw new HTTPException(422, { message: "Invalid external grant cancellation" });
@@ -182,3 +198,35 @@ export async function cancelExternalWorkspaceMemberGrantForRequest(
182
198
  rethrowExternalWorkspaceOperation(error);
183
199
  }
184
200
  }
201
+
202
+ /** Keyed permission change for an existing external member. Widening only
203
+ * rewrites the set; narrowing also advances the member's authorization
204
+ * revision so live authority re-checks. Never cancels or tears down work. */
205
+ export async function updateExternalWorkspaceMemberForRequest(
206
+ c: Context,
207
+ deps: AccessDeps,
208
+ organizationId: string,
209
+ workspaceId: string,
210
+ membershipId: string,
211
+ input: unknown,
212
+ ) {
213
+ const service = await externalService(c, deps, organizationId, workspaceId);
214
+ const request = UpdateExternalWorkspaceMemberRequest.safeParse(input);
215
+ if (!request.success)
216
+ throw new HTTPException(422, {
217
+ message: `Invalid external member update: ${request.error.issues
218
+ .slice(0, 5)
219
+ .map((issue) => `${issue.path.map(String).join(".") || "request"}: ${issue.message}`)
220
+ .join("; ")}`,
221
+ });
222
+ requireApiKeyDelegationContext(await requireAccessContext(c, deps), request.data.permissions);
223
+ try {
224
+ return await updateExternalWorkspaceMemberOperation(
225
+ deps.db,
226
+ { ...service, workspaceId, membershipId },
227
+ request.data,
228
+ );
229
+ } catch (error) {
230
+ rethrowExternalWorkspaceOperation(error);
231
+ }
232
+ }