Repository navigation
Pluggable, competition-neutral modules with a technical permission model #777
Description
This issue tracks a stack of 55 pull requests that take every vendor integration out of core and add the interfaces that modules build on. The 55 rows are complete and are reviewed in order, bottom first, from the pull request that holds every design document (#1342) at row 1 to the module scripts in their crates (#1423) at row 55. Row 51, ts config migrate (#1409), rewrites a settings document written for main into the shape the stack reads, rows 52 to 54 make the request filters, the Edge Cookie and the asset routes, which ran on the Fastly adapter alone, run on the Axum, Cloudflare and Spin adapters too, and row 55 moves each vendor module's browser script and tests, and the Tinybird project, into the crate that supplies it.
Please make no further changes to main until the stack is processed, because each change to main has to be merged through all 55 rows, and since 13 October six versions of main have each been merged through every row, d2f0d8c from 13 October, fa9837f from 15 October, 5bc3886 from 16 October, a903777 from 17 October, and 7791904 and 6ef07ae from 19 October.
Review one row at a time in the order of the list below, starting with row 1. Row 1 is based on main and every other row on the branch of the row below it, so each pull request's Files changed tab shows that row's own change and nothing else. The tables after the list group the rows by kind of change and link each row's issue and design document.
Review order and CI
- Every design document of the stack (Add the integration seam design specs #1342). All 23 checks passed at a854526.
- Edge Cookie identity as a module, with the HMAC module built in (Add a pluggable Edge Cookie module seam with the built-in HMAC module #1343). All 29 checks passed at ec6bdab.
- Device and geo as selectable modules (Add device and geo module selection with the host-signal Edge Cookie module #1344). All 29 checks passed at 42449d9.
- The permission model (Add the permission model with the Privacy Taxonomy vocabulary #1345). All 30 checks passed at db8e1da.
- The client-set Edge Cookie value path (Add the client-set Edge Cookie value path #1346). All 30 checks passed at 5744b2e.
- The module documentation set (Add the module documentation set and finish the decomposition #1347). All 32 checks passed at 643de85.
- The seam opened and every vendor module out of core (Open the integration seam so a vendor module can live outside core #1348). All 32 checks passed at 493119a.
- One module context (Add one module context for every kind of module #1349). All 32 checks passed at 6a0561c.
- Inspection endpoints (Add inspection endpoints that show what a deployment runs and decides #1350). All 32 checks passed at 246863f.
- Page changes as middleware (Add page changes as middleware #1351). All 32 checks passed at 0157d13.
- One app-config blob for each host (Add one app-config blob for each host #1352). All 32 checks passed at 08c3339.
- Attestation (Add attestation of who operates a deployment and which build it runs #1353). All 32 checks passed at 3774c5d.
- Administration leaves the publisher's domain (Remove administration from the publisher's domain #1354). All 32 checks passed at 24a6c79.
- robots.txt from modules (Add robots.txt rules from modules #1355). All 32 checks passed at 3427bc6.
- Headers that describe the deployment stay off a publisher's pages (Keep headers that describe the deployment off a publisher's pages #1356). All 32 checks passed at 21b0bae.
- Auction telemetry is a module
[analytics]selects (Make auction telemetry a module that [analytics] selects #1357). All 32 checks passed at 25af766. - The Model Terms cookie name moves to the one implementation that reads it (Declare the Model Terms cookie name in the module that reads it #1358). All 32 checks passed at f702084.
- The InMobi Choice consent prompt as a module (Add the InMobi Choice consent prompt as a module #1359). All 32 checks passed at 5810af1.
- A middleware may run on a media type other than HTML (Let a middleware run on a media type other than HTML #1360). All 32 checks passed at 18d9ff9.
- The sellers a deployment adds to the publisher's ads.txt (Add the sellers a deployment appends to the publisher's ads.txt #1361). All 32 checks passed at d93c843.
- A consent prompt runs in the phase the publisher chooses (Let a consent prompt run in the phase the publisher chooses #1362). All 32 checks passed at 45df894.
- A file the origin does not have is made by the middleware named for its path (Make a file the origin does not have from the middleware named for its path #1363). All 32 checks passed at 0b7c6a6.
- A middleware may prepare with the request's services, and may refuse (Let a middleware prepare with the request's services and refuse a request #1364). All 32 checks passed at 06ca813.
- The loading cache core declares, and the module that supplies it (Add the loading cache core declares and the module that supplies it #1365). All 32 checks passed at e66b955.
- robots.txt as a middleware (Make robots.txt a middleware core supplies #1366). All 32 checks passed at 95a9634.
- A vendor's modules share one table (Add a table that a vendor's modules share #1367). All 32 checks passed at af0e3e8.
- A module names the browser's cookies and the shared store keys (Let a module name the browser's cookies and the shared store keys #1368). All 32 checks passed at c3f683b.
- A device module may score a person and describe the device (Let a device module score a person and describe the device #1369). All 32 checks passed at c8990bd.
- A format can name the viewport widths at which the page asks for it (Let a format name the viewport widths at which the page asks for it #1370). All 32 checks passed at d7ff53b.
- A module writes Google's tags on the page from the settings (Add a module that writes Google's tags on the page from the settings #1371). All 32 checks passed at cfdaec1.
- The loading cache module reads the released caching crate (Move the loading cache module to the released caching crate #1372). All 32 checks passed at 490a466.
- A module declares the caches it loads through (Let a module declare the caches it loads through #1373). All 32 checks passed at 4a1f03c.
- A geo module is called with the request, as a device module is (Call a geo module with the request #1374). All 29 checks passed at 79a0421.
- The publisher's assets held through the loading cache (Hold the publisher's assets in the loading cache #1375). All 29 checks passed at 2a449ea.
- A deployment may address a request by its path before its configuration is chosen (Address requests by path before choosing settings #1376). All 29 checks passed at c61a4f4.
- The publisher origin can be a folder of signed object storage (Allow a signed object storage folder as the origin #1377). All 29 checks passed at 5e22174.
- A folder origin's URLs in response headers reach the reader without the folder, and a serve middleware may set headers (Map folder origin URLs in headers, add a serve headers step #1378). All 29 checks passed at dbbc8b3.
- A middleware reads its settings from a table of its own (Read middleware settings from [middleware.<name>] #1379). All 29 checks passed at f2e82ce.
- A source module supplies the publisher's response (Fetch pages and assets through a source module #1380). All 29 checks passed at ade271c.
- The Next.js RSC rewriter leaves core for the Next.js crate (Move the Flight rewriter into the Next.js module #1381). All 29 checks passed at 61de267.
- The Prebid EID cookie leaves core for the Prebid crate (Read the Prebid EID cookie in the Prebid crate #1382). All 29 checks passed at e3ec48c.
- The consent string decoders leave core for the permission signal crates (Move consent decoders into their standards' crates #1383). All 29 checks passed at 6f7b3a1.
- No crate holds the list of modules on every adapter's behalf (List linked modules in each adapter #1384). All 26 checks passed at 008f79d.
- The 51Degrees client hints middleware, as a module of its own (Request 51Degrees client hints from one middleware #1386). All 26 checks passed at 6fcd32a.
- OpenRTB leaves core for the auction protocol crate (Move OpenRTB into an auction protocol module #1388). All 26 checks passed at b0f2346.
- The analytics module and the platforms' cache header names leave core (Move analytics and cache header names out of core #1389). All 26 checks passed at 252c068.
- Each platform's backend naming leaves core for its adapter (Name backends in each adapter #1390). All 26 checks passed at 3af8768.
- AWS request signing leaves core for a shared AWS crate (Move AWS request signing into a shared crate #1391). All 26 checks passed at ad2f25e.
- The Fastly names leave core for the Fastly adapter (Move Fastly names out of core #1399). All 27 checks passed at d1aa512.
- A publisher's cookies stay under its own path on a shared host (Scope each publisher's cookies to its own path #1400). All 27 checks passed at cdb0a2b.
ts config migraterewrites a settings document written formain(Add ts config migrate for settings written for main #1409). All 27 checks passed at a71edd1.- Request filters and the device module run on every adapter (Run request filters and the device module on all adapters #1410). All 27 checks passed at 18e8765.
- The Edge Cookie is created and written on every adapter (Create and write the Edge Cookie on every adapter #1411). All 27 checks passed at e28df81.
- Asset routes are served on every adapter (Serve asset routes on every adapter #1412). All 27 checks passed at 1dde36e.
- Each module's browser script and tests in the crate that supplies it (Move module browser scripts and Tinybird into their crates #1423). All 27 checks passed at 5be1147.
What the stack saves core
Every vendor integration in core leaves it. That is 39,035 lines in 24 files (36,394 without comments), 23.7 percent of core on main at 6ef07ae (24.4 percent without comments), and 25.7 percent with the administration routes and version headers that also go (26.3 percent without comments). That quarter of core, nine of its twelve vendor integrations with every line of their tests, goes to crates the vendors maintain. Rows 40, 41, 42, 45, 47, 48 and 49 take 7,285 more lines in 14 files out of core (6,693 without comments), being the code of the standards (OpenRTB and the consent strings), of the platforms (backend naming, AWS signing and the test that keeps the Fastly SDK out of core) and of Prebid and Next.js outside the integrations folder, so 30.1 percent of core leaves it in all (30.8 percent without comments).
Core as a whole then has 3,677 more lines without comments (2.5 percent more) and 10,413 more lines with them (6.3 percent more), while its production code without comments is 1,352 lines smaller (3.9 percent smaller). Core grows because the stack adds the interfaces every module needs (the module context, the middleware, the registry's seam), six features (attestation, the permission model, inspection, robots.txt, the loading and asset caches, request addressing) and the tests of each. Of the 60,035 lines the stack adds to what stays of main, 71 percent are tests. The comment lines grow by 43 percent because every interface is documented where it is declared.
The figures compare main at 6ef07ae with the stack at row 55 (#1423) at 5be1147, counting every text file under crates/trusted-server-core. What stays of main is its core without the 44 files the stack takes out of it. A comment line is one whose first characters are //, /* or * in Rust and JavaScript, or # in TOML. A test line is one of a Rust file's lines from its first #[cfg(test)] to its end, or any line of a file under tests, test_support, migration_guards or fixtures, so all of settings.rs, whose first line is a #[cfg(test)] import, counts as test lines. Both sides are counted the same way.
crates/trusted-server-core |
main at 6ef07ae |
Row 55 at 5be1147 | Change |
|---|---|---|---|
| Lines | 164,652 | 175,065 | 10,413 more (6.3 percent) |
| Lines without comments | 149,108 | 152,785 | 3,677 more (2.5 percent) |
| Comment lines | 15,544 | 22,280 | 6,736 more |
| Production lines | 43,851 | 46,008 | 2,157 more (4.9 percent) |
| Production lines without comments | 34,458 | 33,106 | 1,352 fewer (3.9 percent) |
| Test lines | 120,801 | 129,057 | 8,256 more (6.8 percent) |
| Test lines without comments | 114,650 | 119,679 | 5,029 more (4.4 percent) |
| Vendor integration code inside core | 39,035 lines in 24 files (36,394 without comments) | none | all of it out |
| Standards', platforms' and other vendor code that leaves core | 7,285 lines in 14 files (6,693 without comments) | none | all of it out |
These figures move as the pull requests do.
Of the 39,035 lines of vendor integration code inside core, 23,876 (61 percent) were last changed after 12 June 2026, 23,687 of them in commits by the repository's maintainers. That is git blame -w -M -C on the 24 files at a903777, which 7791904 and 6ef07ae leave unchanged.
How the 55 divide
Apart from row 1 (#1342), which holds every design, the rows fall into three groups. Line counts are each row's own change against the row below, without documentation.
- Core keeps the interfaces, and each vendor's features become modules (32 rows): rows 2 to 8, 10, 16, 17, 19, 22, 23, 25 to 28, 31 to 33, 38 to 43, 45 to 49 and 55. They build on Add pluggable Edge Cookie, device, and geo providers gated by a permission model #838 and implement the designs in Add design specs for the provider architecture and permission model epic #986. Each feature a vendor provides is a crate of its own at
crates/<feature>/<vendor>/, such ascrates/cmp/inmobi,crates/identity/lockrandcrates/loading-cache/51degrees. That is one vendor, one feature, one crate, rather than one crate for each vendor or one crate for every vendor. - Functionality the 51Degrees product needs (15 rows): rows 9, 11, 12, 14, 18, 20, 21, 24, 29, 30, 34 to 36, 44 and 51. Rows 11, 18, 20, 21, 30, 44 and 51 change 180 of their 18,324 lines of code inside core, and a deployment that does not select them is unaffected. The other eight add features any deployment can use.
- Fixes and wider platform support (7 rows): rows 13, 15, 37, 50 and 52 to 54.
The pull requests, by kind of change
Each row is a pull request here on a feature/ branch based on the branch of the row before it, and row 1 is based on main. Row 1's branch is numbered six because it was cut after the five branches it is read before. #1342 holds every design document of the stack, and the Spec column links each row's document there. The parentheses say where each row came from. Rows 1 and 2 were reviewed here on #1084 and #1043, and rows 3 to 7 were first raised here as #1044 to #1047 and #1094. James Rogers approved rows 8 to 37 on the fork's pull requests 3 to 32, and rows 38 to 50 come from the fork's draft pull requests 33 to 45. Row 37 was reworked after that approval to follow the mapping of origin URLs in response headers that main merged in #1238.
Refactors that take a vendor, standard or platform out of core
| Row | Change | Issue | Spec | Pull request |
|---|---|---|---|---|
| 7 | The seam opened, every vendor module out of core, the Fastly adapter as a library | #784, #1100 | integration-provider-seam | #1348 (first raised as #1094) |
| 16 | Auction telemetry is a module [analytics] selects |
#1274 | analytics-module | #1357 (approved on fork pull request 11) |
| 17 | The Model Terms cookie name moves to the one implementation that reads it | #1275 | cookie-constant | #1358 (approved on fork pull request 12) |
| 40 | The Next.js RSC rewriter leaves core for the Next.js crate | #1340 | rsc-flight-middleware | #1381 (from fork pull request 35) |
| 41 | The Prebid EID cookie leaves core for the Prebid crate | #1337 | identifier-modules | #1382 (from fork pull request 36) |
| 42 | The consent string decoders leave core for the permission signal crates | #1335 | consent-decoders | #1383 (from fork pull request 37) |
| 45 | OpenRTB leaves core for the auction protocol crate | #1336 | auction-protocol | #1388 (from fork pull request 40) |
| 46 | The analytics module and the platforms' cache header names leave core | Part of #1341 | last-names-in-core | #1389 (from fork pull request 41) |
| 47 | Each platform's backend naming leaves core for its adapter | #1339 | backend-naming | #1390 (from fork pull request 42) |
| 48 | AWS request signing leaves core for a shared AWS crate | #1338 | origin-signing | #1391 (from fork pull request 43) |
| 49 | The Fastly names leave core for the Fastly adapter | Part of this issue | fastly-names-in-core | #1399 (from fork pull request 44) |
New features
| Row | Change | Issue | Spec | Pull request |
|---|---|---|---|---|
| 4 | The permission model | #779 | permission-model | #1345 (first raised as #1045) |
| 5 | The client-set Edge Cookie value path | #778 | client-cycle-ec-resolve | #1346 (first raised as #1046) |
| 9 | Inspection endpoints | #1268 | inspection-endpoints | #1350 (approved on fork pull request 4) |
| 11 | One app-config blob for each host | #1270 | config-by-host, fastly-per-host-selector | #1352 (approved on fork pull request 6) |
| 14 | robots.txt from modules | #1272 | robots-txt-from-modules | #1355 (approved on fork pull request 9) |
| 20 | The sellers a deployment adds to the publisher's ads.txt | #1278 | ads-txt-sellers | #1361 (approved on fork pull request 15) |
| 21 | A consent prompt runs in the phase the publisher chooses | #1279 | consent-prompt-phases | #1362 (approved on fork pull request 16) |
| 24 | The loading cache core declares, and the module that supplies it | #1282 | loading-cache | #1365 (approved on fork pull request 19) |
| 29 | A format can name the viewport widths at which the page asks for it | #1287 | viewport-sizes | #1370 (approved on fork pull request 24) |
| 34 | The publisher's assets held through the loading cache | #1324 | asset-cache | #1375 (approved on fork pull request 29) |
| 35 | A deployment may address a request by its path before its configuration is chosen | #1328 | request-addressing | #1376 (approved on fork pull request 30) |
| 36 | The publisher origin can be a folder of signed object storage | #1329 | publisher-object-storage-origin | #1377 (approved on fork pull request 31) |
| 51 | ts config migrate rewrites a settings document written for main into the shape the stack reads |
Part of this issue | config-migrate | #1409 |
New modules
| Row | Change | Issue | Spec | Pull request |
|---|---|---|---|---|
| 18 | The InMobi Choice consent prompt as a module | #1276 | inmobi-choice-module | #1359 (approved on fork pull request 13) |
| 30 | A module writes Google's tags on the page from the settings | #1288 | google-tag-module | #1371 (approved on fork pull request 25) |
| 44 | The 51Degrees client hints middleware, as a module of its own | #1333 | 51degrees-client-hints | #1386 (from fork pull request 39) |
The consent prompt rules, with the audit of Didomi, Sourcepoint and Osano, are #1289 and follow the InMobi module.
Interfaces that modules build on
| Row | Change | Issue | Spec | Pull request |
|---|---|---|---|---|
| 2 | Edge Cookie identity as a module, with the HMAC module built in | #778 | pluggable-providers | #1343 (reviewed on #1043) |
| 3 | Device and geo as selectable modules | #780, #781 | pluggable-providers | #1344 (first raised as #1044) |
| 8 | One module context | #1267 | module-context | #1349 (approved on fork pull request 3) |
| 10 | Page changes as middleware | #1269 | page-middleware | #1351 (approved on fork pull request 5) |
| 19 | A middleware may run on a media type other than HTML | #1277 | middleware-media-types | #1360 (approved on fork pull request 14) |
| 22 | A file the origin does not have is made by the middleware named for its path | #1280 | middleware-creates-a-missing-file | #1363 (approved on fork pull request 17) |
| 23 | A middleware may prepare with the request's services, and may refuse | #1281 | middleware-prepare | #1364 (approved on fork pull request 18) |
| 26 | A vendor's modules share one table | #1284 | shared-vendor-tables | #1367 (approved on fork pull request 21) |
| 27 | A module names the browser's cookies and the shared store keys | #1285 | prefix-module | #1368 (approved on fork pull request 22) |
| 28 | A device module may score a person and describe the device | #1286 | device-signals | #1369 (approved on fork pull request 23) |
| 32 | A module declares the caches it loads through | #1320 | module-cache-declarations | #1373 (approved on fork pull request 27) |
| 33 | A geo module is called with the request, as a device module is | #1321 | geo-module-request | #1374 (approved on fork pull request 28) |
| 38 | A middleware reads its settings from a table of its own, [middleware.<name>] |
#1331 | middleware-settings | #1379 (from fork pull request 33) |
| 39 | A source module supplies the publisher's response, with the origin fetch as the default | #1332 | source-modules | #1380 (from fork pull request 34) |
Security
| Row | Change | Issue | Spec | Pull request |
|---|---|---|---|---|
| 12 | Attestation | #161 | attestation | #1353 (approved on fork pull request 7) |
| 13 | Administration leaves the publisher's domain | #1271 | administration-off-the-publisher-domain | #1354 (approved on fork pull request 8) |
| 15 | Headers that describe the deployment stay off a publisher's pages | #1273 | diagnostic-headers | #1356 (approved on fork pull request 10) |
| 50 | A publisher's cookies stay under its own path on a shared host | Follows #1328 | cookies-by-path | #1400 (from fork pull request 45) |
Other refactors
| Row | Change | Issue | Spec | Pull request |
|---|---|---|---|---|
| 25 | robots.txt as a middleware | #1283 | robots-txt-middleware | #1366 (approved on fork pull request 20) |
| 31 | The loading cache module reads the released caching crate | #1282 | loading-cache | #1372 (approved on fork pull request 26) |
| 43 | No crate holds the list of modules on every adapter's behalf | #1334 | no-modules-crate | #1384 (from fork pull request 38) |
| 55 | Each module's browser script and tests live in the crate that supplies it, and the Tinybird project in its crate | Part of this issue | module-scripts-in-crates | #1423 (from fork pull request 50) |
Fixes
| Row | Change | Issue | Spec | Pull request |
|---|---|---|---|---|
| 37 | A folder origin's URLs in response headers reach the reader without the folder, and a serve middleware may set headers | #1330 | origin-redirect | #1378 (approved on fork pull request 32, then reworked to follow #1238) |
Ported to every adapter
| Row | Change | Issue | Spec | Pull request |
|---|---|---|---|---|
| 52 | Request filters and the device module run on every adapter | Part of this issue | request-filters-on-every-adapter | #1410 (from fork pull request 47) |
| 53 | The Edge Cookie is created and written on every adapter | Part of this issue | edge-cookie-on-every-adapter | #1411 (from fork pull request 48) |
| 54 | Asset routes are served on every adapter | Part of this issue | asset-routes-on-every-adapter | #1412 (from fork pull request 49) |
Documentation and design
| Row | Change | Issue | Spec | Pull request |
|---|---|---|---|---|
| 1 | The seam design and every design document of the stack, on the sixth branch but read and merged first, carrying the URL host linter's allowlist for the modules the stack ships | This issue | integration-provider-seam | #1342 (reviewed on #1084, closed when the documents moved here) |
| 6 | The module documentation set | Part of #782 | integration-response-header-hook | #1347 (first raised as #1047) |
The rule the stack keeps
Core is the interfaces, the routing, the origin fetch, the caches and their tests, and nothing else. Every vendor leaves core, and a standard is a vendor as much as a product is, so OpenRTB, the consent strings and a signing scheme each live in a crate of their own and core offers the neutral interface each implements. Every crate outside core sits at crates/<type>/<vendor>/, names its maintainers in its manifest, and is held to the same contract as a module kept outside this repository. A vendor's crates may depend on one another where the functionality is not a crates.io package, and what a vendor, a platform or a standard offers to every crate goes in crates/shared/<vendor>/. What belongs to the host layer stays in the host layer, so a platform's limits and transport are the adapter's or EdgeZero's, not a crate's under shared.
With every pull request listed above merged, every vendor integration is out of core, and what core keeps is the contract, the seams and the permission model. What core still names is counted in the next section.
What is still named in core
Core depends on no CDN or hosting platform. #496 took the Fastly imports out of core, and row 49 (#1399) takes out of core every word that names Akamai, Axum, Cloudflare, Fastly, Fermyon, Spin or Viceroy, in code, comments, tests, documents and file names, and adds a check to the Run Format workflow that fails when one comes back.
The table counts, at the head of row 55 (#1423) at 5be1147, the lines under crates/trusted-server-core that name a vendor, a standard or a platform. Lines counts every line, comments, documents and tests included. Code lines counts the lines outside test code whose code, rather than a comment on the line, carries the name. A line that names two of these counts under both, and each group's figure counts a line once.
2,234 lines in 75 files name one of these names, and 432 of them are code lines, in 35 files. No line names a platform. On main at 6ef07ae, 9,471 lines in 114 files name one of these names, and 2,394 of them are code lines, in 79 files, and 769 lines in 50 files name a platform, 188 of them code lines. Three file paths still name one of these, ec/eids.rs, request_signing/jwks.rs and tdl.rs.
| Name | Spellings | Lines | Code lines | Files with code lines |
|---|---|---|---|---|
| Platforms | 0 | 0 | ||
| Fastly | fastly, viceroy, edgecompute |
0 | 0 | |
| Cloudflare | cloudflare, wrangler |
0 | 0 | |
| Spin | spin, fermyon |
0 | 0 | |
| Axum | axum |
0 | 0 | |
| Akamai | akamai |
0 | 0 | |
| AWS | aws, amazonaws, s3, sigv4, cloudfront, x amz |
0 | 0 | |
| Vercel | vercel |
0 | 0 | |
| Vendors | 937 | 81 | ||
google, googletag, googletagmanager, googlesyndication, googleapis, googleadservices, googletagservices, doubleclick, securepubads, gtag, gtm, gpt, adsense, gam |
316 | 39 | creative_opportunities.rs 29, publisher.rs 9, settings.rs 1 |
|
| Prebid and its targeting keys | prebid, pbjs, hb |
205 | 14 | publisher.rs 9, creative_opportunities.rs 5 |
| APS | aps, apstag, amazon, amzn |
94 | 4 | creative_opportunities.rs 4 |
| Next.js | nextjs, next js |
23 | 4 | settings.rs 4 |
| Tinybird | tinybird |
2 | 0 | |
| SharedID | sharedid, shared id, pubcid |
80 | 21 | ec/identifiers.rs 10, ec/finalize.rs 5, cookies/template_cache_policy.rs 2, publisher.rs 2, constants.rs 1, settings.rs 1 |
| Lockr | lockr |
49 | 0 | |
| Permutive | permutive |
30 | 0 | |
| DataDome | datadome |
9 | 0 | |
| Didomi | didomi |
18 | 0 | |
| Sourcepoint | sourcepoint |
7 | 0 | |
| Osano | osano |
4 | 0 | |
| InMobi | inmobi |
0 | 0 | |
| 51Degrees | 51degrees, fiftyone |
7 | 0 | |
| ID5 | id5 |
72 | 0 | |
| LiveRamp | liveramp |
31 | 0 | |
| Criteo | criteo |
1 | 0 | |
| UID2 and The Trade Desk | uid2, tradedesk, the trade desk |
0 | 0 | |
| LiveIntent | liveintent |
3 | 0 | |
| AppNexus and Xandr | appnexus, xandr |
16 | 0 | |
| Rubicon and Magnite | rubicon, magnite |
6 | 0 | |
| PubMatic | pubmatic |
10 | 0 | |
| OpenX | openx |
2 | 0 | |
| TripleLift | triplelift |
1 | 0 | |
| Kargo | kargo |
29 | 0 | |
| Standards and laws | 1,331 | 352 | ||
| OpenRTB | openrtb, open rtb, ortb |
141 | 26 | auction/formats.rs 10, auction/endpoints.rs 4, settings.rs 4, ec/eids.rs 3, ec/registry.rs 2, Cargo.toml 1, and 2 more files |
| Extended identifiers (EIDs) | eid, eids |
685 | 207 | auction/endpoints.rs 42, ec/finalize.rs 39, publisher.rs 25, ec/kv.rs 21, ec/identifiers.rs 20, ec/mod.rs 20, and 6 more files |
| IAB | iab |
18 | 3 | publisher.rs 3 |
| TCF | tcf, tcfapi, euconsent |
38 | 0 | |
| GPP | gpp |
15 | 0 | |
| US Privacy | usprivacy, us privacy, uspapi, usp |
17 | 0 | |
| GDPR, and DMA metro codes | gdpr, dma |
125 | 17 | ec/kv_types.rs 6, auction/telemetry.rs 4, consent/jurisdiction.rs 3, permissions.rs 2, consent/mod.rs 1, ec/mod.rs 1 |
| JWKS and JWK | jwks, jwk |
131 | 56 | request_signing/rotation.rs 18, request_signing/jwks.rs 12, request_signing/signing.rs 9, request_signing/endpoints.rs 6, request_signing/mod.rs 6, request_signing/discovery.rs 3, and 2 more files |
| JA4 | ja4, ja4h |
129 | 24 | ec/device.rs 15, ec/module.rs 3, attestation.rs 1, constants.rs 1, ec/kv_types.rs 1, evidence.rs 1, and 2 more files |
| Terms Document Locator | tdl, terms document locator |
52 | 19 | permission_signal/mod.rs 7, tdl.rs 6, permissions.rs 5, lib.rs 1 |
| Any of these | 2,234 | 432 |
A line names something when one of its spellings appears in the line as whole words. Words are split at every character that is not a letter or a digit, and where a lower case letter meets an upper case one, so FastlyStore, FASTLY_IS_STAGING and edgezero_adapter_spin each name a platform and spinner does not. A spelling of two words, such as us privacy, matches the two words side by side, as in us_privacy and UsPrivacy, and an upper case run that ends in a lower case s is one word, so EIDs is the word eids. Test code is every file under tests, benches or fixtures, every file whose name holds .test., every module file that only test code declares, and every Rust item compiled for tests alone, by #[cfg(test)] or #[cfg(any(test, feature = "test-utils"))], from its attribute to its end. A comment line holds a comment and no code, and every line of a Markdown file is a comment line. EdgeZero, the platform layer core is written against, is not one of the names, and the DMA counted here is the metro code in the Edge Cookie's stored geo record.
#1341 stays open for the JA4 fingerprint class, the JWKS key format and the platforms' store names, as its body says. The identifier modules design asks in its open question 5 whether a later series takes the SharedID cookie out of core. The other names need series that are not yet written.
Open questions
Each design document below has open questions for the reviewers, in a section of their own or at the end of section 2. The Items column counts the numbered items, 107 in all. Item 5 of the client-set value path design is answered and kept for the record.
Breaking changes, on purpose
The vendor integrations move out of core inside the stack and keep working, and a deployment's configuration changes with them. The previous auction keys are refused rather than read twice, a module changes no page until an entry places it, [[handlers]] and the administration routes go, and [tinybird] is refused, its settings now being [analytics.tinybird], which [analytics] module = "tinybird" selects. At this pre-production stage the breaks are deliberate and acceptable, the repository has no release and no tag, and ts config migrate (#1409) rewrites a document written for main into the new shape, listing what it changed and what is left for the operator. The pull requests of rows 8 to 13, 15, 16, 23 to 25, 27 and 32 to 49 each say what they break under the heading "What breaks". Rows 52 to 54 bring the Axum, Cloudflare and Spin adapters to what the Fastly adapter already did, and each of their pull requests lists what changes on those adapters. Row 53 changes two things on every adapter, Fastly included. A service with no ec_store now writes a created Edge Cookie, and a crate outside core that never sets device signals gets no created cookie.
The administration user and password go with them, in row 13 (#1354). HTTP Basic authentication is removed together with the [[handlers]] list that configured it, so no request is asked for a password, the handler_password secret is no longer read, and nothing is served beneath /_ts/admin apart from an attestation endpoint that a deployment's own settings put there, which the administration design asks about in its open question 3. A configuration that still carries [[handlers]] is refused with directions when it is validated and when the service starts, so that no deployment believes a path is protected when it is not. The cache purge and the signing key rotation that the shared password guarded move to ts cache purge and ts keys rotate, which ask the hosting platform with an operator's own token. A publisher who wants a password on a path of its own puts it at its origin or in the hosting platform.
The permission model
Trusted Server is technology that is neutral on policy. Core does not encode any jurisdiction's law or any single policy. A module declares the technical permissions its data use requires, named by the IAB Tech Lab Privacy Taxonomy's data uses as identifiers only, and core runs the module, or passes it a value whose use needs a permission, only when those permissions are held. How a permission becomes held is established outside core by the permission signal modules, from the country when known, from an interaction with the reader, or from data another source provides, and the deployer brings the policy that decides.
How the stack comes to this repository
The pull requests above come here as a stack of feature/<name> branches, one for each row and each based on the one before it, so that they are reviewed and merged here in order. Each pull request links the pull request it came from, so no comment or decision is lost. All 55 rows are here, so the stack arrives complete, with every module in a crate of its own.
The design document each row links records two or more review rounds in a "Review log" section at its foot, a deep first round and then rounds that check its fixes and new text, each started afresh.
Related
- Establish provider-scoped configuration pattern #683 (provider-scoped configuration pattern)
- Remove fastly from core crate #496 (remove fastly from core, the neutrality foundation)
- Settings without a consumer and consumers without settings #1403 (settings without a consumer, and consumers without settings), a child of this issue, whose design, settings-and-consumers, is in Add the integration seam design specs #1342 and has three open questions of its own
- Integration CI cannot install worker-build after the zip 2.x yank #1402 (the Integration Tests setup failed after crates.io yanked the
zip2.x releases), closed as fixed onmainby Point EdgeZero dependencies and links at the IABTechLab organization #1323, which the stack takes with its merge of 5bc3886
Activity
jwrosewell commentedon Jun 21, 2026
Provider wiring: dependency injection
Recording the provider-wiring approach here, as it is part of this rework rather than a separate issue. Delivered by the neutral-providers PR.
The pluggable providers are wired by dependency injection rather than handed a fixed evidence struct. A provider's constructor takes the services it needs as trait objects, and the adapter, acting as the composition root, supplies concrete instances per request. Core defines the service interfaces and the host-neutral defaults. The interfaces are
RequestInfo(the client IP, User-Agent, and headers) andHostSignals(the TLS JA4 and HTTP/2 fingerprints). Host-specific implementations such asFastlyHostSignalslive in their own crates and are injected by the adapter, so core never calls a host SDK and the default request path makes no host-specific call. A provider that needs a service the host cannot supply cannot be built, so the request stops rather than minting a degraded identifier.As implemented:
RequestInfoandHostSignalsservice traits, with an owned default (OwnedRequestInfo).RuntimeServicescarries the optional host-signal service, supplied by the adapter composition root.build_providerandbuild_device_providerinject the services each provider needs.Address second review: close permission-algebra, egress, durability, …
[-]Pluggable, competition-neutral providers with a technical permission model[/-][+]Pluggable, competition-neutral modules with a technical permission model[/+]22 remaining items