The deprecated array is how a block changes its markup safely

A block declares its old shapes, and the editor tries each in turn when the current save() does not match — so existing content is parsed with the version that produced it.

registerBlockType('turkerdev/notice', {
  attributes: { tone: { type: 'string' }, body: { type: 'string', source: 'html', selector: 'p' } },

  save({ attributes }) { /* the current markup */ },

  deprecated: [
    {
      attributes: { /* the OLD attribute shape */ },
      save({ attributes }) { /* the OLD markup */ },
      migrate(attributes) {
        return { ...attributes, tone: attributes.type || 'info' };
      },
    },
  ],
});

The migrate function is what lets attributes be renamed as well as markup changed, and it runs once when the block is loaded — the post is only rewritten when the editor saves. Deprecations are tried newest first and the array grows forever, which is the honest cost: a block on its fourth markup revision carries four save functions. Adding the deprecation in the same commit as the change is the only version of this that works, because retrofitting means every post has already shown a warning.