mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-10 12:37:55 +01:00
fix(server): document the safe company import request body in the API catalog (#19968)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - A CEO agent can preview and apply a company package import through
`POST /api/companies/{companyId}/imports/preview` and `/imports/apply`
> - Agents find API operations and their request bodies in the OpenAPI
catalog
> - The catalog entries for these two safe import routes have no request
body and no description of the safe-route limits
> - An agent then offered the person a `replace` choice that the safe
route rejects
> - This pull request registers the existing preview and import body
schemas and describes the limits
> - The benefit is that agents can resolve name conflicts with `rename`,
`skip` and `nameOverrides`, and do not offer choices that fail
## Linked Issues or Issue Description
**What happened**
A person attached a company package in chat and asked a CEO agent what
the package would add. The package had an agent named Morgan, and the
company already had a Morgan. The agent previewed the import correctly.
It then offered three choices: `skip`, `rename`, or `replace`
("overwrites the existing Morgan"). The safe import route rejects
`replace`. The catalog entries for `/imports/preview` and
`/imports/apply` have no request body, so the agent could not see the
route's fields or limits. PR #19945 fixed the same gap for `/exports`.
**Expected behavior**
The catalog documents the import body, including `source`, `target`,
`collisionStrategy` and `nameOverrides`. It states that the safe route
rejects `replace` and never overwrites an existing agent.
**Steps to reproduce**
1. Read `GET /api/openapi.json`,
`paths["/api/companies/{companyId}/imports/preview"].post`.
2. See that it has no `requestBody`. The legacy
`/api/companies/import/preview` entry has one.
3. Call the safe route with `collisionStrategy: "replace"`. It returns
403.
## What Changed
- Register `companyPortabilityPreviewSchema` and
`companyPortabilityImportSchema` as the JSON request bodies of `POST
/api/companies/{companyId}/imports/preview` and `/imports/apply` in
`server/src/routes/openapi.ts`.
- Add a description of the safe-route limits: target, allowed collision
strategies, and `nameOverrides`.
- Add a regression test in
`server/src/__tests__/openapi-routes.test.ts`.
## Verification
- `cd server && pnpm exec vitest run
src/__tests__/openapi-routes.test.ts` passes (37 tests). The new test
fails on master because the entries have no `requestBody`.
- An end-to-end check runs the `companies.import-preview` chat eval with
this commit cherry-picked onto the eval branch.
## Risks
- Low risk. This changes only API documentation. The routes already
parse these schemas. Runtime behavior does not change.
## Model Used
- Anthropic Claude Opus 5.5 (`claude-opus-5-5`), with tool use and code
execution in Claude Code.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a480335e69
commit
81f12c21ec
2 files changed
+20
-2
No files matched your search
@@ -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;
|
||||
|
||||
@@ -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 },
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user