From e6f106b8f82ff6bb47c8e08e1aedf3fb8bc57d5f Mon Sep 17 00:00:00 2001 From: Dotta Date: Sun, 8 Nov 2026 20:21:33 -0600 Subject: [PATCH] fix(api-catalog): describe the real backup and restore paths for agents The database backup catalog entry had only a summary. An agent asked to back up an instance and check a restore got 403, found no other path and told the person to use an "Instance settings > Backups" page that does not exist. The entry now says who can call it, that the UI only sets backup retention (on /company/settings), that operators can run `paperclipai db:backup`, what the result file is, and that a restore check goes into a separate empty database with psql, never over the live one. Co-Authored-By: Claude Opus 5.5 --- .../database-backup-catalog-guidance.test.ts | 15 +++++++++++++++ server/src/routes/openapi.ts | 1 + 2 files changed, 16 insertions(+) create mode 100644 server/src/__tests__/database-backup-catalog-guidance.test.ts 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..21b28c9aa45 --- /dev/null +++ b/server/src/__tests__/database-backup-catalog-guidance.test.ts @@ -0,0 +1,15 @@ +import { expect, it } from "vitest"; +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"); +}); diff --git a/server/src/routes/openapi.ts b/server/src/routes/openapi.ts index 08bea0a8c19..e4c2f54bb37 100644 --- a/server/src/routes/openapi.ts +++ b/server/src/routes/openapi.ts @@ -9885,6 +9885,7 @@ registry.registerPath({ path: "/api/instance/database-backups", tags: ["instance"], summary: "Trigger a database backup", + 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: { 200: r.ok(), 401: r.unauthorized, 403: r.forbidden }, });