Skip to content

feat(sites): soft delete with configurable retention and restore - #256

Merged
vigneshrajsb merged 4 commits into
mainfrom
site-soft-delete
Oct 9, 2026
Merged

vigneshrajsb merged 4 commits into
mainfrom
site-soft-delete

Conversation

@vigneshrajsb

@vigneshrajsb vigneshrajsb commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Description

Builds on #254 (merged).

  • Soft delete: deleting a site still takes it offline immediately, but its content is now kept for sites.cleanup.deletedRetentionDays (default 30; 0 purges on the next cleanup run) instead of being purged at once.

  • Restore: owners can restore a deleted site at the same id and URL with POST /api/v2/sites/{siteId}/restore or the new restore_site MCP tool. A restore needs the current expectedAccessRevision, and a site whose expiry has passed gets a fresh TTL.

  • Finding deleted sites: GET /api/v2/sites?view=deleted and list_sites with deleted: true return the caller's restorable sites. Site responses gain deletedAt, restorableUntil and permissions.canRestore.

  • Purge: the existing sites cleanup job moves deleted sites past the retention window to a terminal purged status in a single conditional update, then removes their storage. A concurrent restore holds the row lock, so a site can't be both restored and purged.

  • Sites already deleted before this change had their content purged at the time, so they can't be restored and are left out of the deleted view.

  • MCP catalog size: the new site tool outputs use a smaller schema to stay under the 64 KiB catalog cap. The catalog is now 65,247 bytes, so the next new tool will need the cap raised or other tools trimmed.

  • Behaviour notes:

    • A site whose expiry had passed gets a fresh TTL on restore, or no expiry if TTL is off, so cleanup can't immediately expire it again.
    • If the cleanup job is disabled, delete purges right away, as before, because nothing would purge it later.
    • Retention 0 means no restore window.
    • Changing deletedRetentionDays applies to sites that are already deleted at the next cleanup run: lowering it purges older ones, and raising it can't bring back sites that are already purged.
  • Capabilities now include deletedRetentionDays, so clients can show the retention period.

Test plan

  • Service, route, config, OpenAPI, and MCP unit tests, including restore edge cases (purged, past retention, legacy-purged content, non-owner, stale revision), the deleted view, and the purge job. Full Jest suite, lint, and server typecheck pass.
  • Locally or in a preview: delete a site, check its link stops working, find it in view=deleted, restore it, and check the same link works again.
  • Set retention to 0, delete a site, run the cleanup job, and check the site becomes purged and can no longer be restored.
  • From an MCP client: delete_site, then list_sites with deleted: true, then restore_site.

Deleting a site now keeps its content for sites.cleanup.deletedRetentionDays
(default 30) before the cleanup job purges it. Owners can list their
restorable sites (view=deleted) and restore one at the same id and URL via
POST /api/v2/sites/{siteId}/restore or the restore_site MCP tool.
@vigneshrajsb
vigneshrajsb changed the base branch from mcp-site-write-tools to main October 9, 2026 17:46
@vigneshrajsb
vigneshrajsb marked this pull request as ready for review October 9, 2026 19:55
@vigneshrajsb
vigneshrajsb requested a review from a team as a code owner October 9, 2026 19:55
… cleanup

Restoring a site whose expiry had passed while TTL is off now clears the
expiry, so the next cleanup run cannot expire and purge it. When the
cleanup job is disabled, delete purges at once as before, since nothing
would purge it later. Retention 0 reports no restore window.
@vigneshrajsb
vigneshrajsb merged commit ed1006a into main Oct 9, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants