Skip to content

Add dual-path EdgeZero entry point with feature flag (PR 14)#628

Open
prk-Jr wants to merge 41 commits into
feature/edgezero-pr13-integration-provider-type-migrationfrom
feature/edgezero-pr14-entry-point-dual-path
Open

Add dual-path EdgeZero entry point with feature flag (PR 14)#628
prk-Jr wants to merge 41 commits into
feature/edgezero-pr13-integration-provider-type-migrationfrom
feature/edgezero-pr14-entry-point-dual-path

Conversation

@prk-Jr

@prk-Jr prk-Jr commented Apr 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Adds a feature-flagged dual-path entry point: requests dispatch through the new EdgeZero TrustedServerApp or the preserved legacy_main based on edgezero_enabled in the trusted_server_config Fastly ConfigStore. Missing, unreadable, or non-true flag values fall back to legacy.
  • Keeps GET /health as a cheap entry-point shortcut that bypasses logging, settings, and app construction.
  • Implements TrustedServerApp via edgezero_core::app::Hooks with FinalizeResponseMiddleware and AuthMiddleware; auction and publisher fallback routes fail closed when a configured consent store cannot be opened.
  • Registers named routes from a single table that contains each path, its primary methods, and its handler. The same table also registers publisher fallback methods for non-primary verbs, so adding a named route no longer requires a second primary-method list.
  • Keeps legacy streaming publisher responses from the PR13 base branch while buffering publisher fallback responses inside the EdgeZero route handler.
  • Pins all four EdgeZero workspace dependencies to rev = "38198f9839b70aef03ab971ae5876982773fc2a1" and bumps toml to "1.1".
  • Clarifies ProxyRequestConfig.allowed_domains: &[] is open mode for operator-configured integration proxies; a non-empty slice restricts first-party proxy initial targets and redirect hops.

Changes

File Change
Cargo.toml Pin edgezero-adapter-axum, edgezero-adapter-cloudflare, edgezero-adapter-fastly, and edgezero-core to rev = "38198f9839b70aef03ab971ae5876982773fc2a1"; bump toml to "1.1"
Cargo.lock Updated for the pinned EdgeZero revision and toml version change
fastly.toml Add local trusted_server_config with edgezero_enabled = "false" as the default
crates/trusted-server-adapter-fastly/src/main.rs Add feature-flag dispatch, /health shortcut, EdgeZero dispatch_with_config, entry-point match get_settings() finalize block for router-level 405/404 responses, and PR13 legacy streaming handling
crates/trusted-server-adapter-fastly/src/app.rs Add TrustedServerApp, middleware wiring, consent-aware runtime service selection, table-driven named route registration, and routes_for_state test seam
crates/trusted-server-adapter-fastly/src/middleware.rs Add finalization and auth middleware, including the 401 geo-skip behavior and shared apply_finalize_headers helper
crates/trusted-server-adapter-fastly/src/route_tests.rs Update legacy route tests for HandlerOutcome and the consent-store fail-closed behavior
crates/trusted-server-core/src/proxy.rs Document and test ProxyRequestConfig.allowed_domains open-mode versus restricted-mode semantics

Closes

Closes #495

Test plan

  • cargo test -p trusted-server-adapter-fastly dispatch_auction_with_missing_consent_store_returns_503
  • cargo test -p trusted-server-adapter-fastly
  • cargo test --workspace
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo fmt --all -- --check
  • JS tests: cd crates/js/lib && npx vitest run (not rerun in this cleanup)
  • JS format: cd crates/js/lib && npm run format (not rerun in this cleanup)
  • Docs format: cd docs && npm run format (not rerun in this cleanup)

Checklist

  • Changes follow CLAUDE.md conventions
  • No unwrap() in production code - use expect("should ...")
  • Uses log macros, not println!
  • New EdgeZero consent-store regression coverage added
  • No secrets or credentials committed

prk-Jr added 14 commits April 13, 2026 11:15
Replace the app.rs stub with the full EdgeZero application wiring:
- AppState struct holding Settings, AuctionOrchestrator,
  IntegrationRegistry, and PlatformKvStore
- build_per_request_services() builds RuntimeServices per request using
  FastlyRequestContext for client IP extraction
- http_error() mirrors legacy http_error_response() from main.rs
- All 12 routes from legacy route_request() registered on RouterService
- Catch-all GET/POST handlers using matchit {*rest} wildcard dispatch
  to integration proxy or publisher origin fallback
