Skip to content

fix(routing): dedup decision engine connectors by merchant_connector_id - #13758

Open
prajjwalkumar17 wants to merge 2 commits into
mainfrom
fix/de-routing-dedup-by-merchant-connector-id
Open

fix(routing): dedup decision engine connectors by merchant_connector_id#13758
prajjwalkumar17 wants to merge 2 commits into
mainfrom
fix/de-routing-dedup-by-merchant-connector-id

Conversation

@prajjwalkumar17

@prajjwalkumar17 prajjwalkumar17 commented Aug 18, 2026

Copy link
Copy Markdown
Member

Type of Change

  • Bugfix

Description

transform_de_output_for_router builds the router-facing connector list from a decision engine /routing/evaluate response: evaluated_output first, then the remaining connectors from output as fallbacks, deduplicated.

The dedup set was keyed on the connector name alone:

if seen.insert(eval_conn.connector) { ... }   // merchant_connector_id not part of the key

RoutableConnectorChoice carries both connector and merchant_connector_id, but only connector went into the set. So for a merchant holding several MCAs of the same connector, every MCA after the first is silently dropped.

This shows up on any rule that spans two MCAs of one connector — e.g. a volume split across two paypal MCAs. DE returns the sampled winner in evaluated_output and both arms in output; the second paypal MCA is then discarded, so the router loses its fallback and the list length disagrees with the legacy euclid result.

Found while investigating a persistent shadow-diff (is_equal_length=false) on a sandbox profile with exactly that shape.

Change

Key the set on the full (connector, merchant_connector_id) pair.

The second loop now builds the RoutableConnectorChoice before keying off it, which also removes a duplicated RoutableConnectors::from_str — the TryFrom<ConnectorInfo> impl already performs that parse with equivalent error logging.

Note: entries with merchant_connector_id: None no longer collapse into a same-named entry with Some(id). Both lists are serialized from the same ConnectorInfo objects in the same rule, so this is not expected to trigger in practice.

Motivation and Context

Static routing via the decision engine must preserve MCA-level granularity; collapsing by connector name loses both the routing target and its fallbacks for multi-MCA merchants.

How did you test it?

Unit tests added in crates/router/src/core/payments/routing/utils.rs (the file had no test module before):

running 3 tests
test ...::still_dedups_exact_duplicates ... ok
test ...::preserves_evaluated_first_ordering_for_distinct_connectors ... ok
test ...::keeps_both_mcas_of_the_same_connector ... ok

test result: ok. 3 passed; 0 failed

Verified the coverage is meaningful by reverting the fix and re-running with the tests in place — the regression test fails exactly as the bug predicts, while the two guard tests still pass:

test ...::keeps_both_mcas_of_the_same_connector ... FAILED

assertion `left == right` failed: second paypal MCA was dropped
  left: 1
 right: 2

test result: FAILED. 2 passed; 1 failed

left: 1 / right: 2 is the same shape as the sandbox shadow diff that led here (DE one connector, HS two).

Also run: cargo check -p router, cargo clippy -p router, cargo +nightly fmt -p router --check — all clean.

Checklist

  • I formatted the code cargo +nightly fmt --all
  • I addressed lints thrown by cargo clippy
  • I added unit tests

🤖 Generated with Claude Code

Closes #13759

`transform_de_output_for_router` keyed its dedup set on the connector name
alone, so a merchant holding several MCAs for the same connector kept only
the first one. A rule that spans two of them — e.g. a volume split across
two paypal MCAs — collapsed to a single connector, dropping the fallback.

Key the set on the full (connector, merchant_connector_id) pair instead.

The second loop now builds the `RoutableConnectorChoice` before keying off
it, which also drops a duplicate `RoutableConnectors::from_str` — the
`TryFrom<ConnectorInfo>` impl already performs that parse with the same
error logging.

Co-Authored-By: Claude <noreply@anthropic.com>
@prajjwalkumar17
prajjwalkumar17 requested a review from a team as a code owner August 18, 2026 10:11
@semanticdiff-com

semanticdiff-com Bot commented Aug 18, 2026

Copy link
Copy Markdown

Review changes with  SemanticDiff

Changed Files
File Status
  crates/router/src/core/payments/routing/utils.rs  5% smaller

Three cases around `transform_de_output_for_router`:

- two MCAs of the same connector both survive, winner first (fails before
  the dedup-key fix, with left: 1 / right: 2 — the exact shape seen in the
  sandbox shadow diff)
- an exact duplicate still collapses to one entry
- evaluated-first ordering for distinct connectors is unchanged

The last two pass both before and after the fix, pinning the behaviour the
change is not meant to alter.

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] DE connector dedup drops every MCA after the first for multi-MCA merchants

1 participant