I also think that delegating to AddIndex and RemoveIndex is the way forward. The fact AlterIndexTogether was kept around while raising an unhelpful TypeError instead of producing some instructions about how the problem can be solved appears to have been an oversight?
We can’t have AlterIndexTogether.reduce to list[type[AddIndex] | type[RemoveIndex]] because it requires knowledge about the current project state and one of the flaw of AlterTogether operations is that they don’t treat indices as CRUD atoms so there’s no way for the optimization logic to know in which context (which indices exists prior to the optimization) the operation is being reduced. In other words, there’s no way to have squashmigrations get rid of AlterIndexTogether that I know of; the shim will have to be kept around for the foreseeable future.
We can definitely adapt AlterIndexTogether.state_forwards and .database_(forwards|backwards) to delegate to AddIndex and RemoveIndex by introspecting state and from_state though.
Whichever solution we decide to go forward with should serve as a validation for attempting the same deprecation path on unique_together -> constraints.