Skip to content

Move Fastly names out of core - #1399

Open
jwrosewell wants to merge 63 commits into
feature/48-aws-signingfrom
feature/49-host-features
Open

jwrosewell wants to merge 63 commits into
feature/48-aws-signingfrom
feature/49-host-features

Conversation

@jwrosewell

@jwrosewell jwrosewell commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Changes

Area Change
crates/trusted-server-core/src 38 files, 605 lines added, 662 removed
crates/trusted-server-adapter-fastly/src 9 files, 416 lines added, 59 removed
scripts/lint-core-platform-names.py 1 file, 250 lines added, 0 removed
docs/superpowers 1 file, 189 lines added, 0 removed
crates/trusted-server-adapter-fastly/build.rs 1 file, 76 lines added, 0 removed
crates/trusted-server-cli/src 1 file, 45 lines added, 5 removed
crates/trusted-server-cli/tests 1 file, 44 lines added, 41 removed
crates/bot-protection/datadome 3 files, 41 lines added, 43 removed
docs/guide 4 files, 22 lines added, 18 removed
crates/trusted-server-integration-tests/src 1 file, 21 lines added, 2 removed
.github/workflows 1 file, 13 lines added, 0 removed
crates/trusted-server-core/README.md 1 file, 8 lines added, 8 removed
CHANGELOG.md 1 file, 5 lines added, 0 removed
crates/trusted-server-core/Cargo.toml 1 file, 3 lines added, 3 removed
trusted-server.example.toml 1 file, 3 lines added, 2 removed
crates/audience/permutive 1 file, 2 lines added, 2 removed
crates/device/README.md 1 file, 2 lines added, 2 removed
crates/device/fastly 1 file, 2 lines added, 2 removed
crates/identity/lockr 1 file, 2 lines added, 2 removed
AGENTS.md 1 file, 1 line added, 0 removed
Cargo.lock 1 file, 1 line added, 1 removed
crates/trusted-server-adapter-fastly/Cargo.toml 1 file, 1 line added, 0 removed
scripts/README.md 1 file, 1 line added, 0 removed
crates/trusted-server-core/build.rs 1 file, 0 lines added, 70 removed

Closes

Part of #777, being its Fastly row under "What is still named in core".

Spec

Spec: fastly-names-in-core

Test plan

CI on this pull request at 0fc903b passed all 21 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 0fc903b