- FinalizeResponseMiddleware (outermost) and AuthMiddleware registered
…x handler pattern

- Remove Arc::new() wrapper around build_state() which already returns Arc<AppState>
- Remove dedicated GET /static/{*rest} route and its tsjs_handler closure
- Move tsjs handling into GET /{*rest} catch-all: check path.starts_with("/static/tsjs=") first
- Extract path/method from ctx.request() before ctx.into_request() to keep &req valid
- Replace .map_err(|e| EdgeError::internal(...)) with .unwrap_or_else(|e| http_error(&e)) in all named-route handlers
- Remove configure() method from TrustedServerApp (not part of spec)
- Remove unused App import
…, unused turbofish, and overly-broad field visibility

- Drop `.into_bytes()` in `http_error`; `Body` implements `From<String>` directly
- Remove `Box::pin` wrapper from `get_fallback` closure; plain `async move` matches all other handlers
- Remove `Ok::<Response, EdgeError>` turbofish in `post_fallback`; type is now inferred
- Drop now-unused `EdgeError` import that was only needed for the turbofish
- Narrow `AppState` field visibility from `pub` to `pub(crate)`; struct is internal to this crate
Switches all four edgezero workspace dependencies from rev=170b74b to
branch=main so the adapter can use dispatch_with_config, the non-deprecated
public dispatch path. The main branch requires toml ^1.1, so the workspace
pin is bumped from "1.0" to "1.1" to resolve the version conflict.
Replaces the deprecated dispatch() call with dispatch_with_config(), which
injects the named config store into request extensions without initialising
the logger a second time (a second set_logger call would panic because the
custom fern logger is already initialised above). Adds log::info lines for
both the EdgeZero and legacy routing paths.
matchit's /{*rest} catch-all does not match the bare root path /. Add
explicit .get("/", ...) and .post("/", ...) routes that clone the fallback
closures so requests to / reach the publisher origin fallback rather than
returning a 404.
Registers the trusted_server_config config store in fastly.toml with
edgezero_enabled = "true" so that fastly compute serve routes requests
through the EdgeZero path without needing a deployed service.
@prk-Jr prk-Jr marked this pull request as draft April 13, 2026 14:20
@prk-Jr prk-Jr self-assigned this Apr 13, 2026
@prk-Jr prk-Jr changed the title Add feature-flagged dual-path entry point for EdgeZero migration (PR 14) Add dual-path EdgeZero entry point with feature flag (PR 14) Apr 13, 2026
@prk-Jr prk-Jr linked an issue Apr 13, 2026 that may be closed by this pull request
@aram356 aram356 linked an issue Apr 13, 2026 that may be closed by this pull request
- Normalise get_fallback to extract path/method from req after consuming
  the context, consistent with post_fallback and avoiding a double borrow
  on ctx
- Add comment to http_error documenting the intentional duplication with
  http_error_response in main.rs (different HTTP type systems; removable
  in PR 15)
- Add comment above route handlers explaining why the explicit per-handler
  pattern is kept over a macro abstraction
@prk-Jr prk-Jr marked this pull request as ready for review April 13, 2026 16:43
@prk-Jr prk-Jr requested review from ChristianPavilonis and aram356 and removed request for aram356 April 13, 2026 16:43

@aram356 aram356 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Reviewed the 13 files that PR 628 actually changes vs its base (feature/edgezero-pr13-...). The dual-path entry point is a sensible migration shape, and the explicit GET/POST "/" routes + the header-precedence middleware test are the kind of load-bearing details that prevent future outages. However, the EdgeZero path currently diverges from the legacy path in ways that are security- and reliability-relevant, and the branch = "main" pin on upstream deps makes builds non-deterministic. Blocking on those.

Blocking

🔧 wrench

  • Forwarded-header sanitization missing on EdgeZero path — legacy strips Forwarded / X-Forwarded-* / Fastly-SSL before routing; EdgeZero hands the raw req to dispatch_with_config. With edgezero_enabled = "true" as the local-dev default, this is the default path. (main.rs:95)
  • build_state() panics on misconfigexpect("should …") on settings / orchestrator / registry. Legacy returns a structured error response; EdgeZero now 5xx's with no detail. (app.rs:75)
  • Docstring "built once at startup" is misleading — every request spins up a fresh Wasm instance, so build_state() runs per-request. Invites future false caching. (app.rs:61)
  • Stale #[allow(dead_code)] on now-live middleware — five suppressions with "until Task 4 wires app.rs" comments. Task 4 is this PR. (middleware.rs:50,57,96,103,146)
  • AuthMiddleware flattens Report<TrustedServerError> into EdgeError::internal(io::Error::other(...)) — loses per-variant status code and user message; generic 500 instead of the specific error. (middleware.rs:122)
  • edgezero-* deps pinned to branch = "main" — non-deterministic builds; supply-chain path into prod via a moving upstream branch. Pin to a specific rev or fork tag. (Cargo.toml:59-62)

