Skip to content

September release candidate (DO NOT MERGE) - #1112

Draft
ChristianPavilonis wants to merge 518 commits into
mainfrom
rc/202609
Draft

September release candidate (DO NOT MERGE)#1112
ChristianPavilonis wants to merge 518 commits into
mainfrom
rc/202609

Conversation

@ChristianPavilonis

@ChristianPavilonis ChristianPavilonis commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

prk-Jr and others added 30 commits August 24, 2026 11:05
Stop the malformed-config error printing twice. `creative_config` used
`report_error`, which logs the message and returns it for the top-level
`[ts]` printer to log again, so the whole seven-line `toml::de::Error`
block was emitted twice. It now returns a plain `format!` like its sibling
branch, names the config file from the path the caller already holds, and
leads with the guidance so the multi-line parse error trails unbroken.

Blame the crawl for an unwitnessed `section_root` only when the crawl gap
is what stopped inference. The per-slot reason rewrite was gated on
`root_witness_missing` alone while the run-level note was gated on
`diagnostics.is_empty()`, so a run that stopped on segment ambiguity told
the operator to widen the crawl when the remedy is pinning
`section_segment`.

Give the page audit's settle defaults one source of truth: `BrowserOpts`
and `BrowserCollector::new` now share `PAGE_SETTLE_*` instead of holding
independent literals that nothing pinned equal.

Also: the borrowed-root refusal says `div id(s)`, matching what it prints
and distinguishing it from the run-level diagnostic's slot ids; the
`--page-pattern` docs say the run fails rather than implying inference is
retained; the hidden legacy alias carries `value_name = "URL"` so its
rejection does not name a field absent from `--help`; and the
`ad-templates generate` browser field is documented like its twin.

Test hardening: a `Command::debug_assert` over the crate's `requires`
argument ids, the parent-flag rejection pins `MissingRequiredArgument`
rather than any error, the JS/Rust evidence cap test parses the declared
value instead of matching punctuation, the hidden-flag help test covers
all seven flags, and `audit_page_subcommand_parses` is renamed for the
settle defaults it now protects.
…plates

Conflict: `publisher.rs` imports. This branch added the ad-stack gate
diagnostics (`AdStackGateInput`, `RuntimeAdStackExpected`,
`evaluate_ad_stack_gate`) while main added the ESI template-cache work
(`AssemblyMode`, `CreativeOpportunitiesConfig`) on the same `use`. Both
sets are still referenced after the merge, so the resolution is their
union.
The bundle imported consentManagementTcf, consentManagementGpp and
consentManagementUsp, which only retrieve the consent signal. The activity
controls that act on it live in Prebid's tcfControl module, which was not
bundled, so a denied purpose changed nothing.

Reproduced with the real bundle and shim in JSDOM against a CMP stub denying
Purpose 1 and vendor 97: identityLink still issued a credentialed GET to
LiveRamp's envelope endpoint, still wrote idl_env and its bookkeeping cookies,
and the envelope still reached the ts-eids cookie. The run was identical to the
consent-granted one. Server-side gating stops EID forwarding only, not the
browser's collection and storage.

Import tcfControl alongside the consent collectors and lock the behavior with a
regression test that evaluates both production artifacts: denied purposes must
produce no vendor call and no storage, granted purposes must still resolve.

Note the scope: tcfControl enforces for every bidder and User ID module in the
bundle, not only LiveRamp. Regenerating a bundle is a manual step, so this
reaches a publisher only when `ts prebid bundle` is re-run.
`[integrations.prebid.liveramp]` combined with a pinned
`bundle.user_id_modules` list that lacks identityLinkIdSystem is valid config
that builds a bundle unable to serve it. The managed identityLink entry the
server injects has no submodule to drive, and the only signal is a browser-side
diagnostic.

Fail in `ts prebid bundle`, which already reads both keys from the same file. An
absent list still takes the generator's default preset, which includes the
module.
Three fixes to the setConfig and mergeConfig guards:

Skip seeding when getConfig is missing. `hasPrebidJsApi` only requires
registerBidAdapter, so getConfig can be absent. The previous fallback read an
empty entry list and then seeded the managed entry alone, silently dropping
every publisher module already configured. Log and leave the wrappers installed
instead, so a later publisher userIds call still gets the managed entry.

Build the managed entry per call. Prebid retains what it receives as
`submodule.config` for the life of the page, so one shared instance let any
mutation there leak into every later normalization.

Keep the normalization guard but report the error. Publisher configuration is
arbitrary page data and a throwing accessor must not break the publisher's own
setConfig call, so the guard stays; it now logs what failed instead of
discarding it. The redundant hasUserIdsPath check is dropped since
withManagedLiveRampUserId already performs it.
merge_auction_eids_deduplicates_liveramp_and_prefers_resolved_metadata was
merge_auction_eids_prefers_server_resolved_metadata_on_conflict with the source
string changed. merge_auction_eids is source-agnostic, so it added no coverage.

gate_eids_by_consent is all-or-nothing across sources. Rename its test and give
it a second source so the name no longer implies per-source gating that does not
exist.
The inline closure that mapped PrebidLiveRampConfig into its camel-cased
injection struct pushed rustfmt into indenting the whole payload literal an
extra level. A From implementation keeps the mapping next to the struct it
builds and restores the surrounding formatting.
The degraded-behavior table said only that no EID is forwarded when consent is
absent, which read as though nothing happened. Separate the TCF case, where
tcfControl now blocks resolution outright, from the US-state case, where the
server's forwarding gate is what applies.

