feat(routing): apply sticky routing in the decide flow - #412
Open
prajjwalkumar17 wants to merge 3 commits into
Open
feat(routing): apply sticky routing in the decide flow#412prajjwalkumar17 wants to merge 3 commits into
prajjwalkumar17 wants to merge 3 commits into
Conversation
This was referenced Sep 4, 2026
prajjwalkumar17
changed the base branch from
main
to
feat/sticky-routing-feedback
September 4, 2026 09:10
prajjwalkumar17
changed the base branch from
feat/sticky-routing-feedback
to
main
September 4, 2026 09:15
This was referenced Sep 4, 2026
prajjwalkumar17
force-pushed
the
feat/sticky-routing
branch
from
September 6, 2026 08:48
a7393e4 to
6976f93
Compare
prajjwalkumar17
force-pushed
the
feat/sticky-routing
branch
from
September 8, 2026 10:15
6976f93 to
eab7786
Compare
…r scores Storage layer only; feedback writes and the decide-side read come in the stacked follow-ups. - One Redis hash per (merchant, customer): fields pm:pmt:connector hold a NET count — successes increment, gateway failures decrement (floored at zero; merchant feedback is the source of truth in both directions). Failures never create a key or field, so key-creation stays tied to successful payments and the footprint stays bounded. Only positive counts qualify as pin candidates. Read is a single HGETALL returning counts sorted per exact combo; deliberately no cross-combo fallback (transaction-level fallbacks already cover a missing combo). - Eviction safety: every key volatile with a sliding TTL re-armed only on success; per-customer combo cap with lowest-count pruning that drains full excess; per-merchant two-window admission budget so new-key growth is bounded up front. Defaults overridable via service config. - Sticky on/off is a merchant-level feature flag (sticky_routing_enabled FeatureConf, dashboard plumbing in a follow-up) — deliberately NOT part of the euclid rule store, so toggling never touches the algorithm lifecycle. - New wrapper commands: hincrby_with_expire, hincrby, hgetall_map, hdel_field. Measured: 2-combo customer = 136B, 30-combo = 952B, listpack-encoded (Redis 7.2.7). Refs #393. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
prajjwalkumar17
force-pushed
the
feat/sticky-routing
branch
from
September 8, 2026 10:51
eab7786 to
fb4b4bc
Compare
Write side of sticky routing; the decide-side read comes next in the stack. Merchant feedback is the source of truth in both directions: successes (CHARGED/AUTHORIZED/PARTIAL_CHARGED) increment the habit, failure statuses (AUTHENTICATION_FAILED/AUTHORIZATION_FAILED/ JUSPAY_DECLINED/FAILURE) decrement it, floored at zero. Wider lifecycle statuses (VOIDED, AUTO_REFUNDED, ...) touch neither direction. Failures never create a key or field. - update-gateway-score gains optional customerId/paymentMethod/ paymentMethodType (backward compatible). Payload wins; the decide-time GatewayScoringData snapshot (now also carrying customerId) is the fallback, so callers need the new fields only when webhooks can outlive the snapshot's 30-min TTL. - Two-pass hook in check_and_update_gateway_score_: payload-only pass before the snapshot fetch (survives snapshot expiry, same rationale as the ab-test emit), snapshot pass after. - Deliberately NO dedupe: every feedback event counts, N times if sent N times — matching the engine's default SR scoring behavior (its feedback locks are opt-in per merchant), and avoiding a lock key per payment on the hot path. - GSM healthy-failure early return is now failure-only: a success with errorInfo previously skipped both the SR reward and any snapshot-based sticky write. - Sticky key pm/pmt are case-folded at the key boundary: the snapshot stores them uppercased while payloads arrive verbatim, and the two write sources must land on one hash field. Connector stays verbatim for the eligible-list comparison at decide time. - openapi + api-ref updated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Read side of sticky routing, completing the loop over the two prior commits (storage, feedback writes). Sticky is independent of the euclid rule store: it is gated only by the merchant-level sticky_routing_enabled feature flag, a customerId on the request, and an optional per-request stickyRouting override (mirroring enableMultiObjective). - New post-scoring override in run_decider_flow, after the cost and volume nudges: pin the customer's highest-success-count connector for the pm:pmt combo. Precedence is PL/euclid explicit orders first (they bound the candidate set and are never overridden), then sticky, then SR, then the default ordering — sticky applies over the SR-selection family AND the Default approach, so a pin still wins when SR is off. Downtime relabels and hedging exploration stay untouched. The pinned connector must clear a configurable fraction of the top score (STICKY_ROUTING_MIN_SCORE_RATIO, default 0.5), so a pin can never resurrect a connector the outage/elimination passes just buried. Fails open on any miss. - Every applied pin reports the new STICKY_ROUTING approach — a returning customer's repeat combo reading as SR would confuse callers. The label is admitted by the SRv3 producer-isolation and explore/exploit gates (mirroring the SR_SELECTION_MULTI_OBJECTIVE precedent), so pinned outcomes keep feeding the SR windows and the health veto stays live. A divergent pin additionally clears the superseded multi-objective/volume-steer claims so cost analytics can't credit a pick that didn't reach the customer. - Sticky keys trim merchant/customer ids; customerId is documented as byte-exact, and the UPI doc guidance matches the V2 flow's actual key material. Refs #393. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
prajjwalkumar17
force-pushed
the
feat/sticky-routing
branch
from
September 9, 2026 08:45
fb4b4bc to
0274dfc
Compare
prajjwalkumar17
marked this pull request as ready for review
September 9, 2026 10:31
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 3 of 3 for sticky routing (#393). Merge order: #419 → #420 → this.
Important
This branch necessarily contains phases 1–2 (the code builds on them), so "Files changed" shows all three. Review only this phase's commit:
0274dfc(Commits tab → last commit) — the earlier phases are reviewed in #419 and #420. The branch gets rebased as predecessors merge, collapsing the diff to the one commit.Closes #416.
What this adds
A post-scoring override in
run_decider_flow, running last — after SR scoring, outage/elimination penalties, and the cost/volume post-steps. When the merchant'ssticky_routing_enabledflag is on andpaymentInfo.customerIdis present, it pins the customer's highest-success-count connector for the exactPM:PMTcombo (oneHGETALL).Precedence — PL/euclid first, then sticky > SR > default: explicit orders (priority logic, merchant preference) bound the candidate set and are never overridden; within the dynamic decision a pin wins over the SR pick, and over the Default ordering when SR is off. Downtime relabels and hedging exploration stay untouched. Sticky is independent of the euclid rule store — it is gated only by the merchant-level
sticky_routing_enabledfeature flag, acustomerIdon the request, and a new optional per-requeststickyRoutingoverride (mirroringenableMultiObjective).Health veto: the pinned connector must be in the caller's
eligibleGatewayListand its post-elimination score must clearSTICKY_ROUTING_MIN_SCORE_RATIO(default 0.5) × top score — a pin can never resurrect a connector the outage/elimination passes just buried. Fails open to the plain SR pick on any miss.Label semantics: every applied pin reports
routing_approach: STICKY_ROUTING— a returning customer's repeat combo reading as SR would confuse callers. The label is admitted by the SRv3 producer-isolation and explore/exploit gates (mirroring theSR_SELECTION_MULTI_OBJECTIVEprecedent), so pinned outcomes keep feeding the SR windows and the veto stays live. A divergent pin additionally clears the superseded multi-objective/volume-steer claims (so cost analytics can't credit a pick that never reached the customer); agree-vs-diverge stays measurable via the sticky decision metrics.Unit tests cover the scoring-gate classifiers; builds and rustfmt green on both feature sets.
🤖 Generated with Claude Code