Repository navigation
Conversation
An integration that leaves core still bounds a request body and an upstream response, registers a backend, strips the headers an upstream must not set on a first-party response, matches a host against the allowed domains, marks a synthesized response private, reads a list setting and rewrites a bare host. Those were private to the crate because every caller lived in it. They are public, each with the reason on it. The platform test doubles are offered under the `test-utils` feature, where the adapters already take core's other fixtures, so a moved integration's tests keep the stub HTTP client and stores they were written against. The feature turns on the platform library's feature of the same name, because the doubles are built on that library's own.
The Lockr identity module is `identity.lockr`, and it now lives in `crates/identity/lockr`. The stock list offers it in the place its hooks ran before, and the settings that select and configure it do not change. Three of core's tests used Lockr as their example of a built-in module. The duplicate id test and the standalone script test use core's own modules, the JavaScript asset proxy and the creative browser module. The test that the documented settings template's module tables validate moves to the stock list crate, where each module's settings type is in reach, and the two template helpers it shares with core's own tests are offered from core's test support.
The Permutive audience module is `audience.permutive`, and it now lives in `crates/audience/permutive`. The stock list offers it in the place its hooks ran before, and the settings that select and configure it do not change.
The Sourcepoint consent management module is `cmp.sourcepoint`, and it now lives in `crates/cmp/sourcepoint`. The stock list offers it in the place its hooks ran before, and the settings that select and configure it do not change. Its tests build a registry with the crate's own builder, through `IntegrationRegistry::with_registrations`, which is offered under the `test-utils` feature for that. The test that a selected consent mirror's browser script loads with the page moves here from core, as Osano's half of it did.
The Didomi consent management module is `cmp.didomi`, and it now lives in `crates/cmp/didomi`. The stock list offers it in the place its hooks ran before, and the settings that select and configure it do not change. Core's test that a module's table survives the configuration blob round trip used Didomi's settings as its example. It uses an example module of its own, with a flag set away from its default.
The Google Tag Manager module is `tag.google-tag-manager`, and it now lives in `crates/tag/google-tag-manager`. The stock list offers it in the place its hooks ran before, and the settings that select and configure it do not change. One of its tests runs the HTML pipeline over the publisher page fixture core's own HTML tests use. Core's test support offers that page, so the half megabyte file stays in one place.
The Testlight example integration is `testing.testlight`, and it now lives in `crates/testing/testlight`. The stock list offers it in the place its hooks ran before, and the settings that select and configure it do not change. It reads the Edge Cookie a request carries through `edge_cookie::recognized_ec_id`, so that module is public. The test that serves a page through the HTML pipeline and checks Testlight's script is replaced moves with it. The registry test that lists the modules a deployment does supply names a probe builder of its own.
The Next.js module is `framework.nextjs`, and it now lives in `crates/framework/nextjs`, with its six source files and two fixtures. The stock list offers it in the place its hooks ran before, and the settings that select and configure it do not change. Five of core's page pipeline tests used Next.js as their example of an integration that rewrites script payloads in two passes, a script rewriter that leaves placeholders and a stream processor that fills them. They test core's part of that (the document state, the hold on an unresolved group, the auction seam, the template cache), so they stay in core and select a stand-in that does the same two passes with no vendor's format. The stand-in is compiled for core's tests only. The test of Next.js's own overflow handling moves to its crate, and the registry test that used Next.js as an integration with no browser script uses a probe. Two of Google Tag Manager's tests check that it leaves another module's script rewrite alone, with Next.js as that module, so its crate takes the Next.js crate as a test dependency. The stock list crate gains the test that every stock module refuses a setting it does not know, which core's own list can no longer show for a module that has left it.
The Google Publisher Tags module is `ad-tag.google`, and it now lives in `crates/ad-tag/google` with the bootstrap script it injects. The stock list offers it, and the settings that select and configure it do not change. Its diagnostics module, `ad-tag.google.diagnostics`, belongs to the same crate and stays in core until the page path can carry a module's own decision for a request. Five of core's tests used this module as their example of an integration that tags a page: the HTML processor's test of the attribute an injector asks for on the publisher bundle tag, the streaming test of the head arriving before the origin finishes, and the JavaScript asset proxy's three tests of its place ahead of an integration's own rewriter. They select a second stand-in compiled for core's tests only, which inserts a head script, asks for a bundle attribute and rewrites the address of its own script. The core test that a selected module with invalid settings fails startup uses an example module of its own. The browser tests and the initial render harness read the bootstrap script from its new place.
Two modules decide something about one request and act on it later, and the page path named both. It called the Google Publisher Tags diagnostics module to prepare the request and to finish the response, carried that module's decision to the HTML processor, which wrote its two scripts, and carried a flag for DataDome's tag beside it. Neither module can leave core while core does that for it. A request preparer or a request filter now leaves a value in the request's `IntegrationRequestState` under its integration id. The page path copies those values into the document's state before parsing starts, so the module's own head injector reads them, and hands them to the response finalizer a builder declares with `with_response_finalizer`. A head injector gets a second place to write, straight after the script bundle, which is where the diagnostics module loads its standalone script. The diagnostics module and DataDome both use these and core calls neither. A request that carries any value keeps to the origin path, so its document is never read from a shared template and never stored as one, and its HTML response is sent `private, no-store`. That was true of both modules before, each by its own route. It is now one rule that holds for any module, checked where the request is routed and again where the document is built. The registry runs the request preparers once for a request, so the page path asks it to prepare a request that reached it unprepared and names no module to do it. The token an auction publishes with its winning bids was made when the diagnostics module ran. A builder now declares with `with_auction_token` that its browser script reads the token, the auction plan records whether a selected module does, and the page path asks the orchestrator. Core's tests of all this use a third stand-in compiled for its tests only. It marks a document navigation that carries its query, writes a head script and its standalone module's tag for a marked request, and sets a cookie on the response. The template cache tests that set a diagnostics decision by hand, the template rendering tests and the auction token tests use it. The tests that drive either module itself still do, and hand the page path the request state where they handed it a decision or a flag.
The diagnostics module is `ad-tag.google.diagnostics`, the second module of the crate at `crates/ad-tag/google`, and it now lives there beside the Google Publisher Tags module. The stock list offers it last, where its hooks ran before, and the settings that select it do not change. It prepares the request, writes its two scripts and finishes the response through the hooks any module has, so core calls nothing of its own. Its tests went with it, and four were added for what it now does through those hooks: that only a decision which acts is left for the page path, that the finalizer acts only on such a decision, and that a document gets both scripts in order when diagnostics is active and neither when it is not. The test that the built-in list attached its request preparer is now a test that the stock list does. Core's tests that used the module as their example use core's stand-in: the HTML processor's test of what is written around the bundle, the registry's two tests of a standalone browser module, and the page path's tests of a query a module acts on, a warm template, an inactive ad stack and a standalone module's own path. The Fastly adapter's test that a late filter cannot make such a response public marks the response the way a module's finalizer does and names no module. One of core's template cache tests depended on this module without saying so. The module's preparer writes the Cookie header of every request out again as it strips its own cookie, dropping a field it cannot read, and the test expected a request with such a field to be stored. The stand-in now reserves a cookie and does the same, so the test still shows that the cache classifies a request as a preparer left it. A stock build behaves as before, because the stock list still runs the module's preparer for every request. The page path's two request classifiers the module uses, for a prefetch and for a crawler, are public. The changelog's list of moved modules had been garbled by the full stop in Next.js and reads correctly again.
A setting that holds a secret holds the name of its key in a pushed configuration and the secret once the settings load. Core named DataDome's two such settings in three places: the list of secret paths, the step that clears a reference nothing uses before the lookup, and the deploy check that a reference in use names a key. A module with a secret could not leave core while core listed its settings by name. A builder now declares them with `with_secret_settings`, each as a path inside the module's own table and a rule for when the table as written puts it to use. Core does the three things from the builders it is given, for any module. DataDome declares its server-side key as in use when protection is on, and its test bypass credential when the bypass is on as well, which are the rules core applied for it. The settings load takes the declarations from the builders it already validates against, so a module a deployment added has its secrets looked up as a stock one does. A leaf listed twice is looked up once, because the fixed list and the load can both name a module core itself offers, and a second lookup would read the secret as the name of a key. A module's table sits under the name its section selects it by, which may be the short name or the full one. The paths are listed and cleared at both, where the code this replaces looked only at `bot-protection.datadome`.
DataDome is the module `bot-protection.datadome`, and it now lives in `crates/bot-protection/datadome` with its Protection API filter and its protection scope. The stock list offers it in the place its hooks ran, and the settings that select and configure it do not change. It leaves its tag suppression for one request, and declares its two secret settings, through the hooks any module has. Its 59 tests went with it and eight were added from what core tested for it: that a suppressed document keeps and rewrites the publisher's own tag, that an ordinary one gets the client configuration, that the settings load looks up both secrets when both are in use and clears each one that is not, the deploy check of an invalid test bypass, and the refusal of a removed setting. The stock list crate checks that a registered list puts DataDome's two settings among the leaves a push treats as naming a key. Core's tests that used DataDome use core's stand-in, or the neutral marker they were about. The stand-in gains a request filter that leaves its mark for a header, so core still tests state a filter leaves reaching the document, which is the route DataDome takes. The scheduling test's origin document gains a head. It asserted that DataDome's tag was absent from a document that had no head for any tag to be written into, so it could not fail, and it now asserts that what a module left on the request is written. The tests of a personalized response say personalized where they said suppressed, and no longer set DataDome's marker beside the neutral one. Core's configuration tests use a module that exists only in the test. Core's settings no longer remove DataDome's two deprecated secret store selectors from its table as they are read. DataDome accepts and discards both, as it did, so a configuration that carries one still loads, and a pushed configuration now keeps them.
`ts audit` turns the integrations it finds on a page into the section that selects each one's module and the name written there. It took each module's name from `trusted_server_core::integrations`, so it stopped compiling when the first module left core, and no local check showed that because the `ts` tool cannot be built on the machine this branch was made on. The stock list crate now answers the question by integration id from the builders it already lists, with a test of every id the generator knows, and the generator asks it. The generator names no module of a vendor's. Prebid is the one exception, because it is registered through the auction plan and no builder carries its name, and it stays in core for now.
Prebid and APS each register a page integration that depends on the compiled auction plan, so the registry called both by name before it ran any builder, recorded Prebid's id by hand, and listed Prebid's module name among the ones a section may select. Deploy validation called Prebid's two checks by name for the same reason. Neither module can leave core while core does that. A builder now registers from the plan with `with_plan_registration` and checks its settings against the plan with `with_plan_validator`. A registration from the plan is made whether or not a section selects the module, because the function decides from the plan and the settings whether the module runs, and its hooks go ahead of every module a section selects, which is where Prebid's and APS's ran. Prebid is a builder like any other, selected in `[auction]`, and APS's implementation builder carries its registration. The registry and the deploy check name neither. The page path read the identifier a renderer picks its bid by with APS's type tag and APS's payload key. The code that builds a renderer descriptor now states which payload key holds that identifier, with `picking_bid_by`, and the page path asks the descriptor. A descriptor built without the statement has no such identifier whatever its payload holds, so a key of the same name under another renderer's tag is still not read, which a test pins. The statement is no part of what is sent to a page.
The demonstration ad server is the implementation `ad-server.mock`, and it now lives in `crates/ad-server/mock`. The stock list offers it, so `[ad-server] module = "mock"` selects it as before and its settings do not change. Its 22 tests went with it. Core's tests of the ad server seam selected the mock because it was the one ad server there was. They select a stand-in compiled for core's tests only, `ad-server.fixture`, which reads an endpoint and a timeout, refuses any other setting and answers every request with no bid. That covers the plan compiler's tests of how a name resolves within `[ad-server]` and how a table's settings are checked, the registry's refusal of an auction implementation named in a section, and three tests that built a mock provider only because they needed a provider. One orchestrator test ran APS through the mock ad server and checked that the winner kept its seat, its bidder name and its renderer. It is a test of the two together, so it is in the stock list crate, where both are on offer, and it drives the orchestrator through its public interface. The bid dimension helpers the mock ad server admits a bid with are public, so an auction implementation in a crate of its own applies the rules core's response reader applies.
The Amazon Publisher Services demand implementation is `auction.aps`, and it now lives in `crates/auction/aps`. The stock list offers it, so a `[demand.<name>]` table with `implementation = "auction.aps"` selects it as before and its settings do not change. Its builder registers the page support the APS renderer needs when the auction plan selects an APS source. Its 38 tests went with it, and 24 more came from core, where APS had been the example the engine was tested with: - three tests of the renderer descriptor APS writes, - eight that run an APS source through the orchestrator and the response reader, - four of the plan compiler and the page registration, - six of the request APS builds, with both golden requests carried byte for byte, - three of the deploy check of APS's own settings. Those tests run against core's engine from APS's crate, so core offers what they need under its `test-utils` feature. That is the helpers that build an auction context, a demand table and a plan, two drivers that build a request for a plan's first source and read a response as that source, and the backend double in the platform test doubles. Core's own tests of routing, the plan compiler, the request builder, the response format and the page's bid map use core's plain OpenRTB source, or a renderer descriptor of the test's own tagged `example`, in place of APS. `ERROR_TYPE_HTTP_STATUS` and `ignored_bidder_params_count` are public, so an implementation in a crate of its own reports a failed call and counts ignored bidder parameters the way core does.
`integrations/prebid.rs` held three things: the page integration, the request, response and bidder parameter override code the Prebid Server demand runs, and the provider and combined configuration kept for the test build. The shipped code falls into two parts with nothing shared between them, so the file is divided along that line and nothing it does changes. The page integration stays in `prebid.rs`. The override rules and their engine, the transport headers, the response reader and the upstream error text join the demand in `prebid_server.rs`, with the test-only provider that the tests compare the demand with. Neither file refers to the other. The combined test configuration is divided the same way, so each file keeps the fields its own code reads. The page's is `LegacyPrebidConfig`, the browser settings and the bidder list a plan would supply. The demand's keeps the name `LegacyPrebidServerConfig`. Of the file's 178 tests, 57 stay with the page integration and 120 go with the demand. One is not carried: `register_rejects_invalid_bid_param_override_rule` checked that the page registration built from the combined configuration refused a bad override rule, and that registration no longer compiles override rules. The provider's own test of the same refusal stays. `validate_config_for_startup` was test-only and nothing called it, so it is gone.
The Prebid page integration is the module `auction.prebid`, and it now lives in `crates/auction/prebid`. `[auction] modules = ["prebid"]` selects it as before and its settings do not change. The stock list offers it first, so what it registers from the auction plan still runs ahead of APS's page support and of every module a section selects. Its 57 tests went with it, and seven came from core, where they checked rules that are Prebid's own: the removed `server_url` setting is refused, the two deploy rules for the external bundle, deploy and runtime validation both reaching Prebid's table, a bidder owned by the browser and by the server being refused only while the auction is on, and the shim loading deferred with the bundle route registered. The two bidder tests ran through the configuration blob in core and call runtime validation with Prebid's builder here, which is the step of the blob load they test. Core's shared test settings selected Prebid, so every test of core and of every other module ran with Prebid in it. They now select no auction module, and Prebid's tests select Prebid themselves. Core's tests that used Prebid as the one deferred browser module there was, or as the integration whose settings reach a page template, run on a stand-in compiled for core's tests only, `testing.deferred-fixture`, which carries a deferred module and writes its two settings into `<head>`. The settings tests of a leftover `enabled` key and of a module named twice use the example module they already had. `AuctionPlan::browser_bidder_codes` and `AuctionPlan::with_enabled` are public, so a page module in a crate of its own reads the plan's bidder routes and a test switches a compiled plan off. The `ts` audit generator asks the stock list where Prebid is selected, as it does for every other module, and the `ts prebid server inspect` tests read Prebid's configuration type from Prebid's crate.
The Prebid Server demand implementation is `auction.prebid-server`, and it now lives in `crates/auction/prebid-server` with its request extensions, its bidder parameter overrides, its transport headers, its response reader and the provider kept for its tests. The stock list offers it, so a `[demand.<name>]` table with `implementation = "auction.prebid-server"` selects it as before and its settings do not change. Its 124 tests went with it. Sixteen more are in its crate, run through core's driver from outside core: - eight that core held for it, which are the body consent by source and forwarding mode, the debug query fragment, the ordered overrides with the stored request as fallback, each impression paired with its slot's parameters, the stored intent applied after overrides, empty parameters falling back to a stored request, the exact request golden, and two sources run through the orchestrator, - eight for the Prebid Server half of a test core keeps, which are the consent matrix, the language tag, a disabled empty candidate, the signed request golden, the unsigned request identity, the endpoint completed through the plan, the refusal of all-eligible routing and the default timeout. Both request goldens are carried byte for byte. Core's auction tests used Prebid Server as the implementation that departs from the plain one. They run on a stand-in compiled for core's tests only, `auction.fixture`, which serves stored requests, forbids all-eligible routing, takes bidder parameters, keeps the unsigned request identity and completes an endpoint that names a host alone. Twenty-nine tests of routing, the plan, the formats and the driver pass on it with the name alone changed, and six read the stand-in's extension or its endpoint where they read Prebid Server's. Their names, labels and messages say what they test. `ResponseAdmissionDiagnostics`, `build_bid_dimension_index`, `apply_notification_policy` and `ProviderSlotInput::allows_stored_fallback` are public, so an implementation in a crate of its own admits bids, applies the notification policy and reads a slot's stored request intent the way core does. Two test drivers are added under `test-utils`, `route_to_first_source` and `routed_transport`. Prebid's test of the browser injection compiles its plan with Prebid Server's builder, the `ts` tool reads the implementation's name from its crate, and the stock list states the two rules its order carries.
…list The adapter's test settings select Prebid and a Prebid Server demand source, as a deployment does. Two test helpers compiled the auction plan, and one of them built the registry, from core's built-in modules alone, which held while both lived in core. With Prebid and the Prebid Server demand in crates of their own the helpers failed, and the adapter's test binary stopped at the first of them. Both helpers now take the stock list's builders, which is what the adapter's own entry point does. The two commits before this one left the Fastly tests failing, and they pass again here (3,572 in the Fastly build).
The integration guide, the integrations overview, the configuration guides and the onboarding page still described an integration as code inside `trusted-server-core`, or as "still in core" with the name its crate would have. Every page integration now has a crate of its own, so they say that: an integration is a crate under `crates/<type>/<vendor>/` here or in a repository of the vendor's, and a module's name is that crate's path. The agent notes list the module crate folders in the workspace layout, and the onboarding table gains the rows for a module crate and for the stock list.
Core's error type had a `Prebid` variant that only Prebid raised. Prebid's page integration and the Prebid Server demand raise `Integration` under their own names now, as every other module does, and core's error type names no vendor's variant. A reader sees no difference. Both variants answer 502 with the generic message, and the detail is logged as "Integration error (prebid)" or "Integration error (auction.prebid-server)" where it was "Prebid error".
`models.rs` held `AdResponse` and `Callback`, the response of one ad server, kept with dead code allowed "for external ad-server compatibility". Nothing in the workspace reads either type, so the file goes, with the ten tests of how the two types deserialize. The `Gam` error variant named Google Ad Manager and nothing raised it, so it goes from the error type and from the tables that list every variant.
Core's OpenRTB model held `RequestExt`, `PrebidExt`, `ImpExt`, `PrebidImpExt` and `ImpStoredRequest`, the typed `ext.prebid` objects of a request and an impression. Core's driver writes an implementation's extensions as the objects the implementation hands it and uses none of these types. Only the provider that Prebid Server's crate keeps for its tests builds a request through them, so they sit beside it there, compiled for its tests alone.
The limit on the zone a browser names for a slot was `MAX_PREBID_ZONE_BYTES`, and the admitted request headers were described as approved for "Prebid transport forwarding". Routing admits both for every demand source, so the limit is `MAX_ZONE_BYTES` and the headers are approved for forwarding to a demand source.
Each module crate says who maintains it in `[package.metadata.maintainers]` of its manifest, the way Prebid.js requires a named maintainer of every adapter. Three crates did not: the seam probe, the stock list and, in part, Testlight, which named an owner and no status. They do now, and the demonstration ad server's entry, which read "No vendor has not yet adopted this crate", says that the crate is the project's own. A status is one of three. `vendor owned` is a crate its vendor has adopted, `seeking vendor owner` is one the Trusted Server maintainers hold for a vendor, and `project owned` is one that is the project's own. A test in the stock list crate reads the manifest of every crate under `crates/<type>/<vendor>/`, and its own, and fails for one with no declaration, no owner or a status that is not one of the three. The integration guide states the convention and its checklist asks for it.
The wasm clock lint reaches core's build script, which runs on the host.
Core's version_header module is removed, with its call from the finalize step of the Axum, Cloudflare and Spin adapters and from the Fastly adapter's health probe and its settings fallback, so no response carries x-ts-version unless a [[serve]] entry names the service_version middleware. That middleware now writes the git version the build compiled in from TRUSTED_SERVER__GIT_VERSION or local git as X-TS-Version, when the build knows one, and the version the adapter reports for the service as X-TS-Service-Version, when it reports one. HEADER_X_TS_FASTLY_VERSION, which nothing wrote, goes with the module. The health tests of the three adapters prove the probe carries no version, the middleware's own tests prove each header is written when its version is known and nothing when neither is, and the publisher tests prove both headers reach a buffered page and neither reaches a page no entry names the middleware for. The configuration guide, the getting started guide, the EdgeZero guide and the changelog say the same.
# Conflicts: # docs/guide/configuration.md
jwrosewell
marked this pull request as ready for review
October 14, 2026 12:35
This was referenced Oct 14, 2026
Collaborator
|
Moved into this repository as #1364, one branch of the stack, so the review continues there. This pull request is closed in its favor and its comments stay here for the record. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacks on making a file the origin does not have (#1309). The additions that follow the seven pull requests open here are listed in #777.
Compare
split/22-creatingwithsplit/23-prepareto see only this pull request's change.
What this pull request does
A middleware whose decision needs the request's services, or has to wait on the store or the network, answers
Middleware::preparewith a future. The request's handler runs it before the document is processed, as the middleware's module with the permissions it declares, and what it answers is kept under the middleware's name and phase forcreatein that phase to take.A preparation may answer a
Refusalinstead, with a status, an optionalRetry-Afterin whole seconds rounded up, and a message, and the request is answered with that status, no document is processed, and nothing is stored or served from the store.Why
createis synchronous, is given no services, and is asked inside the synchronous streaming paths that the Fastly adapter needs. A middleware that makes a well known file from what the store holds or a service answers, as robots.txt does, could not decide there, and could not fail closed when it had nothing to give, which for robots.txt matters because an empty file tells crawlers to crawl everything. The preparation runs where the handler is asynchronous on every adapter, and only its answer, aSendvalue, travels to the streaming paths, socreate, the plan and every streaming path are as they were.The rules
A middleware that prepares nothing is asked
createas before, and nothing of it changes.On a document fetched for the request that will not be stored, both phases are prepared, fetch then serve. On a document that may be stored, only the fetch phase is prepared, so the serve middleware of the reader it was fetched for find nothing prepared. On a copy read from the store, the serve phase alone. The preparations of a phase run in the order the entry names the middleware.
The first refusal ends the preparations of a document, and the response it makes is marked private and unstorable.
A fetch preparation of a document that may be stored is handed the request's services and nothing of the reader, neither its permissions, location nor Edge Cookie, because its answer is stored with the document.
What breaks
MiddlewareContext,HtmlProcessorConfigandReaderDocumenteach gain a required pub fieldprepared, so every struct literal of them outside this repository stops compiling.How it was verified
CI passed on
1c0c244f7on the fork. Not exercised: no preparation has run against a real store or network, because the fixtures answer from what they are called with. The preparation on a copy read from the store has no test, because the test services have no template cache.References #1281.