Skip to content

Move analytics and cache header names out of core - #1389

Open
jwrosewell wants to merge 53 commits into
feature/45-openrtbfrom
feature/46-last-names
Open

jwrosewell wants to merge 53 commits into
feature/45-openrtbfrom
feature/46-last-names

Conversation

@jwrosewell

@jwrosewell jwrosewell commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Changes

Area Change
crates/analytics/tinybird 3 files, 1292 lines added, 0 removed
crates/trusted-server-core/src 15 files, 665 lines added, 935 removed
docs/superpowers 1 file, 164 lines added, 0 removed
crates/trusted-server-adapter-cloudflare/src 1 file, 80 lines added, 6 removed
crates/trusted-server-adapter-fastly/src 3 files, 68 lines added, 640 removed
crates/trusted-server-adapter-axum/src 1 file, 65 lines added, 2 removed
crates/trusted-server-adapter-spin/src 1 file, 65 lines added, 2 removed
docs/guide 3 files, 33 lines added, 23 removed
Cargo.lock 1 file, 21 lines added, 1 removed
crates/analytics/README.md 1 file, 17 lines added, 0 removed
.cargo/config.toml 1 file, 6 lines added, 6 removed
CHANGELOG.md 1 file, 2 lines added, 0 removed
Cargo.toml 1 file, 2 lines added, 0 removed
crates/ad-tag/google 1 file, 2 lines added, 8 removed
crates/trusted-server-cli/src 1 file, 2 lines added, 0 removed
crates/trusted-server-adapter-axum/Cargo.toml 1 file, 1 line added, 0 removed
crates/trusted-server-adapter-cloudflare/Cargo.toml 1 file, 1 line added, 0 removed
crates/trusted-server-adapter-fastly/Cargo.toml 1 file, 1 line added, 1 removed
crates/trusted-server-adapter-fastly/README.md 1 file, 1 line added, 1 removed
crates/trusted-server-adapter-spin/Cargo.toml 1 file, 1 line added, 0 removed
crates/trusted-server-cli/Cargo.toml 1 file, 1 line added, 0 removed

Closes

Part of #1341, which stays open for the three names this pull request leaves in core.

Spec

Spec: last-names-in-core

Test plan

CI on this pull request at 252c068 passed all 26 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 252c068

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

