These classes are getting complicated and are beginning to serve as a generic representation of a field across multiple indexes. It's getting worse in in particular.
Let's clean it up:
Define one "*IndexFieldContext" interface per "contruct": sort/projection/aggregation/..., with the methods they need: "SortIndexFieldContext", "ProjectionIndexFieldContext", etc.
Implement these interfaces directly in ElasticsearchIndexSchemaFieldNode/LuceneIndexSchemaFieldNode, so that there is no overhead when searching on a single index.
Implement these interfaces in an "aggregated" node for searches on multiple indexes. Implement each method in such a way that we will throw an exception when a conflict actually arises. I.e. keep a list of the underlying fields, and when e.g. getConverter() is called, check that the converter defined for the field in each index is compatible with the others. In short this should get rid of ElasticsearchCompatibilityChecker/LuceneCompatibilityChecker as we would perform compatibility checks lazily.
Most importantly, pass the "*IndexFieldContext" directly to the sort/projection/etc. factory methods, instead of passing the different components (field path, converter, ...) separately.