In this join:
t1 <= (t2 * t3).pairs(:key => :k1, :key => :k2) {|x,y| [x.val, y.val]}
The arguments to pairs are passed as a hash. Hence, we're supplying two different values for the ":key" hash key, so Ruby silently ignores the first one (k1), taking only the second. Easy to fix (e.g., reverse the join operands), but certainly surprising behavior.