Add trusted-server-adapter-axum native dev server (PR 16)#643
Add trusted-server-adapter-axum native dev server (PR 16)#643prk-Jr wants to merge 23 commits intofeature/edgezero-pr15-remove-fastly-corefrom
Conversation
Move trusted-server-adapter-axum from workspace exclude to members list. Remove the global `target = "wasm32-wasip1"` build override from .cargo/config.toml (which forced the axum crate out of the workspace) and pass --target wasm32-wasip1 explicitly only for Fastly CI commands. Delete the now-redundant crate-local .cargo/config.toml. Update CI test-rust job to exclude the axum crate and pass the explicit target; test-axum job runs from the workspace root with -p flag. Add .edgezero/ to .gitignore to exclude the local KV store file.
Register AxumDevServer alongside FastlyViceroy in RUNTIME_ENVIRONMENTS so the full framework x runtime scenario matrix (WordPress, Next.js) runs against both platforms. AxumDevServer spawns the native trusted-server-axum binary (no WASM or Viceroy), binds to the fixed port 8787 (baked into axum.toml at compile time), and polls for any HTTP response as readiness (root returns 403 in test env). Binary path defaults to target/debug/trusted-server-axum, overridable via AXUM_BINARY_PATH. Settings are baked in at build time via TRUSTED_SERVER__* env vars, same as Fastly. The integration-tests.sh script now builds both the WASM and the native Axum binary with test-specific overrides (origin=127.0.0.1:8888). Add test_wordpress_axum and test_nextjs_axum individual test functions. Ignore .edgezero/ at workspace root (local KV store from the dev server).
- README: update Quick Start and Development commands for both runtimes - getting-started: add Axum as Option A (no Fastly CLI needed) - architecture: add trusted-server-adapter-axum to Core Components; add Runtime Targets table - testing: fix cargo test commands; add Axum adapter section; split Local Server Testing into Axum vs Fastly; restore clippy step in CI/CD workflow example alongside new test-axum job
The integration test matrix includes AxumDevServer which requires the native trusted-server-axum binary. Add build-axum step to the shared setup action, package the binary alongside the WASM artifact, and pass AXUM_BINARY_PATH to the integration test run step.
aram356
left a comment
There was a problem hiding this comment.
Summary
Introduces a native Axum dev-server adapter that runs the full trusted-server pipeline locally without Fastly Compute or Viceroy, promotes the crate to a workspace member, and extends the integration-test matrix to cover both runtimes. Requesting changes for a handful of small but concrete issues: unused deps, stale lockfile, doc/code drift, and a middleware bypass on the startup-error path.
Blocking
🔧 wrench
- Unused direct dependencies:
serde_jsonandtrusted-server-jsdeclared incrates/trusted-server-adapter-axum/Cargo.tomlbut never referenced insrc/ortests/. See inline. - Stale crate-local
Cargo.lock: ~100 KB file that cargo now silently ignores (crate is a workspace member). Delete it to avoid lockfile drift. CLAUDE.mdcomment contradicts the PR: says "excluded from workspace" at line 14 while the PR makes it a workspace member. See inline.- Log text references a non-existent
NoopKvStore: the code usesUnavailableKvStore(src/platform.rs:367vs :379). See inline. - Startup-error router bypasses middleware:
startup_error_router()has noFinalizeResponseMiddleware/AuthMiddleware, breaking the "every response has X-Geo-Info-Available" invariant and bypassing operator headers + basic auth on startup-failure responses. See inline onsrc/app.rs:134.
Non-blocking
🤔 thinking
send_async/selectdiverges from Fastly (eager execution changes error-surface timing and ordering). Trade-off is documented, but consider a breadcrumb log or realtokio::spawnfan-out. See inline.Body::Streamoutbound body silently truncated to empty on axum. See inline.- Env-var namespace collisions possible due to
-/./→_normalization. See inline. - Fixed port 8787 baked at compile time can TIME_WAIT-flake across sequential integration tests. See inline.
♻️ refactor
reqwest::Clientrebuilt per request — defeats connection pool. Move to sharedArc<AxumPlatformHttpClient>inAppState. See inline.- Env-var tests not isolated — use
temp-env(already a workspace dep). See inline. bytes::Bytes→Vec<u8>round-trip on request/response bodies is wasted allocation. See inline.tests/routes.rsuses.unwrap()instead of.expect("should ...")— CLAUDE.md applies to test code. See inline.
🌱 seedling
- No
/healthendpoint —AxumDevServer::health_check_path()returns/healthbut the app never registers it; tests work around withwait_for_any_response. See inline.
⛏ nitpick
- Crate-local
.gitignoreis redundant with the workspace.gitignore. See inline. build_per_request_servicesis a no-op wrapper aroundbuild_runtime_services. See inline.
📝 note
test-axumCI job runs withoutTRUSTED_SERVER__*env vars — every request goes through the startup-error path. Smoke tests still pass, but not a great signal. See inline.
CI Status
- fmt: PASS (verified locally,
cargo fmt --all -- --check) - clippy (axum crate, host target): PASS with zero warnings
cargo test -p trusted-server-adapter-axum: 18 tests PASS- GitHub CI on
75fe0d01: prepare artifacts + integration tests + browser tests all PASS
… bypass, refactors
Blocking:
- Remove unused serde_json and trusted-server-js from Cargo.toml
- Delete crate-local Cargo.lock (now silently ignored as workspace member)
- Fix CLAUDE.md workspace layout comment (drop "excluded from workspace")
- Fix log message naming NoopKvStore → UnavailableKvStore in build_runtime_services
- Wrap startup_error_router with FinalizeResponseMiddleware(Settings::default()) so
startup-error responses carry X-Geo-Info-Available and operator response_headers
Refactor:
- Move AxumPlatformHttpClient to AppState; share Arc across requests via
build_runtime_services(ctx, Arc::clone(&state.http_client)) to preserve
the reqwest connection pool
- Remove build_per_request_services no-op wrapper; inline at call sites
- Use temp_env::with_var in config/secret store tests for proper isolation
- Replace .unwrap() with .expect("should ...") throughout test code per CLAUDE.md
Nitpick:
- Delete redundant crate-local .gitignore (covered by workspace .gitignore)
…ce breadcrumbs Port fix: - main.rs reads PORT env var at startup; when set, uses AxumDevServer::with_config with a dynamic SocketAddr instead of the run_app default (hardcoded 8787) - Integration test spawner (axum.rs) now calls find_available_port() and passes PORT=<port> to the child process, matching the Fastly env's dynamic-port pattern - Fallback to AXUM_DEFAULT_PORT=8787 if find_available_port fails (offline runner) Divergence breadcrumbs: - send_async: debug log noting that execution is eager and errors surface immediately, not at select() time as they do on Fastly - select: debug log noting that index 0 is popped unconditionally (sequential, not first-to-complete) — any fan-out ordering tests should use the Fastly runtime
ChristianPavilonis
left a comment
There was a problem hiding this comment.
Summary
Nice addition overall — the Axum adapter is headed in a useful direction. I found a few correctness gaps in the platform shim plus a couple of documentation mismatches around what the Axum runtime can currently support.
| self.execute(request).await | ||
| } | ||
|
|
||
| async fn send_async( |
There was a problem hiding this comment.
Auction fan-out becomes serial on Axum. send_async() awaits self.execute(), and execute() fully buffers the upstream response before returning. The auction orchestrator in trusted-server-core assumes send_async() only launches the request and that select() yields whichever backend finishes first, so this changes response ordering, deadline enforcement, and response_time_ms accounting for multi-provider auctions.
Could we keep a real in-flight handle here (for example by storing spawned futures/joins and polling them from select()), or gate /auction off on Axum until the platform layer can preserve Fastly's concurrency semantics?
| builder = builder.body(bytes); | ||
| } | ||
| } | ||
| edgezero_core::body::Body::Stream(_) => { |
There was a problem hiding this comment.
Streaming outbound bodies are dropped. On the Axum path, non-JSON inbound bodies are intentionally preserved as Body::Stream by edgezero_adapter_axum::into_core_request(). handle_publisher_request() then forwards the original request through services.http_client().send(...), but this match arm only logs and sends an empty body. That means proxied POSTs/uploads can reach the publisher origin with no payload.
Please either stream/buffer the body into reqwest here, or fail the request explicitly instead of silently truncating it.
| .middleware(AuthMiddleware::new(Arc::clone(&state.settings))) | ||
| .get("/.well-known/trusted-server.json", discovery_handler) | ||
| .post("/verify-signature", verify_handler) | ||
| .post("/admin/keys/rotate", rotate_handler) |
There was a problem hiding this comment.
These admin routes are wired up even though Axum can't persist the requested writes. AxumPlatformConfigStore::{put,delete} and AxumPlatformSecretStore::{create,delete} always return "not supported", but /admin/keys/rotate and /admin/keys/deactivate are still exposed here. With request signing enabled and valid admin auth, /admin/keys/rotate currently returns 500 as soon as the first store write happens.
I think we should either hide these routes on Axum or convert the unsupported-operation path into a clear 501/4xx response instead of advertising working admin endpoints.
|
|
||
| ```bash | ||
| cargo test | ||
| # Copy and edit the environment file |
There was a problem hiding this comment.
The Axum quick-start implies .env is auto-loaded, but the adapter never does that. The new dev-server path reads process environment variables directly (plus axum.toml); nothing in the adapter sources .env. As written, cp .env.dev .env looks sufficient when users actually need to export/source those variables into their shell.
Can we update this section to show how to load the variables into the environment and mention the TRUSTED_SERVER_CONFIG_* / TRUSTED_SERVER_SECRET_* conventions the Axum platform store uses?
|
|
||
| Native Axum dev server adapter (native binary): | ||
|
|
||
| - Runs the full trusted-server pipeline locally without Fastly or Viceroy |
There was a problem hiding this comment.
This overstates Axum parity with Fastly. The new adapter still has several important limitations (no KV store, no geo lookup, no config/secret-store writes, and send_async() does not preserve Fastly-style fan-out semantics). Calling it "the full trusted-server pipeline" and useful for "non-Fastly deployments" sets the wrong expectation for readers.
I'd reframe this as a local development / integration-test adapter and call out the current limitations explicitly.
Conflict resolutions: - Cargo.lock: regenerated after adding futures dep - docs/guide/testing.md: kept Axum adapter test command (PR16) and EC ID rename + specific-test example (PR15) - .cargo/config.toml: kept PR16 version (no WASM default target) because trusted-server-adapter-axum is now a workspace member and tokio/reqwest cannot compile to wasm32-wasip1 - AGENTS.md: updated cargo test instruction to document both required commands now that the workspace contains both WASM-only and native-only crates API breakage from PR15 (handle_publisher_request now returns PublisherResponse): - Add resolve_publisher_response() helper mirroring the Fastly adapter pattern (Buffered / Stream / PassThrough -> Response) - Drop the removed None arg from handle_auction and handle_publisher_request call sites
PR15 merge — modifications to existing files not captured in the merge commit: - Fastly adapter: migrate to EdgeZero router/middleware, rewrite management API, update platform/compat/logging/backend to new EdgeZero abstractions - Core: remove KV-backed consent layer (consent/kv.rs deleted), update auction orchestrator and integrations to new API surface, refactor html_processor, cookies, and error types - Delete stale Fastly KV store test fixtures (counter_store.json, opid_store.json) - Update fastly.toml, trusted-server.toml, and integration test config to match PR16 workspace restructuring: - Add both adapters to workspace members; set default-members=[core, axum] so bare `cargo test` runs natively without WASM target conflicts - Replace test-wasm alias with test-fastly (--workspace --exclude axum --target wasm32-wasip1) and keep test-axum (-p trusted-server-adapter-axum) - Update CI: cargo test-fastly in the Fastly job, cargo test-axum in native job - Axum admin routes (/admin/keys/rotate, /admin/keys/deactivate) return 501 instead of falling through to an error handler - Update CLAUDE.md, AGENTS.md, README, and all guide docs to use new aliases
Viceroy internally runs `cargo run --bin trusted-server-adapter-fastly` against the default-run packages. With core + axum as defaults, Cargo fails to find the binary. Restricting default-members to the fastly adapter fixes the lookup. Updated stale comments in CLAUDE.md and .cargo/config.toml to reflect the new setup.
aram356
left a comment
There was a problem hiding this comment.
Summary
Re-review of the Axum dev-server adapter against main. Most prior feedback (aram356, ChristianPavilonis) has been addressed, and the latest commit even fixed the auction fan-out concern via real tokio::spawn + select_all. Deep verification finds five blocking CI/correctness issues plus several non-blocking refactor and documentation items.
A compounding factor: this PR targets feature/edgezero-pr15-remove-fastly-core, not main. The Run Tests and Run Format workflows only trigger on PRs to main, so no CI run on this PR has actually exercised fmt/clippy/test-fastly/test-axum. Only Integration Tests runs. The fmt/clippy failures below would surface for the first time when this lands on main.
Blocking
🔧 wrench
cargo fmt --all -- --checkfails oncrates/trusted-server-adapter-axum/src/app.rs:217-225(auction handlerOk(...)block). See inline.cargo clippy ... -- -D warningsfails oncrates/trusted-server-adapter-axum/src/platform.rs:194-196withclippy::type_complexity. See inline.TRUSTED_SERVER__SYNTHETIC__SECRET_KEYis dead — should beTRUSTED_SERVER__EDGE_COOKIE__SECRET_KEYafter PR15's rename. Two locations:scripts/integration-tests.sh:63and.github/actions/setup-integration-test-env/action.yml:97. Effect: Axum CI integration tests run with the placeholder secret. See inline.- CLAUDE.md self-contradicts —
CLAUDE.mdlines 67-69 (Build & Test Commands, modified in this PR) saycargo test-fastly/cargo test-axum; the CI Gates section near line 256 (unchanged) still says3. `cargo test --workspace`, which AGENTS.md (also updated in this PR) explicitly forbids: "Do NOT use barecargo test --workspace— it will attempt to compile the Fastly adapter for the host target." Update CI Gates item 3 to list both aliases. (Body-level because the affected line wasn't part of this PR's diff.) cargo clippy --workspace --all-targets --all-features -- -D warningswill fail post-merge to main. Removing[build] target = "wasm32-wasip1"from.cargo/config.tomlmeansformat.yml's clippy step now targets host, where thefastlycrate's wasm-only APIs don't compile. TheRun Formatworkflow doesn't trigger on PRs to feature branches, so this is hidden until rebase onto main. Fix options: add--target wasm32-wasip1to the clippy CI step (excluding axum), or split intoclippy-fastly/clippy-axumaliases analogous to the test aliases.
Non-blocking
🤔 thinking
- Fan-out parity isn't actually tested —
send_async× 2 +selecthas no unit coverage. See inline onplatform.rs:309. Body::Streamis silently buffered intoVec<u8>with no log emission; large-payload divergence vs Fastly will not surface in test output. See inline onplatform.rs:247.
♻️ refactor
startup_error_router's closure-of-closure (make) is convoluted. See inline onapp.rs:134.rotate_handler/deactivate_handlerare aliases for the same closure. See inline onapp.rs:211.
🌱 seedling / 📌 out of scope
health_check_path()returns/healthbutTrustedServerApp::routes()never registers it. See inline onenvironments/axum.rs:71.- Per-request
reqwest::Clientrevert masks a likely keep-alive bug in the empty-body branch (if !body_bytes.is_empty() { builder = builder.body(...) }). Worth a follow-up issue. See inline onplatform.rs:456. fastly.toml:42-48adds an[local_server.config_stores.trusted_server_config]config store withedgezero_enabled = "true". That's the EdgeZero gate, not the Axum adapter — likely a merge-resolution remnant from theMerge feature/edgezero-pr15-remove-fastly-core into PR16commit. Confirm it belongs here or rebase it out.
📝 note
test-axumCI job runs with noTRUSTED_SERVER__*env vars; every request flows throughstartup_error_router. The 8 route tests only assertstatus != 404and< 500, both satisfied by 401/501 — pre-existing finding (aram356), deferred. See inline ontest.yml:75.
CI Status
- fmt: FAIL (verified locally — diff in
app.rs:217) - clippy: FAIL (verified locally —
type_complexityinplatform.rs:194) cargo test-axum(host target): PASS (18 tests)- Integration Tests workflow on
9a0d38c: not yet run for the latest commit (last green run wasa780034) - Run Tests / Run Format workflows: not exercised — this PR doesn't target
main
| let services = build_runtime_services(&ctx); | ||
| let req = ctx.into_request(); | ||
| Ok( | ||
| handle_auction(&s.settings, &s.orchestrator, &services, req) |
There was a problem hiding this comment.
🔧 wrench — cargo fmt --all -- --check fails on this block. rustfmt wants to collapse the multi-line Ok(...). Run cargo fmt --all and recommit.
Verified locally:
Diff in crates/trusted-server-adapter-axum/src/app.rs:217:
- Ok(
- handle_auction(&s.settings, &s.orchestrator, &services, req)
- .await
- .unwrap_or_else(|e| http_error(&e)),
- )
+ Ok(handle_auction(&s.settings, &s.orchestrator, &services, req)
+ .await
+ .unwrap_or_else(|e| http_error(&e)))
The Run Format workflow doesn't trigger on PRs to feature/edgezero-pr15-remove-fastly-core, so this fmt failure has been hidden. It will surface as soon as this PR retargets main.
| backend_name: String, | ||
| handle: tokio::task::JoinHandle< | ||
| Result<(u16, Vec<(String, Vec<u8>)>, Vec<u8>), Report<PlatformError>>, | ||
| >, |
There was a problem hiding this comment.
🔧 wrench — cargo clippy --workspace --all-targets --all-features -- -D warnings fails here with clippy::type_complexity:
error: very complex type used. Consider factoring parts into `type` definitions
--> crates/trusted-server-adapter-axum/src/platform.rs:194:13
|
194 | handle: tokio::task::JoinHandle<
195 | | Result<(u16, Vec<(String, Vec<u8>)>, Vec<u8>), Report<PlatformError>>,
196 | | >,
Fix — extract a type alias:
type SpawnedRequestResult =
Result<(u16, Vec<(String, Vec<u8>)>, Vec<u8>), Report<PlatformError>>;
struct AxumPendingHandle {
backend_name: String,
handle: tokio::task::JoinHandle<SpawnedRequestResult>,
}Like the fmt failure on app.rs:221, this is hidden because format.yml only runs on PRs to main.
| echo "==> Building Axum native binary (origin=http://127.0.0.1:$ORIGIN_PORT)..." | ||
| TRUSTED_SERVER__PUBLISHER__ORIGIN_URL="http://127.0.0.1:$ORIGIN_PORT" \ | ||
| TRUSTED_SERVER__PUBLISHER__PROXY_SECRET="integration-test-proxy-secret" \ | ||
| TRUSTED_SERVER__SYNTHETIC__SECRET_KEY="integration-test-secret-key" \ |
There was a problem hiding this comment.
🔧 wrench — Wrong env var name; Axum integration tests run with a placeholder secret key.
PR15 renamed the synthetic settings section to edge_cookie. The codebase reads TRUSTED_SERVER__EDGE_COOKIE__SECRET_KEY (crates/trusted-server-core/src/settings_data.rs:47), and the Fastly build at line 56 of this script sets it correctly. The Axum build was missed.
Effect: the Axum binary used by test_wordpress_axum / test_nextjs_axum falls back to the placeholder secret, only emits a log::warn, and the tests pass without exercising the real signing path.
Fix:
-TRUSTED_SERVER__SYNTHETIC__SECRET_KEY="integration-test-secret-key" \
+TRUSTED_SERVER__EDGE_COOKIE__SECRET_KEY="integration-test-secret-key" \Same change required at .github/actions/setup-integration-test-env/action.yml:97.
| env: | ||
| TRUSTED_SERVER__PUBLISHER__ORIGIN_URL: http://127.0.0.1:${{ inputs.origin-port }} | ||
| TRUSTED_SERVER__PUBLISHER__PROXY_SECRET: integration-test-proxy-secret | ||
| TRUSTED_SERVER__SYNTHETIC__SECRET_KEY: integration-test-secret-key |
There was a problem hiding this comment.
🔧 wrench — Same synthetic→edge_cookie rename miss as scripts/integration-tests.sh:63. The Fastly build above (line 87) uses the correct name; this Axum build does not.
Fix:
- TRUSTED_SERVER__SYNTHETIC__SECRET_KEY: integration-test-secret-key
+ TRUSTED_SERVER__EDGE_COOKIE__SECRET_KEY: integration-test-secret-keyWithout this, the CI Axum binary in Integration Tests runs with the placeholder secret and the EdgeCookie/HMAC path is effectively unverified for the Axum runtime.
| self.execute(request).await | ||
| } | ||
|
|
||
| async fn send_async( |
There was a problem hiding this comment.
🤔 thinking — Fan-out parity isn't actually tested.
The new tokio::spawn + select_all implementation looks correct, but no unit or integration test calls send_async twice and select to verify (a) both spawn concurrently, (b) the ready handle is dropped from remaining, and (c) the backend-name mapping survives the ready_idx shift. The auction orchestrator is the canonical caller and would be the worst place to discover a bug here.
Suggestion: add a small unit test in this #[cfg(test)] module that boots two short-lived tokio::time::sleep-backed handles via send_async (skip reqwest), selects, and asserts both ready and remaining. Same harness used in edgezero-core would do.
| ); | ||
| async move { Ok::<Response, EdgeError>(resp) } | ||
| } | ||
| }; |
There was a problem hiding this comment.
♻️ refactor — Closure-of-closure is hard to read. make returns a move |msg| that returns the actual handler closure, then is called four times to bind a clone of message.
Suggestion — define a small helper fn or just inline:
let make_handler = |msg: Arc<String>| {
move |_ctx: RequestContext| {
let body = edgezero_core::body::Body::from((*msg).clone());
let mut resp = Response::new(body);
*resp.status_mut() = status;
resp.headers_mut().insert(
header::CONTENT_TYPE,
HeaderValue::from_static("text/plain; charset=utf-8"),
);
async move { Ok::<Response, EdgeError>(resp) }
}
};
RouterService::builder()
.middleware(FinalizeResponseMiddleware::new(Arc::new(Settings::default())))
.get("/", make_handler(Arc::clone(&message)))
// ...Drops one indirection.
| Ok::<Response, EdgeError>(resp) | ||
| }; | ||
| let rotate_handler = admin_not_supported; | ||
| let deactivate_handler = admin_not_supported; |
There was a problem hiding this comment.
♻️ refactor — These two bindings are aliases for the same closure. Pass the closure directly to both .post(...) calls and drop the let rotate_handler = ...; let deactivate_handler = ...; lines. Saves four lines and the cognitive load of "are these two handlers actually different?"
|
|
||
| fn health_check_path(&self) -> &str { | ||
| "/health" | ||
| } |
There was a problem hiding this comment.
🌱 seedling — Pre-existing finding (aram356, deferred): health_check_path() returns /health but TrustedServerApp::routes() never registers it. This works only because wait_for_any_response on line 95 was added as a workaround. A future contributor swapping to the shared wait_for_ready helper would silently break the readiness gate.
The clean fix is one line in app.rs:
.get("/health", |_ctx: RequestContext| async {
Ok::<Response, EdgeError>(Response::new(edgezero_core::body::Body::from("ok")))
})Then wait_for_any_response can be deleted in favor of the shared helper. Worth a follow-up issue.
| // client across requests previously regressed the Next.js server-action → | ||
| // API-route integration flow by reusing a poisoned connection after a | ||
| // truncated POST. Revisit pooling if profiling shows allocation cost. | ||
| .http_client(Arc::new(AxumPlatformHttpClient::new())) |
There was a problem hiding this comment.
🌱 seedling — The comment says sharing a pooled client "regressed the Next.js server-action → API-route integration flow by reusing a poisoned connection after a truncated POST."
That description is consistent with the empty-body branch at lines 267-269 / 337-339:
if !body_bytes.is_empty() {
builder = builder.body(body_bytes);
}For a request that should have an empty body (POST with Content-Length: 0), this skips setting the body — and reqwest may send the request without Content-Length at all, which can confuse keep-alive on the server side. A subsequent reused connection then desyncs.
Suggestion — open a follow-up issue and reference it from this comment. The right long-term fix is connection-level (always set body, even when empty), not per-request client construction. Keeping a fresh client per request hides the underlying bug and burns TCP+TLS handshakes on every outbound call.
| run: cargo build -p trusted-server-adapter-axum | ||
|
|
||
| - name: Run Axum adapter tests | ||
| run: cargo test-axum |
There was a problem hiding this comment.
📝 note — Pre-existing finding (aram356, deferred to follow-up): this test-axum job runs with no TRUSTED_SERVER__* env vars. get_settings() fails at startup, so every request in tests/routes.rs flows through startup_error_router and gets 401 (basic auth) or the structured error response.
The 8 tests in tests/routes.rs only assert status != 404 and < 500, both of which are satisfied by 401/501. So the smoke tests pass without ever touching real handler logic.
Not blocking — but please document this in tests/routes.rs or wire the env vars (already centralized in setup-integration-test-env/action.yml) in a follow-up. As written, this CI job is mostly a routing-and-auth smoke check, not a handler smoke check.
Summary
trusted-server-adapter-axumas a native (non-WASM) dev server so the full trusted-server pipeline can be run and tested locally without Fastly Compute or Viceroytarget = "wasm32-wasip1"override from.cargo/config.toml; Fastly-specific commands now pass--target wasm32-wasip1explicitlyChanges
crates/trusted-server-adapter-axum/src/platform.rsPlatformConfigStore,PlatformSecretStore,PlatformBackend,PlatformGeo,PlatformHttpClient— env-var-backed implementationscrates/trusted-server-adapter-axum/src/middleware.rsFinalizeResponseMiddleware+AuthMiddleware— mirrors Fastly adapter, always emitsX-Geo-Info-Available: falsecrates/trusted-server-adapter-axum/src/app.rsTrustedServerAppimplementingHookswith all 11 routes wiredcrates/trusted-server-adapter-axum/src/main.rs+axum.tomlcrates/trusted-server-adapter-axum/tests/routes.rsEdgeZeroAxumService(no live TCP server)crates/integration-tests/tests/environments/axum.rsAxumDevServerruntime environment added to the matrixcrates/integration-tests/tests/environments/mod.rsAxumDevServeralongsideFastlyViceroycrates/integration-tests/tests/integration.rstest_wordpress_axum+test_nextjs_axumindividual test functionsscripts/integration-tests.sh.cargo/config.tomltarget = "wasm32-wasip1"; keep only the viceroy runnerCargo.tomltrusted-server-adapter-axumfrom[exclude]to[members]crates/trusted-server-adapter-axum/Cargo.toml.github/workflows/test.ymltest-axumCI job;test-rustnow passes--target wasm32-wasip1explicitlyCLAUDE.md.gitignore(root + adapter).edgezero/(local KV store created by dev server)Closes
Closes #497
Test plan
cargo test --workspace(Fastly/WASM crates via Viceroy)cargo test -p trusted-server-adapter-axum(8 route + middleware tests)cargo clippy --workspace --all-targets --all-features -- -D warningscargo fmt --all -- --checkcd crates/js/lib && npx vitest run(282 tests)test_wordpress_fastly,test_nextjs_fastly,test_wordpress_axum,test_nextjs_axumall passcargo run -p trusted-server-adapter-axumstarts on port 8787Checklist
unwrap()in production code — useexpect("should ...")logmacros (notprintln!)