Skip to content

fix(cwire): repair cookie sync and consent guard - #36

Open
roarc0 wants to merge 80 commits into
masterfrom
roarc0/cwire-rewire-cookie-sync
Open

roarc0 wants to merge 80 commits into
masterfrom
roarc0/cwire-rewire-cookie-sync

Conversation

@roarc0

@roarc0 roarc0 commented Oct 20, 2026

Copy link
Copy Markdown
Member

Type of change

  • Bugfix

  • Feature

  • New bidder adapter

  • Updated bidder adapter

  • Code style update (formatting, local variables)

  • Refactoring (no functional changes, no api changes)

  • Build related changes

  • CI related changes

  • Does this change affect user-facing APIs or examples documented on http://prebid.org?

  • Other

Description of change

Other information

patmmccann and others added 30 commits October 5, 2026 11:04
* Consent Management USP: deduplicate deletion listener

* Consent Management USP: retain deletion listener state
…prebid#15666)

* Core: preserve eager module initialization in Babel 8 tests

* Core: preserve type-only import removal with Babel 8 (prebid#15668)

* Core: preserve Babel 7 type import elision (prebid#15669)

* Shinez RTB Bid Adapter: stabilize resolution assertion (prebid#15677)

* Core: target Safari 11 in ES5 builds (prebid#15680)

* Define legacy syntax targets for Babel configuration

* Update webpack.common.js

* remove targets

* cleanup comments

---------

Co-authored-by: Demetrio Girardi <dgirardi@prebid.org>
…15691)

* User ID: initialize submodules added after config

* User ID: preserve stored IDs during late initialization

* User ID: test that retained submodules are not refreshed by a later config

With retainConfig, a submodule left out of a later userSync config keeps
its previous container, including the refreshIds flag set when it was
first configured. The config listener then refreshes it again although
its config did not change. This test fails until that is fixed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* decouple refresh / retain logic from state tracking flags

* User ID: test that data deletion sees IDs of retained submodules

A submodule left out of a later userSync config is retained by
copying its container, while its pending callback still writes to the
original. requestDataDeletion reads the copy, so onDataDeletionRequest
receives an idObj that predates the callback's result.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* do not refresh on first setConfig

* preserve submoduleContainer identity

---------

Co-authored-by: Demetrio Girardi <dgirardi@prebid.org>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…bution (prebid#15329)

* web loader

* add prebid.web.js

* fix tests

* fix disclosures with no module list input

* improve buildOptionsInjector

* include parent modules in dependencies

* lint

* increase callback name randomness

* improve logging on libLoaded case; fix duplicate parents in getModules

* lint

* Build system: scope test-build-logic glob to spec files

The merge surfaced a conflict neither side had on its own. This branch changed
the mocha spec argument from the directory `./test/build-logic` to the glob
`./test/build-logic/**/*` (853e428, 2026-07-08), which was harmless then --
test/build-logic held only spec files. Master later added .ts fixtures under
test/build-logic/fixtures/augmentation (ea8b183, prebid#15395, 2026-08-07). The
combination breaks.

mocha's lookup-files treats the two argument forms differently: a directory is
filtered by --extension (js,cjs,mjs) and is non-recursive, while a glob is used
"as is without extensions" and fully recursive. So `**/*` swept the fixtures in
as if they were specs, and mocha aborted importing broken.ts -- a deliberately
broken fixture for augmentationReachable_spec, whose `import './inCore.js'`
resolves to inCore.ts only under TypeScript's rules, not node's.

Scope the glob to *_spec.@(js|mjs): 6 spec files, 37 passing.
CI runs this task (.github/workflows/browser-tests.yml).

* isolate bundle.js from other prebid bundles

* do not fetch buildOptions from distUrlBase

* fix buildOptions exclusion

* mark libLoaded before loading

* separate loading and loaded flags; do not attempt to load when defineGlobal = false

* bump node version in package.json to match .nvmrc

* move source maps together with the bundles

* clean up loading flag after all script loads are settled

* Reject web bundle loads after chunks settle (prebid#15615)

---------

Co-authored-by: Patrick McCann <patmmccann@gmail.com>
* Build system: call Babel traverse default export directly

* Build system: cover build options AST injection
* Update package-lock.json

* Update package dependency declarations (prebid#15704)

---------

Co-authored-by: Patrick McCann <pmccann@raptive.com>
Bumps [proxy-addr](https://github.com/jshttp/proxy-addr) from 2.0.7 to 2.0.8.
- [Release notes](https://github.com/jshttp/proxy-addr/releases)
- [Changelog](https://github.com/jshttp/proxy-addr/blob/master/HISTORY.md)
- [Commits](jshttp/proxy-addr@v2.0.7...v2.0.8)

---
updated-dependencies:
- dependency-name: proxy-addr
  dependency-version: 2.0.8
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…rom single file test runs (prebid#15729)

* Build system: stop watch precompiles from rewriting unchanged output

`serve-and-test --file` (and the other watch tasks) re-run `precompile` on every
source change. Since precompilation started materialising declarations from
`.cache/ts`, each of those runs copies every `.d.ts` into `dist/src`, changed or
not. The `dist/src/**/*.js` watch in `watchTaskMaker` (chokidar 3) answers a
write to a file outside its pattern by re-reading that file's directory, so one
spec edit caused ~1,600 directory walks, ~900 of them over `dist/src/modules`.
The gulp process stalled ~50s per edit, grew by ~5GB each time, and died on the
second or third edit with a V8 heap OOM (exit 134), or past a raised heap limit
with `RangeError: Map maximum size exceeded` in node:domain's async hook.

Two other steps rewrite identical output each cycle, and karma's webpack
re-runs tests on both:
- `buildOptions.mjs`, written before babel, so the suite ran once more against
  the previous version of the edited file before the right run;
- the creative renderers, which `buildCreative` re-emits every run, so the
  suite ran a second time after it.

All three now skip the write when the destination already holds the same
bytes. Measured with `serve-and-test --file` over four spec edits: one karma
run per edit, always on the edited code, ~2s after save; directory walks per
edit down to 85; gulp RSS flat at ~1.3GB.

gulp lint --files gulp.precompilation.js --nolintfix: clean.
gulp test-build-logic: 72 passing.
Cold precompile (dist/src and build/creative removed) still produces the
renderers, buildOptions.mjs and all 443 declarations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Build system: don't run the build logic suite on `test-only --file`

`test-build-logic` was prepended to `test-only` so that CI would run it;
CI has since moved it to a job of its own and never invokes `test-only`.
What remained is a local cost on the spec-iteration loop: every
`test-only --file <spec>` ran the mocha build-logic suite first (~3s), and a
failing build-logic test stopped the run before karma started, blocking work
on unrelated specs.

`test-only` without `--file`, and so `gulp test`, still run it.

`gulp --tasks`: test-only's series is test-build-logic, precompile, test
without --file, and precompile, test with it.
`gulp test-only --file test/spec/modules/adoceanBidAdapter_spec.js`: 22 passing,
5.8s, no build-logic step.
gulp lint --files gulpfile.js,gulp.precompilation.js --nolintfix: clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Build system: address review of the watch and test-only changes

- `test-only` decides whether `--file` was given the way karmaRunner does, by
  truthiness. With `== null`, `--file=` (e.g. an unset variable expanded into
  `--file=$SPEC`) dropped the build-logic suite while karma, reading "" as no
  file, still ran the full suite.
- copyDeclarations_spec: cover a destination that is already present - left
  untouched when identical, overwritten when it differs. The existing cases
  always removed it first, so neither always-write nor a comparison that misses
  a difference would have failed them. Checked by mutation: forcing
  isUpToDate to false fails the first new case, comparing sizes only fails the
  second.
- Narrow the isUpToDate and renderer comments to the local mechanism. They
  described other components' behaviour (karma-webpack, chokidar, the
  watchTaskMaker glob, buildCreative), and claimed every identical-output step
  checks before writing, which generateGlobalDef, generateTypeSummary and
  stampPrecompiled do not.

For the record, the mechanisms the comments no longer name: karma-webpack
re-runs tests on any rebuild of a module in its graph (buildOptions.mjs is
imported throughout); the `dist/src/**/*.js` watch in watchTaskMaker re-reads a
directory when a file outside its pattern there is written; buildCreative
re-emits every renderer on each run.

gulp lint (changed files, --nolintfix): clean. gulp test-build-logic: 74 passing.
`gulp --tasks`: test-build-logic present in test-only for no --file and
--file=, absent for --file x.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Build system: don't re-run karma watch tests when a source map changes

karma's webpack config is a clone of webpack.conf.js, so it inherits the
`extractSourceMap: true` rule: webpack reads each module's `.map` while building
and records it as an input. A precompile writes a module's `.js` and its `.map`
separately, ~0.1-0.6s apart, so after one edit to a non-spec source file the
watch invalidated once or twice on the `.js` and once or twice on the `.map`,
each a separate rebuild and test run - 2-4 runs per edit, rising over a session,
and the same on master. A rebuild that caught the map mid-write also warned
"Unexpected end of JSON input" and dropped the map.

In watch mode, ignore `.map` changes. A map only changes along with its `.js`.
Single-run mode and the bundle builds are unchanged.

Also: the isUpToDate comment named "the steps below" as checking it, which
generateGlobalDef and generateTypeSummary do not; it now names the three that do.

Measured with `serve-and-test --file`: 5 edits to a module's source and 4 to the
spec, 20s apart - one karma run per edit, each on the edited code, no map
warnings, gulp RSS peak 1.6GB / 1.3GB.

gulp test-build-logic: 76 passing; flipping the watch-mode condition fails both
new karmaConfig cases.
gulp lint (changed files, --nolintfix): clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Build system: batch karma watch changes instead of ignoring source maps

Ignoring `.map` changes kept one edit to one test run, but left the edited
module's map stale in karma's bundle: the precompile always writes a module's
`.js` before its `.map` (vinyl-fs dest), 0.1-0.6s apart, so the rebuild the `.js`
triggers reads the previous map, and with maps ignored nothing rebuilds again.
Test output is not source-mapped, so this would only show in a debugger attached
to karma's page, but it is wrong.

Keep watching maps and give webpack's watch a 1s aggregateTimeout in karma watch
mode instead (default 20ms), so a `.js` and its `.map` land in one rebuild. A map
later than that gives a second, correct rebuild rather than a stale one. Each
run starts up to 1s later.

Side effect: the one rebuild karma's webpack already did at startup (an
invalidation on `dist/src/test/helpers`, which the precompile touched just before
karma started) used to finish before the browser connected; it now lands after
the first run, so a watch session starts with two runs of the initial code.

Also reword the isUpToDate comment: describe the mechanism without listing its
callers, and say a rewrite gives a stale run only when it lands before the edited
file's own output.

Measured with `serve-and-test --file`: 3 source edits and 3 spec edits, 20s apart,
one karma run per edit on the edited code, no source map warnings, gulp RSS peak
1.4GB / 1.3GB (and 5 + 4 edits earlier, same result).

gulp test-build-logic: 76 passing. gulp lint (changed files, --nolintfix): clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Build system: pin karma's watch batching delay in its test

The test asserted `aggregateTimeout >= 1000`, which also passes a much longer
wait. Every karma watch run starts that long after the last write, so a longer
value is a regression too; pin it.

For the record, since 530b553's message understates it: each watch run now
starts ~1s after the last write (previously 20ms), not "up to 1s later".

npx mocha test/build-logic/karmaConfig_spec.mjs: 6 passing.
gulp lint (changed file, --nolintfix): clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…rebid#15657)

* dspx: Prebid 11 interface update and test improvements

* dspx: outstream renderer, video params

---------

Co-authored-by: avj <avj8383@gmail.com>
…uildVideoUrl (prebid#15640)

buildVideoUrl() documents both options.bid and options.params.cust_params
as optional, but dereferenced them unconditionally:

- bid.adserverTargeting threw when options.bid was omitted and
  targeting.getWinningBids(adUnit.code) had no winner for the ad unit.
- Object.entries(custParams) threw when params.iu was a full URL that
  already carried cust_params and params.cust_params was not set.

Default both to {} the same way gamAdServerVideo.js already does for
adserverTargeting, and add regression tests for each path.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Send the result of the shared autoplay detection library (isAutoplayEnabled) as `autoplay`
in the bid request, so that the SSP can avoid video demand when muted autoplay is
forbidden on the page.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… the publisher's ext (prebid#15720)

* Insticator Bid Adapter: add audio media type support

Declares audio alongside banner and video, validates the OpenRTB 2.6 audio
object, and resolves audio bids on the way back.

The request carries the full audio object: 26 optional fields plus context,
including the ad pod set and the audio-only signals feed, stitched and nvol.
Each field is validated before it is sent, from both the ad unit and the bidder
params, and ext deep merges across the two so a bidder level ext extends the ad
unit's rather than replacing it.

On the response, mtype decides the media type. Where it is absent VAST markup
resolves to video as it did before, unless the ad unit declares audio: an
audio-only unit resolves to audio, and a unit declaring both is separated by the
MediaFile MIME type, which is the only thing that tells the two apart.

params.audio is copied before it is read, so a publisher passing null there does
not throw out of buildRequests and cost every bid in the auction. buildVideo
already copies for the same reason.

Also in this change, each of which stands alone:

- The data URI is built from UTF-8 bytes for video as well as audio. btoa only
  accepts code units up to U+00FF, so a creative above that threw and lost every
  bid in the response, while Latin-1 characters were accepted and silently
  mangled into a payload declared as charset=utf-8.
- Creative markup is no longer rewritten. The response body is JSON parsed
  before interpretResponse runs, so a backslash quote still present is creative
  content, and replacing it corrupted JSON carried inside a VAST element.
- meta from bid.ext is type checked and copied. A DSP sending a string there
  made the assignments below it throw, which discards the whole response.
- video.podseq is bounded to -1, 0 and 1, the values OpenRTB 2.6 defines for it.

* Insticator Bid Adapter: forward the ext the publisher set on every object

Each ORTB object was assembled from a named list of fields, so an ext the
publisher set reached the endpoint only where the list happened to mention it.
site and site.publisher sent none at all, banner had no path for one, and video
carried only the bidder params copy, by way of an unfiltered spread rather than
by intent. The request, source, user, regs and imp objects each sent an ext
built here while dropping the publisher's.

Every object now merges the publisher's ext under our own, so a field we do not
model still arrives and the signals we compute still win where the two name the
same scalar key. Arrays are concatenated rather than replaced, which is how
mergeDeep treats them throughout Prebid.

Two cases were losing data outright rather than never carrying it:

- eids replaced user.ext, discarding a publisher ext already on it.
- The device config was applied with a shallow assign, so a publisher ext
  replaced the localStorage and cookies signals instead of joining them.

buildVideo also copies the bidder params before pruning invalid entries. It was
deleting them from the ad unit config itself, which outlives the request.

Three further fields the publisher sets were dropped, and are forwarded now:

- ortb2Imp.rwdd, the rewarded flag, had no path onto the impression at all.
- ortb2.user.data, where only the bidder params copy was sent. Both sources now
  contribute, and each is checked for being an array first: spreading a value
  that is not iterable throws out of buildRequests, which loses every bid in the
  auction rather than the one field.
- ortb2.site.mobile was validated by isInteger() called without its argument, so
  the check never passed. The guard around it also tested the value itself for
  truthiness, which dropped the legitimate mobile: 0.

A publisher null inside imp.ext is no longer fatal either. The impression ext is
assembled in one merge rather than written into afterwards, because dset walks
into any existing value whose typeof is object, and typeof null is object.

The module documentation listed adUnitId as the only bid parameter. It now
covers the rest of params, including the ext keys this change makes reachable.

* Insticator Bid Adapter: add native media type support

Declare NATIVE and build imp.native from the core-validated
nativeOrtbRequest, skipping the imp when the config carries no assets so a
multi-format unit keeps bidding its other media types. Forward the api,
battr and ext the publisher set on mediaTypes.native, and derive the spec
version from the request so imp.native.ver and the serialized request
cannot disagree.

On the response, map mtype 4 and native-JSON adm to the native media type,
expose the parsed creative on bid.native.ortb (unwrapping legacy wrapped
responses), and drop a bid whose adm is not a usable native document rather
than let a shapeless object reach core's asset validation.

* Insticator Bid Adapter: cover the defensive paths flagged by the coverage report

Adds tests for paths the coverage report reported as unreached: video
mimes validation, an unsupported optional video parameter, a stored user
id that is not 36 characters, an explicit ortb2.regs.coppa, a response
carrying no seatbid, and native detection of markup that is not JSON.

* Insticator Bid Adapter: send bid requests as text/plain

application/json is not a CORS-safelisted Content-Type, so every cross-origin bid
request needs a preflight OPTIONS before it can be sent — a round trip ahead of the
bid, against a tmax that is usually about a second. text/plain is safelisted and
skips it. The body is unchanged JSON; only the declared header moves.

This is what prebid/json-request-content-type flags.

* Insticator Bid Adapter: fix COPPA test to pin the global flag off, matching the prebid#15462 refactor semantics

* Insticator Bid Adapter: require audio mimes

OpenRTB marks Audio.mimes as required, but an audio config without a valid
mimes list still passed validation and was sent anyway — as an imp.audio
missing the field, or as an empty object when nothing else was valid.

An audio-only ad unit without valid mimes is now rejected. On a multi-format
ad unit the audio object is left off the imp and the other media types still
bid, matching the rule already used for native.

Renames OPTIONAL_AUDIO_PARAMS to ORTB_AUDIO_PARAMS, since the map now holds a
required field, and documents the rule in the adapter md.

---------

Co-authored-by: mokshgulati-cool <moksh.gulati@cool.co>
* fix(cwire): send ad unit code in bidder.adslot

* fix(cwire): simplify adslot fallback guard
…rebid#15643)

* Zeta Global Ssp Adapter: migrate to ortbConverter, add param types

* Zeta Global Ssp Adapter: remove unused param in test

* Zeta Global Ssp Adapter: fix tests

* Zeta Global Ssp Adapter: add more tests for coverage
…15648)

* ● Adquery ID System: replace oversized stored qid via extendId

  getId isn't called when a qid is already stored, so oversized qids
  were never replaced. Add extendId to validate the stored qid (max 40
  chars) and reuse a valid localStorage qid before generating a new one.

* Adquery ID System: only check qid length in extendId

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Adquery ID System: replace non-string stored qid in extendId

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Adrian Dzida <a.dzida@openmobi.pl>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: BidTags <info@bidtags.com>
…ettles; add experimental GPT `registerRequestHook` integration example (prebid#15727)

* Integration examples: add hello world using GPT request hook

Copy of hello_world.html that runs the Prebid auction from
googletag.pubads().registerRequestHook instead of a bidsBackHandler and
failsafe timer. GPT awaits the hook (racing it against its own timeout)
before sending the ad request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Core: add failing tests for requestBids promises that never settle

The promise returned by requestBids is only resolved from auctionDone, so
it stays pending when the auction never starts:

- consent management cancels the auction (e.g. CMP not found, or static
  config without consentData); bidsBackHandler is called but the promise
  is not resolved
- a startAuction hook throws, or first party data enrichment rejects; the
  error is swallowed by the unhandled enrichFPD -> startAuction chain

These tests fail until the underlying behavior is fixed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* clean up example

* fix bug where requestBids will sometimes never settle

* do not log error when bidsBackHandler is missing

* Rename hello_world_request_hook.html to hello_world_experimental_request_hook.html

* update example

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Patrick McCann <patmmccann@gmail.com>
Declare the IAB TCF GVL ID and drop the local disclosureURL in favour
of the one from the GVL entry.
… auctions (prebid#15735)

* Core: add auctionOptions.yield config to toggle yielding between adapters

* Core: test that a later s2sConfig is called when an earlier one has no bidders in the auction

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* keep old counter behavior

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ression (prebid#15703)

* Core: validate gzip compression support

* Core: fall back when gzip streams reject

* Core: preserve bidder context for gzip requests

* extract gzip utils

* remove redundant catch

* do not assume browser supports gzip

* start gzip check on page load

* lint

---------

Co-authored-by: Demetrio Girardi <dgirardi@prebid.org>
* adding acpId

* - adding addtlConsents to hash fields
- sanitizing consent string

* adding acp id to selected adapters
* native video support

* native video example

* fixing content scenario

* Make LegacyVideoAssetRequest properties required

---------

Co-authored-by: Patrick McCann <patmmccann@gmail.com>
* New adapter: Epom Ad Server (epom_as)

Bid adapter for the sell side of the Epom platform, where a publisher's
own direct-sold campaigns are booked. Distinct from the existing epom_dsp
adapter, which is the buy side.

Epom Ad Server is white-label, so the serving host arrives per bid in
params.host and the adapter groups impressions by host, emitting one
request per host with every impression addressed to it. Keeping a host's
impressions in one auction is a requirement rather than an optimisation:
the ad server resolves a page as a unit, so its roadblock and
one-campaign-per-page rules only hold when all slots are decided together.

Banner only, built on ortbConverter.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Group bids by host without casting through unknown

Array.from over the map builds the requests directly, so the accumulator
and the `as unknown as` cast around the empty group both go away.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Add channel and customParams to the Epom Ad Server adapter

Two Epom concepts the standard ORTB request cannot express. Channel is a
publisher traffic-slice label that drives targeting and reporting, and it
has no ORTB analogue of any kind, so it goes under our own imp.ext
namespace. Custom parameters go to imp.ext.data instead, because that is
where first-party data and RTD modules already contribute, and the ad
server should have one place to read targetable key-values from.

Both are sanitised to the limits the ad server enforces on ingest -
scalars only, 32 keys, 128-byte keys, 512-byte values - so a publisher
sees the same set accepted here and applied there rather than discovering
the difference in a report.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Accept a host with a port, matching the Prebid Server schema

The two adapters are meant to take byte-identical params, but the Prebid
Server JSON schema allowed host:port while the Prebid.js pattern did not.
A port cannot redirect the request: scheme, path, query and userinfo stay
excluded, which is the property that matters.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Send credentials, matching how the ad server answers CORS

The endpoint echoes the request Origin and sets Allow-Credentials, so a
first-party Epom identity on the ad-server domain can reach the auction
instead of every request looking like a new user.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Set the bid TTL to the window the impression beacon actually has

The adapter told Prebid a bid was good for 300 s while the ad server only
accepts its impression beacon within 30 s of the bid, so a cached bid
rendered a minute later counted for nothing - Prebid documents exactly
this: an ad rendered after the TTL may not be attributed.

25 s leaves five for the beacon to travel. A deployment that widens its
window can say so per bid in bid.exp, which the converter already prefers
over this default, so nothing here has to change for it.

Also fixes a test title that still said "without credentials".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Epom Ad Server: stop the TTL comment naming one deployment's window

The comment stated the beacon is accepted within 30 s, which is one deployment's
configuration rather than anything about the protocol — and a deployment widening
that window makes the comment wrong without touching the code. The constant is
the floor; a server with a wider window says so per bid in bid.exp, which is what
the sentence now says.

* Epom Ad Server: point the test parameters at a placement that fills

The block used ads.example.com, which does not resolve, so the one thing a
reviewer is asked to do — load the ad unit and confirm a bid comes back — could
not be done. The first block is now a live placement kept filled for this; the
fictional hosts stay in the second, where they illustrate grouping by deployment
rather than something to run.

* Epom Ad Server: make the two transports agree, and stop dropping a publisher's parameters

The host pattern is now byte-identical to the expression Prebid Server validates the same parameter
with. The previous one was strictly narrower — it demanded two labels, so an internal deployment
reachable as a single-label host was refused here and accepted there — while a comment claimed the
two matched. The comment is true now.

Custom parameters are no longer capped in the adapter. The caps mirrored an ad-server ingest limit,
but enforcing them here dropped entries silently, so a publisher whose targeting set was too large
saw half of it never arrive with nothing to indicate why. What the adapter does check is the shape:
a nested object or an array stringifies to something no campaign can match, so it is a
misconfiguration and the bid is refused outright, which is what the server's schema does with the
same input. The ingest limits remain the ad server's own and are documented as such.

bidFloor applies only as a positive finite number, and a negative one is refused to match the
schema's minimum. The request options block is gone: it set exactly the content type and credentials
core already applies to every POST, and the assertions that covered it were testing core.

The module declares its device-storage disclosure at the URL published on GVL vendor 849. The field
is not derived from the vendor list when omitted — it is simply recorded as null — so a vendor that
declares cookie use has to name it.

Tests cover what a misconfiguration actually looks like: every rejection path, a response body that
is not ORTB at all, and the floor precedence in both directions.

* Epom Ad Server: refuse a port a URL cannot carry

The host shape alone accepted a port outside 1-65535. `new URL()` throws on one rather than
returning something unusable, and core does not call buildRequests inside a try, so a single
mistyped port in a page's configuration took the whole request down instead of costing one bid.

The range is checked on top of the same expression Prebid Server validates this parameter with,
which is deliberately about keeping URL control characters out of a host rather than about
resolving one — hostname labels are left alone, so a single-label internal deployment still works
and the two transports still accept the same inputs.

* Epom Ad Server: support video and native

The adapter declared banner only, and Prebid will not offer a slot to a bidder
that has not declared its media type — so a video or native auction never
reached the ad server whatever it could serve.

The converter no longer pins context.mediaType. Pinning one makes it believe
every response whatever came back; left off, it reads mtype off the bid, which
is the field the ad server sets per creative. A bid naming no media type is
discarded rather than rendered as the wrong one, which is now covered by a case
of its own.

The mixed banner+video expectation is inverted with it. Stripping video from a
mixed ad unit was right while this adapter sold banner only; keeping it now
would decide the auction on the page's behalf. Assertions that need the video
or native processors are guarded on FEATURES, since the features-disabled build
compiles them out.

* Epom Ad Server: document the cache setting instream video needs

Prebid refuses an instream bid carrying only vastXml when no cache is
configured, and it does so in validation rather than in caching - so the ad
server answers correctly, the slot reports no bid, and the reason appears only
in the console. Names the three settings and which one each player needs, and
that a hosted Prebid Cache is required only for Google Ad Manager.

* Epom Ad Server: name a test placement for video and native

The adapter declares three media types but the Test Parameters section offered
only a banner placement, so a reviewer could verify one third of the claim. The
ad server decides what a placement may answer from the placement's own type, so
each media type needs its own key rather than a second mediaTypes block on the
same one. All three fill on the same demo host.

* Epom Ad Server: reject a non-string channel or bidFloorCur

isBidRequestValid now matches the Prebid Server params schema for these two
params (type: string), so an object or number is rejected on the page instead
of being stringified into imp.ext or passed straight into imp.bidfloorcur.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Epom Ad Server: address the network by networkId, keep host optional

Prebid Server does not accept an endpoint host taken from a publisher
parameter, so the server-side adapter now reaches a network at
https://{networkId}.eashb.com/hb/bid. The client-side adapter takes the same
params: a bid names its network with networkId and goes to that address by
default, which also gives the adapter the default endpoint asked for in
review. host stays as an option: when given, the bid goes to the network's
own serving domain, where its identity cookie lives.

A bid needs networkId or host; networkId must be "n" followed by digits, since
it becomes a hostname label. Impressions are grouped by destination host, as
before.

Also corrects the advertiser-domain note: the ad server sends adomain when
the advertiser has a domain set.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Anton Ruin <a.ruin@epom.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Patrick McCann <patmmccann@gmail.com>
* Update domain for Adhese bid requests

* Update Adhese test

* Format code for consistency in adheseBidAdapter_spec.js

---------

Co-authored-by: mefjush <mefjush@gmail.com>
Co-authored-by: westerschmal <30859973+westerschmal@users.noreply.github.com>
msmolenschi and others added 30 commits October 16, 2026 07:12
* Add Sevio user ID submodule

* Add Sevio user ID submodule - add docs

* Add Sevio user ID submodule - add docs

* Add Sevio user ID submodule - add docs

* Add Sevio user ID submodule - add docs

* Add Sevio user ID submodule - add docs

* Add Sevio user ID submodule - add docs

* Add Sevio user ID submodule - add docs

* Add Sevio user ID submodule - add docs

* Add Sevio user ID submodule - add docs

* Add Sevio user ID submodule - add docs

* Add Sevio user ID submodule - add docs
* New Bid Adapter

* version

* put coppa from getConfig as fallback

* addressing codex feedback

* addressing maintainer's review

* addressed codex review comments

* updated unit tests

* address codex review
* Repo: make .claude a symlink to .agents

Replace the per-skill symlink .claude/skills/prebid-chrome-devtools with a
single top-level .claude -> .agents symlink, so every skill (and anything
else) added under .agents is picked up by Claude Code without adding a
new symlink each time. Ignore .agents/settings.local.json, where Claude
Code's personal settings now land.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Keep .claude a real directory; symlink .claude/skills to .agents/skills

Addresses review feedback: a top-level .claude symlink becomes a plain
file when core.symlinks=false, and replacing the directory can delete
existing ignored .claude/settings.local.json files on update.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…rebid#15762)

* TPMN Bid Adapter: stub out adapter, remove tests, docs and examples

TPMN has shut down its bidding and cookie sync services. Replace the
adapter with a no-op module that logs a warning, and delete its spec, md
file and integration examples. Keeping the module file means builds that
still pass --modules=tpmnBidAdapter keep working; full removal can happen
in Prebid 12 (prebid#15745).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* TPMN Bid Adapter: register stub as a no-op bidder

Register a 'tpmn' bidder whose isBidRequestValid logs the removal warning
and rejects every bid, instead of only logging at load time. This keeps
the module matched to its bidder code so metadata generation (and the
adapter naming check) keeps working, and surfaces the warning when a
tpmn bid is actually requested, after the publisher's debug config
has applied.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* CI: skip spec files in the polyfill check

Spec files are not part of the shipped bundle, and they already run on
older browsers in the normal PR checks, so flagging polyfills they may
need is noise.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* exclude _spec.mjs

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Demetrio Girardi <dgirardi@prebid.org>
* Apply suggested fix to modules/intenzeBidAdapter.js from Copilot Autofix

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* Intenze bidder: test zero params.bidfloor

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Adapex Bid Adapter: restore on current Prebid core

Reintroduce the reviewed Adapex adapter and shared Floxis helper after the upstream revert. Update integrated bidder tests to stub the new gzip module and await asynchronous dispatch, so they pass against current master.

* Adapex: honor CMP deletion after storage access withdrawal

* Scope consent deletion to configured adapter storage keys

* Adapex: keep restoration limited to adapter changes
…prebid#15785)

enforceStorageTypeRule compared the single storage type of each write
('cookie' or 'html5') with the raw storage.type string. A submodule
configured with a combined type such as 'cookie&html5' never matched.
Every write, including the core's own writes through the submodule's
storage manager, then logged a warning. With
userSync.enforceStorageType: true, every write was denied and the ID
was never persisted.

Check whether the type is in getConfiguredStorageTypes() instead. The
rest of the module already parses storage.type with it. The warn-or-deny
behaviour for unlisted types is unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Anna Yablonsky <annay+perion@perion.com>
… to 1.7.0 (prebid#15786)

* adgenerationBidAdapter: stop URL-encoding privacyLink, bump version to 1.7.0

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* adgenerationBidAdapter: add test for privacyLink with query string

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Adspiro Bid Adapter: initial release

* Adspiro Bid Adapter: update maintainer email

* Adspiro Bid Adapter: drop per-format floors in other currencies

* Adspiro Bid Adapter: rename the bidder code to ad_spiro
…d#15794)

Set meta.adshield on every bid to 1 when the bid response carries an
X-Adshield header and 0 otherwise. The header has no value, so presence
is tested against null: getResponseHeader returns "" for a header sent
without a value and null for an absent one.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* CI: clear hidden files before restoring artifacts

* Update action.yml
* ortbConverter: convert audio mtype / ext.prebid.type

* remove audio workaround from adferry

* remove audio workaround from ad_spiro

* remove ORTB_MTYPES duplication from connectad
* Core: expose most desirable bid API

* Refactor comment in getMostDesirableBid function

Removed unnecessary comment line from getMostDesirableBid function.

* Core: filter ineligible bids before desirability reduction

* Core: replace getMostDesirableBid with pbjs.getBestBids

Follow the proposal in prebid#15602: add pbjs.getBestBids, mirroring
getHighestCpmBids but using getHighestDesirability as the win reducer,
and add a no-ad-server integration example that uses it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Core: break getBestBids desirability ties on size and deals; rename example

Add a getBestBid reducer for getBestBids: highest desirability, then the
smaller creative (bids without a size rank as largest), then a deal, then
the fastest response. getHighestDesirability is unchanged, so GAM
targeting tie-breaking is unaffected.

Rename the integration example to adserver_first/basic_adserver_first.html
and describe the ad-server-first setup, where the ad server bids into
Prebid instead of Prebid setting targeting for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Core: drop size and deal tie-breaks from getBestBids

getBestBids uses getHighestDesirability again, so desirability ties fall
back to the fastest response, the same as targeting. Size and deal
tie-breaking is out of scope for this change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* update example

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Demetrio Girardi <dgirardi@prebid.org>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…rebid#15803)

* Core: add failing tests for desirability in winning targeting (prebid#15800)

getAllTargeting picks each bidder's representative bid by desirability,
but the ad unit's winning keys (hb_adid, hb_pb, hb_bidder) still come
from getWinningBids' default getHighestCpm reducer. Add tests for the
scenario in prebid#15800: a lower-CPM, more desirable bid should win the
top-level targeting, with and without send-all-bids, and agree with
getWinningBids under getHighestDesirability (what getBestBids uses).

Three tests fail until the fix lands; the control (desirability equal
to CPM still targets the highest CPM) passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* use desirability for winner calculation

* Core: add failing tests for zero desirability in deal-prioritized sort

sortByDealAndPriceBucketOrDesirability(true) still uses a truthiness
check on desirability, so a score of 0 is treated as absent and the
comparison falls back to CPM. With enableSendAllBids, a bidLimit and
sendBidsControl.dealPrioritization, getHighestCpmBidsFromBidPool and the
multibid targetBidPoolHook use that sorter to cut the pool, so a
higher-CPM bid with desirability 0 can displace a lower-CPM bid with
desirability 2 before the winner is chosen.

Add tests for the sorter itself, for getAllTargeting under that config,
and for the multibid hook. All three fail until the sorter uses the same
null check as sortByHighestDesirability.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Core: use a single desirability comparator for bid sorting

sortByHighestDesirability, the inline branch in
sortByDealAndPriceBucketOrDesirability(true) and the bidDesirabilityCompare
key used by getHighestDesirability each encoded the comparison rule
separately. The deal-prioritized one tested desirability for truthiness,
so a score of 0 fell back to CPM and a higher-CPM, zero-desirability bid
could displace a more desirable one when bidLimit and dealPrioritization
were set.

Export bidDesirabilityCompare and a descending highestDesirabilityFirst
built from it, and use that for every default winSorter, the multibid
bid-limit sort and the deal-prioritized sort. Remove
sortByHighestDesirability.

Bids now score as desirability ?? cpm individually, so comparing a bid
with a desirability against one without no longer depends on the pair;
the old pairwise fallback was not a consistent ordering for mixed lists.
Comparator results are now -1/0/1 rather than a difference, and the
tests that asserted exact values check the sign instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Add interstitial example

* address review
)

* ortbConverter: populate bid.meta.deal from ext.prebid.deal

Normalize the ORTB seatbid[].bid[].ext.prebid.deal extension proposed in
prebid#15602 into bidResponse.meta.deal: map the numeric type to a string,
camel-case pacing_adjusted_cpm and on_schedule, and copy only known,
correctly typed fields. Deal metadata is ignored for bids without a dealid.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ortbConverter: convert deal processor to TypeScript, keep numeric deal type

Move the ext.prebid.deal normalization to deal.ts, type the extension on
ORTBBid.ext.prebid, and keep meta.deal.type as the ORTB numeric enum
(defaulting to 0, unknown) instead of mapping it to a string.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ortbConverter: record currencies for deal CPMs

Add optional ext.prebid.deal.cpm_cur and pacing_adjusted_cpm_cur, exposed
as meta.deal.cpmCurrency and pacingAdjustedCpmCurrency. When a bidder
omits them they default to the response currency, so the deal amounts
keep an explicit unit after the currency module converts the bid's own
cpm. Also drop JSON.stringify from a test title.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ortbConverter: drop deal CPM currencies; convert them with the bid

meta.deal.cpm and pacingAdjustedCpm are in the bid's currency. Instead of
separate cpm_cur / pacing_adjusted_cpm_cur fields, the currency module
scales both amounts by the same rate it applies to bid.cpm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* use consistent rounding on cpm fields

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Demetrio Girardi <dgirardi@prebid.org>
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.