As a straw man, if we went to a typeclass-driven 'next arg is representational' approach then we could possibly recover 'join' here.
e.g.
class Representational f where
rep :: Coercion f g -> Coercion a b -> Coercion (f a) (g b)
class Representational f => Phantom f where
phantom :: Coercion f g -> Coercion (f a) (g b)
When we look at for the constraint head Coercible (f a) (g b) we'd then check for Phantom f and if so look for Coercible f g, or fall back to Representational f, and look for Coercible f g and Coercible a b.
Then to derive GND for C m it'd need to check Representational m due to the type of join needing m's next arg to be representational, find it and move on.
The set of these instances can be left open like any other typeclass and users could supply their own for tricky conversions if need be.
If you just wrote the existing role annotations, but let them request currently impossible roles you could use the desired role to infer the constraints involved.
e.g.
newtype StateT s m a = StateT { runStateT :: s -> m (a, s) }
should be able to derive something like
instance Representational (StateT s)
instance Representational m => Representational (StateT s m)
instance Phantom m => Phantom (StateT s m)
If we put nominal or representational on the m argument
type role StateT nominal nominal nominal
then we could just elide the instances for anything lower in the nominal -> representational -> phantom lattice than the named role.
The positional argument scheme isn't perfect, as in the above it'd fail to let you derive `instance Representational StateT` due to the s occurring under m, an argument later in the argument list, but it does fix most of the problems with Coercible and monad transfomers.
There might be some fancier form that can cover all of the use cases, but I'm not seeing it off hand.
-Edward