From d85d2ec2908ea724e989948e813587b771c8a73f Mon Sep 17 00:00:00 2001 From: antoniogargaro Date: Wed, 22 Apr 2026 10:03:51 +0100 Subject: [PATCH 1/2] Permutive RTD: iterate bidders configured via params.bidders Previously setBidderRtb only wrote ortb2 for the union of acBidders and Permutive's SSP list. A bidder declared under params.bidders with a customCohorts mapping was iterated only if it was also present in one of those lists, leaving publishers' configuration silently inert. Include Object.keys(params.bidders) in the iterated set so an explicit per-bidder customCohorts mapping is always honored. Co-Authored-By: Claude Opus 4.7 (1M context) --- modules/permutiveRtdProvider.js | 2 +- modules/permutiveRtdProvider.md | 2 +- test/spec/modules/permutiveCombined_spec.js | 34 +++++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/modules/permutiveRtdProvider.js b/modules/permutiveRtdProvider.js index f5a37d934d..050c913941 100644 --- a/modules/permutiveRtdProvider.js +++ b/modules/permutiveRtdProvider.js @@ -115,7 +115,7 @@ export function setBidderRtb (bidderOrtb2, moduleConfig, segmentData) { const sspCohorts = segmentData?.ssp?.cohorts ?? [] const topics = segmentData?.topics ?? {} - const bidders = new Set([...acBidders, ...ssps]) + const bidders = new Set([...acBidders, ...ssps, ...Object.keys(biddersConfig)]) bidders.forEach(function (bidder) { const bidderConfig = biddersConfig[bidder] || {}; const currConfig = { ortb2: bidderOrtb2[bidder] || {} } diff --git a/modules/permutiveRtdProvider.md b/modules/permutiveRtdProvider.md index fc13a5e6a4..314161ecf2 100644 --- a/modules/permutiveRtdProvider.md +++ b/modules/permutiveRtdProvider.md @@ -48,7 +48,7 @@ as well as enabling settings for specific use cases mentioned above (e.g. acbidd | params.acBidders | String[] | An array of bidder codes to share cohorts with in certain versions of Prebid, see below | `[]` | | params.maxSegs | Integer | Maximum number of cohorts to be included in either the `permutive` or `p_standard` key-value. | `500` | | params.enforceVendorConsent | Boolean | When `true`, require TCF vendor consent for Permutive (vendor 361). See note below. | `false` | -| params.bidders | Object | Per-bidder configuration for custom cohort sources. Keys are bidder codes. | `{}` | +| params.bidders | Object | Per-bidder configuration for custom cohort sources. Keys are bidder codes; listing a bidder here also causes the module to write ortb2 data for that bidder, even if it is not in `acBidders` or on Permutive's SSP list. | `{}` | | params.bidders.\.customCohorts | Object | Custom cohorts source configuration for a specific bidder. | - | | params.bidders.\.customCohorts.source | String | Storage type to read custom cohorts from. Currently only `'ls'` (localStorage) is supported. | - | | params.bidders.\.customCohorts.key | String | The localStorage key to read custom cohorts from. | - | diff --git a/test/spec/modules/permutiveCombined_spec.js b/test/spec/modules/permutiveCombined_spec.js index 60d3672b8d..58a5663df1 100644 --- a/test/spec/modules/permutiveCombined_spec.js +++ b/test/spec/modules/permutiveCombined_spec.js @@ -749,6 +749,40 @@ describe('permutiveRtdProvider', function () { }, ]) }) + + it('should write ortb2 for a bidder configured only via params.bidders (not in acBidders or ssps)', function () { + const segmentsData = transformedTargeting() + const expectedAppnexusCohorts = segmentsData.appnexus + + const moduleConfig = { + name: 'permutive', + waitForIt: true, + params: { + acBidders: [], + maxSegs: 500, + bidders: { + msft: { + customCohorts: { source: 'ls', key: '_papns' } + } + } + } + } + const bidderConfig = {} + + setBidderRtb(bidderConfig, moduleConfig, segmentsData) + + expect(bidderConfig['msft']).to.not.be.undefined + expect(bidderConfig['msft'].user.data).to.deep.include.members([ + { + name: 'permutive', + segment: expectedAppnexusCohorts.map(id => ({ id })), + }, + ]) + + expectedAppnexusCohorts.forEach(id => { + expect(bidderConfig['msft'].user.keywords).to.include(`permutive=${id}`) + }) + }) }) }) From 25ce7401c0250b30ab9c70bafd0b18ec687380a4 Mon Sep 17 00:00:00 2001 From: antoniogargaro Date: Wed, 22 Apr 2026 13:55:48 +0100 Subject: [PATCH 2/2] Permutive RTD: type the public params interface Add a `.d.ts` declaring `PermutiveRtdProviderParams` / `PermutiveRtdProviderConfig` and wire them into the module via JSDoc typedef imports (follows the pattern from prebid/Prebid.js#14773). Co-Authored-By: Claude Opus 4.7 (1M context) --- modules/permutiveRtdProvider.js | 18 ++++-- modules/permutiveRtdProviderTypes.d.ts | 89 ++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 7 deletions(-) create mode 100644 modules/permutiveRtdProviderTypes.d.ts diff --git a/modules/permutiveRtdProvider.js b/modules/permutiveRtdProvider.js index 050c913941..04b950e6cc 100644 --- a/modules/permutiveRtdProvider.js +++ b/modules/permutiveRtdProvider.js @@ -16,6 +16,10 @@ import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; /** * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + * @typedef {import('./permutiveRtdProviderTypes.d.ts').PermutiveRtdProviderConfig} PermutiveRtdProviderConfig + * @typedef {import('./permutiveRtdProviderTypes.d.ts').PermutiveRtdProviderParams} PermutiveRtdProviderParams + * @typedef {import('./permutiveRtdProviderTypes.d.ts').PermutiveBidderConfig} PermutiveBidderConfig + * @typedef {import('./permutiveRtdProviderTypes.d.ts').PermutiveTransformationConfig} PermutiveTransformationConfig */ const MODULE_NAME = 'permutive' @@ -77,8 +81,8 @@ function getParamsFromPermutive() { * As items with a higher priority will be deeply merged into the previous config, deep merges are performed by * reversing the priority order. * - * @param {Object} customModuleConfig - Publisher config for module - * @return {Object} Deep merges of the default, Permutive and custom config. + * @param {PermutiveRtdProviderConfig} customModuleConfig - Publisher config for module + * @return {PermutiveRtdProviderConfig} Deep merges of the default, Permutive and custom config. */ export function getModuleConfig(customModuleConfig) { // Use the params from Permutive if available, otherwise fallback to the cached value set by Permutive. @@ -102,7 +106,7 @@ export function getModuleConfig(customModuleConfig) { /** * Sets ortb2 config for ac bidders * @param {Object} bidderOrtb2 - The ortb2 object for the all bidders - * @param {Object} moduleConfig - Publisher config for module + * @param {PermutiveRtdProviderConfig} moduleConfig - Publisher config for module * @param {Object} segmentData - Segment data grouped by bidder or type */ export function setBidderRtb (bidderOrtb2, moduleConfig, segmentData) { @@ -141,7 +145,7 @@ export function setBidderRtb (bidderOrtb2, moduleConfig, segmentData) { /** * Resolves custom cohorts data for a bidder, reading from localStorage if configured. - * @param {Object} bidderCfg - Bidder-specific configuration from params.bidders + * @param {PermutiveBidderConfig} bidderCfg - Bidder-specific configuration from params.bidders * @param {string} bidder - The bidder identifier * @param {Object} segmentData - Segment data grouped by bidder or type * @param {number} maxSegs - Maximum number of segments @@ -162,7 +166,7 @@ function getCustomCohortsData (bidderCfg, bidder, segmentData, maxSegs) { * @param {string[]} segmentIDs - Permutive segment IDs * @param {string[]} sspSegmentIDs - Permutive SSP segment IDs * @param {Object} topics - Privacy Sandbox Topics, keyed by IAB taxonomy version (600, 601, etc.) - * @param {Object[]} transformationConfigs - array of objects with `id` and `config` properties, used to determine + * @param {PermutiveTransformationConfig[]} transformationConfigs - array of objects with `id` and `config` properties, used to determine * the transformations on user data to include the ORTB2 object * @param {string[]} customCohortsData - Custom cohort IDs for this bidder * @return {Object} Merged ortb2 object @@ -258,7 +262,7 @@ function updateOrtbConfig(bidder, currConfig, segmentIDs, sspSegmentIDs, topics, /** * Set segments on bid request object * @param {Object} reqBidsConfigObj - Bid request object - * @param {Object} moduleConfig - Module configuration + * @param {PermutiveRtdProviderConfig} moduleConfig - Module configuration * @param {Object} segmentData - Segment object */ function setSegments (reqBidsConfigObj, moduleConfig, segmentData) { @@ -314,7 +318,7 @@ function getCustomBidderFn (moduleConfig, bidder) { /** * Check whether ac is enabled for bidder - * @param {Object} moduleConfig - Module configuration + * @param {PermutiveRtdProviderConfig} moduleConfig - Module configuration * @param {string} bidder - Bidder name * @return {boolean} */ diff --git a/modules/permutiveRtdProviderTypes.d.ts b/modules/permutiveRtdProviderTypes.d.ts new file mode 100644 index 0000000000..210b4527d1 --- /dev/null +++ b/modules/permutiveRtdProviderTypes.d.ts @@ -0,0 +1,89 @@ +/** + * Source for custom cohorts. + * + * Currently only `ls` (localStorage) is supported. + */ +export type PermutiveCustomCohortsSource = 'ls'; + +export interface PermutiveCustomCohortsConfig { + /** + * Where to read the custom cohorts from. Currently only `ls` (localStorage) is supported. + */ + source: PermutiveCustomCohortsSource; + /** + * The localStorage key to read the custom cohort IDs from. + * The value must be a JSON-encoded array of cohort IDs. + */ + key: string; +} + +export interface PermutiveBidderConfig { + /** + * Custom cohort source for this bidder. When set, the module will read cohort + * IDs from the configured source and attach them to the bidder's ortb2 payload. + */ + customCohorts?: PermutiveCustomCohortsConfig; +} + +export interface PermutiveIabTransformationConfig { + /** + * IAB audience taxonomy version (e.g. `4` for v1.1). + */ + segtax: number; + /** + * Mapping of Permutive segment IDs (keys) to IAB taxonomy IDs (values). + */ + iabIds: Record; +} + +export interface PermutiveTransformationConfig { + /** + * Transformation identifier. Currently only `iab` is supported. + */ + id: 'iab'; + /** + * Transformation-specific configuration. + */ + config: PermutiveIabTransformationConfig; +} + +export interface PermutiveRtdProviderParams { + /** + * Bidder codes to share Permutive cohorts with via the Audience Connector + * (`p_standard` / `ac` segments). + */ + acBidders?: string[]; + /** + * Maximum number of cohorts to include in either the `permutive` or + * `p_standard` key-value. Defaults to `500`. + */ + maxSegs?: number; + /** + * When `true`, require TCF vendor consent for Permutive (vendor 361). + * Defaults to `false`. + */ + enforceVendorConsent?: boolean; + /** + * Per-bidder configuration for custom cohort sources. Keys are bidder codes. + * Listing a bidder here also causes the module to write ortb2 data for that + * bidder, even if it is not in `acBidders` or on Permutive's SSP list. + */ + bidders?: Record; + /** + * Transformations to apply to `user.data` entries before they are written + * to the ortb2 payload. + */ + transformations?: PermutiveTransformationConfig[]; +} + +export interface PermutiveRtdProviderConfig { + /** + * When `true`, the auction waits for the Permutive SDK to reach real-time + * before completing the bid request data callback. Defaults to `false`. + */ + waitForIt?: boolean; + /** + * Module-specific parameters. + */ + params?: PermutiveRtdProviderParams; +}