Checklist

  • Feature PRs: spec linked above, ending in a review log of two or more review rounds
  • 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 AWS signing series (#1391). Issue: #777, the Fastly row of "What is still named in core".

What changed after the 51Degrees PR review

Agreed at the review on 23 October, point 4. Attestation leaves the stack, with no attestation interface in core.

Fixes found in review

  • The design copy names the published commit its count is taken at.
  • The Viceroy config generator's test store joins the chunks a pointer names, as the Fastly config store does since this pull request, so the_runtime_loader_reads_the_generated_entries reads the whole settings. The test failed with "missing field data" when core read the pointer as the settings.

Kept current

What this adds

What changes.

Core names no hosting platform. The places core still named Fastly move to the Fastly adapter, and core keeps a neutral declaration or interface in the place of each. They are the configuration chunk format, the staging variable, the fastly-ssl, fastly-client-ip and x-debug-fastly-pop headers, the fastly device module selector, and the names of the asset proxy's two response conversions. Counted the way #777 counts, the shipped lines of core that name Fastly go from 43 to none.

[device]
module = "platform" # was "fastly"

[trusted_client_ip]
ip_header = "fastly-client-ip" # still valid, as a header ending -client-ip

How it works.

  • The config store returns whole documents. PlatformConfigStore::get returns an entry whole. The Fastly adapter's store (config_chunks.rs) follows a chunk pointer, reads each chunk and checks every length and hash, so core's settings load reads what any platform's store returns. A long document reads as before, and one whose chunks disagree with the pointer is refused as before.
  • The platform says whether it runs in staging. An adapter declares it with platform::declare_platform_staging, and a module asks platform::platform_is_staging. The Fastly adapter declares it from FASTLY_IS_STAGING, and the DataDome module's test bypass follows the declaration.
  • The platform names the request headers it sets. An adapter declares three lists. Its TLS headers are read for the scheme after the TLS status the adapter reports and the standard forwarding headers, and are removed from every request to an origin. Its other spoofable headers are stripped at entry with forwarded, x-forwarded-host and x-forwarded-proto. Its internal headers are never copied to a third party and are refused as trusted client IP headers. constants::is_internal_header answers for core's list and the declared one together.
  • Declared before serving. The Fastly adapter makes every declaration in app::declare_platform(), which run() calls before it serves and every state build also calls. Settings are validated as they load, so a declaration made only in the state build would come after the first validation.
  • The trusted client IP header. ip_header must start with x- or end with -client-ip, which is the shape of a platform's own client address header, because ts config validate checks a document where no adapter has declared anything. fastly-client-ip stays valid.
  • The device selector. [device] module = "platform" selects the platform's own device module, as [geo] module = "platform" selects the platform's geo lookup, and build_device_module takes a build_platform factory. The Fastly module keeps its id.
  • Names that were not Fastly's. platform_response_to_fastly and platform_response_to_fastly_asset are platform_response_to_buffered and platform_response_to_asset, because neither touches Fastly.

Why

Core is the interfaces, the routing, the origin fetch, the caches and their tests, and a hosting platform is a vendor to core. Each of these names was a place where a change by Fastly meant a change in core. Each name moves with the code that reads it, as the platforms' cache headers did in the last names series.

What breaks

  • A settings document that writes [device] module = "fastly" writes module = "platform". The old value is refused at startup as a device module no running module supplies.
  • For crates outside core, ENV_FASTLY_IS_STAGING and HEADER_X_DEBUG_FASTLY_POP leave core, SPOOFABLE_FORWARDED_HEADERS holds the three standard forwarding headers, build_device_module takes a build_platform factory, and a PlatformConfigStore returns each entry whole.
  • The Axum, Cloudflare and Spin adapters declare none of these headers. On those adapters a client's fastly-ssl is no longer removed from the request to the publisher's origin, and on Spin a client's fastly-ssl and fastly-client-ip are no longer stripped at entry. Trusted Server reads neither header there. A client's x-debug-fastly-pop is copied to a third party like any other x- header, and the DataDome test bypass no longer follows a FASTLY_IS_STAGING variable.

The commits

  1. A platform's config store returns a long document whole.
  2. The platform says whether it serves from its staging environment.
  3. The adapter names the request headers its platform sets.
  4. The platform's own device module is selected as platform.
  5. Proxy response conversions are named for what they produce.
  6. A written document is read back whole in the Fastly adapter's own test, because core reads a config store entry as the platform's store returns it.
  7. The design page's header names the pull request that carries it.
  8. Core's comments and doc comments say what a kind of adapter or platform does without naming one, in 32 source files and core's manifest.
  9. Core's tests name no hosting platform. The Surrogate-Control tests, a creative test, a registry test and a publisher test take neutral names and domains, and every test checks what it checked before.
  10. Core's README files show the one command that runs core's own tests and point at TESTING.md for the adapter aliases.
  11. The test that the identity graph's largest metadata fits a store's size limit moves to the Fastly adapter's store tests, because the limit is that store's rule.
  12. The test that keeps the Fastly SDK out of core moves from core to the Fastly adapter, whose build script lists core's sources for it.
  13. Three HTML processor tests take a reserved example domain as the request host, where they used a host under a platform's domain.
  14. A guard outside core, scripts/lint-core-platform-names.py, which the Run Format workflow runs, fails on any line or path under core that names a hosting platform, a tool named for one or a domain a platform serves deployments from.
  15. The Fastly names design says that core names no hosting platform, counts the names before and after the change, and describes the guard.
  16. The Fastly adapter's chunk check writes each SHA-256 in hex one byte at a time, because the sha2 release that Tech Lab main moved to (Upgrade to Rust 1.98.1 and align dependencies with EdgeZero #1123) returns a digest that does not format as hex, and the text it writes is the same as before.
  17. This row's copy of the Fastly names in core design takes its final text from Add the integration seam design specs #1342.

Each of the first five carries its section of the series' design page, docs/superpowers/specs/2026-10-15-fastly-names-in-core-design.md, and each of the first four its changelog entry. The design page takes its round 1 review and round 2 corrections in commits of its own (b319bf0 and 67d34c2), and merges of the series below carry Tech Lab main at 7aa5c99. 6fee073 marks HTTPS in the handler tests of the header rewrite merged in main (#1238) with x-forwarded-proto rather than a platform's header, so they pass and core names no platform.

What is not in this pull request

Fastly's name outside core. The Axum, Cloudflare and Spin adapters compare their behavior with the Fastly adapter's in comments, and two of their errors suggest the Fastly adapter for parallel bidder fan-out. The ts development proxy strips fastly-ssl. Core itself names no hosting platform, in code, comments or tests.

A Fastly config store entry holds at most 8,000 characters, so a longer
settings document is stored as chunks with a pointer at its key. Core
followed that pointer itself while loading settings, which put the Fastly
entry limit and pointer format in platform-neutral core.

The Fastly adapter's config store now follows a chunk pointer when it reads
a key, checking every length and hash as before, so every reader receives
the whole document, and core's settings load reads the entry as the
platform's config store returns it. The error messages are unchanged. The
reassembly tests move to the adapter, with two more for a chunk whose hash
disagrees and for other JSON at the key.

Two comments on core's store selector helpers that named the Fastly
adapter now describe the behavior without naming the platform.
Core held the name of Fastly's staging variable, FASTLY_IS_STAGING, so that
the Fastly adapter and the DataDome module could each read it, and DataDome
gated its test bypass on that one platform's variable.

Core now has a neutral declaration, declare_platform_staging and
platform_is_staging, which the adapter makes once before it serves, as it
declares its cache headers. The Fastly adapter declares it from its own
variable, whose name it now holds, and uses the same reading for the
service's identity and the x-ts-env header. DataDome asks core whether the
platform serves from staging, so its test bypass works on any platform that
declares it, and its tests declare the flag rather than setting the
variable.
Core read and stripped `fastly-ssl` and `fastly-client-ip`, and kept
`x-debug-fastly-pop` among its internal headers. Each now comes from the
adapter, the way the cache headers do.

- `platform/request_headers.rs` holds three lists an adapter declares. TLS
  headers are read for the scheme and never forwarded to an origin,
  spoofable headers are stripped at entry, and internal headers are never
  forwarded to a third party.
- `SPOOFABLE_FORWARDED_HEADERS` keeps the three standard names, and
  `sanitize_forwarded_headers` also strips the declared TLS and spoofable
  headers.
- `detect_request_scheme` checks the declared TLS headers as its fourth
  signal, and the origin request strips them.
- `is_internal_header` joins `INTERNAL_HEADERS` with the declared internal
  headers. The trusted client IP validation, Permutive and Lockr use it.
- The trusted client IP header may be an `x-` header or a platform's own
  header ending `-client-ip`, so `fastly-client-ip` stays valid without
  core naming it. The guide states the new rule.
- The Fastly adapter declares its three headers in `declare_platform`,
  which `run` calls before serving so that settings validation sees them,
  and which every state build also calls. Its entry strip names its own
  two headers directly.
- The `build_state_with_registrations` doc comment had become attached to
  the cache header constant and is back on the function.
`[device] module = "fastly"` selected the module the Fastly adapter
builds, so core's device selection and the registry both named Fastly.
The value is now `platform`, as it already is for `[geo] module`, and
`build_device_module` takes a `build_platform` factory. The Fastly module
keeps its own id, `fastly`.

A document that still writes `fastly` names a device module no running
module supplies, which the registry refuses with a configuration error,
so documents move to `platform` with this change. The guide, the example
settings and the device crates' docs say `platform`.
`platform_response_to_fastly` returns a buffered response and
`platform_response_to_fastly_asset` an asset response that keeps a
stream, and neither touches Fastly. They are now
`platform_response_to_buffered` and `platform_response_to_asset`, and the
error and doc text that called the result a Fastly response says what it
is.
The review found two blocking issues. A client-sent x-debug-fastly-pop now reaches Permutive and Lockr on the Axum, Cloudflare and Spin adapters, and the declared spoofable header list is read only by a function whose one production caller declares nothing. The spec now states both as the code has them today and leaves the code fixes as open questions.

It also adds the effects on the other adapters, the scheme change on Axum and Cloudflare, where each refusal happens, the order for changing the package and the device selector together, the Fastly name that stays, the untested paths and the corrected counts and test descriptions. The changelog entry gains the x-debug-fastly-pop change and the DataDome guide names the staging condition as the platform's declaration.
@jwrosewell
jwrosewell added this pull request to stack #1401 October 15, 2026 17:56
The second review found no blocking issues. The changelog now gives the strip of declared spoofable headers its precondition, that the adapter calls sanitize_forwarded_headers, and adds the scheme change on the Axum and Cloudflare adapters and the move of the x-debug-fastly-pop refusal from ts config validate to the service load on Fastly.

The spec names that move in its Fastly sentence, corrects when the Axum, Cloudflare and Spin adapters strip fastly-client-ip, which each does where trusted_client_ip names it as ip_header, and lists the module comment in request_headers.rs among the comments left for a later change.
From this branch core reads a config store entry as the platform's store
returns it, and the Fastly adapter follows a chunk pointer itself in
config_chunks::whole_entry. The config push test that read a chunked
document back through core's reader therefore moves its round trip to
config_chunks.rs, as the selector design says.

Test: a_written_document_is_read_back_whole reads a fixture of the
entries `ts config push --local --key a.publisher.example` wrote for a
document too long for one entry, chunks first and the pointer last, and
checks whole_entry gives back the envelope with the pushed publisher
domain. The CLI test keeps its checks of the layout, that every chunk is
written before the pointer and that the pointer names the chunks in the
order they were written.
@jwrosewell
jwrosewell added this pull request to stack #1418 October 19, 2026 12:18
The handler tests of the header rewrite merged in main (#1238) build their
request with fastly-ssl: 1 to mark it HTTPS. This change takes that header
out of core, so core reads the request as plain HTTP and three of those
tests receive http:// where they expect https://. The core platform name
check also fails on the header's name. The helper now sends
x-forwarded-proto: https, which core reads on every platform and which the
origin redirect tests already use.

python scripts/lint-core-platform-names.py passes, and the three tests,
transformed_page_headers_map_origin_urls_to_serving_host,
bodiless_redirect_location_maps_origin_url_to_serving_host and
same_scheme_redirect_to_current_url_stays_on_serving_host, pass.
@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 Fastly names leave core for the Fastly adapter Move Fastly names out of core Oct 19, 2026
@jwrosewell
jwrosewell marked this pull request as ready for review October 19, 2026 20:24
sha2 0.11, which the workspace takes from main (#1123), returns its
digest as a hybrid-array Array, and Array does not implement LowerHex,
so format!("{:x}", ...) no longer compiles. Main switched core's copy
of this function, which this branch moves into the adapter, to
hex::encode. The adapter does not depend on hex, so sha256_hex writes
each byte as two lowercase hex digits, the way backend.rs writes its
spec digest, which gives the same text as before.
The changelog's entry for this row no longer says what the attestation
prefixes accept or sends the reader to the attestation guide, because
both left the stack with attestation, as the design this row takes says.
…ns it

The Fastly config store returns a long entry whole, so core's settings
load reads the entry as the store returns it and no longer follows a
chunk pointer itself. The generator's test store still returned the
pointer at the blob's key, so the_runtime_loader_reads_the_generated_entries
failed with "missing field `data`" when core parsed the pointer as the
blob.

The test store now joins the chunks a pointer names, in order, as the
Fastly config store does and as the keyed push tests' store does.
@jwrosewell
jwrosewell removed this pull request from stack #1422 October 28, 2026 11:47
@jwrosewell
jwrosewell added this pull request to stack #1465 October 28, 2026 18:28

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