I’ve wondered whether it would be possible to work with immutable code versions - I remember reading something along those lines in Erlang? Keep the version as part of the type. If MyClass has a field of type YourClass then it knows which version of it it is, and if you release a new version of YourClass then I keep using the old one until I release a new version of MyClass, and code tries to mismatch versions of YourClass then it’ll be a compile error (or there could be an adapter typeclass or some such).
This is true in Rust at least. We allow you to include two incompatible versions of transitive dependencies, as they should be internal But sometimes they’d leak through into a public interface, and then you’d get a very confusing error “expected type Foo, got type Foo”. The ecosystem has learned some lessons and people are more careful about this kind of thing now.