Save / restore semantics for an object are implemented by clients reaching into the object's internals, copying or referencing its fields, and writing them back later. Aliasing bugs (shared references mutate underneath), shallow-clone bugs (nested fields not deep-copied), and encapsulation breaches accumulate.
Client-side snapshot logic the agent must verify across every consumer for deep-copy correctness. Shared-reference aliasing is invisible to static reads; the agent cannot prove from one call site whether 'snapshot then mutate' is safe.
The object exposes save() returning an opaque Memento and restore(memento) accepting one. Clients treat the Memento as a token they cannot inspect or modify; the object owns deep-copy semantics and field selection.
One save / restore pair the agent reads inside the editor class. Cross-client correctness is structurally guaranteed by treating Memento as opaque; the agent verifies one location for clone depth and one location for restore field-set completeness.
Before the pattern
class GraphEditor {constructor() {this.nodes = [];this.edges = [];this.selection = new Set();}}function undoableMutation(editor, fn) {const prev = {nodes: editor.nodes,edges: editor.edges,selection: editor.selection,};fn();return () => {editor.nodes = prev.nodes;editor.edges = prev.edges;editor.selection = prev.selection;};}// Clients reach into internals to snapshot; references shared with the editor// mutate underneath; restore is a partial revert because nested objects were// not cloned. Insider Trading + Mutable Data conspire to produce silent// state-leak bugs.
After the pattern
class GraphMemento {constructor(snapshot) {this.snapshot = snapshot;}}class GraphEditor {constructor() {this.nodes = [];this.edges = [];this.selection = new Set();}save() {return new GraphMemento({nodes: structuredClone(this.nodes),edges: structuredClone(this.edges),selection: new Set(this.selection),});}restore(memento) {this.nodes = memento.snapshot.nodes;this.edges = memento.snapshot.edges;this.selection = memento.snapshot.selection;}}const before = editor.save();/* mutate */editor.restore(before);
Per-client snapshot logic is Insider Trading on the object's internals AND Mutable Data risk through shared references. Subtle state-leak bugs accumulate over time; undo that 'mostly works' is the worst kind of broken because it survives every test that checks the happy path.
Per-client snapshot/restore code is N×M cells (N consumers × M state fields) the agent verifies on every state-shape change. Adding a new field requires editing every snapshot site; one missed site silently produces a partial undo that survives review.
Mementos can be expensive — deep-cloning a large state every save() costs CPU and memory. Persistent data structures or copy-on-write are advanced alternatives but more complex. Naive Memento on a big object scales badly with save frequency.
Opaque Memento means the agent investigating a runtime issue (e.g., 'why did this undo restore the wrong selection?') cannot inspect the Memento in stack traces. Debugging requires save/restore-aware instrumentation; without it, mementos look like black boxes the agent must trust.
Save / restore is one call per side; the editor owns the cloning depth; clients cannot accidentally peek into or mutate a Memento; undo history is a list of opaque tokens the editor knows how to apply. New fields land in save() / restore() together, not in every client.
Save and restore live on the editor in two methods; adding a new field is one assignment in save plus one in restore, and consumers pass the memento as an opaque token without reading its field set.
Mementos that expose their internal snapshot field (memento.snapshot.nodes) to outside readers defeat the encapsulation. Clients that depend on snapshot inspection become coupled to the editor's representation; the pattern's promise degrades to 'a copy of the editor's state in a different name'. Keep the Memento truly opaque.
Clients that read memento.snapshot directly defeat the encapsulation and create new Insider Trading on the editor's representation. The agent reading client code trusts the Memento contract; the runtime coupling contradicts that trust silently. Lint or document the opacity invariant or it will erode commit-by-commit.