It's the intended behavior.
Type any StarField is an existential type, which means it's a kind of "box" that can hold any concrete type that conforms to StarField and Codable and Equatable.
The box itself, though, does not conform to StarField or Codable or Equatable. That is for technical reasons, not an arbitrary decision.**
When you have a function parameter of some StarField, the compiler generates code on your behalf to extract the value of concrete type from the box, and pass that value into runSome. The some keyword here means the concrete type is opaque — we can't "see" what concrete type the box contains — but that doesn't matter, because all possible concrete types are conforming.
OK, that's why runSome works.
For runSome2, something completely different is going wrong. You want to pass in a keypath, and the WritableKeyPath type requires a base type (FieldContainer in your example) which must be Equatable.
But your FieldContainer isn't Equatable!
You would need to do at least this:
class FieldContainer: Equatable {
but that's not enough on its own. FieldContainer has a property of type any StarField which isn't Equatable (see above), so the compiler can't synthesize Equatable conformance for FieldContainer for you.
To make this work, you'd have to provide your own implementation of Equatable for your FieldContainer class.
** Here's the reason why existential box types cannot in general conform to their underlying protocol:
Think about any Equatable as a simplified example. If any Equatable conformed to Equatable, you could have one any Equatable box containing an Int, and another containing a String, because both Int and String conform to Equatable.
But you couldn't check whether those 2 boxes were == to each other, since there's no equality function to test types Int and String.