Skip to content

Paywalls Modules: VAI RTD Provider and Analytics Adapter#14541

Open
paywalls-mike wants to merge 9 commits intoprebid:masterfrom
paywalls-net:agent/paywalls-rtd-provider
Open

Paywalls Modules: VAI RTD Provider and Analytics Adapter#14541
paywalls-mike wants to merge 9 commits intoprebid:masterfrom
paywalls-net:agent/paywalls-rtd-provider

Conversation

@paywalls-mike
Copy link

Type of issue

New module (RTD Provider + Analytics Adapter)

Description

Adds two new Paywalls modules that integrate VAI (Validated Actor Inventory) into Prebid.js:

Paywalls RTD Provider (paywallsRtdProvider.js) — Automates VAI loading, timing, and signal injection into the bid stream:

  • ORTB2 enrichment: site.ext.vai (domain provenance + signed assertion) and user.ext.vai (actor classification)
  • GAM targeting: vai_vat and vai_act key-value pairs per ad unit
  • Graceful degradation: if VAI is unavailable or times out, the auction proceeds normally

Paywalls Analytics Adapter (paywallsAnalyticsAdapter.js) — Emits VAI classification (vat, act) per auction to the publisher's analytics pipeline (GA4 gtag, GTM dataLayer, or custom callback), enabling publishers to segment performance by traffic class in their existing reporting stack.

What is VAI?

VAI classifies page impressions by actor type (human, AI agent, sharing/preview bot, other) and confidence tier (ACT-1 through ACT-3), producing a cryptographically signed JWS assertion that SSPs and DSPs can independently verify. No user identifiers, PII, cookies, or fingerprints are involved — classification is based on aggregate session-level behavioral signals processed entirely in the browser.

ORTB2 placement

site.ext.vai  → { iss, aud, dom, kid, assertion_jws }   (domain provenance)
user.ext.vai  → { vat, act }                             (actor classification)

Test Results

Lint

$ npx eslint --cache --cache-strategy content modules/paywallsRtdProvider.js modules/paywallsAnalyticsAdapter.js test/spec/modules/paywallsRtdProvider_spec.js test/spec/modules/paywallsAnalyticsAdapter_spec.js
(no output — clean)

Unit Tests

RTD Provider:  ✔ 22 tests completed
Analytics Adapter: ✔ 29 tests completed

Coverage

Module Statements Branches Functions Lines
paywallsRtdProvider.js 90% 83.78% 100% 91.76%
paywallsAnalyticsAdapter.js 93.9% 85.36% 100% 93.58%

All metrics exceed the 80% threshold.

Files Changed

File Description
modules/paywallsRtdProvider.js RTD submodule: VAI loading, ORTB2 injection, GAM targeting
modules/paywallsRtdProvider.md RTD module documentation (config, ORTB2 output, hosting modes)
modules/paywallsAnalyticsAdapter.js Analytics adapter: VAI emission via gtag/dataLayer/callback
modules/paywallsAnalyticsAdapter.md Analytics adapter documentation
test/spec/modules/paywallsRtdProvider_spec.js RTD provider unit tests (22 tests)
test/spec/modules/paywallsAnalyticsAdapter_spec.js Analytics adapter unit tests (29 tests)
plugins/eslint/approvedLoadExternalScriptPaths.js Added both modules to approved external script paths
integrationExamples/gpt/paywallsRtdProvider_example.html E2E integration test page for RTD
integrationExamples/gpt/paywallsAnalyticsAdapter_example.html E2E integration test page for analytics
integrationExamples/gpt/mock-vai.js Mock VAI script for integration testing

External Script Loading

Both modules use loadExternalScript to inject vai.js (the VAI classifier). This follows the same pattern as other RTD providers (confiant, geoedge, browsi, humansecurity, 51degrees, etc.). The script URL is configurable and defaults to a publisher-hosted relative path (/pw/vai.js).

Privacy

  • No user identifiers, PII, cookies, or fingerprints
  • Browser-side only — no data leaves the page except the classification result
  • Signed JWS assertions verifiable via public JWKS endpoint
  • No consent-gated data collection required