Add the browser-side footprint the previous text omitted: the credentialed
request the page makes to LiveRamp's envelope endpoint, that Trusted Server does
not proxy it, the connect-src allowance a strict CSP needs, and every storage
entry IdentityLink writes. Note that the submodule prefers an ATS library
already on the page, and that this is therefore not a first-party replacement
for LiveRamp recognition.

Also record that the environment override cannot introduce the subsection, that
`ts prebid bundle` rejects a module list without identityLinkIdSystem, that
regenerating a bundle now enforces TCF for every bidder and User ID module, and
that KV persistence needs a matching [[ec.partners]] entry. Correct the design
document's consent section, which assumed the consent-management modules were
themselves the enforcement point, and flag the remaining GPP gap.
CI lints trusted-server-cli and trusted-server-openrtb-codegen explicitly
because no adapter-scoped alias covers them, but both invocations pin
x86_64-unknown-linux-gnu. There was no command a developer could run locally to
reproduce them, and CLAUDE.md's CI gate list named only the six adapter aliases,
so running the documented gates locally passed while CI failed on a
clippy::doc_markdown error in the CLI.

Add clippy-cli and clippy-codegen. Both omit --target so they build for the
host, and record them in the gate list.
CodeQL flagged js/incomplete-url-substring-sanitization on both assertions.
These are not sanitizers — the URLs come from the test's own fetch spy — so the
alert is not a vulnerability, but the substring test is still too loose for an
assertion: it would also match an unrelated host carrying api.rlcdn.com in its
name or query string, letting the granted-consent case count the wrong request.

Compare the parsed hostname instead. Verified the test is still non-vacuous:
dropping the tcfControl import fails two of its three cases.
prk-Jr and others added 30 commits September 9, 2026 13:03
Main split the Prebid configuration into a browser-only
`PrebidIntegrationConfig` and a test-only `LegacyPrebidServerConfig`, so
move `managed_user_ids` onto the live browser config and map it through
the legacy conversion. Left on the legacy struct the field would have
compiled but never reached a running service.

Also un-gate the `HashSet` import now that managed User ID name
validation runs in production builds, and update the script-breakout
assertion for main's stronger JSON escaping, which escapes every `<`
rather than only `</script>`.
TCF activation read `window.__tcfapi` once, but managed User ID seeding
ran regardless. A CMP that installed itself after the deferred shim left
managed modules seeded with Prebid's GDPR handler disabled, and the
module fired its vendor request with no TCF parameters. No later
reconfiguration can recall that request, so hold managed entries out of
every configuration Prebid sees until CMP discovery concludes: at once
when `__tcfapi` is already callable, on a watched late assignment, or at
the first `requestBids`, since a conforming CMP installs its stub before
vendor tags request bids.

Prebid 10.26 reads `consentManagement` as
`config.gdpr || config.usp || config.gpp ? config.gdpr : config`, so a
legacy top-level TCF configuration carries no namespace key. Recognizing
only an own `gdpr` property let the shim append `gdpr: { cmpApi: "iab" }`
to such an object, which flipped Prebid to the namespaced shape and
discarded the publisher's static consent. Mirror Prebid's own rule in
both ownership checks. Retirement then needs a matching path: a
`mergeConfig` deep merge carries `gdpr: { enabled: false }` forward and
would disable the TCF module entirely, so remove the namespace instead of
leaving it disabled.

`ts prebid bundle` accepted each managed name independently even when
several resolved to one module. The registry maps both `sharedId` and
`pubCommonId` to `sharedIdSystem`; Prebid registers one submodule for a
module's name and its aliases and selects the first matching entry, so
the second managed configuration was silently ignored. Reject duplicate
resolved module names before generation.
Resolve 146 conflict hunks across 38 files. Most were the same work in two
shapes: rc had already merged the impl branches of #1016, #1103 and #1036
before those branches were force-pushed and took further review fixes, which
then reached main as squashes.

Notable resolutions:

- Upgrade edgezero from the pre-merge feature rev 055f7e94 to the released
  tag v0.0.8, which contains that branch as upstream #344 plus a Fastly CLI
  fix. Reword the now-stale "pinned feature branch" comments.
- Take main's reviewed refinements: the precomputed bid dimension index, the
  separate wait-delay queue in the platform test double, trusted_client_ip
  secret resolution, whitespace-only secret key rejection, and the removal of
  the lgtm annotations.
- Keep rc's later work: access telemetry, managed Prebid User ID modules, and
  request timing instrumentation.
- Keep rc's secret-reference documentation, which stays consistent with the
  surrounding text, and its example.com placeholders.

Fix three semantic conflicts that produced no markers: implement the new
EcKvStore::key_exists on rc's TimedKvStore wrapper, ungate the HashSet import
now used outside tests, and drop duplicate test-scope definitions that only
the target-matched clippy runs surface.
…2609

# Conflicts:
#	crates/trusted-server-core/src/publisher.rs
#	crates/trusted-server-js/lib/src/integrations/prebid/index.ts
#	crates/trusted-server-js/lib/test/integrations/prebid/index.test.ts
Publisher delivery and creative ownership must remain exact across default Prebid/GPT mappings, overlapping callbacks, responsive containers, and deferred SPA startup. Preserve captured identities and rank authenticated source matches so ambiguous deliveries continue to fail closed.
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.

4 participants