Attribution audit — spec

Table of Contents

1. Purpose

The attribution-audit sits on top of the six-model attribution engine and adds the four things a rules-based engine structurally cannot supply on its own:

  1. Provenance — is this touchpoint observed (user did it) or asserted (system claimed it)? Recorded at write time.
  2. Credit-conservation — an inflation ratio that quantifies how much stuffing has bent the credit vector.
  3. Incrementality — a holdout arm that suppresses ads and measures the true lift. The only falsifying measurement in the whole stack.
  4. A same-origin /go/:token affiliate-link resolver with a static destination allowlist — merchant networks are structurally unreachable, which is the demonstration.

Refutation condition (from the source docstring): if all five attribution models agree with each other and with the incrementality number across a real holdout, the six-model vector has no information and R19 (per-model credit) is not earning its place.

ORACLE: src/wal_sh/adtech/attribution_audit/core.cljc (pure), browser.cljs (window._srAttribution).

2. Requirements

R18
Provenance is a closed set: #{"observed" "asserted"}. Enforced by valid-provenance?; C3 says the touchpoint constructor throws on any other value.
R19
attribute returns a MAP of model-name → per-touchpoint credit vector, never a single scalar. Every reader sees all five models simultaneously.
R20
claimed-credit-ratio computes each claimant's most flattering model, sums across claimants. Honest ledger sums to 1.0; anything above is the inflation stuffing exploits.
R21
incrementality is defined only for holdout > 0. Without a holdout it returns nil — R22 makes this explicit: no holdout, no incrementality claim.
R22
Default holdout is 0.10; the arm assignment is deterministic via arm = djb2(user-id) mod 10000 / 10000 < holdout ? "holdout" : "exposed". The djb2 is the same hash the ab-engine uses so a holdout user is consistent across the whole surface.
R23
The /go/:token resolver appends an observed OnClick touchpoint on every hit. Never on miss (fail-closed under C1).
C1
/go/ destinations are a static allowlist destinations in core.cljc. No dynamic destination is possible; the resolver's return value is nil for anything unknown.
C3
touchpoint constructor throws on unlisted :provenance — the ledger cannot end up with unlabelled asserted touches.

3. Contract signature

Pure (core.cljc):

(valid-provenance? p)                             ; -> bool
(touchpoint {:keys [id t channel source medium campaign claimant provenance]})
;; -> map; throws on invalid provenance

(last-click ts)                                   ; -> credit vector
(first-click ts)                                  ; -> credit vector
(linear ts)                                       ; -> credit vector
(time-decay ts)                                   ; -> half-life 7d, normalised
(position-based ts)                               ; -> 40/40/20

(attribute ts)                                    ; -> {model-name [{:claimant :provenance :credit}]}
(claimed-credit-ratio ts)                         ; -> {:ratio n :claims [...]}
(incrementality {:keys [holdout exposed-conversions ...]})
;; -> {:exposed-rate :holdout-rate :lift :relative-lift :incremental-conversions} or nil

(djb2 s)                                          ; -> uint32
(arm user-id holdout)                             ; -> "exposed" | "holdout"
(resolve-token token)                             ; -> {:dest :token} or nil
(hops-to-content ts)                              ; -> count of sponsored/affiliate hops

Browser surface:

window._srAttribution = {
  Ledger: {
    add:      (fn [touchpoint] -> nil)
    entries:  (fn [] -> [touchpoint...])
    clear:    (fn [] -> nil)
  }
  Audit: {
    arm:      (fn [] -> "exposed" | "holdout")
    attribute: (fn [] -> {model -> credit-vec})
    ratio:    (fn [] -> {:ratio :claims})
    lift:     (fn [] -> incrementality-map or nil)
  }
  Go: {
    resolve:  (fn [token] -> {:dest :token} or nil)
  }
  Demo: {
    explain:  (fn [] -> string)
    stuff:    (fn [claimant] -> nil)              ; asserts a phantom touchpoint
  }
}

4. The demonstration

Demo.stuff("bad-actor") asserts a phantom touchpoint labelled :provenance "asserted" with :claimant "bad-actor". Two things then observably change:

  • claimed-credit-ratio inflates above 1.0.
  • incrementality stays flat — the holdout arm never saw the ad, so a phantom touchpoint on the exposed arm doesn't move the lift.

The asymmetry is the demonstration. A rules-based engine bends; a holdout-anchored measurement doesn't.

