From 0afe9514ab2acd9bf321c364a1f6c004f1167113 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 (cherry picked from commit 1171878148ca8301f532e73e36e60e4b9612ff56) --- 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 28202426e70..60ce5435036 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 b52c6cf4842..bf57a7ac753 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 }, });