From e8e965da36b2c9cff021b9331ca156025a74b51a Mon Sep 17 00:00:00 2001 From: Dotta Date: Wed, 4 Nov 2026 07:43:53 -0600 Subject: [PATCH] fix(openapi): expose strict catalog team request schemas Co-Authored-By: Paperclip --- server/src/__tests__/teams-catalog-routes.test.ts | 11 +++++++++++ server/src/routes/openapi.ts | 3 +++ server/src/services/native-runtime/runner-api.test.ts | 11 +++++++++++ 3 files changed, 25 insertions(+) diff --git a/server/src/__tests__/teams-catalog-routes.test.ts b/server/src/__tests__/teams-catalog-routes.test.ts index bf808a73667..4696f297b92 100644 --- a/server/src/__tests__/teams-catalog-routes.test.ts +++ b/server/src/__tests__/teams-catalog-routes.test.ts @@ -211,6 +211,17 @@ describe("teams catalog routes", () => { expect(mockCatalogModule.listCatalogTeams).not.toHaveBeenCalled(); }); + it("still rejects unknown preview and install fields before service mutation", async () => { + const app = await createApp({ type: "agent", agentId: "agent-1", companyId, runId: "run-1" }); + for (const operation of ["preview", "install"]) { + const res = await request(app).post(`/api/companies/${companyId}/teams/catalog/product-engineering/${operation}`) + .send({ parentAgentId: "undocumented-field" }); + expect(res.status).toBe(400); + } + expect(mockTeamsCatalogService.previewCatalogTeamImport).not.toHaveBeenCalled(); + expect(mockTeamsCatalogService.installCatalogTeam).not.toHaveBeenCalled(); + }); + it("previews catalog teams with company access and actor/source policy context", async () => { const app = await createApp({ type: "board", diff --git a/server/src/routes/openapi.ts b/server/src/routes/openapi.ts index aeed99aa9fa..c1efdf31fec 100644 --- a/server/src/routes/openapi.ts +++ b/server/src/routes/openapi.ts @@ -1,3 +1,4 @@ +import { catalogTeamInstallSchema, catalogTeamPreviewSchema } from "@paperclipai/shared"; import { MUSE_ASSET_VERSION, museStopBoundarySchema, museDetectorCleanupAttestationSchema, createGrokInvitationSchema, configureGrokRoutineSchema, verifyGrokRoutineSchema, recoverGrokRoutineSchema, pairGrokBindingSchema, stopGrokWorkSchema, retryGrokDeliverySchema, updateFastResponseSchema } from "@paperclipai/shared"; import { slackTestMessageSchema, slackTestMessageResultSchema, slackManagerAuthorizationSchema, slackSetupOptionsSchema, slackManagedProvisionSchema, slackManagedResultSchema, slackRegistrationSchema, slackSetupActionSchema, slackInstallAuthorizationSchema, slackRegistrationStateSchema, slackAppConfigurationSchema, slackAvatarStateSchema, slackAccountStateSchema } from "@paperclipai/shared"; import { experimentalApiMetadata } from "./experimental-api-metadata.js"; @@ -3103,6 +3104,8 @@ for (const route of [ path: route[1], tags: ["teams"], summary: route[2], + body: route[1].endsWith("/preview") ? catalogTeamPreviewSchema + : route[1].endsWith("/install") ? catalogTeamInstallSchema : undefined, ...(route[1].endsWith("/install") ? { description: "Imports team resources; HTTP success does not prove every required skill is ready. Read warnings for post-import failures and skillPreparations for required sources; a preparation plan is not proof of installation. Report unresolved requirements as partial completion and preserve existing resources. Use the actual returned portabilityImport agents and projects for company-scoped resource links. Before repeating an uncertain install, inspect installed teams and saved resource identities rather than creating another copy. resourceLinks provides canonical company-scoped hrefs for actual saved agents, projects and routines, including reused resources; share these links with the installation outcome.", responses: { 201: r.ok(z.object({ resourceLinks: z.array(z.object({ diff --git a/server/src/services/native-runtime/runner-api.test.ts b/server/src/services/native-runtime/runner-api.test.ts index ad6f68a3816..e03c6d9452d 100644 --- a/server/src/services/native-runtime/runner-api.test.ts +++ b/server/src/services/native-runtime/runner-api.test.ts @@ -164,6 +164,17 @@ describe("runner API catalog", () => { expect(operation.authorization.actor).toBeTruthy(); }); + it("exposes team preview and install bodies through the actual adapter override search", () => { + const results = searchRunnerApi({ query: "adapterOverrides install team under manager parentAgentId", limit: 10 }).results; + const preview = results.find(row => row.operationId === "POST /api/companies/{companyId}/teams/catalog/{catalogId}/preview")!; + const install = results.find(row => row.operationId === "POST /api/companies/{companyId}/teams/catalog/{catalogId}/install")!; + const previewBody = preview.requestBody?.content["application/json"].schema; + const installBody = install.requestBody?.content["application/json"].schema; + expect(previewBody).toMatchObject({ additionalProperties: false, properties: { targetManagerAgentId: expect.any(Object), nameOverrides: expect.any(Object) } }); + expect(installBody).toMatchObject({ additionalProperties: false, properties: { targetManagerAgentId: expect.any(Object), adapterOverrides: expect.any(Object), secretValues: expect.any(Object) } }); + expect(previewBody.properties).not.toHaveProperty("adapterOverrides"); + }); + it("ranks natural language, explains dedicated alternatives, and supports exact lookup", () => { expect( searchRunnerApi({ query: "create project" }).results.map(