Non-blocking

🤔 thinking

  • TLS metadata dropped on EdgeZero pathtls_protocol / tls_cipher hardcoded to None; legacy populates both. Low impact today (debug logging only), but a silent regression if any future signing/audit path reads them. (app.rs:123-124)

♻️ refactor

  • 11 near-identical handler closures in routes() — a pair of file-local make_sync_handler / make_async_handler helpers would cut ~120 lines without harming auditability. (app.rs:175-301)
  • FinalizeResponseMiddleware hardcodes FastlyPlatformGeo — take Arc<dyn PlatformGeo> instead so Middleware::handle can be unit-tested end-to-end. (middleware.rs:68)
  • build_per_request_services duplicates platform::build_runtime_services — extract a shared helper that takes ClientInfo. (app.rs:111-127)

🌱 seedling

  • fastly.toml flips local dev default to EdgeZero — combined with the blockers above, every fastly compute serve now exposes them. Consider defaulting to "false" until the blockers land. (fastly.toml:52)

⛏ nitpick

  • AppState fields can be private (not pub(crate)). (app.rs:62-66)
  • Root-route pairs clone closures four times — upstream RouterService::get_many would help. (app.rs:374-377)

📝 note

  • The dispatch_with_config comment explaining the set_logger panic is excellent "why, not what" documentation. (main.rs:91-94)

👍 praise

  • operator_response_headers_override_earlier_headers codifies a brittle precedence contract. (middleware.rs:217-233)
  • Explicit GET "/" / POST "/" routes with the in-code explanation of matchit's wildcard gap prevent a future 404 outage. (app.rs:374-377)

CI Status

  • browser integration tests: PASS
  • integration tests: PASS
  • prepare integration artifacts: PASS
  • fmt / clippy / unit tests: not surfaced by gh pr checks — please confirm these ran.

Comment thread crates/trusted-server-adapter-fastly/src/main.rs Outdated
Comment thread crates/trusted-server-adapter-fastly/src/app.rs Outdated
Comment thread crates/trusted-server-adapter-fastly/src/app.rs Outdated
Comment thread crates/trusted-server-adapter-fastly/src/middleware.rs Outdated
Comment thread crates/trusted-server-adapter-fastly/src/middleware.rs Outdated
Comment thread fastly.toml
Comment thread crates/trusted-server-adapter-fastly/src/app.rs
Comment thread crates/trusted-server-adapter-fastly/src/app.rs Outdated
Comment thread crates/trusted-server-adapter-fastly/src/main.rs Outdated
Comment thread crates/trusted-server-adapter-fastly/src/middleware.rs

@ChristianPavilonis ChristianPavilonis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Supplementary review — see aram356's review for the primary findings. This review covers additional items not raised there.

Non-blocking

🔧 wrench (cross-cutting, from earlier PRs in this stack)

  • set_header drops multi-valued headers: edge_request_to_fastly in platform.rs:187 uses set_header instead of append_header, silently dropping duplicate headers. Pre-existing pattern (also in compat::to_fastly_request), but the EdgeZero path creates a new copy of the same bug.

🌱 seedling

  • parse_edgezero_flag is case-sensitive: "TRUE" and "True" silently fall through to legacy path. Consider eq_ignore_ascii_case or logging unrecognized values.

📝 note (cross-cutting, from earlier PRs)

  • Stale doc comment in platform/mod.rs:31: References fastly::Body in publisher.rs, but PR 11 already migrated to EdgeBody.

♻️ refactor (cross-cutting, from earlier PRs)

  • Duplicated body_as_reader helper: Identical function in proxy.rs:24 and publisher.rs:23. Extract to shared utility.

⛏ nitpick (cross-cutting)

  • Management API client re-created per write: Each put/delete in platform.rs constructs a new FastlyManagementApiClient. Fine for current usage, noted for future batch writes.