Steps to reproduce / test

# Build
gulp build --modules=rtdModule,paywallsRtdProvider,paywallsAnalyticsAdapter,appnexusBidAdapter

# Run unit tests
gulp test --nolint --file test/spec/modules/paywallsRtdProvider_spec.js
gulp test --nolint --file test/spec/modules/paywallsAnalyticsAdapter_spec.js

# Integration test
gulp serve-fast --modules=rtdModule,paywallsRtdProvider,paywallsAnalyticsAdapter,appnexusBidAdapter
# Open http://localhost:9999/integrationExamples/gpt/paywallsRtdProvider_example.html
# Open http://localhost:9999/integrationExamples/gpt/paywallsAnalyticsAdapter_example.html

Platform details

  • Prebid.js: current master (rebased)
  • Node.js: >=20
  • Tested on Chrome Headless 145 / macOS

Other information

Copilot AI review requested due to automatic review settings March 1, 2026 21:15
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dc9bc90910

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

- Register RTD submodule 'paywalls' with init, getBidRequestData, getTargetingData
- ORTB2 split: site.ext.vai (iss, aud, dom, kid, assertion_jws) + user.ext.vai (vat, act)
- Dynamic vai.js injection via loadExternalScript with poll/hook/timeout
- Graceful degradation: never blocks the auction
- 22 unit tests covering all acceptance criteria
- Added to approvedLoadExternalScriptPaths whitelist
- paywallsRtdProvider_example.html: E2E test with 5 assertions
  (ORTB2 site/user enrichment, GAM targeting, timing budget, VAI load)
- mock-vai.js: local VAI mock for self-contained testing
- Supports ?real mode (real vai.js from worker) and ?degrade mode
  (verifies graceful degradation with unreachable script)
