From 1171878148ca8301f532e73e36e60e4b9612ff56 Mon Sep 17 00:00:00 2001 From: Dotta Date: Sat, 7 Nov 2026 21:43:47 -0600 Subject: [PATCH] fix(server): document the safe company import request body in the API catalog POST /api/companies/{companyId}/imports/preview and /imports/apply registered no request body, so agents reading the catalog could not learn the source, target, collisionStrategy or nameOverrides fields. Register the existing preview and import schemas and describe the safe-route limits. Co-Authored-By: Claude Opus 5.5 --- server/src/__tests__/openapi-routes.test.ts | 10 ++++++++++ server/src/routes/openapi.ts | 12 ++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/server/src/__tests__/openapi-routes.test.ts b/server/src/__tests__/openapi-routes.test.ts index e70b913ba2d..3500aa68f94 100644 --- a/server/src/__tests__/openapi-routes.test.ts +++ b/server/src/__tests__/openapi-routes.test.ts @@ -340,6 +340,16 @@ describe("openapi routes", () => { expect(operation.description).toMatch(/include\.projects/); } }); + it("documents the safe company import body so agents can preview and resolve agent name conflicts", () => { + const spec = buildOpenApiSpec(); + for (const path of ["/api/companies/{companyId}/imports/preview", "/api/companies/{companyId}/imports/apply"]) { + const operation = spec.paths[path].post; + const body = operation.requestBody.content["application/json"].schema; + expect(body.required).toEqual(expect.arrayContaining(["source", "target"])); + expect(body.properties).toMatchObject({ collisionStrategy: expect.anything(), nameOverrides: expect.anything(), include: expect.anything() }); + expect(operation.description).toMatch(/replace/); + } + }); it("documents generic webhook provenance headers and accepted responses without a provider-specific endpoint", () => { const spec = buildOpenApiSpec(); const operation = spec.paths["/api/routine-triggers/public/{publicId}/fire"].post; diff --git a/server/src/routes/openapi.ts b/server/src/routes/openapi.ts index 82c03636034..548232c49ff 100644 --- a/server/src/routes/openapi.ts +++ b/server/src/routes/openapi.ts @@ -2213,7 +2213,11 @@ registry.registerPath({ path: "/api/companies/{companyId}/imports/preview", tags: ["companies"], summary: "Preview company import", - request: { params: z.object({ companyId: z.string() }) }, + description: "Safe import into this company only: target must be { mode: \"existing_company\", companyId } for the route company or a new company, and collisionStrategy may be \"rename\" or \"skip\" (\"replace\" is rejected, so an existing agent is never overwritten). nameOverrides maps an incoming agent, project or issue slug to the name it should get. Preview never changes the company; apply creates or reuses resources and returns them.", + request: { + params: z.object({ companyId: z.string() }), + body: jsonBody(companyPortabilityPreviewSchema), + }, responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized }, }); @@ -2222,7 +2226,11 @@ registry.registerPath({ path: "/api/companies/{companyId}/imports/apply", tags: ["companies"], summary: "Apply company import", - request: { params: z.object({ companyId: z.string() }) }, + description: "Safe import into this company only: target must be { mode: \"existing_company\", companyId } for the route company or a new company, and collisionStrategy may be \"rename\" or \"skip\" (\"replace\" is rejected, so an existing agent is never overwritten). nameOverrides maps an incoming agent, project or issue slug to the name it should get. Preview never changes the company; apply creates or reuses resources and returns them.", + request: { + params: z.object({ companyId: z.string() }), + body: jsonBody(companyPortabilityImportSchema), + }, responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized }, });