@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.
- package/README.md +4 -4
- package/dist/access/index.d.ts +131 -7
- package/dist/access/service-initiator.d.ts +3 -0
- package/dist/application/archived-session-imports.d.ts +6 -0
- package/dist/application/composer-submit.d.ts +1 -1
- package/dist/application/connect-operation.d.ts +6 -0
- package/dist/application/external-workspace-members.d.ts +13 -1
- package/dist/application/modal-native-live-declaration-join.d.ts +37 -0
- package/dist/application/modal-native-original-configuration.d.ts +79 -0
- package/dist/application/modal-native-worker-host-transport.d.ts +297 -0
- package/dist/application/new-session-drafts.d.ts +29 -2
- package/dist/application/organization-privacy.d.ts +57 -0
- package/dist/application/prepared-mcp-connection.d.ts +31 -0
- package/dist/application/sandbox-recovery.d.ts +58 -0
- package/dist/application/session-commands.d.ts +3 -0
- package/dist/application/session-tenancy.d.ts +13 -4
- package/dist/billing/agent-run-admission.d.ts +21 -0
- package/dist/billing/limits.d.ts +27 -1
- package/dist/billing/model-call-admission.d.ts +34 -0
- package/dist/billing/model-usage-settlement.d.ts +112 -0
- package/dist/canonical-human-identities.js +1 -1
- package/dist/{chunk-VCZFFEPB.js → chunk-7T2SZ7BI.js} +16 -2
- package/dist/chunk-7T2SZ7BI.js.map +1 -0
- package/dist/{chunk-ZYCNLYGH.js → chunk-OMJFIIWK.js} +178 -12
- package/dist/chunk-OMJFIIWK.js.map +1 -0
- package/dist/{chunk-H7X52RI2.js → chunk-QI2M2B2K.js} +83 -8
- package/dist/chunk-QI2M2B2K.js.map +1 -0
- package/dist/{chunk-OF65T3PM.js → chunk-TTWTXQB2.js} +94 -6
- package/dist/chunk-TTWTXQB2.js.map +1 -0
- package/dist/codex-model-availability.d.ts +55 -0
- package/dist/default-session-model.d.ts +192 -0
- package/dist/dependencies.d.ts +41 -7
- package/dist/developer-setup-credential-ceiling.d.ts +10 -0
- package/dist/domain/agent-config-resolution.d.ts +49 -0
- package/dist/domain/automations.d.ts +8 -1
- package/dist/domain/capabilities.d.ts +112 -8
- package/dist/domain/connection-account-selection-error.d.ts +8 -0
- package/dist/domain/connector-tool-permissions.d.ts +62 -0
- package/dist/domain/direct-model-provider.d.ts +3 -0
- package/dist/domain/editable-artifacts/agent-application.d.ts +10 -1
- package/dist/domain/editable-artifacts/durable-export.d.ts +22 -1
- package/dist/domain/editable-artifacts/ports.d.ts +10 -0
- package/dist/domain/editable-artifacts/service.d.ts +3 -1
- package/dist/domain/editable-artifacts/types.d.ts +1 -1
- package/dist/domain/encryption-key.d.ts +12 -0
- package/dist/domain/insights-timing.d.ts +16 -0
- package/dist/domain/insights-usage.d.ts +27 -0
- package/dist/domain/insights.d.ts +21 -3
- package/dist/domain/knowledge-files.d.ts +1 -1
- package/dist/domain/knowledge-preparation.d.ts +10 -0
- package/dist/domain/knowledge-preparation.test.d.ts +1 -0
- package/dist/domain/knowledge-search.d.ts +15 -76
- package/dist/domain/mcp-account-bindings.d.ts +17 -0
- package/dist/domain/mcp-account-routes.d.ts +21 -0
- package/dist/domain/native-mcp-connection-admission.d.ts +3 -0
- package/dist/domain/organization-integration-catalog.d.ts +15 -0
- package/dist/domain/personal-connection-delegations.d.ts +73 -12
- package/dist/domain/pr-review.d.ts +121 -6
- package/dist/domain/realtime-voice-billing.d.ts +103 -0
- package/dist/domain/resources.d.ts +17 -1
- package/dist/domain/scheduled-task-access.d.ts +265 -0
- package/dist/domain/scheduled-task-account-audit.d.ts +27 -0
- package/dist/domain/scheduled-task-audience.d.ts +70 -0
- package/dist/domain/scheduled-tasks.d.ts +61 -18
- package/dist/domain/session-connection-accounts.d.ts +53 -0
- package/dist/domain/session-file-access.d.ts +4 -0
- package/dist/domain/session-native-connection-replacement.d.ts +14 -0
- package/dist/domain/session-start-timing.d.ts +7 -0
- package/dist/domain/session-tool-policy.d.ts +102 -4
- package/dist/domain/sessions.d.ts +179 -15
- package/dist/domain/site-audience.d.ts +52 -0
- package/dist/domain/skills.d.ts +29 -1
- package/dist/domain/slack-bot.d.ts +46 -1
- package/dist/domain/slack-publication-secret-safety.d.ts +1 -1
- package/dist/domain/vaults/index.d.ts +7 -0
- package/dist/domain/vaults/mapping.d.ts +25 -0
- package/dist/domain/vaults/provider-registry.d.ts +72 -0
- package/dist/domain/vaults/provider-sync.d.ts +176 -0
- package/dist/domain/vaults/provider-writes.d.ts +57 -0
- package/dist/domain/vaults/secret-resolver.d.ts +34 -0
- package/dist/domain/vaults/types.d.ts +24 -0
- package/dist/domain/vaults/vault-service.d.ts +35 -0
- package/dist/domain/voice-input-billing.d.ts +77 -0
- package/dist/domain/web-search-billing.d.ts +51 -0
- package/dist/domain/workspace-credits.d.ts +7 -0
- package/dist/editable-artifact-live/application.d.ts +2 -1
- package/dist/editable-artifact-live/server.d.ts +5 -1
- package/dist/editable-artifact-live/ticket.d.ts +2 -1
- package/dist/editable-artifact-live/types.d.ts +9 -0
- package/dist/editable-artifact-live.js +2 -2
- package/dist/editable-artifacts.js +10 -2
- package/dist/goal-admission.d.ts +627 -0
- package/dist/host-session-usage-limit.d.ts +15 -0
- package/dist/index.d.ts +43 -8
- package/dist/index.js +22682 -11743
- package/dist/index.js.map +1 -1
- package/dist/managed-session.d.ts +110 -7
- package/dist/model-call.d.ts +58 -0
- package/dist/model-catalog.d.ts +29 -3
- package/dist/model-provider-credentials.d.ts +43 -0
- package/dist/product-usage-metrics.d.ts +30 -0
- package/dist/rigs/index.d.ts +1 -13
- package/dist/sandbox/fleet.d.ts +58 -4
- package/dist/sandbox/routing.d.ts +42 -2
- package/dist/sandbox/runtime-settings.d.ts +27 -17
- package/dist/session-authorization.d.ts +10 -0
- package/dist/session-usage-limit-metrics.d.ts +60 -0
- package/dist/session-usage-limits.d.ts +89 -0
- package/dist/site-agent-clamp.d.ts +71 -0
- package/dist/site-session-origin.d.ts +39 -2
- package/dist/transcription.d.ts +120 -3
- package/dist/turn-surface.d.ts +20 -0
- package/dist/user-presence.d.ts +32 -0
- package/dist/workflow-wake-contract.d.ts +13 -1
- package/package.json +15 -22
- package/src/access/index.ts +961 -38
- package/src/access/service-initiator.ts +50 -0
- package/src/application/api-integration-servers.ts +9 -0
- package/src/application/archived-session-imports.ts +161 -0
- package/src/application/composer-submit.ts +3 -2
- package/src/application/connect-authority.ts +3 -3
- package/src/application/connect-operation.ts +37 -3
- package/src/application/external-continuation.ts +20 -3
- package/src/application/external-link-work-admission.ts +3 -0
- package/src/application/external-workspace-members.ts +51 -3
- package/src/application/modal-native-live-declaration-join.ts +196 -0
- package/src/application/modal-native-original-configuration.ts +261 -0
- package/src/application/modal-native-worker-host-transport.ts +263 -0
- package/src/application/new-session-drafts.ts +172 -55
- package/src/application/organization-privacy.ts +161 -0
- package/src/application/prepared-mcp-connection.ts +133 -0
- package/src/application/sandbox-recovery.ts +78 -0
- package/src/application/session-commands.ts +73 -1
- package/src/application/session-mcp-credential-rotation.ts +61 -18
- package/src/application/session-tenancy.ts +262 -44
- package/src/application/user-resource-grants.ts +1 -29
- package/src/billing/agent-run-admission.ts +117 -0
- package/src/billing/limits.ts +138 -14
- package/src/billing/model-call-admission.ts +118 -0
- package/src/billing/model-usage-settlement.ts +472 -0
- package/src/codex-model-availability.ts +264 -0
- package/src/default-session-model.ts +692 -0
- package/src/dependencies.ts +48 -6
- package/src/developer-setup-credential-ceiling.ts +60 -0
- package/src/domain/agent-config-resolution.ts +148 -0
- package/src/domain/automations.ts +76 -2
- package/src/domain/capabilities.ts +830 -181
- package/src/domain/connection-account-selection-error.ts +19 -0
- package/src/domain/connector-tool-permissions.ts +572 -0
- package/src/domain/direct-model-provider.ts +62 -0
- package/src/domain/editable-artifacts/agent-application.ts +44 -6
- package/src/domain/editable-artifacts/durable-export.ts +100 -5
- package/src/domain/editable-artifacts/in-memory.ts +6 -0
- package/src/domain/editable-artifacts/ports.ts +11 -0
- package/src/domain/editable-artifacts/service.ts +20 -0
- package/src/domain/editable-artifacts/types.ts +1 -1
- package/src/domain/encryption-key.ts +26 -0
- package/src/domain/file-owner.ts +16 -1
- package/src/domain/github-action-policies.ts +1 -1
- package/src/domain/github-skill-source.ts +3 -1
- package/src/domain/insights-timing.ts +95 -0
- package/src/domain/insights-usage.ts +67 -0
- package/src/domain/insights.ts +167 -43
- package/src/domain/knowledge-files.ts +10 -3
- package/src/domain/knowledge-messages.ts +1 -0
- package/src/domain/knowledge-preparation.test.ts +276 -0
- package/src/domain/knowledge-preparation.ts +147 -0
- package/src/domain/knowledge-search.test.ts +617 -5
- package/src/domain/knowledge-search.ts +240 -23
- package/src/domain/mcp-account-bindings.ts +169 -0
- package/src/domain/mcp-account-routes.ts +152 -0
- package/src/domain/native-mcp-connection-admission.ts +14 -0
- package/src/domain/organization-integration-catalog.ts +88 -0
- package/src/domain/personal-connection-delegations.ts +399 -257
- package/src/domain/pr-review.ts +24 -8
- package/src/domain/realtime-voice-billing.ts +425 -0
- package/src/domain/resources.ts +50 -4
- package/src/domain/scheduled-task-access.ts +1089 -0
- package/src/domain/scheduled-task-account-audit.ts +90 -0
- package/src/domain/scheduled-task-audience.ts +249 -0
- package/src/domain/scheduled-tasks.ts +1313 -374
- package/src/domain/session-connection-accounts.ts +239 -0
- package/src/domain/session-file-access.ts +47 -0
- package/src/domain/session-native-connection-replacement.ts +64 -0
- package/src/domain/session-start-timing.ts +60 -0
- package/src/domain/session-tool-policy.ts +489 -14
- package/src/domain/sessions.ts +2254 -717
- package/src/domain/site-audience.ts +130 -0
- package/src/domain/skills.ts +9 -0
- package/src/domain/slack-bot.ts +141 -4
- package/src/domain/slack-publication-secret-safety.ts +1 -1
- package/src/domain/vaults/index.ts +19 -0
- package/src/domain/vaults/mapping.ts +190 -0
- package/src/domain/vaults/provider-registry.ts +201 -0
- package/src/domain/vaults/provider-sync.ts +531 -0
- package/src/domain/vaults/provider-writes.ts +288 -0
- package/src/domain/vaults/secret-resolver.ts +464 -0
- package/src/domain/vaults/types.ts +26 -0
- package/src/domain/vaults/vault-service.ts +1316 -0
- package/src/domain/voice-input-billing.ts +330 -0
- package/src/domain/web-search-billing.ts +169 -0
- package/src/domain/workspace-credits.ts +13 -0
- package/src/editable-artifact-live/application.ts +8 -1
- package/src/editable-artifact-live/server.ts +154 -6
- package/src/editable-artifact-live/ticket.ts +86 -1
- package/src/editable-artifact-live/types.ts +10 -0
- package/src/goal-admission.ts +220 -0
- package/src/host-session-usage-limit.ts +25 -0
- package/src/index.ts +75 -11
- package/src/managed-session.ts +137 -6
- package/src/model-call.ts +79 -0
- package/src/model-catalog.ts +345 -91
- package/src/model-provider-credentials.ts +394 -0
- package/src/product-usage-metrics.ts +101 -0
- package/src/rigs/index.ts +23 -111
- package/src/sandbox/fleet.ts +253 -28
- package/src/sandbox/routing.ts +204 -43
- package/src/sandbox/runtime-settings.ts +159 -85
- package/src/session-authorization.ts +172 -23
- package/src/session-usage-limit-metrics.ts +95 -0
- package/src/session-usage-limits.ts +415 -0
- package/src/site-agent-clamp.ts +226 -0
- package/src/site-session-origin.ts +84 -5
- package/src/transcription.ts +123 -4
- package/src/turn-surface.ts +47 -0
- package/src/user-presence.ts +134 -0
- package/src/workflow-wake-contract.ts +14 -1
- package/dist/application/host-mcp-owner.d.ts +0 -6
- package/dist/application/host-mcp-resolvers.d.ts +0 -13
- package/dist/chunk-5WZYFL45.js +0 -266
- package/dist/chunk-5WZYFL45.js.map +0 -1
- package/dist/chunk-H7X52RI2.js.map +0 -1
- package/dist/chunk-OF65T3PM.js.map +0 -1
- package/dist/chunk-VCZFFEPB.js.map +0 -1
- package/dist/chunk-ZYCNLYGH.js.map +0 -1
- package/dist/domain/connection-authority.d.ts +0 -97
- package/dist/domain/connection-authority.js +0 -169
- package/dist/domain/connection-authority.js.map +0 -1
- package/dist/domain/environments.d.ts +0 -33
- package/dist/domain/host-mcp-authority-source-admission.d.ts +0 -10
- package/dist/domain/host-mcp-task-admission.d.ts +0 -18
- package/dist/domain/packs.d.ts +0 -59
- package/dist/domain/product-integration-pack.d.ts +0 -44
- package/dist/domain/product-integration-skill.gen.d.ts +0 -5
- package/dist/remote-mcp-credentials.d.ts +0 -23
- package/dist/remote-mcp-credentials.js +0 -11
- package/dist/remote-mcp-credentials.js.map +0 -1
- package/src/application/host-mcp-owner.ts +0 -41
- package/src/application/host-mcp-resolvers.ts +0 -84
- package/src/domain/connection-authority.ts +0 -329
- package/src/domain/environments.ts +0 -120
- package/src/domain/host-mcp-authority-source-admission.ts +0 -25
- package/src/domain/host-mcp-task-admission.ts +0 -121
- package/src/domain/packs.ts +0 -699
- package/src/domain/product-integration-pack.ts +0 -42
- package/src/domain/product-integration-skill.gen.ts +0 -52
- 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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
57
|
-
if (!
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
71
|
+
const authority = await lockActiveExternalOrganizationKeyAuthority(
|
|
69
72
|
tx,
|
|
70
73
|
scope.accountId,
|
|
71
74
|
actor.authenticatingApiKeyId,
|
|
72
75
|
);
|
|
73
|
-
|
|
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
|
|
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(
|
|
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
|
+
}
|