📌 out of scope

  • compat.rs in core depends on fastly types: Already tracked as PR 15 removal target.

CI Status

  • browser integration tests: PASS
  • integration tests: PASS
  • prepare integration artifacts: PASS

Comment thread crates/trusted-server-adapter-fastly/src/app.rs
Comment thread crates/trusted-server-adapter-fastly/src/app.rs
Comment thread crates/trusted-server-adapter-fastly/src/middleware.rs Outdated
Comment thread crates/trusted-server-adapter-fastly/src/main.rs
prk-Jr added 2 commits April 16, 2026 13:31
@prk-Jr prk-Jr requested a review from ChristianPavilonis May 14, 2026 06:11

@aram356 aram356 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Dual-path entry point with feature-flag dispatch is sound in shape: clean middleware split, fail-closed consent semantics applied uniformly across both legacy_main and the EdgeZero TrustedServerApp, and the fastly-ssl re-injection preserves HTTPS scheme detection despite EdgeZero's RequestContext not surfacing TLS metadata. One blocker around unbounded default buffering of the EdgeZero publisher fallback; several non-blocking refactors and naming concerns.

Blocking

🔧 wrench

  • Unbounded default max_buffered_body_bytes is unsafe at flag-flip (crates/trusted-server-core/src/settings.rs:31-37, see inline)

Non-blocking

🤔 thinking

  • Hidden mutation in response_was_finalized_by_middleware (crates/trusted-server-adapter-fastly/src/main.rs:242, see inline)
  • max_buffered_body_bytes placement is misleading: lives under [publisher] but only affects the EdgeZero fallback path. Consider moving under a dedicated [edgezero] block — or renaming — once legacy_main is removed in PR 15.
  • TRACE / CONNECT / WebDAV verbs return router-level 405 on EdgeZero; legacy proxied them through (crates/trusted-server-adapter-fastly/src/app.rs:32-35). Documented and covered by dispatch_unregistered_method_returns_405_at_router_level, but it's a real semantic change at flag-flip — worth a release-note line so downstream auditors/proxies aren't surprised.

♻️ refactor

  • Inline apply_entry_point_finalize (crates/trusted-server-adapter-fastly/src/main.rs:249, see inline)
  • Extract build_state_from_settings to dedupe build_state and app_state_for_settings (crates/trusted-server-adapter-fastly/src/app.rs:568, see inline)
  • named_routes() returns [NamedRoute; 9]: the size literal must be updated alongside the array body. Switch to const NAMED_ROUTES: &[NamedRoute] = &[...] and return &'static [NamedRoute] — removes the size coupling and avoids stack-copying the table on every request. (crates/trusted-server-adapter-fastly/src/app.rs:321-369)

📝 note

  • BoundedWriter consistency (crates/trusted-server-adapter-fastly/src/main.rs:556, see inline)
  • dispatch_with_config_handle couples to an undocumented edgezero internal: the choice over dispatch_with_config is driven by which upstream fn calls init_logger. If upstream changes that, this path silently re-panics. Add a short comment pointing to the pinned edgezero rev so the assumption can be re-verified after a bump. (crates/trusted-server-adapter-fastly/src/main.rs:204-216)

CI Status

  • fmt: PASS
  • clippy: PASS
  • rust tests: PASS
  • js tests: PASS
  • browser integration tests: PASS
  • integration tests: PASS

Comment thread crates/trusted-server-core/src/settings.rs Outdated
Comment thread crates/trusted-server-adapter-fastly/src/main.rs Outdated
Comment thread crates/trusted-server-adapter-fastly/src/main.rs Outdated
Comment thread crates/trusted-server-adapter-fastly/src/app.rs Outdated
Comment thread crates/trusted-server-adapter-fastly/src/main.rs Outdated
Blocking:
- Default max_buffered_body_bytes to Some(16 MiB) so EdgeZero flag-flip
  fails-safe; operators must opt out for pages larger than 16 MiB

Non-blocking:
- Rename response_was_finalized_by_middleware to take_finalize_sentinel so
  the side-effecting header removal is visible in the name
- Inline apply_entry_point_finalize at its single call site; remove the
  thin wrapper function; update the test to call resolve_geo_for_response
  and apply_finalize_headers directly
- Extract build_state_from_settings(settings) so build_state() and the
  test-only app_state_for_settings() share a single constructor path