5. Related literature

  • Cook, Nithyanand & Shafiq on tracker-advertiser topology (Cook, Nithyanand, and Shafiq 2020) shows the structural opacity that makes provenance recording at write time the only reliable defence.
  • ANA 2023/2024 MFA transparency studies (Association of National Advertisers 2023), (Association of National Advertisers 2024) document the industry-scale version of this failure: 15 % of impressions on "made-for-advertising" sites, credited by rules-based attribution the same as premium publisher inventory.
  • Zeng, Kohno & Roesner on bad ads (Zeng, Kohno, and Roesner 2020) is the observational analogue — deceptive ads measured at scale, no assertion of provenance possible from the outside.
  • Bashir et al. on retargeted-ad tracing (Bashir et al. 2016) documents how attribution across 20+ touchpoints, most of which the user never observed, is the norm not the exception.
  • Ali et al. on discrimination through delivery (Ali et al. 2019) — if the audit surface is opaque, the bias is undetectable. Our audit surface is deliberately visible.
  • Google's Attribution Reporting API (Google 2022) is the privacy-sandbox proposal that pushes attribution to a browser-mediated aggregation; our /go/ resolver is the same-origin analogue at a much smaller scale.
  • Srinivasan on Google's advertising dominance (Srinivasan 2020) frames why the audit layer is structurally unavailable to publishers: the buyer, seller, and auctioneer are the same firm, and the ledger is proprietary.
  • Hwang on ad-market fragility (Hwang 2020) — an unfalsifiable metric fed to a bid-optimizer is the definition of a subprime signal.

6. Cross-references

  • attribution-engine/spec.org — the v1 six-model layer this namespace sits on top of.
  • stuffing-detectors/spec.org — v5 addendum; four distributional detectors that consume the same ledger, without reading :provenance.
  • intent-signals/spec.org — v6; every signal enters this ledger as observed (R24).
  • exit-intent/spec.org — reads arm; suppressed in holdout (R31), which is what makes incrementality measurable.
  • ORACLE:
    • src/wal_sh/adtech/attribution_audit/core.cljc (pure)
    • src/wal_sh/adtech/attribution_audit/browser.cljs (window._srAttribution)
  • web-history — post-cookie improvisation on Google's Attribution Reporting API and the sandbox context.
  • CPRR Methodology — the Conjecture-Proof-Refutation-Refinement framing this audit's refutation gates borrow (holdout re-randomisation, credit conservation are structurally the same shape).

7. Open questions

  • Should the /go/ resolver support signed destinations? The static allowlist (C1) is a structural guard against dynamic redirect chains — the class of exploit documented in the OpenRTB ecosystem (IAB Tech Lab 2016). A signed-destination scheme would let us cover more of the surface without giving up the guard, at the cost of a key-rotation runbook.
  • Holdout re-randomisation. The current arm assignment is stable per user-id; a user in holdout stays there. Long-lived holdouts bias the sample toward returning visitors. A time-boxed re-randomisation would fix this at the cost of per-arm sample independence.
Ali, Muhammad, Piotr Sapiezynski, Miranda Bogen, Aleksandra Korolova, Alan Mislove, and Aaron Rieke. 2019. “Discrimination through Optimization: How Facebook’s Ad Delivery Can Lead to Biased Outcomes.” In Proceedings of the Acm on Human-Computer Interaction. Vol. 3. CSCW. https://doi.org/10.1145/3359301.
Association of National Advertisers. 2023. “Programmatic Media Supply Chain Transparency Study.” ANA. https://www.ana.net/miccontent/show/id/rr-2023-06-ana-programmatic-transparency-study.
———. 2024. “Made-for-Advertising (Mfa) Second Look and Update.” ANA. https://www.ana.net/miccontent/show/id/rr-2024-01-ana-mfa-second-look.
Bashir, Muhammad Ahmad, Sajjad Arshad, William Robertson, and Christo Wilson. 2016. “Tracing Information Flows between Ad Exchanges Using Retargeted Ads.” In 25Th Usenix Security Symposium (Usenix Security 16), 481–96. https://www.usenix.org/conference/usenixsecurity16/technical-sessions/presentation/bashir.
Cook, John, Rishab Nithyanand, and Zubair Shafiq. 2020. “Inferring Tracker-Advertiser Relationships in the Online Advertising Ecosystem Using Header Bidding.” Proceedings on Privacy Enhancing Technologies (Popets) 2020 (1): 65–82. https://doi.org/10.2478/popets-2020-0005.
Google. 2022. “Attribution Reporting Api.” https://developers.google.com/privacy-sandbox/relevance/attribution-reporting.
Hwang, Tim. 2020. Subprime Attention Crisis: Advertising and the Time Bomb at the Heart of the Internet. FSG Originals x Logic.
IAB Tech Lab. 2016. “Openrtb Specification.” https://iabtechlab.com/standards/openrtb/.
Srinivasan, Dina. 2020. Why Google Dominates Advertising Markets: Competition Policy Should Lean on the Principles of Financial Market Regulation. Vol. 24. Stanford Technology Law Review. https://law.stanford.edu/publications/why-google-dominates-advertising-markets/.
Zeng, Eric, Tadayoshi Kohno, and Franziska Roesner. 2020. “Bad News: Clickbait and Deceptive Ads on News and Misinformation Websites.” In Workshop on Technology and Consumer Protection (Conpro). https://badads.cs.washington.edu/.