On top of the OpenRTB series (#1388). Issue: #1341.

What this adds

What changes.

Two of the issue's five rows: the Tinybird telemetry module lives in the crate named for its vendor, and each adapter declares the headers its platform reads a cache policy from.

How it works.

  • The analytics module. crates/analytics/tinybird holds the settings, the checks and the sink that core's settings and the Fastly adapter held, selected by [analytics] as today. Core gains the analytics module kind on a registration, resolved by the registry, and the adapter asks the registry for the module it links. The token is declared as a secret setting through the builder, so the generic secret handling covers it. The vendor's previous top-level table is refused as any section nothing supplies is, with no direction naming the vendor in core.
  • The platforms' cache headers. Core's cache policy reads the shared cache headers every platform may read, and the adapter for each platform declares the name of its own through platform/cache_headers.rs once per process, as the adapters already declare their registered integrations. Core's cache policy, the response privacy rules, the proxy and the publisher read the declared list.

Why

Core is the interfaces, the routing, the origin fetch, the caches and their tests. Each name here was a place where a change by one vendor or one platform was a change in core.

What breaks

Three refusals of the analytics settings move from settings load to the registry's build and to ts config validate, because the module reads its table as every module does. A deployment's settings do not change.

The commits

  1. The analytics module lives in the crate named for its vendor.
  2. The adapter declares the headers its platform reads a cache policy from.
  3. A merge of the client hints middleware, after which each adapter and the ts command list the analytics module after it, where the removed modules crate listed it, and the registry keeps both kinds of module a registration may now declare.
  4. The lock lists the analytics crate after the merge.
  5. A merge of the OpenRTB series.
  6. The analytics selection reads the section's method directly.
  7. A module written with its type folder reads the table without it. The guide lets a selector name a module with or without its type folder, and the type sections' table check and a module's own table lookup now shorten a written full name first, which the analytics module's test of both spellings found.
  8. A merge of the series below, which carries the fixes made lower in the stack.
  9. The Tinybird crate's README and the telemetry guide name the adapters' module lists in place of the deleted modules crate.
  10. The Google Publisher Tags diagnostics test declares an example platform cache header, as an adapter would, and checks it is stripped with the two standard headers, in place of two platforms' own headers that no adapter declares in that crate's tests.
  11. A merge of the series below, which carries the page origin change.
  12. The last names design states which adapters strip which headers and send telemetry, and takes its round 2 corrections.
  13. Later merges of the series below carry Tech Lab main at 6ef07ae.

What is not in this pull request

The issue's other three rows: the platforms' store names and the removed store ids in the settings and the signing code, the JA4 fingerprint class in the Edge Cookie device classification, and the JWKS key format in request signing. The design page records where each goes.

Core read `[analytics]` itself, with one vendor's settings typed into
settings.rs, their checks beside them and the module that sends the rows
built by the Fastly adapter alone. Every other module type is a section
core reads generically and a crate that supplies the module, and analytics
now follows.

A registration declares the sink it supplies with `with_analytics_module`,
the registry resolves `[analytics] module` against the sinks on offer as it
resolves `[geo] module`, and `IntegrationRegistry::analytics_module` gives
the one selected, or none, so core's no-op sink stands. A section selecting
two names is refused.

The Tinybird module, its settings, its checks and its sink move to
crates/analytics/tinybird. It reads its table through `module_config`,
declares the token as a secret setting so the token is looked up and
checked as every module's secret is, and is offered by
trusted-server-modules, so every adapter that builds from that list links
it. The Fastly adapter asks the registry for the sink.

The settings a deployment writes do not change. A table missing its host
or token, an unknown key and a reserved setting turned on are refused when
the deployment starts and by `ts config validate` rather than as the
settings load. A `[tinybird]` table at the top level is refused as the
section of a module type nothing supplies, where it was refused with
directions that named the vendor in core.
Core wrote a shared cache policy in one of four headers it named itself,
two of them one platform's own, and stripped all four from every response
it made private. It now knows the two headers every platform may read,
`Surrogate-Control` from the Edge Architecture Specification and
`CDN-Cache-Control` from RFC 9213, and asks the running platform for the
rest.

An adapter declares its platform's headers with
`platform::declare_platform_cache_headers` as it builds its state, once per
process, and `cache_policy::edge_cache_header_names` lists the two core
reads and then the declared ones. That list is what core strips from a
response it makes private, skips among an operator's response headers on
an uncacheable response, and treats as a policy it does not read when it
decides whether an origin page may be kept. The header an adapter writes
the policy in is `EdgeCacheHeader::Platform(name)`, which replaces the
variant that named one platform.

The Fastly adapter declares `fastly-surrogate-control` and writes
`Surrogate-Control`. The Cloudflare adapter declares
`cloudflare-cdn-cache-control` and writes it. The Axum and Spin adapters
declare nothing. Responses carry the headers they carried.

Not yet built: this commit was formatted and checked for leftover names
but no cargo check, clippy or test has run on it.
The modules crate is gone, so the analytics module is listed by each
adapter and the ts command after the client hints middleware, and the hook
order tests name it there. The registry keeps both kinds of module a
registration may now declare, and the settings lose both the consent
validation and the analytics normalization to the crates that read them.
# Conflicts:
#	CHANGELOG.md
Clippy refuses the closure that only called it.
The guide lets a section's selector name a module with or without its
type folder, as analytics.tinybird or tinybird in [analytics], and its
table is keyed by the name without it. The type sections' table check and
a module's own table lookup now shorten a written full name first, and the
shipped module test gives a module's declared secrets a value so the
unknown key is what the module refuses.
The Tinybird crate's README and the telemetry guide still said the
deleted modules crate offers the module. Each adapter and the ts tool
list its builder among the modules they link.
Core strips the two standard cache headers and the ones the running
adapter declares, so the test declares an example platform header as an
adapter would and checks all three are stripped, in place of two
platforms' own headers that no adapter declares in this crate's tests.
…end telemetry

Review round 1 found that the design named a composition crate that no
longer exists, said nothing changes for an operator when each adapter now
strips a different set of cache headers, left out that only the Fastly
adapter sends auction telemetry, and did not list the core change that lets
a section name a module with its own type folder. The design now says each
of these, puts the narrowing of the stripped headers as an open question,
and gives the defaults, the removed interfaces, the rollout, the paths with
no test and the review log. The changelog and the telemetry guide no longer
repeat the same claims.
Review round 2 found that the full name of the analytics module,
analytics.tinybird, fails ts config validate and the load of a stored
configuration, because the deploy check and the load look for the module
table at the name the selector wrote, and that the selection test reaches
the full name through the settings load and the module registration only.
The design now says so plainly, lists the code fix under the open
question, adds a rollout note for a deployment that writes the full name,
and adds that core reads fastly-ssl as a scheme signal and removes it
before calling the origin. The design never named the closed specs pull
request, so no reference changed.
@jwrosewell
jwrosewell added this pull request to stack #1401 October 15, 2026 17:56
@jwrosewell
jwrosewell removed this pull request from stack #1401 October 15, 2026 20:51
… its type folder

With `[analytics] module = "analytics.tinybird"`, the deploy check looked
for the token at `[analytics.analytics.tinybird]` and refused an empty
reference, and the load of a stored configuration cleared the token from
`[analytics.tinybird]` as unused, so the module refused a missing token.
The short name `tinybird` worked through both.

The deploy check of a module's secret settings and
`module_secrets::clear_unused` now shorten a name written with the
section's own type folder, as `check_tables` and `Settings::module_config`
already do, so both use the table the module reads. A name under another
type's folder is unchanged.

Core tests a module of another type selected by its full name through the
deploy check, the clearing of secrets not in use and the load, and the
Tinybird crate tests its full name through the deploy check of its token
and the load of a stored configuration.
…ponse

With the platform header names out of core, each adapter declared only
the header its own platform reads. A Fastly service then kept
`cloudflare-cdn-cache-control` on a response core made private, a
Cloudflare service kept `fastly-surrogate-control`, and the Axum and
Spin adapters kept both, where core stripped all four headers on every
adapter before. A policy left in one of them could let a shared cache of
that platform in front of the service keep a personalized response.

Each adapter now declares both platform headers, so the list core strips
from a private response, removes before writing its own policy, skips
among operator headers on an uncacheable response and treats as a reason
not to keep an origin response holds the same four names as before on
every adapter. Core still names neither platform.

A test in each of the four adapters builds the state and checks that a
response core makes private loses both headers. The Fastly adapter's
header constant moves above the doc comment of
`build_state_with_registrations`, which it had split.
The design closes its open question. Every adapter declares both platform
cache headers and strips the four names core stripped before, so nothing
changes for an operator, and the deploy check and the load of a stored
configuration read a module selected by its full name at the table
without its type folder, so `analytics.tinybird` works as `tinybird`
does. Section 4 lists the new tests, the untested paths drop what is now
tested, and the revision record has a row for the fixes.

The changelog says each adapter strips the same four headers as before,
and that a module named in full in its own type's section reads its
table without the type folder.
…vate response"

This reverts commit 6a22766.

Each adapter named the other platform's cache header so that core would
strip it, which breaks the rule that an adapter names only its own
platform. Removing a header the origin sent is a fetch middleware's job
that a deployment chooses, and the cache headers a response is served
with follow the cache policy of the item as stored, so neither core nor
an adapter strips a platform's header by name. The change that follows
this one applies that.
Only a middleware changes an asset or a response, and the cache policy of
an item as stored is model. So removing a header an origin sent is a
fetch middleware's job that a deployment chooses, and the cache headers a
response is served with mirror the item's policy in the headers core
owns, being `Cache-Control`, the two standard headers and a platform
header core wrote its policy in on that response.

A new section 2.3 says what each use of the declared list becomes: core
removes from a private response, and skips among an operator's headers,
only the headers it owns; it removes the two standard headers before it
writes a policy; and the declaration of each adapter's own platform
header is read only to decide whether an origin response may be kept.
It says what a deployment configures, a fetch middleware none of which
exists yet, needing a core change that lets a fetch middleware remove a
header and one crate per platform under `crates/middleware/`, and it
lists every place in this row where core or an adapter still writes or
removes cache headers outside a middleware.
Core removed every header an adapter declared from a response it made
private, skipped them among an operator's headers and removed them before
writing its own policy, so removing a platform's header that an origin
sent was core's job and an adapter had to name headers for core to strip.
Removing what an origin sent is a fetch middleware's job that a
deployment chooses, and the cache headers a response carries follow the
policy of the item as stored, so core now writes or removes only the
headers it owns: `Cache-Control`, `Surrogate-Control`,
`CDN-Cache-Control` and, on one response, a platform header it wrote its
policy in.

`CachePolicy::apply_to_response` records that header on the response as
a `WrittenPlatformCacheHeader`, and `remove_owned_cache_headers` and
`is_owned_cache_header_name` read it, so the privacy steps clear the
policy core wrote there and nothing else. A private policy now leaves
nothing in the header it is written in. `EDGE_CACHE_HEADER_NAMES` lists
the two standard headers again, in place of `edge_cache_header_names()`.

An adapter still declares its own platform's header, and core reads the
declaration only to keep an origin response with a policy in that header
out of the stored copies. Each adapter holds the header it writes its
policy in as `EDGE_CACHE_HEADER`, and the Fastly adapter's header
constant moves out of the doc comment of `build_state_with_registrations`,
which it had split.

Core's tests cover a declared header left on private and cookie
responses, a policy written in a platform header cleared and not
restored by an operator header, and a private policy emptying its
header. Each adapter tests that it declares its own platform's header
alone, and that a response core makes private carries the private
policy, loses the policy core wrote and keeps the header the origin
sent.
…owns

Section 2.2 says each adapter declares only its own platform's header,
read only to decide whether an origin response may be kept, and what
changes for an operator: `fastly-surrogate-control` and
`cloudflare-cdn-cache-control` stay on a private response where an origin
or an operator wrote them, and only the declaring adapter keeps an origin
response with a policy in its header out of the stored copies. Section
2.3 drops a line 2.2 now carries, says which headers the keep decision
reads and adds the no-store helper to the places that still alter the
response. Section 3 names the fetch middleware and the move into a serve
middleware as later work, section 4 lists the new tests, and the
revision record corrects the row for the reverted change and adds one
for this change.

The changelog entry says the same, and the configuration and Didomi
guides say which cache headers are removed and which policies bypass the
template cache.
@jwrosewell
jwrosewell removed this pull request from stack #1408 October 18, 2026 20:15
@jwrosewell jwrosewell closed this Oct 18, 2026
@jwrosewell jwrosewell reopened this Oct 18, 2026
@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 closed this Oct 19, 2026
@jwrosewell jwrosewell reopened this Oct 19, 2026
@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 changed the title The analytics module and the platforms' cache headers leave core Move analytics and cache header names out of core Oct 19, 2026
@jwrosewell
jwrosewell marked this pull request as ready for review October 19, 2026 20:24

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.

1 participant