I wonder whether we could instead fix things by deeming that the result of the pushed-down B/C join does not yet produce correct C* variables, so that we won't allow conditions involving them to drop below the pushed-up A/B join. This would be a little bit messy in some places because now we'd consider that the A/B join is adding two OJ relids not just one to the output join relid set, while the B/C join is adding no relids even though it must execute an outer join. (But we already have that notion for semijoins and some antijoins, so maybe it's fine.)
I think I see your points. Semijoins and antijoins derived from semijoins do not have rtindex, so they do not add any OJ relids to the output join relid set. Do you mean we do the similar thing to the pushed-down B/C join here by not adding B/C's ojrelid to the output B/C join, but instead add it later when we've formed the pushed-up A/B join?
I tried the codes below to adjust joinrelids and then use the modified joinrelids when constructing restrict and join clause lists for the joinrel. It seems to be able to solve the presented case. But I'm not sure if it'd break other cases.
Also I'm wondering whether we can just copy what we once did in check_outerjoin_delay() to update required_relids of a pushed-down clause. This seems to be a lazy but workable way.