diff --git a/server/src/__tests__/database-backup-catalog-guidance.test.ts b/server/src/__tests__/database-backup-catalog-guidance.test.ts new file mode 100644 index 00000000000..fe47d774c2f --- /dev/null +++ b/server/src/__tests__/database-backup-catalog-guidance.test.ts @@ -0,0 +1,24 @@ +import { expect, it } from "vitest"; +import { buildOpenApiDocument } from "../routes/openapi.js"; +import { searchRunnerApi } from "../services/native-runtime/runner-api-catalog.js"; + +// An agent asked to back up and verify a restore cannot do either. Its API search must give it the real operator +// paths, so it does not invent a backup page: the retention setting, the CLI, and restore into a separate database. +it.each(["backup instance restore snapshot", "database backups list restore"])("search %j describes the real backup and restore paths", query => { + const operation = searchRunnerApi({ query, limit: 10 }).results.find(row => row.operationId === "POST /api/instance/database-backups"); + expect(operation, query).toBeDefined(); + const description = operation!.description; + expect(description).toContain("agent credentials receive 403"); + expect(description).toContain("The web UI has no control that creates or restores a backup"); + expect(description).toContain("Backup retention section of the company-prefixed /company/settings page"); + expect(description).toContain("paperclipai db:backup"); + expect(description).toContain("separate, empty PostgreSQL database, never over the live one"); +}); + +// The route answers a successful manual backup with 201. This guards the published document, which agents and +// clients read; the registration and the CREATED_OPERATIONS fixup in openapi.ts both supply that 201. +it("publishes the status the backup route returns on success", () => { + const responses = buildOpenApiDocument().paths["/api/instance/database-backups"].post.responses; + expect(Object.keys(responses)).toContain("201"); + expect(Object.keys(responses)).not.toContain("200"); +}); diff --git a/server/src/routes/openapi.ts b/server/src/routes/openapi.ts index 8535b74e30f..b3a9bb92797 100644 --- a/server/src/routes/openapi.ts +++ b/server/src/routes/openapi.ts @@ -9886,7 +9886,8 @@ registry.registerPath({ path: "/api/instance/database-backups", tags: ["instance"], summary: "Trigger a database backup", - responses: { 200: r.ok(), 401: r.unauthorized, 403: r.forbidden }, + description: "Instance admins only: agent credentials receive 403, and cloud-managed instances run backups for you. Automatic gzip-compressed backups run about hourly by default; instance admins set how long they are kept in the Backup retention section of the company-prefixed /company/settings page. The web UI has no control that creates or restores a backup. For a backup now, an instance admin calls this endpoint, or an operator with shell access to the server runs `paperclipai db:backup` (add --json for metadata). The result names a gzip-compressed SQL file (backupFile) on the server host and its size. Paperclip has no restore endpoint or command: to check a backup, an operator restores the file with psql into a separate, empty PostgreSQL database, never over the live one, and then checks the restored data.", + responses: { 201: r.ok(), 401: r.unauthorized, 403: r.forbidden }, }); // ─── LLM text endpoints ───────────────────────────────────────────────────────