Skip to content

Let a consent prompt run in the phase the publisher chooses - #1362

Open
jwrosewell wants to merge 29 commits into
feature/20-ads-txtfrom
feature/21-cmp-phases
Open

jwrosewell wants to merge 29 commits into
feature/20-ads-txtfrom
feature/21-cmp-phases

Conversation

@jwrosewell

@jwrosewell jwrosewell commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Changes

Area Change
crates/cmp/inmobi 1 file, 479 lines added, 20 removed
crates/cmp/didomi 1 file, 42 lines added, 2 removed
crates/cmp/sourcepoint 1 file, 42 lines added, 3 removed
docs/guide 2 files, 31 lines added, 14 removed
crates/trusted-server-modules/src 1 file, 25 lines added, 8 removed
CHANGELOG.md 1 file, 1 line added, 0 removed

Closes

Closes #1279

Spec

Spec: consent-prompt-phases

Test plan

CI on this pull request at 45df894 passed all 32 checks.

  • cargo test-fastly && cargo test-axum
  • cargo clippy-fastly && cargo clippy-axum
  • cargo fmt --all -- --check
  • JS tests: cd crates/trusted-server-js/lib && npx vitest run
  • JS format: cd crates/trusted-server-js/lib && npm run format
  • Docs format: cd docs && npm run format
  • WASM build: cargo build --package trusted-server-adapter-fastly --release --target wasm32-wasip1
  • Manual testing via fastly compute serve
  • Other: the full workflow set of this repository, run on this pull request at 45df894

Checklist

  • Changes follow AGENTS.md conventions
  • No unwrap() in production code (clippy denies unwrap_used in CI)
  • Uses log macros as AGENTS.md asks (not println!)
  • New code has tests
  • No secrets or credentials committed

Stacks on the sellers module (#1361).

What this pull request does

cmp.inmobi, cmp.didomi and cmp.sourcepoint may be named in a [[fetch]] or a [[serve]] entry. Each ran in the fetch phase alone, and an entry of the other phase naming one was refused when the settings loaded. Osano is not changed, because it changes no page and so has no middleware.

[[serve]]
media_type = "text/html"
middleware = ["cmp.inmobi"]

A serve middleware's head markup is written straight before the script bundle, after every fetch middleware's. So a prompt placed in [[serve]] comes after a vendor's tag a [[fetch]] entry writes, and [[serve]] middleware = ["cmp.inmobi"] with [[fetch]] middleware = ["tag.google-tag-manager"] puts Tag Manager ahead of the prompt. The InMobi guide now says to place the prompt in [[fetch]] wherever a fetch middleware writes a vendor's tag.

Why

What a consent prompt writes is the same for every reader, so the fetch phase stores it in the page every reader is served from, and that is the phase ts audit generate places it in. A publisher who wants the stored page without it, so that one stored copy can serve readers with and without the prompt, places it in the serve phase instead, where it is written on each reader's copy. Which phase is the publisher's choice, and the modules no longer make it for them.

What is checked

  • Each of the three modules declares both phases.
  • The InMobi module selected with no entry of either phase naming it is refused, as before, and an entry of the serve phase alone is accepted and registers the prompt for that phase.
  • The stock list keeps each prompt in the fetch phase, and its test allows a middleware that runs in several phases to be listed in one of them, checking that what is listed for a phase is registered for it and that everything registered is listed somewhere. The test asserted per-phase equality before and now asserts containment, so it no longer holds the order of the stock list to the registry's, nor that a middleware registered for a phase is listed for that phase.

How it was verified

Not exercised: no page has been served through an adapter with a prompt placed in the serve phase. Each module's registration is checked for both phases, and the stock list's registry for its listing.

References #1279.

jwrosewell and others added 11 commits October 10, 2026 10:03
`cmp.inmobi`, `cmp.didomi` and `cmp.sourcepoint` declare both phases, so an
entry of either phase may name them. What a prompt writes is the same for
every reader, so the fetch phase stores it in the page every reader is
served from, which is what a generated configuration places, and the serve
phase writes it on each reader's copy for a publisher who wants the stored
page without it. Selecting the InMobi module with no entry of either phase
naming it is still refused.

The stock list keeps each prompt's row in the fetch phase, which is the
phase `ts audit generate` writes, and its test allows a middleware that
runs in several phases to be listed in one of them, checking that what is
listed for a phase is registered for it and that everything registered is
listed somewhere.
The InMobi guide places the prompt by an entry of either phase and says
what each phase does with it, the configuration guide's middleware table
says the same for every consent management platform, and the changelog
records the change.
# Conflicts:
#	crates/cmp/inmobi/src/lib.rs
#	docs/guide/integrations/inmobi.md
A serve middleware's head markup is written after every fetch middleware's, so a prompt placed in the serve phase comes after a vendor's tag a fetch entry writes. The guide now says to place the prompt in the fetch phase wherever a fetch middleware writes a vendor's tag. The Sourcepoint page change's doc names both phases, as Didomi's does.
A serve middleware's head markup is written after every fetch
middleware's, so a prompt named in [[serve]] followed any vendor's tag
the [[fetch]] entry for the same page wrote into the head. The InMobi
module now refuses, when the service starts and in ts config validate,
a [[serve]] entry naming cmp.inmobi for a page whose [[fetch]] entry
names another middleware. The module cannot tell which middleware write
a vendor's tag, so it refuses any. Pages are compared by the entries
each phase gives them, so entries under paths that never meet are
accepted.
A prompt named in both a [[fetch]] and a [[serve]] entry for the same
page was written on it twice, loader included. The InMobi module now
refuses that when the service starts and in ts config validate, and
accepts each phase naming it for pages the other never reaches.

The refusal of a serve prompt after fetch middleware now says that the
serve phase writes after what the fetch phase writes ahead of the
script bundle, since fetch markup written after the bundle lands after
a serve prompt.
No test ran cmp.inmobi, cmp.didomi or cmp.sourcepoint from a [[serve]]
entry through the registry's check or a page. Each crate now builds the
registry with its recorded page's settings moved from [[fetch]] to
[[serve]] and checks that the reader receives the recorded page, which
a page with no other middleware gets from either phase.
The Sourcepoint page change became a struct that holds its template key
and is built with `PageChange::new`, so the test that reads its phases
builds it the same way, as the registration and the other tests do.
@jwrosewell
jwrosewell removed this pull request from stack #1408 October 18, 2026 20:15
@jwrosewell
jwrosewell added this pull request to stack #1418 October 19, 2026 12:18
@jwrosewell
jwrosewell removed this pull request from stack #1418 October 19, 2026 16:30
@jwrosewell
jwrosewell added this pull request to stack #1421 October 19, 2026 17:38
@jwrosewell
jwrosewell removed this pull request from stack #1421 October 19, 2026 18:29
@jwrosewell
jwrosewell added this pull request to stack #1422 October 19, 2026 19:20
@jwrosewell
jwrosewell marked this pull request as ready for review October 19, 2026 20:23

This branch has not been deployed

No deployments
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.

A consent prompt always goes into the stored page, so one stored copy cannot serve readers with and without it

2 participants