Skip to content

Undo: snapshot/restore for AI-driven changes#19

Merged
oxyc merged 3 commits into
masterfrom
undo-restore
May 27, 2026
Merged

Undo: snapshot/restore for AI-driven changes#19
oxyc merged 3 commits into
masterfrom
undo-restore

Conversation

@oxyc

@oxyc oxyc commented May 26, 2026

Copy link
Copy Markdown
Member

Summary

Adds a comprehensive, uniform undo for AI-assistant-driven writes. Builds on the failsafe guards (#18) and follows the per-ability reversibility audit. Companion to the gds-assistant undo PR.

A mutating ability captures a "before" snapshot via the Reversible trait + Snapshot helpers and attaches it to its result under _undo. gds-assistant peels that off (never shown to the LLM), stores it on the audit log, and replays it through the gds-mcp/restore_snapshot filter — RestoreSnapshot dispatches by kind. Restores use low-level APIs so they don't recurse into the destructive-write guards.

Reversibility by ability (per the audit)

  • Faithful, same-id: content update (fields+meta+terms), content delete→untrash, term update, term delete → recreated under its ORIGINAL term_id + term_taxonomy_id (so menu items / ACF fields / query blocks that stored the id keep working — no reference rewriting needed), form update, translation re-link, string translation, blocks-patch, revisions-restore, nav-menu update/reorder/move.
  • Undo a create → trash/delete the new object: content/duplicate/media/translation creates, term/term-translation creates, form/feed creates.
  • nav-menu-items-delete switched from hard delete to trash so undo is a clean untrash and child→parent links survive.
  • bulk-update: captures only touched status/meta (merge restore, never full content) with a size guard.
  • machine-translate: branches the snapshot on create-new vs overwrite-existing vs string-group.
  • feeds-delete: recreated via add_feed (new feed id — noted as a caveat).
  • redirects-create: provider-aware undo (Safe Redirect Manager / Redirection / Yoast).
  • Not undoable (by design): mail-send (email is irreversible) and cache-clear (transient). Reads have no undo.

Caveats surfaced to the user

When a restore can't be perfectly faithful (e.g. a term id had to change because the old one was reused, or DeepL credits were spent), the restore returns caveats that the assistant relays.

Tests

RestoreSnapshotTest round-trips the core kinds against real WP, including the term-id-preservation guarantee and the partial (merge) restore.

🤖 Generated with Claude Code

oxyc and others added 2 commits May 26, 2026 13:02
Mutating abilities capture a before-state via the Reversible trait + Snapshot helpers and attach it to their result under _undo. gds-assistant peels that off (never shown to the LLM), stores it, and replays it through the gds-mcp/restore_snapshot filter, which RestoreSnapshot dispatches by kind: restore-post (+ partial merge), untrash, trash, restore/delete/recreate-term (term recreate preserves the ORIGINAL id + term_taxonomy_id so references stay valid), restore/delete/recreate-feed, restore-redirect (provider-aware), restore-translation-link, restore-string, and bulk. Restores use low-level APIs so they don't recurse into the destructive-write guards.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Every reversible write now records how to undo it: content + terms + forms + feeds (create/update/delete/duplicate) + redirects + nav menu items + media + bulk-update + duplicate + blocks-patch + revisions-restore + Polylang create/link/string/machine-translate. Notable: nav-menu-items-delete switched from hard delete to trash (so undo is a simple untrash and child->parent links survive); bulk-update captures only touched status/meta (merge restore) with a size guard; machine-translate branches its snapshot on whether it creates or overwrites a translation; mail-send and cache-clear are intentionally left non-undoable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@oxyc

oxyc commented May 26, 2026

Copy link
Copy Markdown
Member Author

Companion (assistant side: stores snapshots, adds assistant__undo): generoi/gds-assistant#26

Term undo captured/replayed with the REST base (e.g. "categories") instead
of the real taxonomy slug ("category"), so termForRecreate silently returned
[] and no _undo was attached; and wp_set_object_terms skipped the freshly
raw-inserted term id, so posts were not reattached. Resolve the REST base to
the slug for all term snapshots, and reattach objects by inserting
term_relationships rows directly. Verified end-to-end on a live site.

Adds UndoRoundTripTest driving the real abilities through their REST-base
inputs (not the restore handlers directly), so this class of bug is caught
at the integration layer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@oxyc oxyc merged commit 7ce5c6e into master May 27, 2026
2 checks passed
@oxyc oxyc deleted the undo-restore branch May 27, 2026 15:22
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.

1 participant