- BoundedWriter: use std::io::Error::other() for consistency
- Convert named_routes() -> [NamedRoute; 9] to const NAMED_ROUTES: &[NamedRoute];
  removes the size literal coupling and avoids a stack copy on every call
@prk-Jr

prk-Jr commented May 28, 2026

Copy link
Copy Markdown
Collaborator Author

All findings from the round-1 review resolved in commit 1973b5d.

Blocking — fixed

  • Unbounded max_buffered_body_bytes (settings.rs:35): now defaults to Some(16 MiB) via default_max_buffered_body_bytes(). Flag-flip fails-safe; operators explicitly opt out for larger pages.

Non-blocking — addressed

  • Hidden mutation (main.rs): response_was_finalized_by_middlewaretake_finalize_sentinel. Mutation is in the name.
  • Thin wrapper (main.rs): apply_entry_point_finalize removed; resolve_geo_for_response + apply_finalize_headers inlined at the single call site. Test updated to call both directly.
  • Duplicate constructor (app.rs): build_state_from_settings(settings) extracted. build_state() and test-only app_state_for_settings() both delegate to it.
  • BoundedWriter (main.rs): std::io::Error::other(...) replaces Error::new(ErrorKind::Other, ...).
  • named_routes() (app.rs): replaced fn named_routes() -> [NamedRoute; 9] with const NAMED_ROUTES: &[NamedRoute]. No size literal to keep in sync; no stack copy per call.

CI: cargo fmt --all -- --check passes, cargo clippy --workspace --all-targets --all-features -- -D warnings passes, cargo test --workspace passes (1046 tests).

…PR14

Conflicts resolved:

- .claude/settings.json: take PR13's additional MCP allowlist entry
- main.rs (doc comment): merge AuthChallenge variant doc from PR13 into PR14's
  "legacy routes" wording
- main.rs (match arm): take PR13's Buffered | AuthChallenge combined arm;
  keep PR14's state.settings reference (legacy_main uses AppState)
- sourcepoint.rs: take PR13's collect_response_bounded fix; PR14 HEAD still
  had the old empty-body-on-stream bug that PR13 addressed
@prk-Jr prk-Jr requested a review from aram356 May 28, 2026 05:16

@aram356 aram356 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Reviewed PR14's own changes (11 files, ~1.8k insertions) — the diff against the stacked base feature/edgezero-pr13-integration-provider-type-migration, not the full main...HEAD superset. The dual-path entry point, middleware wiring, fail-closed consent handling, and test coverage are solid. Two documentation defects around the new max_buffered_body_bytes cap need fixing because they misrepresent real runtime behavior; the remaining items are non-blocking parity/clarity notes.

Blocking

🔧 wrench

  • trusted-server.toml comment contradicts the code default: says omit = unbounded, but the serde default is now Some(16 MiB) and overflow returns 500 (trusted-server.toml:17).
  • null (unbounded) is undocumentable: TOML has no null literal and #[serde(default)] makes omission yield 16 MiB, so the documented unbounded mode is unreachable via config (settings.rs:33).

Non-blocking

🤔 thinking

  • Geo 401-skip parity gap: EdgeZero skips geo for all 401s by status; legacy skips only its own AuthChallenge, leaving geo headers on origin-forwarded 401s (middleware.rs:162 vs main.rs).
  • Cap scope is publisher-fallback-only: integration-proxy passthrough on the EdgeZero path is not bounded by max_buffered_body_bytes (app.rs:222).

📝 note

  • PR description drift: the body references dispatch_with_config, but the code calls dispatch_with_config_handle (the pinned rev is correct).

CI Status

  • browser integration tests: PASS
  • integration tests: PASS
  • prepare integration artifacts: PASS
  • fmt / clippy / unit tests: not surfaced as separate PR checks; PR test plan reports them green locally

Comment thread trusted-server.toml Outdated
Comment thread crates/trusted-server-core/src/settings.rs Outdated
Comment thread crates/trusted-server-adapter-fastly/src/middleware.rs
Comment thread crates/trusted-server-adapter-fastly/src/app.rs Outdated

@ChristianPavilonis ChristianPavilonis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated Yesman review

This is an automated review by Yesman. I found blocking correctness issues in the new EdgeZero-enabled path. In particular, enabling the feature can drop duplicate response headers such as Set-Cookie, and the fallback router resolves the consent KV store before routes that do not require consent. Please address the inline findings before enabling/merging this path.

