@opengeni/api-router 3.0.1 → 4.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.
- package/dist/admission-trace.d.ts +8 -0
- package/dist/app.js +1 -1
- package/dist/auth/managed-auth-attempt-context.d.ts +2 -0
- package/dist/auth/managed-auth-session-adapter.d.ts +2 -0
- package/dist/auth/managed-auth.d.ts +1 -0
- package/dist/auth/managed-sign-in-notifications.d.ts +4 -0
- package/dist/{chunk-AJ62T5QN.js → chunk-E7TZFJ65.js} +22300 -21113
- package/dist/{chunk-AJ62T5QN.js.map → chunk-E7TZFJ65.js.map} +1 -1
- package/dist/connection-ownership.d.ts +1 -2
- package/dist/fatal-process-boundary.d.ts +1 -1
- package/dist/index.js +24 -6
- package/dist/index.js.map +1 -1
- package/dist/integrations/atlassian.d.ts +8 -3
- package/dist/integrations/fiken.d.ts +3 -1
- package/dist/integrations/github-lens-connect.d.ts +5 -65
- package/dist/integrations/google-drive.d.ts +2 -0
- package/dist/integrations/integration-commit-authority.d.ts +13 -0
- package/dist/integrations/oauth-client.d.ts +119 -11
- package/dist/integrations/oauth-profiles.d.ts +8 -13
- package/dist/integrations/slack-interactions.d.ts +1 -1
- package/dist/mcp/capability-account-readiness.d.ts +7 -0
- package/dist/mcp/first-party-tool-permissions.d.ts +4 -0
- package/dist/mcp-oauth.d.ts +22 -1
- package/dist/routes/api-integrations.d.ts +1 -0
- package/dist/routes/codex.d.ts +28 -0
- package/dist/routes/managed-sign-in-methods.d.ts +6 -0
- package/dist/routes/organization-integration-policy.d.ts +3 -0
- package/dist/routes/sessions.d.ts +24 -2
- package/dist/scheduled-task-deletion.d.ts +3 -4
- package/dist/workspace-tool-gateway.d.ts +3 -1
- package/package.json +15 -15
- package/src/admission-trace.ts +14 -0
- package/src/app.ts +141 -94
- package/src/auth/managed-auth-attempt-context.ts +9 -0
- package/src/auth/managed-auth-session-adapter.ts +11 -0
- package/src/auth/managed-auth.ts +39 -2
- package/src/auth/managed-sign-in-notifications.ts +81 -0
- package/src/connection-ownership.ts +2 -9
- package/src/editable-artifact-production.ts +6 -0
- package/src/fatal-process-boundary.ts +17 -2
- package/src/index.ts +3 -4
- package/src/integrations/atlassian.ts +184 -76
- package/src/integrations/fiken.ts +57 -35
- package/src/integrations/github-app-connect.ts +51 -15
- package/src/integrations/github-lens-connect.ts +7 -28
- package/src/integrations/google-drive.ts +296 -125
- package/src/integrations/integration-commit-authority.ts +69 -0
- package/src/integrations/oauth-client.ts +231 -132
- package/src/integrations/oauth-profiles.ts +10 -45
- package/src/integrations/personal-github.ts +55 -31
- package/src/integrations/provider-oauth.ts +57 -82
- package/src/integrations/social-oauth.ts +83 -56
- package/src/mcp/capability-account-readiness.ts +17 -0
- package/src/mcp/editable-artifacts.ts +7 -2
- package/src/mcp/first-party-tool-permissions.ts +1 -0
- package/src/mcp/knowledge-entries.ts +25 -8
- package/src/mcp/server.ts +135 -43
- package/src/mcp-oauth.ts +336 -34
- package/src/routes/api-integrations.ts +116 -4
- package/src/routes/automations.ts +5 -13
- package/src/routes/billing.ts +52 -0
- package/src/routes/canonical-human-identities.ts +61 -0
- package/src/routes/capabilities.ts +51 -1
- package/src/routes/codex.ts +4 -2
- package/src/routes/connect.ts +90 -31
- package/src/routes/connections.ts +487 -307
- package/src/routes/files.ts +21 -12
- package/src/routes/github.ts +48 -19
- package/src/routes/insights.ts +17 -7
- package/src/routes/integration-facets.ts +66 -25
- package/src/routes/knowledge.ts +48 -1
- package/src/routes/managed-sign-in-methods.ts +433 -0
- package/src/routes/organization-integration-policy.ts +113 -0
- package/src/routes/organization-memberships.ts +22 -2
- package/src/routes/plugins.ts +42 -4
- package/src/routes/pr-review-github.ts +67 -57
- package/src/routes/pr-review.ts +74 -71
- package/src/routes/rigs.ts +32 -13
- package/src/routes/scheduled-tasks.ts +6 -6
- package/src/routes/sessions.ts +233 -11
- package/src/routes/skill-content.ts +19 -3
- package/src/routes/social.ts +24 -13
- package/src/sandbox/channel-a.ts +7 -1
- package/src/sandbox/rematerialize.ts +2 -1
- package/src/scheduled-task-deletion.ts +13 -3
- package/src/workspace-tool-gateway.ts +31 -31
- package/dist/connection-authority-owner.d.ts +0 -59
- package/dist/routes/connection-authorities.d.ts +0 -3
- package/dist/routes/host-mcp-bindings.d.ts +0 -5
- package/dist/routes/host-mcp-resolvers.d.ts +0 -3
- package/dist/routes/packs.d.ts +0 -3
- package/src/connection-authority-owner.ts +0 -61
- package/src/routes/connection-authorities.ts +0 -139
- package/src/routes/host-mcp-bindings.ts +0 -122
- package/src/routes/host-mcp-resolvers.ts +0 -41
- package/src/routes/packs.ts +0 -784
package/src/routes/packs.ts
DELETED
|
@@ -1,784 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
EnablePackRequest,
|
|
5
|
-
InstallPackRequest,
|
|
6
|
-
MarketingDailyAnalysisTaskRequest,
|
|
7
|
-
PackInstallation,
|
|
8
|
-
PackInstallationPreview,
|
|
9
|
-
PackUninstallPreview,
|
|
10
|
-
PreviewPackInstallationRequest,
|
|
11
|
-
RegisterCapabilityPackRequest,
|
|
12
|
-
UninstallPackRequest,
|
|
13
|
-
UninstallPackResult,
|
|
14
|
-
stableJson,
|
|
15
|
-
type SocialConnection,
|
|
16
|
-
} from "@opengeni/contracts";
|
|
17
|
-
import {
|
|
18
|
-
adoptPackComponentReferences,
|
|
19
|
-
CapabilityComponentVersionConflictError,
|
|
20
|
-
deferPackInstallationOperation,
|
|
21
|
-
deleteWorkspacePack,
|
|
22
|
-
disableCapabilityInstallation,
|
|
23
|
-
enablePackInstallation,
|
|
24
|
-
finalizePackComponentOwnership,
|
|
25
|
-
finalizePackInstallationOperation,
|
|
26
|
-
finalizePackUninstallOperation,
|
|
27
|
-
getCapabilityInstallation,
|
|
28
|
-
getPackInstallation,
|
|
29
|
-
getWorkspacePack,
|
|
30
|
-
getSocialConnection,
|
|
31
|
-
installPortableSkill,
|
|
32
|
-
SkillSourceRemovalAuthorityError,
|
|
33
|
-
listPackInstallations,
|
|
34
|
-
listSocialConnections,
|
|
35
|
-
PackManifestChangedError,
|
|
36
|
-
PackComponentResolutionError,
|
|
37
|
-
PackInstallationVersionConflictError,
|
|
38
|
-
PackInstallationVersionRequiredError,
|
|
39
|
-
PackOperationClaimLostError,
|
|
40
|
-
PackOperationIdempotencyError,
|
|
41
|
-
PackOperationInProgressError,
|
|
42
|
-
preparePackInstallationOperation,
|
|
43
|
-
preparePackUninstallOperation,
|
|
44
|
-
previewPackComponentRelease,
|
|
45
|
-
recordPackInlineSkillComponent,
|
|
46
|
-
registerWorkspacePack,
|
|
47
|
-
releasePackComponents,
|
|
48
|
-
touchPackInstallationOperation,
|
|
49
|
-
updatePackInstallationStatus,
|
|
50
|
-
} from "@opengeni/db";
|
|
51
|
-
import { getDocumentBase } from "@opengeni/documents";
|
|
52
|
-
import type { Hono } from "hono";
|
|
53
|
-
import { HTTPException } from "hono/http-exception";
|
|
54
|
-
import { requireAccessGrant, requireAccessGrantAuthorization } from "@opengeni/core";
|
|
55
|
-
import { skillInstallerActor, skillRemovalActor } from "./skill-install-authority";
|
|
56
|
-
import { requireLimit } from "@opengeni/core";
|
|
57
|
-
import type { ApiRouteDeps } from "@opengeni/core";
|
|
58
|
-
import { validateVariableSetAttachment } from "@opengeni/core";
|
|
59
|
-
import {
|
|
60
|
-
assertPackSandboxImageCompatible,
|
|
61
|
-
buildMarketingDailyAnalysisAgentConfig,
|
|
62
|
-
capabilityPackRequiresInstallationPlan,
|
|
63
|
-
inlinePackSkillInstall,
|
|
64
|
-
isBuiltInCapabilityPack,
|
|
65
|
-
listWorkspaceCapabilityPacks,
|
|
66
|
-
MARKETING_SOCIAL_PACK_ID,
|
|
67
|
-
previewCapabilityPackInstallation,
|
|
68
|
-
resolveCapabilityPack,
|
|
69
|
-
} from "@opengeni/core";
|
|
70
|
-
import { createValidatedScheduledTask, syncCreatedScheduledTask } from "@opengeni/core";
|
|
71
|
-
|
|
72
|
-
export function registerPackRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
73
|
-
const { settings, db, objectStorage, workflowClient } = deps;
|
|
74
|
-
|
|
75
|
-
app.get("/v1/workspaces/:workspaceId/packs", async (c) => {
|
|
76
|
-
const workspaceId = c.req.param("workspaceId");
|
|
77
|
-
await requireAccessGrant(c, deps, workspaceId, "workspace:read");
|
|
78
|
-
return c.json({
|
|
79
|
-
packs: await listWorkspaceCapabilityPacks(db, workspaceId),
|
|
80
|
-
installations: await listPackInstallations(db, workspaceId),
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
// Registers (or replaces) a workspace-scoped pack from a manifest payload.
|
|
85
|
-
// Built-in pack ids stay reserved so a registration can never shadow them.
|
|
86
|
-
app.post("/v1/workspaces/:workspaceId/packs", async (c) => {
|
|
87
|
-
const workspaceId = c.req.param("workspaceId");
|
|
88
|
-
const grant = await requireAccessGrant(c, deps, workspaceId, "capabilities:manage");
|
|
89
|
-
const manifest = RegisterCapabilityPackRequest.parse(await c.req.json());
|
|
90
|
-
if (isBuiltInCapabilityPack(manifest.id)) {
|
|
91
|
-
throw new HTTPException(409, {
|
|
92
|
-
message: `pack id ${manifest.id} is a built-in pack and cannot be replaced`,
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
const { pack, created } = await registerWorkspacePack(db, {
|
|
96
|
-
accountId: grant.accountId,
|
|
97
|
-
workspaceId,
|
|
98
|
-
pack: manifest,
|
|
99
|
-
});
|
|
100
|
-
return c.json(pack, created ? 201 : 200);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
app.delete("/v1/workspaces/:workspaceId/packs/:packId", async (c) => {
|
|
104
|
-
const workspaceId = c.req.param("workspaceId");
|
|
105
|
-
await requireAccessGrant(c, deps, workspaceId, "capabilities:manage");
|
|
106
|
-
const packId = c.req.param("packId");
|
|
107
|
-
if (isBuiltInCapabilityPack(packId)) {
|
|
108
|
-
throw new HTTPException(409, {
|
|
109
|
-
message: "built-in packs cannot be unregistered",
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
if (!(await getWorkspacePack(db, workspaceId, packId))) {
|
|
113
|
-
throw new HTTPException(404, { message: "pack not found" });
|
|
114
|
-
}
|
|
115
|
-
// V2 Packs own explicit components. Unregistering before the ownership
|
|
116
|
-
// ledger is safely released would orphan those components, so require the
|
|
117
|
-
// dedicated uninstall flow first. Legacy installations retain the old
|
|
118
|
-
// disable-before-delete behavior for compatibility.
|
|
119
|
-
const installation = await getPackInstallation(db, workspaceId, packId);
|
|
120
|
-
if (
|
|
121
|
-
installation &&
|
|
122
|
-
installation.status !== "disabled" &&
|
|
123
|
-
(installation.manifestDigest !== null || installation.manifestSnapshot !== null)
|
|
124
|
-
) {
|
|
125
|
-
throw new HTTPException(409, {
|
|
126
|
-
message: "uninstall this Pack before unregistering it",
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
if (installation && installation.status === "active") {
|
|
130
|
-
await updatePackInstallationStatus(db, workspaceId, packId, "disabled");
|
|
131
|
-
}
|
|
132
|
-
const capabilityInstallation = await getCapabilityInstallation(
|
|
133
|
-
db,
|
|
134
|
-
workspaceId,
|
|
135
|
-
`pack:${packId}`,
|
|
136
|
-
);
|
|
137
|
-
if (capabilityInstallation && capabilityInstallation.status === "active") {
|
|
138
|
-
await disableCapabilityInstallation(db, workspaceId, `pack:${packId}`);
|
|
139
|
-
}
|
|
140
|
-
await deleteWorkspacePack(db, workspaceId, packId);
|
|
141
|
-
return c.body(null, 204);
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
app.get("/v1/workspaces/:workspaceId/packs/installations", async (c) => {
|
|
145
|
-
const workspaceId = c.req.param("workspaceId");
|
|
146
|
-
await requireAccessGrant(c, deps, workspaceId, "workspace:read");
|
|
147
|
-
return c.json(await listPackInstallations(db, workspaceId));
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
app.get("/v1/workspaces/:workspaceId/packs/:packId", async (c) => {
|
|
151
|
-
const workspaceId = c.req.param("workspaceId");
|
|
152
|
-
await requireAccessGrant(c, deps, workspaceId, "workspace:read");
|
|
153
|
-
const pack = await requirePack(db, workspaceId, c.req.param("packId"));
|
|
154
|
-
return c.json({
|
|
155
|
-
pack,
|
|
156
|
-
installation: await getPackInstallation(db, workspaceId, pack.id),
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
app.post("/v1/workspaces/:workspaceId/packs/:packId/installation-preview", async (c) => {
|
|
161
|
-
const workspaceId = c.req.param("workspaceId");
|
|
162
|
-
const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:read");
|
|
163
|
-
const pack = await requirePack(db, workspaceId, c.req.param("packId"));
|
|
164
|
-
const payload = PreviewPackInstallationRequest.parse(await c.req.json());
|
|
165
|
-
return c.json(
|
|
166
|
-
PackInstallationPreview.parse(
|
|
167
|
-
await previewCapabilityPackInstallation(db, grant, pack, {
|
|
168
|
-
...(payload.rigId ? { rigId: payload.rigId } : {}),
|
|
169
|
-
...(payload.variableSetId ? { variableSetId: payload.variableSetId } : {}),
|
|
170
|
-
}),
|
|
171
|
-
),
|
|
172
|
-
);
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
app.post("/v1/workspaces/:workspaceId/packs/:packId/install", async (c) => {
|
|
176
|
-
const workspaceId = c.req.param("workspaceId");
|
|
177
|
-
const access = await requireAccessGrantAuthorization(
|
|
178
|
-
c,
|
|
179
|
-
deps,
|
|
180
|
-
workspaceId,
|
|
181
|
-
"capabilities:manage",
|
|
182
|
-
);
|
|
183
|
-
const { grant } = access;
|
|
184
|
-
const pack = await requirePack(db, workspaceId, c.req.param("packId"));
|
|
185
|
-
const skillActor = pack.skills.length ? skillInstallerActor(access) : undefined;
|
|
186
|
-
const payload = InstallPackRequest.parse(await c.req.json());
|
|
187
|
-
const preview = PackInstallationPreview.parse(
|
|
188
|
-
await previewCapabilityPackInstallation(db, grant, pack, {
|
|
189
|
-
...(payload.rigId ? { rigId: payload.rigId } : {}),
|
|
190
|
-
...(payload.variableSetId ? { variableSetId: payload.variableSetId } : {}),
|
|
191
|
-
}),
|
|
192
|
-
);
|
|
193
|
-
if (preview.manifestDigest !== payload.expectedManifestDigest) {
|
|
194
|
-
throw new HTTPException(409, {
|
|
195
|
-
message: "The Pack manifest changed after preview. Review the updated installation plan.",
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
if (!preview.ready) {
|
|
199
|
-
throw new HTTPException(422, {
|
|
200
|
-
message: `The Pack is not ready to install: ${preview.blockers.join("; ")}`,
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
if (preview.variableSetId) {
|
|
204
|
-
await validateVariableSetAttachment(
|
|
205
|
-
{ settings, db },
|
|
206
|
-
grant,
|
|
207
|
-
workspaceId,
|
|
208
|
-
preview.variableSetId,
|
|
209
|
-
{ preauthorized: payload.variableSetId === undefined },
|
|
210
|
-
);
|
|
211
|
-
}
|
|
212
|
-
const metadata = {
|
|
213
|
-
...payload.metadata,
|
|
214
|
-
platformVersion: 2,
|
|
215
|
-
packVersion: pack.version,
|
|
216
|
-
...(preview.variableSetId ? { variableSetId: preview.variableSetId } : {}),
|
|
217
|
-
};
|
|
218
|
-
const requestDigest = sha256(
|
|
219
|
-
stableJson({
|
|
220
|
-
packId: pack.id,
|
|
221
|
-
manifestDigest: preview.manifestDigest,
|
|
222
|
-
expectedInstallationVersion: payload.expectedInstallationVersion ?? null,
|
|
223
|
-
selectedRigId: preview.rig.rigId,
|
|
224
|
-
variableSetId: preview.variableSetId,
|
|
225
|
-
metadata,
|
|
226
|
-
}),
|
|
227
|
-
);
|
|
228
|
-
let prepared: Awaited<ReturnType<typeof preparePackInstallationOperation>>;
|
|
229
|
-
try {
|
|
230
|
-
prepared = await preparePackInstallationOperation(db, {
|
|
231
|
-
accountId: grant.accountId,
|
|
232
|
-
workspaceId,
|
|
233
|
-
subjectId: grant.subjectId,
|
|
234
|
-
pack,
|
|
235
|
-
manifestDigest: preview.manifestDigest,
|
|
236
|
-
selectedRigId: preview.rig.rigId,
|
|
237
|
-
metadata,
|
|
238
|
-
idempotencyKey: payload.idempotencyKey,
|
|
239
|
-
requestDigest,
|
|
240
|
-
...(!isBuiltInCapabilityPack(pack.id)
|
|
241
|
-
? { registeredManifestDigest: preview.manifestDigest }
|
|
242
|
-
: {}),
|
|
243
|
-
...(payload.expectedInstallationVersion !== undefined
|
|
244
|
-
? { expectedInstallationVersion: payload.expectedInstallationVersion }
|
|
245
|
-
: {}),
|
|
246
|
-
});
|
|
247
|
-
} catch (error) {
|
|
248
|
-
throw packMutationHttpError(error);
|
|
249
|
-
}
|
|
250
|
-
if (prepared.replayResult) {
|
|
251
|
-
const replayed = await getPackInstallation(db, workspaceId, pack.id);
|
|
252
|
-
if (!replayed) {
|
|
253
|
-
throw new HTTPException(500, {
|
|
254
|
-
message: "The completed Pack operation lost its installation record",
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
return c.json(
|
|
258
|
-
PackInstallation.parse({
|
|
259
|
-
...replayed,
|
|
260
|
-
...(Array.isArray(prepared.replayResult.skillWrites)
|
|
261
|
-
? { skillWrites: prepared.replayResult.skillWrites }
|
|
262
|
-
: {}),
|
|
263
|
-
...(Array.isArray(prepared.replayResult.skillReleases)
|
|
264
|
-
? { skillReleases: prepared.replayResult.skillReleases }
|
|
265
|
-
: {}),
|
|
266
|
-
}),
|
|
267
|
-
200,
|
|
268
|
-
);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
const retainedComponentKeys: string[] = [];
|
|
272
|
-
const skillWrites: import("@opengeni/contracts").SkillWriteReceipt[] = [];
|
|
273
|
-
const retainedFacetInstallationIds: string[] = [];
|
|
274
|
-
const retainedBindingIds: string[] = [];
|
|
275
|
-
let activeComponentKey = "manifest";
|
|
276
|
-
const heartbeat = async (): Promise<void> =>
|
|
277
|
-
await touchPackInstallationOperation(db, {
|
|
278
|
-
accountId: grant.accountId,
|
|
279
|
-
workspaceId,
|
|
280
|
-
operationId: prepared.operationId,
|
|
281
|
-
operationVersion: prepared.operationVersion,
|
|
282
|
-
});
|
|
283
|
-
try {
|
|
284
|
-
for (const skill of pack.skills) {
|
|
285
|
-
if (!skillActor)
|
|
286
|
-
throw new HTTPException(403, { message: "Skill installer authority is missing" });
|
|
287
|
-
await heartbeat();
|
|
288
|
-
const inline = inlinePackSkillInstall(pack, skill);
|
|
289
|
-
activeComponentKey = inline.componentKey;
|
|
290
|
-
const installed = await installPortableSkill(db, {
|
|
291
|
-
skillActor,
|
|
292
|
-
accountId: grant.accountId,
|
|
293
|
-
workspaceId,
|
|
294
|
-
subjectId: grant.subjectId,
|
|
295
|
-
capabilityId: inline.capabilityId,
|
|
296
|
-
pluginKey: inline.pluginKey,
|
|
297
|
-
source: "pack",
|
|
298
|
-
sourceUrl: inline.sourceUrl,
|
|
299
|
-
repositoryUrl: inline.repositoryUrl,
|
|
300
|
-
sourceCommit: inline.sourceCommit,
|
|
301
|
-
sourcePath: inline.sourcePath,
|
|
302
|
-
name: inline.name,
|
|
303
|
-
description: inline.description,
|
|
304
|
-
activationMode: inline.activationMode,
|
|
305
|
-
contentSha256: inline.contentSha256,
|
|
306
|
-
totalBytes: inline.totalBytes,
|
|
307
|
-
files: inline.files,
|
|
308
|
-
owner: {
|
|
309
|
-
kind: "pack",
|
|
310
|
-
id: prepared.installation.id,
|
|
311
|
-
removable: true,
|
|
312
|
-
},
|
|
313
|
-
});
|
|
314
|
-
skillWrites.push(installed.skillReceipt);
|
|
315
|
-
await heartbeat();
|
|
316
|
-
await recordPackInlineSkillComponent(db, {
|
|
317
|
-
accountId: grant.accountId,
|
|
318
|
-
workspaceId,
|
|
319
|
-
packInstallationId: prepared.installation.id,
|
|
320
|
-
componentKey: inline.componentKey,
|
|
321
|
-
capabilityId: inline.capabilityId,
|
|
322
|
-
facetInstallationId: installed.facetInstallationId,
|
|
323
|
-
contentSha256: inline.contentSha256,
|
|
324
|
-
name: inline.name,
|
|
325
|
-
});
|
|
326
|
-
retainedComponentKeys.push(inline.componentKey);
|
|
327
|
-
retainedFacetInstallationIds.push(installed.facetInstallationId);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
activeComponentKey = "pinned-components";
|
|
331
|
-
await heartbeat();
|
|
332
|
-
const adopted = await adoptPackComponentReferences(db, {
|
|
333
|
-
accountId: grant.accountId,
|
|
334
|
-
workspaceId,
|
|
335
|
-
packInstallationId: prepared.installation.id,
|
|
336
|
-
references: pack.components,
|
|
337
|
-
});
|
|
338
|
-
retainedComponentKeys.push(...adopted.components.map((component) => component.key));
|
|
339
|
-
retainedFacetInstallationIds.push(...adopted.retainedFacetInstallationIds);
|
|
340
|
-
retainedBindingIds.push(...adopted.retainedBindingIds);
|
|
341
|
-
|
|
342
|
-
activeComponentKey = "ownership-finalize";
|
|
343
|
-
await heartbeat();
|
|
344
|
-
const removalActor = skillRemovalActor(access);
|
|
345
|
-
const { skillReleases } = await finalizePackComponentOwnership(db, {
|
|
346
|
-
...(removalActor ? { skillActor: removalActor } : {}),
|
|
347
|
-
accountId: grant.accountId,
|
|
348
|
-
workspaceId,
|
|
349
|
-
packInstallationId: prepared.installation.id,
|
|
350
|
-
retainedComponentKeys,
|
|
351
|
-
retainedFacetInstallationIds,
|
|
352
|
-
retainedBindingIds,
|
|
353
|
-
});
|
|
354
|
-
await heartbeat();
|
|
355
|
-
const finalized = await finalizePackInstallationOperation(db, {
|
|
356
|
-
accountId: grant.accountId,
|
|
357
|
-
workspaceId,
|
|
358
|
-
operationId: prepared.operationId,
|
|
359
|
-
operationVersion: prepared.operationVersion,
|
|
360
|
-
packInstallationId: prepared.installation.id,
|
|
361
|
-
packId: pack.id,
|
|
362
|
-
result: {
|
|
363
|
-
status: "installed",
|
|
364
|
-
...(skillWrites.length ? { skillWrites } : {}),
|
|
365
|
-
packId: pack.id,
|
|
366
|
-
manifestDigest: preview.manifestDigest,
|
|
367
|
-
componentCount: retainedComponentKeys.length,
|
|
368
|
-
...(skillReleases.length ? { skillReleases } : {}),
|
|
369
|
-
},
|
|
370
|
-
});
|
|
371
|
-
return c.json(
|
|
372
|
-
PackInstallation.parse({
|
|
373
|
-
...finalized,
|
|
374
|
-
...(skillWrites.length ? { skillWrites } : {}),
|
|
375
|
-
...(skillReleases.length ? { skillReleases } : {}),
|
|
376
|
-
}),
|
|
377
|
-
preview.installationVersion === null ? 201 : 200,
|
|
378
|
-
);
|
|
379
|
-
} catch (error) {
|
|
380
|
-
await deferPackInstallationOperation(db, {
|
|
381
|
-
accountId: grant.accountId,
|
|
382
|
-
workspaceId,
|
|
383
|
-
operationId: prepared.operationId,
|
|
384
|
-
operationVersion: prepared.operationVersion,
|
|
385
|
-
packInstallationId: prepared.installation.id,
|
|
386
|
-
phase: `component_failed:${activeComponentKey}`,
|
|
387
|
-
errorCode: packFailureCode(error),
|
|
388
|
-
}).catch(() => undefined);
|
|
389
|
-
if (error instanceof PackComponentResolutionError) {
|
|
390
|
-
throw new HTTPException(409, {
|
|
391
|
-
message: `Pack component ${error.componentKey} changed after preview. Review the installation plan and retry with the same idempotency key.`,
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
if (error instanceof CapabilityComponentVersionConflictError) {
|
|
395
|
-
throw new HTTPException(409, {
|
|
396
|
-
message: `Pack component ${activeComponentKey} is pinned by another installed owner. Resolve that version conflict and retry with the same idempotency key.`,
|
|
397
|
-
});
|
|
398
|
-
}
|
|
399
|
-
if (
|
|
400
|
-
error instanceof PackOperationClaimLostError ||
|
|
401
|
-
error instanceof SkillSourceRemovalAuthorityError
|
|
402
|
-
) {
|
|
403
|
-
throw packMutationHttpError(error);
|
|
404
|
-
}
|
|
405
|
-
if (error instanceof HTTPException) throw error;
|
|
406
|
-
throw new HTTPException(422, {
|
|
407
|
-
message: `Pack component ${activeComponentKey} could not be installed. No Pack-owned component is active until the operation completes; retry with the same idempotency key.`,
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
app.get("/v1/workspaces/:workspaceId/packs/:packId/uninstall-preview", async (c) => {
|
|
413
|
-
const workspaceId = c.req.param("workspaceId");
|
|
414
|
-
await requireAccessGrant(c, deps, workspaceId, "workspace:read");
|
|
415
|
-
const pack = await requirePack(db, workspaceId, c.req.param("packId"));
|
|
416
|
-
const installation = await getPackInstallation(db, workspaceId, pack.id);
|
|
417
|
-
const installed = Boolean(installation && installation.status !== "disabled");
|
|
418
|
-
const components = installed
|
|
419
|
-
? await previewPackComponentRelease(db, workspaceId, installation!.id)
|
|
420
|
-
: [];
|
|
421
|
-
return c.json(
|
|
422
|
-
PackUninstallPreview.parse({
|
|
423
|
-
packId: pack.id,
|
|
424
|
-
installed,
|
|
425
|
-
installationVersion: installation?.version ?? null,
|
|
426
|
-
components,
|
|
427
|
-
}),
|
|
428
|
-
);
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
app.delete("/v1/workspaces/:workspaceId/packs/:packId/installation", async (c) => {
|
|
432
|
-
const workspaceId = c.req.param("workspaceId");
|
|
433
|
-
const access = await requireAccessGrantAuthorization(
|
|
434
|
-
c,
|
|
435
|
-
deps,
|
|
436
|
-
workspaceId,
|
|
437
|
-
"capabilities:manage",
|
|
438
|
-
);
|
|
439
|
-
const { grant } = access;
|
|
440
|
-
const skillActor = skillRemovalActor(access);
|
|
441
|
-
const pack = await requirePack(db, workspaceId, c.req.param("packId"));
|
|
442
|
-
const payload = UninstallPackRequest.parse(await c.req.json());
|
|
443
|
-
const requestDigest = sha256(
|
|
444
|
-
stableJson({
|
|
445
|
-
packId: pack.id,
|
|
446
|
-
expectedInstallationVersion: payload.expectedInstallationVersion,
|
|
447
|
-
}),
|
|
448
|
-
);
|
|
449
|
-
let prepared: Awaited<ReturnType<typeof preparePackUninstallOperation>>;
|
|
450
|
-
try {
|
|
451
|
-
prepared = await preparePackUninstallOperation(db, {
|
|
452
|
-
accountId: grant.accountId,
|
|
453
|
-
workspaceId,
|
|
454
|
-
subjectId: grant.subjectId,
|
|
455
|
-
packId: pack.id,
|
|
456
|
-
expectedInstallationVersion: payload.expectedInstallationVersion,
|
|
457
|
-
idempotencyKey: payload.idempotencyKey,
|
|
458
|
-
requestDigest,
|
|
459
|
-
});
|
|
460
|
-
} catch (error) {
|
|
461
|
-
throw packMutationHttpError(error);
|
|
462
|
-
}
|
|
463
|
-
if (!("installation" in prepared)) {
|
|
464
|
-
return c.json(
|
|
465
|
-
UninstallPackResult.parse({
|
|
466
|
-
packId: pack.id,
|
|
467
|
-
status:
|
|
468
|
-
prepared.replayResult.status === "not_installed" ? "not_installed" : "uninstalled",
|
|
469
|
-
retainedComponents: Array.isArray(prepared.replayResult.retainedComponents)
|
|
470
|
-
? prepared.replayResult.retainedComponents
|
|
471
|
-
: [],
|
|
472
|
-
...(Array.isArray(prepared.replayResult.skillReleases)
|
|
473
|
-
? { skillReleases: prepared.replayResult.skillReleases }
|
|
474
|
-
: {}),
|
|
475
|
-
}),
|
|
476
|
-
);
|
|
477
|
-
}
|
|
478
|
-
try {
|
|
479
|
-
await touchPackInstallationOperation(db, {
|
|
480
|
-
accountId: grant.accountId,
|
|
481
|
-
workspaceId,
|
|
482
|
-
operationId: prepared.operationId,
|
|
483
|
-
operationVersion: prepared.operationVersion,
|
|
484
|
-
});
|
|
485
|
-
const released = await releasePackComponents(db, {
|
|
486
|
-
...(skillActor ? { skillActor } : {}),
|
|
487
|
-
accountId: grant.accountId,
|
|
488
|
-
workspaceId,
|
|
489
|
-
packInstallationId: prepared.installation.id,
|
|
490
|
-
});
|
|
491
|
-
await touchPackInstallationOperation(db, {
|
|
492
|
-
accountId: grant.accountId,
|
|
493
|
-
workspaceId,
|
|
494
|
-
operationId: prepared.operationId,
|
|
495
|
-
operationVersion: prepared.operationVersion,
|
|
496
|
-
});
|
|
497
|
-
const result = UninstallPackResult.parse({
|
|
498
|
-
packId: pack.id,
|
|
499
|
-
status: "uninstalled",
|
|
500
|
-
retainedComponents: [...new Set(released.retainedComponents)],
|
|
501
|
-
...(released.skillReleases?.length ? { skillReleases: released.skillReleases } : {}),
|
|
502
|
-
});
|
|
503
|
-
await finalizePackUninstallOperation(db, {
|
|
504
|
-
accountId: grant.accountId,
|
|
505
|
-
workspaceId,
|
|
506
|
-
operationId: prepared.operationId,
|
|
507
|
-
operationVersion: prepared.operationVersion,
|
|
508
|
-
packInstallationId: prepared.installation.id,
|
|
509
|
-
packId: pack.id,
|
|
510
|
-
result,
|
|
511
|
-
});
|
|
512
|
-
return c.json(result);
|
|
513
|
-
} catch (error) {
|
|
514
|
-
await deferPackInstallationOperation(db, {
|
|
515
|
-
accountId: grant.accountId,
|
|
516
|
-
workspaceId,
|
|
517
|
-
operationId: prepared.operationId,
|
|
518
|
-
operationVersion: prepared.operationVersion,
|
|
519
|
-
packInstallationId: prepared.installation.id,
|
|
520
|
-
phase: "uninstall_failed",
|
|
521
|
-
errorCode: packFailureCode(error),
|
|
522
|
-
}).catch(() => undefined);
|
|
523
|
-
if (
|
|
524
|
-
error instanceof PackOperationClaimLostError ||
|
|
525
|
-
error instanceof SkillSourceRemovalAuthorityError
|
|
526
|
-
) {
|
|
527
|
-
throw packMutationHttpError(error);
|
|
528
|
-
}
|
|
529
|
-
if (error instanceof HTTPException) throw error;
|
|
530
|
-
throw new HTTPException(422, {
|
|
531
|
-
message: "The Pack could not be safely uninstalled. Retry with the same idempotency key.",
|
|
532
|
-
});
|
|
533
|
-
}
|
|
534
|
-
});
|
|
535
|
-
|
|
536
|
-
app.post("/v1/workspaces/:workspaceId/packs/:packId/enable", async (c) => {
|
|
537
|
-
const workspaceId = c.req.param("workspaceId");
|
|
538
|
-
const grant = await requireAccessGrant(c, deps, workspaceId, "capabilities:manage");
|
|
539
|
-
const pack = await requirePack(db, workspaceId, c.req.param("packId"));
|
|
540
|
-
if (capabilityPackRequiresInstallationPlan(pack)) {
|
|
541
|
-
throw new HTTPException(409, {
|
|
542
|
-
message:
|
|
543
|
-
"This Pack uses component or Rig requirements. Preview and install it through the Pack installation flow.",
|
|
544
|
-
});
|
|
545
|
-
}
|
|
546
|
-
await assertPackSandboxImageCompatible(db, workspaceId, pack);
|
|
547
|
-
const existing = await getPackInstallation(db, workspaceId, pack.id);
|
|
548
|
-
const payload = EnablePackRequest.parse(await c.req.json());
|
|
549
|
-
// Re-enabling without variableSetId keeps the stored attachment instead of
|
|
550
|
-
// silently dropping it; the inherited attachment is re-validated below in
|
|
551
|
-
// case the variableSet was deleted or its variables changed since. The
|
|
552
|
-
// inherited attachment was authorized with variableSets:use when it was
|
|
553
|
-
// first attached, so only a fresh attachment re-checks that permission.
|
|
554
|
-
// Back-compat: installations enabled before the Variable Set rename stored
|
|
555
|
-
// the attachment under `metadata.environmentId`; read it as a fallback so a
|
|
556
|
-
// re-enable without an explicit id still inherits the existing attachment.
|
|
557
|
-
const storedVariableSetId =
|
|
558
|
-
typeof existing?.metadata.variableSetId === "string"
|
|
559
|
-
? existing.metadata.variableSetId
|
|
560
|
-
: typeof existing?.metadata.environmentId === "string"
|
|
561
|
-
? existing.metadata.environmentId
|
|
562
|
-
: undefined;
|
|
563
|
-
const variableSetId = payload.variableSetId ?? storedVariableSetId;
|
|
564
|
-
if (pack.variableSet?.required && !variableSetId) {
|
|
565
|
-
throw new HTTPException(422, {
|
|
566
|
-
message: "this pack requires a variableSet attachment; pass variableSetId",
|
|
567
|
-
});
|
|
568
|
-
}
|
|
569
|
-
if (variableSetId) {
|
|
570
|
-
const variableSet = await validateVariableSetAttachment(
|
|
571
|
-
{ settings, db },
|
|
572
|
-
grant,
|
|
573
|
-
workspaceId,
|
|
574
|
-
variableSetId,
|
|
575
|
-
{ preauthorized: !payload.variableSetId },
|
|
576
|
-
);
|
|
577
|
-
const missing = (pack.variableSet?.requiredVariables ?? []).filter(
|
|
578
|
-
(name) => !variableSet.variables.some((variable) => variable.name === name),
|
|
579
|
-
);
|
|
580
|
-
if (missing.length > 0) {
|
|
581
|
-
throw new HTTPException(422, {
|
|
582
|
-
message: `variableSet is missing required variable(s): ${missing.join(", ")}`,
|
|
583
|
-
});
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
const installation = await enablePackInstallation(db, {
|
|
587
|
-
accountId: grant.accountId,
|
|
588
|
-
workspaceId,
|
|
589
|
-
packId: pack.id,
|
|
590
|
-
metadata: {
|
|
591
|
-
...payload.metadata,
|
|
592
|
-
packVersion: pack.version,
|
|
593
|
-
...(variableSetId ? { variableSetId } : {}),
|
|
594
|
-
},
|
|
595
|
-
});
|
|
596
|
-
return c.json(installation, existing ? 200 : 201);
|
|
597
|
-
});
|
|
598
|
-
|
|
599
|
-
app.post(
|
|
600
|
-
"/v1/workspaces/:workspaceId/packs/marketing-social-daily-analysis/scheduled-tasks",
|
|
601
|
-
async (c) => {
|
|
602
|
-
const workspaceId = c.req.param("workspaceId");
|
|
603
|
-
const grant = await requireAccessGrant(c, deps, workspaceId, "scheduled_tasks:manage");
|
|
604
|
-
const pack = await requirePack(db, workspaceId, MARKETING_SOCIAL_PACK_ID);
|
|
605
|
-
const installation = await getPackInstallation(db, workspaceId, pack.id);
|
|
606
|
-
if (installation?.status !== "active") {
|
|
607
|
-
throw new HTTPException(409, {
|
|
608
|
-
message: "enable the marketing social pack before creating its scheduled tasks",
|
|
609
|
-
});
|
|
610
|
-
}
|
|
611
|
-
const payload = MarketingDailyAnalysisTaskRequest.parse(await c.req.json());
|
|
612
|
-
await requireLimit(deps, {
|
|
613
|
-
accountId: grant.accountId,
|
|
614
|
-
workspaceId,
|
|
615
|
-
action: "schedule:create",
|
|
616
|
-
quantity: 1,
|
|
617
|
-
});
|
|
618
|
-
const connections = await resolveSocialConnections(
|
|
619
|
-
db,
|
|
620
|
-
workspaceId,
|
|
621
|
-
payload.connectionIds,
|
|
622
|
-
grant.subjectId,
|
|
623
|
-
);
|
|
624
|
-
if (connections.length === 0) {
|
|
625
|
-
throw new HTTPException(422, {
|
|
626
|
-
message: "at least one connected social account is required",
|
|
627
|
-
});
|
|
628
|
-
}
|
|
629
|
-
await validateDocumentBaseIds(db, workspaceId, payload.documentBaseIds);
|
|
630
|
-
const agentConfig = buildMarketingDailyAnalysisAgentConfig({
|
|
631
|
-
connections,
|
|
632
|
-
documentBaseIds: payload.documentBaseIds,
|
|
633
|
-
...(payload.promptInstructions ? { promptInstructions: payload.promptInstructions } : {}),
|
|
634
|
-
});
|
|
635
|
-
// Installation-inherited variableSet attachment: it was authorized with
|
|
636
|
-
// variableSets:use at pack-enable time, so the scheduled_tasks:manage
|
|
637
|
-
// caller here is not re-checked for that permission.
|
|
638
|
-
const installationVariableSetId =
|
|
639
|
-
typeof installation.metadata.variableSetId === "string"
|
|
640
|
-
? installation.metadata.variableSetId
|
|
641
|
-
: typeof installation.metadata.environmentId === "string"
|
|
642
|
-
? installation.metadata.environmentId
|
|
643
|
-
: undefined;
|
|
644
|
-
const task = await createValidatedScheduledTask({
|
|
645
|
-
settings,
|
|
646
|
-
db,
|
|
647
|
-
objectStorage,
|
|
648
|
-
grant,
|
|
649
|
-
variableSetPreauthorized: true,
|
|
650
|
-
payload: {
|
|
651
|
-
name: payload.name ?? "Daily social media analysis",
|
|
652
|
-
status: payload.status,
|
|
653
|
-
action: { kind: "agent_turn" },
|
|
654
|
-
connectionAuthorities: [],
|
|
655
|
-
schedule: {
|
|
656
|
-
type: "calendar",
|
|
657
|
-
timeZone: payload.timeZone,
|
|
658
|
-
hour: payload.hour,
|
|
659
|
-
minute: payload.minute,
|
|
660
|
-
},
|
|
661
|
-
runMode: payload.runMode,
|
|
662
|
-
overlapPolicy: payload.overlapPolicy,
|
|
663
|
-
agentConfig,
|
|
664
|
-
...(installationVariableSetId ? { variableSetId: installationVariableSetId } : {}),
|
|
665
|
-
...(installation.selectedRigId ? { rigId: installation.selectedRigId } : {}),
|
|
666
|
-
metadata: {
|
|
667
|
-
packId: pack.id,
|
|
668
|
-
packVersion: pack.version,
|
|
669
|
-
packTemplateId: "daily-social-analysis",
|
|
670
|
-
socialConnectionIds: connections.map((connection) => connection.id),
|
|
671
|
-
documentBaseIds: payload.documentBaseIds,
|
|
672
|
-
},
|
|
673
|
-
},
|
|
674
|
-
});
|
|
675
|
-
await syncCreatedScheduledTask({ db, workflowClient, task });
|
|
676
|
-
return c.json(task, 201);
|
|
677
|
-
},
|
|
678
|
-
);
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
function packMutationHttpError(error: unknown): HTTPException {
|
|
682
|
-
if (error instanceof SkillSourceRemovalAuthorityError)
|
|
683
|
-
return new HTTPException(403, { message: error.message });
|
|
684
|
-
if (error instanceof PackManifestChangedError) {
|
|
685
|
-
return new HTTPException(409, {
|
|
686
|
-
message: "The Pack manifest changed after preview. Review the updated installation plan.",
|
|
687
|
-
});
|
|
688
|
-
}
|
|
689
|
-
if (error instanceof PackOperationClaimLostError) {
|
|
690
|
-
return new HTTPException(409, {
|
|
691
|
-
message: "This Pack operation was recovered by another request; review the current state",
|
|
692
|
-
});
|
|
693
|
-
}
|
|
694
|
-
if (error instanceof PackOperationIdempotencyError) {
|
|
695
|
-
return new HTTPException(409, {
|
|
696
|
-
message: "Pack idempotency key was already used",
|
|
697
|
-
});
|
|
698
|
-
}
|
|
699
|
-
if (error instanceof PackOperationInProgressError) {
|
|
700
|
-
return new HTTPException(409, {
|
|
701
|
-
message: "Another Pack operation is still running; wait for it to finish, then review again",
|
|
702
|
-
});
|
|
703
|
-
}
|
|
704
|
-
if (error instanceof PackInstallationVersionConflictError) {
|
|
705
|
-
return new HTTPException(409, {
|
|
706
|
-
message: "Pack installation changed after preview",
|
|
707
|
-
});
|
|
708
|
-
}
|
|
709
|
-
if (error instanceof PackInstallationVersionRequiredError) {
|
|
710
|
-
return new HTTPException(400, {
|
|
711
|
-
message: "Updating or repairing a Pack requires the previewed installation version",
|
|
712
|
-
});
|
|
713
|
-
}
|
|
714
|
-
if (error instanceof HTTPException) return error;
|
|
715
|
-
return new HTTPException(422, {
|
|
716
|
-
message: error instanceof Error ? error.message : "Pack mutation failed",
|
|
717
|
-
});
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
function packFailureCode(error: unknown): string {
|
|
721
|
-
if (error instanceof HTTPException) return `http_${error.status}`;
|
|
722
|
-
if (error instanceof PackComponentResolutionError) return `component_${error.status}`;
|
|
723
|
-
if (error instanceof CapabilityComponentVersionConflictError) return "component_version_conflict";
|
|
724
|
-
return "component_install_failed";
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
function sha256(value: string): string {
|
|
728
|
-
return createHash("sha256").update(value).digest("hex");
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
async function requirePack(db: ApiRouteDeps["db"], workspaceId: string, packId: string) {
|
|
732
|
-
const pack = await resolveCapabilityPack(db, workspaceId, packId);
|
|
733
|
-
if (!pack) {
|
|
734
|
-
throw new HTTPException(404, { message: "pack not found" });
|
|
735
|
-
}
|
|
736
|
-
return pack;
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
async function resolveSocialConnections(
|
|
740
|
-
db: ApiRouteDeps["db"],
|
|
741
|
-
workspaceId: string,
|
|
742
|
-
connectionIds: string[],
|
|
743
|
-
subjectId: string,
|
|
744
|
-
): Promise<SocialConnection[]> {
|
|
745
|
-
const ids = [...new Set(connectionIds)];
|
|
746
|
-
const connections =
|
|
747
|
-
ids.length > 0
|
|
748
|
-
? await Promise.all(
|
|
749
|
-
ids.map(async (id) => {
|
|
750
|
-
const connection = await getSocialConnection(db, workspaceId, id, subjectId);
|
|
751
|
-
if (!connection) {
|
|
752
|
-
throw new HTTPException(422, {
|
|
753
|
-
message: `unknown social connection: ${id}`,
|
|
754
|
-
});
|
|
755
|
-
}
|
|
756
|
-
return connection;
|
|
757
|
-
}),
|
|
758
|
-
)
|
|
759
|
-
: (await listSocialConnections(db, workspaceId, 500, subjectId)).filter(
|
|
760
|
-
(connection) => connection.status === "connected",
|
|
761
|
-
);
|
|
762
|
-
const inactive = connections.find((connection) => connection.status !== "connected");
|
|
763
|
-
if (inactive) {
|
|
764
|
-
throw new HTTPException(422, {
|
|
765
|
-
message: `social connection ${inactive.id} is ${inactive.status}`,
|
|
766
|
-
});
|
|
767
|
-
}
|
|
768
|
-
return connections;
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
async function validateDocumentBaseIds(
|
|
772
|
-
db: ApiRouteDeps["db"],
|
|
773
|
-
workspaceId: string,
|
|
774
|
-
documentBaseIds: string[],
|
|
775
|
-
): Promise<void> {
|
|
776
|
-
for (const baseId of [...new Set(documentBaseIds)]) {
|
|
777
|
-
const base = await getDocumentBase(db, workspaceId, baseId);
|
|
778
|
-
if (!base) {
|
|
779
|
-
throw new HTTPException(422, {
|
|
780
|
-
message: `unknown document base: ${baseId}`,
|
|
781
|
-
});
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
}
|