- Overview, build instructions, configuration parameters
- ORTB2 split placement: site.ext.vai (provenance) + user.ext.vai (classification)
- GAM targeting keys (vai_vat, vai_act)
- Activity Controls, privacy section, how-it-works
- Testing instructions for unit and integration tests
- Publisher-hosted is preferred (same-origin, no CORS, dom claim match)
- Paywalls-hosted uses paywalls.net (not cdn.paywalls.net)
- Clarify CDN/server integration vs reverse proxy
- Correct activity name: loadExternalScript (not fetchBids)
- Update example default to false (realistic scenario)
- Editorial refinements
- Fix broken VAI URL in RTD provider links section (docs.publishers → docs/publishers)
- Normalize all VAI URLs to https://docs.paywalls.net/publishers/vai
- Align RTD provider heading structure with analytics adapter (# Overview)
- Clarify params.waitForIt vs top-level waitForIt distinction
- Document __PW_VAI_HOOK__, exp checking, and localStorage key in How It Works
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Paywalls modules to integrate VAI (Validated Actor Inventory) into Prebid.js auctions, enriching ORTB2/GAM targeting via an RTD provider and emitting per-auction classification via an analytics adapter.

Changes:

  • Introduces paywallsRtdProvider RTD submodule to load VAI and merge site.ext.vai / user.ext.vai into ORTB2 plus vai_vat / vai_act into GAM targeting.
  • Introduces paywallsAnalyticsAdapter to emit vai_vat / vai_act per auction via gtag, dataLayer, or callback (with sampling + de-dup).
  • Adds module docs, unit tests, integration example pages + mock VAI script, and updates the ESLint allowlist for loadExternalScript usage.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
modules/paywallsRtdProvider.js New RTD provider implementing VAI discovery/loading and ORTB2 + targeting enrichment.
modules/paywallsRtdProvider.md Documentation for RTD config, ORTB2 output, GAM targeting, and activity controls.
modules/paywallsAnalyticsAdapter.js New analytics adapter emitting VAI classification per auction to publisher analytics destinations.
modules/paywallsAnalyticsAdapter.md Documentation for analytics adapter config, output modes, sampling, and privacy notes.
test/spec/modules/paywallsRtdProvider_spec.js Unit tests for VAI payload detection, ORTB2 merge behavior, targeting, and timeouts.
test/spec/modules/paywallsAnalyticsAdapter_spec.js Unit tests for classification reading, emission modes, sampling, and de-dup behavior.
plugins/eslint/approvedLoadExternalScriptPaths.js Approves the two new modules for loadExternalScript.
integrationExamples/gpt/paywallsRtdProvider_example.html E2E example page for RTD provider behavior (including degrade/real modes).
integrationExamples/gpt/paywallsAnalyticsAdapter_example.html E2E example page for analytics emission behavior (including degrade/real modes).
integrationExamples/gpt/mock-vai.js Mock VAI script used by integration examples to simulate vai.js.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +23 to +31
before(function () {
sandbox = sinon.createSandbox();
clock = sandbox.useFakeTimers(1896134400000);
});

after(function () {
clock.restore();
sandbox.restore();
});
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This spec shares a single Sinon sandbox and fake clock across the entire suite (before/after). Since multiple tests advance the clock and create stubs/spies, this can make the suite order-dependent and harder to debug when a test fails mid-suite. Consider creating/restoring the sandbox + fake timers in beforeEach/afterEach (or resetting the clock in beforeEach) to keep tests isolated.

Copilot uses AI. Check for mistakes.
@paywalls-mike paywalls-mike force-pushed the agent/paywalls-rtd-provider branch from dc9bc90 to c2e15ae Compare March 1, 2026 21:22
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c2e15aea5d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@paywalls-mike
Copy link
Author

paywalls-mike commented Mar 1, 2026

Addressed automated review feedback in commit de4cf00.

Implemented fixes:

  • RTD: preserve late hook-delivered VAI payloads after timeout for subsequent auctions
  • RTD: respect explicit waitForIt=0 and validate waitForIt type/range
  • RTD: clean up timers/hook lifecycle and preserve/chain pre-existing PW_VAI_HOOK
  • Analytics: only skip injection when classification is valid (not just truthy PW_VAI)
  • Analytics: reject expired VAI payloads (exp) to align with RTD behavior
  • Analytics: clamp samplingRate to [0,1]
  • Analytics: use Object.create(null) for emittedAuctions map safety
  • Docs: corrected init() comment to match caching behavior

Regression coverage added for these behaviors.

Validation run locally:

  • npx gulp test --nolint --file test/spec/modules/paywallsRtdProvider_spec.js (24 passing)\n- npx gulp test --nolint --file test/spec/modules/paywallsAnalyticsAdapter_spec.js (32 passing)
  • npx eslint --cache --cache-strategy content modules/paywallsRtdProvider.js modules/paywallsAnalyticsAdapter.js test/spec/modules/paywallsRtdProvider_spec.js test/spec/modules/paywallsAnalyticsAdapter_spec.js (clean)

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: de4cf00754

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@paywalls-mike paywalls-mike force-pushed the agent/paywalls-rtd-provider branch from de4cf00 to 12b1099 Compare March 1, 2026 22:02
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 12b1099de4

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@paywalls-mike
Copy link
Author

Companion docs PR: prebid/prebid.github.io#6457

- P1: Store late VAI hook payloads for subsequent auctions (was dropping permanently after timeout)
- Fix waitForIt=0 treated as falsy (use typeof check instead of ||)
- Clean up timers and hook in resolve() to avoid leaking globals
- Preserve existing __PW_VAI_HOOK__ instead of clobbering
- Fix ensureVai skipping injection when __PW_VAI__ is truthy but invalid
- Clamp samplingRate to [0,1] with warning
- Use Object.create(null) for emittedAuctions map
- Fix init() JSDoc to match actual behavior (caches, not injects)
- Add regression tests for all bug fixes
@paywalls-mike paywalls-mike force-pushed the agent/paywalls-rtd-provider branch from 12b1099 to d145be8 Compare March 2, 2026 00:32
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.

2 participants