Comment thread crates/trusted-server-adapter-fastly/src/main.rs Outdated
Comment thread crates/trusted-server-adapter-fastly/src/app.rs Outdated
prk-Jr added 4 commits June 8, 2026 14:55
Integrate trusted-server-adapter-fastly/main.rs with PR13 API changes
(RouteResult, ProxyDispatchInput, handle_auction 7-arg signature).
Fix AppState/build_state wiring in app.rs and update three test helpers
in app.rs, middleware.rs, and main.rs to use inline test settings with
non-placeholder secrets instead of the embedded build-time TOML.
- Fix trusted-server.toml comment: max_buffered_body_bytes defaults to 16 MiB
  when omitted and responses exceeding the cap return 500, not unbounded
- Remove incorrect "null (unbounded)" language from Publisher settings doc
- Add parity note to resolve_geo_for_response explaining intentional divergence
  from legacy path (EdgeZero skips geo for all 401s, not just AuthChallenge ones)
- Add comment to dispatch_fallback clarifying integration-proxy responses bypass
  max_buffered_body_bytes (body already consumed upstream)
- Replace dispatch_with_config_handle with app.router().oneshot() in edgezero_main
  to avoid the fastly::Response round-trip that used set_header and silently
  dropped duplicate header values (e.g. multiple Set-Cookie headers)
- Add finalize_handle_preserves_duplicate_set_cookie_headers regression test
  verifying FinalizeResponseMiddleware does not collapse duplicate headers

@ChristianPavilonis ChristianPavilonis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review:

Review Summary

Reviewed PR #628 against feature/edgezero-pr13-integration-provider-type-migration, focusing on the new dual-path EdgeZero entry point, route registration, middleware/finalization, and config/proxy changes. The latest iteration resolves several prior review items, but the EdgeZero-enabled path still has blocking parity regressions around EC/identity routing and response finalization that can break cookie/consent behavior and leak internal EC API requests to the publisher origin.

Findings

Blocking findings are provided as inline comments. Because these are correctness/privacy/compatibility regressions on the feature-flagged production path, this automated review is submitting REQUEST_CHANGES.

CI / Existing Reviews

GitHub currently reports browser integration tests, integration tests, and prepare integration artifacts as passing. Existing reviews already cover several earlier issues; the inline comments below focus on regressions that remain in the current head and were not duplicates of the resolved comments.

Comment thread crates/trusted-server-adapter-fastly/src/app.rs
Comment thread crates/trusted-server-adapter-fastly/src/app.rs Outdated
Comment thread crates/trusted-server-adapter-fastly/src/main.rs Outdated

@aram356 aram356 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Round-2 review of the dual-path EdgeZero entry point. The design is sound and fail-safe — every config-store/flag-read error falls back to the legacy path, the flag is off by default, and the new middleware chain is well tested (header precedence, duplicate Set-Cookie preservation, 401 geo-skip, router-level 405 sentinel boundary). Verified locally against the PR13 base: cargo fmt clean, adapter-fastly 39/39 tests pass on wasm32-wasip1, core lib 1242/1242 pass; GitHub CI green.

Requesting changes on the parity/clarity items below before merge — none is a runtime regression while the flag stays off, but each should be resolved or explicitly tracked so the gap isn't lost.

Blocking

