Custom rewrite rule ignored

The rewrite rules are correct, but because you are modifying the $wp_rewrite directly, you aren’t getting the benefit of being able to add the rule to the top of the stack using add_rewrite_rules.

You are on the right track with the location of the rule in the array, but rules at the top are checked first, and the first rule to match is used.

Replacing your two functions with the code below should handle the query_var and the rewrite rule + positioning.

/**
 * Adds rewrite rule for template variation paramater
 */
function gtp_add_variation_rewrite_rules() {
    add_rewrite_tag( '%variation%', '([0-9]*)' );
    add_rewrite_rule( 'products/(.+?)/variation/([0-9]*)/?$', 'index.php?post_type=products&products=$matches[1]&variation=$matches[2]', 'top' );
}

add_action( 'init', 'gtp_add_variation_rewrite_rules' );