fix(session): session exemption must not swallow a competing attribution param#57
Merged
Merged
Conversation
…ion param A 'session' self-exemption re-covers a host's ambient lingering signals (a first-party cookie, the initiator) so our own attribution keeps owning the session. But it also dropped ANY later same-network match, including a fresh landing-param / redirect-domain carrying someone else's click id. So after we self-exempt a host, a competitor's click back to that same host was suppressed and we took the sale (the #52 item 3 edge case). applySessionExemptions now keeps (does not suppress) a landing-param or redirect-domain match whose scope is not a self-match on the current navigation. Ambient signals (cookie, initiator) are still re-attributed to us, so the documented ignore_param case is unchanged. This only bites with value-specific self-patterns, which is the documented, correct way to author them. A name-only self-pattern already claims every value of that param as ours, so a competing value looks like a self-match and is suppressed — the same footgun the self-exemption docs already warn about. Verified the competing-click test fails without the change (suppressed) and passes with it; the existing cookie-based ignore_param tests are unchanged. Addresses item 3 of #52 (precise fix). Part of #52. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PXZmuMFkq8dE2e2KLXvitx
i8ramin
added a commit
that referenced
this pull request
Jul 14, 2026
…mpetitor click The TTL tests (#56) used a competing same-network click to show the TTL boundary. #57 (Option C) landed in parallel and now stands down on a competing attribution param, so the within-window "still suppressed" assertion broke once both merged (each PR was green against a base without the other). Switch the TTL demonstration to the lingering-cookie signal, which a live exemption still re-attributes to us under Option C, so the test isolates the TTL boundary cleanly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PXZmuMFkq8dE2e2KLXvitx
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.
Second of two PRs for item 3 of #52, and the precise fix. (#56 is the TTL bound; this is independent of it and can land in either order.)
The edge case
Land on
merchant.comvia our own CJ link (self-exempt the host), then in the same session click a competitor's CJ link back tomerchant.com. Both arepolicyId: 'cj', so the persisted exemption dropped the competitor's match and we did not stand down. We kept a sale we should have ceded.The fix
applySessionExemptionsnow keeps (does not suppress) a match that is:landing-paramorredirect-domain(an explicit attribution click), anddetection.selfExemptScopes).Ambient lingering signals (
cookie,initiator) are still re-attributed to us, so the documentedignore_param"our click owns this host's session" case is unchanged. This surgically removes only the competitor-hijack behavior.Why this is the right seam
The dedicated
self-exemption-scope.test.tsencodes the legitimate'session'use as "param seen once, cookie seen later" — a cookie-kind signal. This fix deliberately preserves that and only refuses to swallow a fresh competing param. No existing test changes behavior.Contingent on value-specific self-patterns
The fix bites only when self-patterns are value-specific (
value+match), which is exactly whatself-exemption.tsandADOPTING.mdalready tell adopters to do. A name-only self-pattern claims every value of that param as ours, so a competitor's value looks like a self-match and is still suppressed. That is the same name-only footgun the docs already warn about, so behavior there is unchanged and consistent.Behavior-change note (the sign-off you approved)
In the "merchant re-decorates the URL with a real, non-ours affiliate param later in the session" case, we now stand down instead of keeping the sale. That is the honest outcome (a real competing attribution is present), and it is the deliberate change flagged in the #52 write-up.
Tests
Full suite green (165 tests, +2), typecheck / lint / citations / build clean.