🔧 wrench

  • EdgeZero route/EC parity gap: missing named routes (/_ts/api/v1/identify, /_ts/api/v1/batch-sync, non-_ts admin aliases) and no EC identity lifecycle on the EdgeZero path — needs a tracked parity checklist (#495) and an honest route-inventory doc (app.rs:308, inline).
  • max_buffered_body_bytes unbounded mode unreachable from config (settings.rs:40, inline).
  • expect() messages don't follow the "should …" convention (middleware.rs:221/223, inline).

❓ question

  • /health shortcut also changes the legacy path, which is documented as byte-for-byte preserved (main.rs:136, inline).
  • Lockfile churn: crates/integration-tests/Cargo.lock bumps ~30 unrelated transitive deps (anstream, clap, hashbrown 0.16, etc.) beyond the edgezero rev pin. Was this an intentional --locked CI fix, or an incidental cargo update? Please confirm the broad bump is intended.

CI Status

  • fmt: PASS (verified locally)
  • adapter-fastly tests (wasm32-wasip1): PASS — 39/39
  • core lib tests: PASS — 1242/1242
  • GitHub integration checks: PASS (3/3)

Comment thread crates/trusted-server-adapter-fastly/src/app.rs
Comment thread crates/trusted-server-adapter-fastly/src/main.rs
Comment thread crates/trusted-server-core/src/settings.rs Outdated
Comment thread crates/trusted-server-adapter-fastly/src/middleware.rs Outdated
prk-Jr added 3 commits June 12, 2026 17:19
- Document EdgeZero parity gaps: add a "Not yet ported (legacy-only)"
  section to the app.rs route inventory enumerating the missing EC API
  routes and EC identity lifecycle, referenced from fastly.toml and
  tracked in issue #495
- Register legacy /admin/keys/rotate and /admin/keys/deactivate aliases
  in NAMED_ROUTES with an auth-gating parity test
- Make publisher.max_buffered_body_bytes a plain usize so the dead
  unbounded mode is unrepresentable; drop the dead None arm in
  resolve_publisher_response_buffered
- Reword apply_finalize_headers expect() messages to the "should ..."
  convention
- Clarify legacy_main doc comment: /health was already short-circuited
  before routing pre-PR14; only logger init moved after the probe
- Minimize integration-tests Cargo.lock churn to just the edgezero rev
  pin update (reverts ~30 unrelated transitive bumps)
Resolves the PR14 P0 parity findings: the EdgeZero path previously used
EcContext::default() with no KV graph or partner registry, omitted the EC
API routes, and never ran ec_finalize_response or pull sync. The EC
subsystem landed on main (#621) after this branch's EdgeZero wiring was
written, so the merge from PR13 stubbed the new EC-aware signatures.

- Register POST /_ts/api/v1/batch-sync and GET/OPTIONS /_ts/api/v1/identify
  as named routes mirroring the legacy arms (identity graph, partner
  registry, rate limiter, CORS)
- Add build_ec_request_state reproducing the legacy pre-routing prelude:
  device signals, bot gate, ts-eids/sharedid cookie capture, geo lookup,
  EcContext creation, and KV-graph gating
- Wire EcContext, KvIdentityGraph, and PartnerRegistry into handle_auction
  and integration proxy dispatch; generate EC IDs for browser navigations
  before the publisher fallback
- Thread EcFinalizeState through response extensions so edgezero_main runs
  ec_finalize_response and the pull-sync hook on the converted fastly
  response, mirroring legacy_main
- Derive Clone on EcContext (required for http response extensions)
- Document the remaining intentional deviations (401 auth challenges skip
  EC finalize, buffered streaming, router-level 405s) in the app.rs module
  docs and update the fastly.toml flag comment
- Add parity tests proving the EC API routes never reach the publisher
  fallback and that responses carry EcFinalizeState
The shared integration build environment check requires direct
dependencies shared between the workspace and the integration-tests
crate to resolve to the same versions. Bump log (0.4.29 -> 0.4.32) and
serde_json (1.0.149 -> 1.0.150) to match the workspace lock file, which
picked them up with the edgezero rev update. Verified locally with
scripts/check-integration-dependency-versions.sh.
@prk-Jr

prk-Jr commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator Author

Re the lockfile churn question: the broad bump was an incidental cargo update while fixing the --locked CI failure — not required. Reverted in 4fbafd5: the integration-tests lock now differs from the PR13 base only by the edgezero rev pins, plus targeted --precise bumps of log (0.4.32) and serde_json (1.0.150) in cc9a2e5, which scripts/check-integration-dependency-versions.sh requires to match the workspace lock (the workspace picked those up with the edgezero rev update). Verified locally with the script.

@ChristianPavilonis ChristianPavilonis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review: I reviewed PR #628 at head cc9a2e5 against base feature/edgezero-pr13-integration-provider-type-migration, including the EdgeZero entry-point dispatch, route table, middleware/finalization path, EC lifecycle wiring, config/settings changes, and existing review feedback. I did not find any blocking correctness, security, data-loss, authorization, or severe compatibility issue in the current head. I left two P2 inline comments for follow-up: one outbound-header parity concern caused by the temporary trusted fastly-ssl scheme signal, and one configuration/documentation mismatch around the new publisher buffering limit versus the existing 10 MiB platform response cap. CI shown by GitHub is passing for browser integration tests, integration tests, and prepare integration artifacts; I did not rerun the full local test suite in this automated review pass.

Comment thread crates/trusted-server-adapter-fastly/src/main.rs
Comment thread crates/trusted-server-core/src/settings.rs Outdated
The EdgeZero entry point re-injects fastly-ssl from trusted Fastly TLS
metadata so in-process scheme detection works after header sanitization.
The publisher fallback forwarded the client request verbatim, leaking this
internal edge signal to publisher backends — the legacy path never re-added
it. Strip the header after RequestInfo extracts the scheme and before the
outbound send, restoring legacy outbound-header parity. Add a regression
test asserting the header is not forwarded.

Also clarify the max_buffered_body_bytes doc comment: the effective ceiling
for a publisher page on Fastly is bounded by the platform HTTP client's
10 MiB raw-body cap, which runs before this decoded-output buffer. Raising
the value only helps highly compressible pages whose decoded size exceeds
the 16 MiB default while their compressed origin body stays under 10 MiB.

@ChristianPavilonis ChristianPavilonis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review: I reviewed PR #628 against feature/edgezero-pr13-integration-provider-type-migration at head afecb5db7cd1df1c1e09d4eddccd0204f303ac74, including the EdgeZero entry point, router/middleware wiring, EC finalization path, publisher fallback buffering, config changes, CI status, and existing review threads. I did not find a blocking correctness/security issue requiring changes before merge, but I found one high-priority resource-limit gap in the new buffering cap that should be fixed or explicitly scoped before relying on the EdgeZero flag for bounded publisher fallback behavior. See inline comment.

Comment thread crates/trusted-server-adapter-fastly/src/main.rs
…sing path

The buffering cap was only applied when the EdgeZero entry point converted a
streaming publisher response into a buffered one. The shared
handle_publisher_request BufferedProcessed arm — taken for HTML responses with
a registered post-processor such as NextJS — decoded and re-wrote the body into
an unbounded Vec, so a highly-compressible origin response under the platform
raw-body cap could expand past max_buffered_body_bytes and exhaust the Wasm
heap.

Move BoundedWriter into trusted-server-core and apply it in the
BufferedProcessed arm so both that path and the EdgeZero stream-to-buffer
conversion enforce the same configured limit. Add a regression test driving a
registered HTML post-processor with a response that exceeds a small cap and
asserting the request errors instead of allocating past the limit.

@ChristianPavilonis ChristianPavilonis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review: I reviewed PR #628 against feature/edgezero-pr13-integration-provider-type-migration, focusing on the current dual-path EdgeZero entry point, EC/publisher fallback parity, and the latest buffering/config changes. CI checks surfaced by GitHub are green, and most previously raised blockers appear addressed. I found one blocking reliability issue: the new publisher.max_buffered_body_bytes guard is applied after the HTML post-processor has already accumulated the full decoded document, so the cap does not actually prevent the Wasm-heap OOM class it is intended to mitigate. I also left one non-blocking operator-doc correction.

integration_registry,
};
let mut output = Vec::new();
let mut output = BoundedWriter::new(settings.publisher.max_buffered_body_bytes);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review: P1 / High — max_buffered_body_bytes is enforced after the post-processor accumulator has already grown.

BoundedWriter only sees bytes emitted by process_response_streaming, but the HTML path with registered post-processors first appends every rewritten chunk to HtmlWithPostProcessing::accumulated_output and returns Vec::new() until the final chunk. For a highly-compressible HTML page, that internal Vec can grow far beyond publisher.max_buffered_body_bytes before this writer gets a chance to reject the response. That leaves the EdgeZero publisher fallback vulnerable to the same Wasm-heap OOM failure this setting is meant to prevent.

Please thread the configured limit into the HTML post-processing accumulator and check before accumulated_output.extend_from_slice(...) (and account for the final post-processor output as well), then update the regression test so it proves the accumulator itself cannot grow past the cap rather than only asserting that the final write fails.

Comment thread trusted-server.toml
origin_url = "https://origin.test-publisher.com"
proxy_secret = "change-me-proxy-secret"
# Maximum bytes buffered when processing a streaming publisher response on the EdgeZero path.
# Defaults to 16 MiB when omitted; responses exceeding the cap return 500.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review: P3 / Low — this example documents the wrong failure status.

BoundedWriter errors are converted through TrustedServerError::Proxy, whose status_code() is 502 Bad Gateway, not 500. Operators may build alerts or runbooks from this sample config, so the comment should say 502 or more generally 5xx proxy error unless the code intentionally maps this cap failure to 500.

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.

Fastly entry point switch (dual-path with flag)

3 participants