Add trusted-server-adapter-cloudflare crate (PR 17)#644
Conversation
…ator wasm32-unknown-unknown (Cloudflare Workers) does not support std::time::Instant — it panics at runtime. web_time::Instant is a zero-cost drop-in on native and JS-backed on WASM.
Implements the Cloudflare Workers adapter following the same pattern as trusted-server-adapter-axum: TrustedServerApp implements the Hooks trait, platform services use noop stubs on native (CI-compilable), and the #[event(fetch)] entry point delegates to edgezero_adapter_cloudflare::run_app. Also adds UnavailableHttpClient to trusted-server-core platform module, parallel to the existing UnavailableKvStore.
CI: test-cloudflare job checks native host compile, wasm32-unknown-unknown compile (with cloudflare feature), and runs host-target unit tests. CLAUDE.md: add cloudflare crate to workspace layout and build commands.
…un_app The rev (38198f9) of edgezero used in this workspace requires worker 0.7 (not 0.6) and run_app() takes a manifest_src: &str as first argument. Updated Cargo.toml and lib.rs accordingly.
- Implement CloudflareHttpClient (wasm32 only) using worker::Fetch for real
outbound proxy requests; strip content-encoding/transfer-encoding headers
since the Workers runtime auto-decompresses responses
- Add build.sh with cd-to-SCRIPT_DIR guard so worker-build always runs from
the correct crate root regardless of invocation directory
- Switch wrangler dev task to use --cwd from workspace root (same DX as Fastly)
- Add js-sys to workspace dependencies; reference it via { workspace = true }
- Fix #[ignore] messages on Cloudflare integration tests
- Replace std::time::{SystemTime,UNIX_EPOCH} with web_time in test code for
signing.rs and proxy.rs (consistency with production paths)
- Add NoopConfigStore/NoopSecretStore TODO comment tracking the gap
- Add extract_client_ip unit tests (parses cf-connecting-ip, absent, invalid)
- Remove empty crate_compiles_on_host_target test
- Add CloudflareHttpClient timeout doc noting Workers CPU-budget tradeoff
Replace direct worker::Env store construction with edgezero handles already injected by run_app, reducing #[cfg(target_arch = "wasm32")] blocks from 5 to 2. - ConfigStoreHandleAdapter: bridges ctx.config_store() to PlatformConfigStore — reuses the already-parsed TRUSTED_SERVER_CONFIG JSON handle rather than re-parsing it on every request - KvHandleAdapter: bridges ctx.kv_handle() to PlatformKvStore — reuses the env.kv() handle opened by run_app rather than opening a new one - CloudflareGeo: moved outside #[cfg]; reads cf-ipcountry and related headers via ctx.request().headers() which needs no platform import - CloudflareSecretStoreAdapter: kept under #[cfg] — env.secret() is synchronous at the JS level but PlatformSecretStore::get_bytes is sync while SecretHandle::get_bytes is async; direct env access is required - Add .dev.vars to .gitignore (wrangler convention for local secrets) - Add bytes workspace dep for KvStore impl
…ezero-pr17-cloudflare-adapter
- Implement CloudflareWorkers::spawn() to start wrangler dev; in CI uses wrangler.ci.toml (no build step, uses pre-built bundle); locally uses wrangler.toml (triggers build.sh rebuild) - Add wrangler.ci.toml: no [build] section so wrangler dev skips the worker-build step when the bundle is pre-built as a CI artifact - Add build-cloudflare input to setup-integration-test-env: adds wasm32-unknown-unknown target and runs build.sh with integration test env vars - In prepare-artifacts: enable build-cloudflare and upload build/ dir as artifact - In integration-tests: restore CF build dir, install wrangler, set CLOUDFLARE_WRANGLER_DIR, and remove --skip flags for cloudflare tests
- Add GeoInfo happy-path test: build_geo_lookup_returns_some_with_populated_country verifies country, city, continent, latitude, and longitude are correctly populated when Cloudflare headers are present - Simplify CloudflareSecretStoreAdapter::get_bytes: collapse brittle JsError string-matching guards into a single error arm with contextual message - Document sequential fan-out latency in CloudflareHttpClient: explain sum(DSP_i) vs max(DSP_i) implication and why true parallelism is blocked by the ?Send bound on PlatformHttpClient - Fix stale wrangler.toml comment: update to reflect ConfigStoreHandleAdapter wiring rather than the now-fallback NoopConfigStore - Extend CI triggers to feature branches: format.yml and test.yml now run fmt/clippy/tests on PRs targeting feature/** so stacking PRs are gated - Fix fmt violations caught during pre-review verification: platform.rs two-line Err wrapping and plan doc Prettier formatting
The adapter's tokio dev-dependency uses rt-multi-thread which is not supported on wasm32. It is already tested in the dedicated test-cloudflare job; exclude it from the workspace wasm test to avoid the compile error.
ChristianPavilonis
left a comment
There was a problem hiding this comment.
Summary
Adds the Cloudflare adapter, runtime wiring, and CI coverage. CI is green, but I found two correctness issues that should be addressed before merge, plus two smaller follow-ups.
Resolved conflicts: - .cargo/config.toml: extend test-fastly alias to also exclude trusted-server-adapter-cloudflare - .github/workflows/test.yml: use cargo test-fastly alias in CI (from PR16) and keep test-cloudflare job (from PR17) - CLAUDE.md: keep Cloudflare workspace entry and cargo test-axum alias; merge both adapters' commands - crates/trusted-server-core/src/proxy.rs: use #[tokio::test] + std::time pattern (consistent with PR16 tests) - Cargo.lock: regenerated to reflect merged workspace state
Brings in the default-members fix: restricts workspace default-members to `trusted-server-adapter-fastly` so Viceroy can locate the binary via `cargo run --bin` during `cargo test-fastly`.
aram356
left a comment
There was a problem hiding this comment.
Summary
Adds the Cloudflare Workers adapter, with web_time migration in core and a new CI job. The middleware-bypass and backend-naming concerns from the prior review are addressed and have regression tests, but the second prior 🔧 (sequential fan-out + missing per-request deadline) is mitigated only by documentation and a noisy log warning — the underlying behavior is unchanged. CI has three required gates failing.
Blocking
🔧 wrench
- CI is red on three required gates —
cargo fmt --all -- --check,format-docs, andintegration testsare failing on the latest run. The integration-tests failure is especially concerning because this PR is the one adding theCloudflareWorkersruntime to that matrix and the new env-var/wrangler wiring (integration-tests.yml:74-105). Either the new Cloudflare integration test broke, or it isn't actually running and another regression slipped in. Runcargo fmt --all,cd docs && npm run format, and reproduce the integration-tests failure locally withCLOUDFLARE_WRANGLER_DIRset; address whatever surfaces. Don't merge with red required checks. - Per-provider auction timeout is silently dropped on Cloudflare (
crates/trusted-server-adapter-cloudflare/src/platform.rs:289-315) — see inline. select()warning firesn-1times per auction → log noise on every multi-provider request (crates/trusted-server-adapter-cloudflare/src/platform.rs:329-337) — see inline.
Non-blocking
🤔 thinking
send_asyncalways materializes the body, then re-serializes it forselect(platform.rs:289-315) — see inline.Body::Streamsilently dropped in outbound request body (platform.rs:228-235) — see inline.
♻️ refactor
get_fallback/post_fallbacknear-duplicates (app.rs:307-363) — see inline.auth_middleware_rejects_with_401_when_credentials_requireddoesn't actually test 401 (tests/routes.rs:43-75) — see inline.
📌 out of scope
wrangler.tomlships a placeholder KV id (wrangler.toml:11) —id = "REPLACE_WITH_YOUR_KV_NAMESPACE_ID"is fine forwrangler dev --localbut will fail at deploy. Either call this out ingetting-started.mdnext to the deploy instructions, or use akv_namespaces[].preview_idso dev still works while the prod id stays empty/required. Follow-up issue is fine.
📝 note
worker::Env::clone()per request (platform.rs:444-451) — see inline.
Prior-review status
- ✅ Cloudflare routes skip middleware → fixed (new
src/middleware.rs+ regression tests intests/routes.rs:23-75). ⚠️ send_asyncserializes / no deadline → only documentation added; the underlying serialization and missing per-request timeout remain (see blocking findings above).- ✅ Backend names too coarse → fixed (
platform.rs:62-77includes scheme, host, port, timeout_ms, cert flag). - ✅ Stale ignore message → fixed (
integration.rs:139,147).
CI Status
- cargo fmt: FAIL
- format-docs: FAIL
- integration tests: FAIL
- cargo check (cloudflare native + wasm32-unknown-unknown): PASS
- cargo test: PASS
- cargo test (axum native): PASS
- vitest: PASS
- format-typescript: PASS
- browser integration tests: PASS
aram356
left a comment
There was a problem hiding this comment.
Summary
Adds the trusted-server-adapter-cloudflare crate (edgezero handle adapters for Config/KV/secrets, CloudflareHttpClient, geo extraction from cf-* headers), an integration test environment, and a web_time migration in core. Crate scaffolding and middleware wiring are sound after the prior two review rounds, but CI is red on three jobs (one introduced by this PR) and several correctness/quality concerns from the earlier reviews remain open.
Note: the PR base is feature/edgezero-pr16-axum-dev-server, not main. Two of the four CI failures listed below are inherited from the base — but they still block merge to main.
Blocking
🔧 wrench
cargo fmtfails oncrates/integration-tests/tests/environments/cloudflare.rs— introduced by this PR. Theintegration-testscrate is in the workspaceexcludelist, socargo fmt --allfrom the repo root passes; the CI rustfmt action checks every Rust file. See inline comment.use worker::*;violatesCLAUDE.md—crates/trusted-server-adapter-cloudflare/src/lib.rs:7. See inline.- Integration tests fail with orphaned
workerdprocesses —crates/integration-tests/tests/environments/cloudflare.rs:104-109only kills thewranglerparent;workerdgrandchildren leak. Likely root cause of thetest_all_combinations/test_nextjs_axumfailures observed in CI. See inline. - Per-provider auction timeout silently dropped on Cloudflare (still open from prior review) —
platform.rs:277. See inline. select()warning logsn-1times per multi-provider auction (still open from prior review) —platform.rs:337. See inline.- CI:
clippy::type_complexityfailure —crates/trusted-server-adapter-axum/src/platform.rs:194. Inherited from PR-16 base; cannot merge tomainuntil fixed in the base chain. Suggested:type ResponseTriplet = (u16, Vec<(String, Vec<u8>)>, Vec<u8>);. - CI:
format-docsfailure —docs/guide/architecture.mdanddocs/guide/getting-started.md. Inherited from PR-15 (6d1184d6). Same merge-to-main concern.
Non-blocking
🤔 thinking
send_asynceagerly awaits → multi-provider auctions degrade tosum(latencies)— see inline atplatform.rs:315.Body::Streamsilently dropped on outbound — see inline atplatform.rs:235.- Top-level
worker dispatch error: {e}may leak internals to clients — see inline atlib.rs:20. - Eager full-response buffering with no cap — see inline at
app.rs:96. - Confirm
worker::Error::Displaydoes not leak the secret value — see inline atplatform.rs:391. - Hardcoded port 8787 conflicts with local
wrangler dev— see inline atcloudflare.rs:23.
♻️ refactor
get_fallbackandpost_fallbackare near-duplicates — see inline atapp.rs:363.auth_middleware_rejects_with_401_when_credentials_requireddoesn't actually test 401 — see inline attests/routes.rs:75.
📝 note
- Stale
#[ignore]reason on Cloudflare integration tests — see inline atintegration.rs:139. - The PR base also has red CI (
format-docs,clippy::type_complexity). Recommend rebasing on the merged base before re-requesting review so CI status reflects only this PR's deltas.
⛏ nitpick
Method::from(... .to_ascii_uppercase())— see inline atplatform.rs:216.wrangler.tomlshipsid = "REPLACE_WITH_YOUR_KV_NAMESPACE_ID"—wrangler devrefuses to start until edited. Worth a one-line note inCLAUDE.mdor atopbuild.sh.- The
cloudflarefeature in this crate'sCargo.tomlis effectively redundant —[target.'cfg(target_arch = "wasm32")'.dependencies]already pullsworkerand the adapter feature unconditionally on wasm32, so--features cloudflareonly matters forcargo checkon native. The doc comment names the use case but a one-line "why it isn't default" note would help future readers.
CI Status
- cargo fmt: FAIL (introduced by this PR —
crates/integration-tests/tests/environments/cloudflare.rs) - clippy: FAIL (inherited from base — axum
type_complexity) - rust tests (fastly, axum, cloudflare): PASS
- vitest: PASS
- format-docs: FAIL (inherited from base)
- format-typescript: PASS
- integration tests: FAIL (
test_all_combinations,test_nextjs_axum— likely the workerd cleanup issue) - browser integration tests: PASS
- cargo check (cloudflare native + wasm32-unknown-unknown): PASS
Blocking: - Fix cargo fmt failure in integration-tests cloudflare.rs (long import, struct init) - Replace `use worker::*` with explicit imports in adapter lib.rs - Return generic 500 body from top-level dispatch error; log detail server-side - Fix workerd process-group leak: spawn wrangler as group leader, killpg on drop - Use find_available_port() for wrangler dev instead of hardcoded 8787 - Reject multi-provider fan-out in select() with PlatformError::HttpClient instead of a noisy warn; per-provider timeout is now enforced by failing loudly rather than silently degrading to sum(latencies) - Fix clippy::type_complexity in axum platform.rs with ResponseTriplet alias - Fix docs prettier formatting Non-blocking: - Return Err from execute() on Body::Stream outbound instead of silent empty body - Assert unreachable! on Body::Stream in send_async (execute always returns Once) - Extract shared dispatch() helper from get_fallback/post_fallback duplicates - Rename auth test to auth_middleware_runs_in_chain_for_protected_routes - Update stale #[ignore] reasons for Cloudflare integration tests
- Merge latest PR16 base (bff1adc, 314575b): removes unused backend.rs and fixes .gitignore comment - Use SpawnedRequestResult type alias from PR16 (includes Result wrapper) - Add check-cloudflare alias for wasm32-unknown-unknown target check - Add clippy-cloudflare alias to complete the per-adapter clippy pattern
…ezero-pr17-cloudflare-adapter
aram356
left a comment
There was a problem hiding this comment.
Summary
Round 4 review. The three round-3 🔧 blockers (workerd cleanup, cargo fmt, wildcard import) are resolved and CI is fully green on 15206b6a (10/10 jobs). The fail-loud multi-provider design replaces the silent degradation cleanly. Two new blocking items: the critical multi-provider rejection has no test coverage despite being #[cfg]-gateable into a testable helper, and the deferred Secret::to_string() audit needs either inline confirmation or a tracking issue before merge. Everything else is non-blocking — cleanup, defense-in-depth, and follow-ups.
Blocking
🔧 wrench
- Multi-provider
select()rejection has zero test coverage —platform.rs:327. Extract the length check as a target-agnostic free function and add 3-4 unit tests. See inline.
❓ question
- Confirm
worker::Secret::to_string()returns only the raw value —platform.rs:384. Either inline-confirm against worker 0.7 source or open a tracking issue and link it. See inline.
Non-blocking
🤔 thinking
from_utf8_lossycorrupts non-ASCII outbound header values —platform.rs:215.worker::Envcloned per request for secret adapter —platform.rs:444.compatibility_date = "2024-09-23"is 18+ months old —cloudflare.toml:3.wrangler.tomlplaceholder KV ID needs a heads-up note —wrangler.toml:11.
♻️ refactor
- 13 near-identical per-route handler closures —
app.rs:178-303. ~150 LOC reducible via amacro_rules!or generic helper. Same pattern in the Axum adapter — candidate for a shared core helper. apply_finalize_headerssilently skips invalid operator-config headers —middleware.rs:117. CLAUDE.md says invalid enabled config should surface as startup/config errors. Same issue in Axum.
🌱 seedling
CloudflareGeomissingregion—platform.rs:500. CF Enterprise injectscf-region/cf-region-code.cloudflarefeature is wasm32-only-effective but doesn't say so —Cargo.toml:18. Latent broken combo forcargo check --features cloudflareon native.
⛏ nitpick
Method::from(...uppercase())still deferred from prior review —platform.rs:210.to_ascii_lowercase()allocation per response header —platform.rs:251. Useeq_ignore_ascii_case.- Set Content-Length but don't strip Transfer-Encoding —
app.rs:90. Defense-in-depth. cargo installruns unconditionally inbuild.sh—build.sh:20. Gate withcommand -v.
CI Status
- cargo fmt: PASS
- cargo test (fastly): PASS
- cargo test (axum native): PASS
- cargo check (cloudflare native + wasm32-unknown-unknown): PASS
- vitest: PASS
- format-docs: PASS
- format-typescript: PASS
- integration tests: PASS
- browser integration tests: PASS
- prepare integration artifacts: PASS
Open from prior round (acknowledged)
- 🤔 Eager full-response buffering with no cap (
app.rs:88) — deferred to follow-up issue. - ❓
Secret::to_string()audit — now blocking (see above).
Blocking: - Extract reject_multi_provider_fanout(len) as a target-agnostic free function gated #[cfg(any(target_arch = \"wasm32\", test))]; add 4 unit tests covering len=0 (pass), len=1 (pass), len=2 (reject), len=5 (reject with count in msg) - Inline-confirm Secret::to_string() returns the raw JsValue string with no wrapping — verified against worker-rs src/env.rs Display impl Non-blocking: - from_utf8_lossy -> std::str::from_utf8 with a hard error on non-UTF-8 outbound header values; lossy conversion silently mutated bytes - to_ascii_lowercase() -> eq_ignore_ascii_case() for content-encoding / transfer-encoding header checks — no allocation, same semantics - Remove to_ascii_uppercase() on Method::from; worker 0.7 uppercases internally - Strip Transfer-Encoding before setting Content-Length in buffered publisher response (defense-in-depth; Workers likely strips it anyway) - build.sh: gate cargo install with command -v to skip reinstall when warm - wrangler.toml: add comment above placeholder KV ID explaining local vs remote - wrangler.toml: add comment explaining compatibility_date and when to bump it
The prior commit used Method::from(method.as_str()) based on reviewer note that From<&str> is case-insensitive, but From<&str> is not implemented at all in worker 0.7 — only From<String>. http::Method::to_string() already returns uppercase so the to_ascii_uppercase() allocation is removed without changing semantics.
Enables the cloudflare feature on a native target pulls worker which requires wasm-bindgen and produces cryptic linker errors. The guard catches it immediately with a clear message pointing to the correct --target wasm32-unknown-unknown flag.
Extract make_handler<F, Fut>(state, f) -> impl Fn(RequestContext) -> BoxedHandlerFuture that owns the build_per_request_services + ctx.into_request() boilerplate. The routing table now reads as a flat list of (METHOD, PATH, handler-expr) triples instead of 10 named handler variables each spanning 7-9 lines. ~120 lines removed.
aram356
left a comment
There was a problem hiding this comment.
Summary
Round-5 re-review of the Cloudflare Workers adapter. Every blocker from the prior CHANGES_REQUESTED review is resolved and all CI gates are green, including the integration tests job this PR adds the Cloudflare runtime to. No correctness, security, WASM, or convention issues remain in the changed code. Approving; the findings below are non-blocking and left to the author's discretion.
Note: the base is feature/edgezero-pr16-axum-dev-server, not main — approving this greenlights merging into that feature branch; the full edgezero chain still has to land to reach production.
Non-blocking
🤔 thinking
- Multi-provider rejection fires after the budget is already spent —
platform.rs:547-567(see inline). The supported single-provider path is unaffected. - Geo lat/long default to 0.0 (Null Island) when absent —
platform.rs:520-529(see inline).
♻️ refactor
- Duplicate outbound request headers silently collapsed (
headers.setvsappend) —platform.rs:213-223(see inline).
📝 note
unreachable!panic insend_async—platform.rs:303-305(see inline).- Stacked base / stale
compatibility_date— base is a feature branch;compatibility_date = "2024-09-23"is ~20 months old (the comment already invites a bump).
⛏ nitpick
- Test code uses
unwrap()instead ofexpect("should …")—platform.rstest module (see inline).
📌 out of scope
wrangler.tomlplaceholder KV id —wrangler.toml:13(see inline).
Prior-review status
- ✅ CI red (fmt / format-docs / integration) → all green
- ✅ Per-provider timeout silently dropped → now rejects loudly (with the timing caveat above)
- ✅
select()warning loop → removed - ✅
use worker::*→ explicit imports - ✅
workerdprocess leak →killpg(pgid, SIGTERM)on Drop - ✅ Auth test didn't assert behavior → renamed + honest doc
- ✅
get/post_fallbackduplication →dispatch()helper - ✅
Body::Streamsilently dropped → now returnsErr
The web_time migration is correctly scoped: a no-op std re-export on wasm32-wasip1, so Fastly's production path is unchanged.
CI Status
- cargo fmt: PASS
- clippy (cloudflare native + wasm32-unknown-unknown): PASS
- cargo test: PASS
- cargo test (axum native): PASS
- vitest: PASS
- format-typescript: PASS
- format-docs: PASS
- integration tests: PASS
- browser integration tests: PASS
ChristianPavilonis
left a comment
There was a problem hiding this comment.
Automated review by Yesman.
I found two blocking correctness issues in the new Cloudflare adapter: compressed origin responses can be returned with a stale Content-Length after decoding, and the unsupported multi-provider auction path is rejected only after all provider requests have already been sent and awaited. Please address these before merging.
Conflict resolutions: - .github/workflows/test.yml: keep both PR16's Fastly WASM release build verification step and PR17's test-cloudflare job - crates/integration-tests/Cargo.toml: union of libc (PR17) and urlencoding (PR16) dev-dependencies; lock file regenerated - crates/trusted-server-core/src/consent/mod.rs: keep PR17's web_time clock (wasm32-unknown-unknown support) plus PR16's AtomicBool import - crates/js/lib/package-lock.json: regenerated from merged package.json Semantic fixes for PR16 API changes in the Cloudflare adapter: - handle_proxy now takes ProxyDispatchInput - handle_auction gained kv, partner registry, and EC context parameters - GeoInfo gained the asn field - PlatformSelectResult gained failed_backend_name - resolve_publisher_response delegates to the shared buffer_publisher_response so the max_buffered_body_bytes cap applies on Workers too - Cloudflare middleware tests build explicit test settings instead of loading the baked placeholder secrets that get_settings() rejects - clippy-cloudflare alias drops --all-features, which always tripped the cloudflare feature's non-wasm32 compile_error! guard
CI:
- Install the wasm32-wasip1 target in the test-axum job — the
'Verify Fastly WASM release build' step added by PR16 builds for
that target but the toolchain setup never installed it
Blocking findings:
- Strip stale Content-Length from decoded Workers fetch responses and
set it from the decoded body length. The origin value describes the
compressed payload while the Workers runtime auto-decompresses, so
pass-through responses could be sent with a truncating length
- Reject multi-provider auction fan-out before any request launches:
add PlatformHttpClient::supports_concurrent_fanout() (default true),
report false from CloudflareHttpClient whose send_async executes
eagerly, and validate in the orchestrator before the launch loop.
Previously the select()-time rejection fired only after every
provider request had already run sequentially and spent the auction
budget. The select()-time check remains as defense-in-depth.
Regression test asserts rejection happens with zero requests sent
Non-blocking findings:
- Outbound request headers use Headers::append instead of set so
duplicate header names forward every value, matching the response path
- Replace the unreachable! panic in send_async with a typed
PlatformError so an edgezero behavior change cannot panic a Worker
- Replace bare unwrap() with expect("should ...") in platform tests
per the testing conventions
…ezero-pr17-cloudflare-adapter
…ezero-pr17-cloudflare-adapter
The build-fastly/check-fastly aliases merged from PR16 predate the Cloudflare adapter and did not exclude it, so they compiled the wasm32-unknown-unknown crate for wasm32-wasip1. Add the exclusion (matching test-fastly/clippy-fastly), add a build-cloudflare alias mirroring check-cloudflare, and update every dev-tooling doc that lists the per-target commands: /check-ci, /verify, /test-all, the build-validator agent, CLAUDE.md, AGENTS.md, and README.md now include the cloudflare clippy/test/build/check steps alongside fastly and axum. All aliases verified locally (check-fastly, check-axum, check-cloudflare, build-cloudflare).
ChristianPavilonis
left a comment
There was a problem hiding this comment.
Summary
Approving per request, with the findings below recorded for follow-up. CI is currently green on the latest head.
Findings folded into body
- P1 — EC timestamp helper still uses
std::time::SystemTime—crates/trusted-server-core/src/ec/mod.rs:480: this core path can be exercised by Cloudflare EC generation/sync/tombstone flows, andstd::timeis not safe onwasm32-unknown-unknown. Suggested fix: migratecurrent_timestamp()toweb_time::{SystemTime, UNIX_EPOCH}and update the stale wasm32-wasip1-only comment. - P2 — Cloudflare clippy alias is documented but not run in CI —
.github/workflows/format.yml:35: CI runs Fastly/Axum clippy but notcargo clippy-cloudflare, so Cloudflare adapter lint regressions can merge unnoticed. Suggested fix: add a Cloudflare clippy workflow step. - P2 — clean-checkout Cloudflare build target is not installed by
rust-toolchain.toml—rust-toolchain.toml:3:cargo build-cloudflaretargetswasm32-unknown-unknown, but the pinned toolchain only installswasm32-wasip1; CI installs the target explicitly, but local documented commands fail on a clean checkout. Suggested fix: addwasm32-unknown-unknownto the toolchain targets or document/runrustup target add wasm32-unknown-unknown.
CI
Latest gh pr checks 644 shows all current checks passing.
| #[cfg(target_arch = "wasm32")] | ||
| #[event(fetch)] | ||
| pub async fn main(req: Request, env: Env, ctx: Context) -> Result<Response> { | ||
| match edgezero_adapter_cloudflare::run_app::<app::TrustedServerApp>( |
There was a problem hiding this comment.
P1 — Cloudflare response conversion drops duplicate response headers
The adapter delegates final response conversion to the pinned edgezero-adapter-cloudflare. At the pinned rev in Cargo.toml, from_core_response iterates parts.headers and calls worker::Headers::set(...), which overwrites prior values for repeated header names. The Cloudflare outbound fetch bridge also uses resp.headers().entries(), which is not sufficient for preserving multi-value Set-Cookie.
Why it matters: Trusted Server publisher proxy responses may contain multiple Set-Cookie headers from the origin plus Trusted Server EC/consent cookies. Collapsing to the last value breaks publisher sessions and identity/privacy cookie behavior.
Suggested fix: Update/pin EdgeZero to use append for response headers, and explicitly preserve multi-value Set-Cookie from Workers fetch responses via the appropriate multi-value API. Add a Cloudflare integration/regression test with multiple Set-Cookie headers.
| handle_first_party_proxy_rebuild(&s.settings, &services, req).await | ||
| }), | ||
| ) | ||
| .get("/", get_fallback.clone()) |
There was a problem hiding this comment.
P1 — Cloudflare publisher fallback only supports GET/POST
The Cloudflare router registers only .get/.post for / and /{*rest}. Fastly/Axum route publisher fallback for GET, POST, HEAD, OPTIONS, PUT, PATCH, and DELETE.
Why it matters: Cloudflare will reject or miss normal publisher-origin traffic such as HEAD requests, CORS OPTIONS preflights, and non-GET/POST API calls instead of proxying them. That is a behavioral compatibility break for a transparent edge proxy.
Suggested fix: Mirror Axum/Fastly: add a shared publisher_fallback_methods() list and register both root and wildcard fallback routes for all supported methods. Also apply this to the startup-error router.
| // platform executes `send_async` eagerly (e.g. Cloudflare Workers): | ||
| // sequential execution would accrue the sum of provider latencies and | ||
| // blow the auction budget before a later `select` could reject it. | ||
| if provider_names.len() > 1 && !context.services.http_client().supports_concurrent_fanout() |
There was a problem hiding this comment.
P2 — Sequential-fanout guard rejects based on configured provider count before filtering launchable providers
The new Cloudflare safety guard checks provider_names.len() > 1 before the existing skip logic for unknown, disabled, timed-out, or no-backend providers.
Why it matters: A Cloudflare deployment with two configured names but only one actually launchable provider now fails the entire auction, whereas the prior logic would safely skip the inactive provider and run the single valid one.
Suggested fix: Build/filter launch candidates first without sending requests, then reject only when more than one request would actually be launched on a non-concurrent platform.
|
|
||
| # Allow cloudflare-specific overrides on top (not committed). | ||
| # Copy .env.cloudflare.dev.example → .env.cloudflare.dev to customise. | ||
| [ -f "$SCRIPT_DIR/.env.cloudflare.dev" ] && . "$SCRIPT_DIR/.env.cloudflare.dev" |
There was a problem hiding this comment.
P3 — Cloudflare-specific env overrides are sourced but not exported
.env.cloudflare.dev is sourced without set -a, unlike the root .env.
Why it matters: Local override variables may not be exported to worker-build, so the local Cloudflare bundle can ignore intended overrides.
Suggested fix: Source it with set -a && source ... && set +a.
Summary
Changes
Closes
Closes #498
Test plan
Checklist
Known deferred items