The editor re-runs save() on load and compares the result with what is stored in the post. Any difference is a validation error, and the block offers to convert itself to raw HTML.
save({ attributes }) {
return (
<div className={`notice is-${attributes.tone}`}>
<RichText.Content tagName="p" value={attributes.body} />
</div>
);
}
// changing this — even adding a wrapper div — invalidates every
// post that already contains the block.
Whitespace, attribute order and class names are all part of the comparison, so a change that looks cosmetic is not. The failure is not loud: an editor opening an old post sees a warning they did not cause and usually clicks the button that rewrites the content. That is why the change has to be paired with a deprecation rather than shipped on its own, and why a block’s markup should be conservative from the first release.