Skip to content

fix(session): session exemption must not swallow a competing attribution param#57

Merged
i8ramin merged 1 commit into
mainfrom
fix/session-exemption-competing-attribution
Jul 14, 2026
Merged

fix(session): session exemption must not swallow a competing attribution param#57
i8ramin merged 1 commit into
mainfrom
fix/session-exemption-competing-attribution

Conversation

@i8ramin

@i8ramin i8ramin commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

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.com via our own CJ link (self-exempt the host), then in the same session click a competitor's CJ link back to merchant.com. Both are policyId: '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

applySessionExemptions now keeps (does not suppress) a match that is:

  • a landing-param or redirect-domain (an explicit attribution click), and
  • not a self-match on the current navigation (its scope is absent from detection.selfExemptScopes).

Ambient lingering signals (cookie, initiator) are still re-attributed to us, so the documented ignore_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.ts encodes 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 what self-exemption.ts and ADOPTING.md already 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

  • New: a competing same-network click (different value) on an exempted host stands down. Verified it fails without the change (suppressed) and passes with it.
  • New: our own value keeps re-exempting (no over-correction), and the lingering cookie stays attributed to us.
  • All existing exemption-scope tests unchanged.

Full suite green (165 tests, +2), typecheck / lint / citations / build clean.

…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
i8ramin merged commit aa42847 into main Jul 14, 2026
2 checks passed
@i8ramin
i8ramin deleted the fix/session-exemption-competing-attribution branch July 14, 2026 19:54
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
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.

1 participant