Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
cf16418
Add request phase timing design spec and implementation plan (#1069)
jevansnyc Aug 25, 2026
052eada
Add RequestTimings phase collection and Server-Timing rendering
jevansnyc Aug 25, 2026
7505fb8
Add observability settings and decouple tinybird access and auction e…
jevansnyc Aug 25, 2026
4ce413f
Add test coverage for the access_enabled without tinybird.enabled rej…
jevansnyc Aug 25, 2026
f83092a
Emit Server-Timing at the send freeze point on conclusively private r…
jevansnyc Aug 25, 2026
4b19c44
Record filter and geo spans and dedupe the per-request geo lookup
jevansnyc Aug 25, 2026
ce295f1
Record origin, template cache, and KV phase spans in core
jevansnyc Aug 25, 2026
c222ae2
Capture stream duration, auction wait placement, and response bytes
jevansnyc Aug 25, 2026
8b028bb
Add access telemetry snapshot, route classes, and coarse route templates
jevansnyc Aug 25, 2026
48acd81
Emit confirmed access telemetry rows after pull-sync post-send
jevansnyc Aug 25, 2026
72d5755
Extend access_logs_raw with phase columns and a non-null sorting key
jevansnyc Aug 25, 2026
c50be03
Widen time_elapsed_ms to nullable so dropped snapshots cannot quarant…
jevansnyc Aug 25, 2026
0aead79
Emit Server-Timing from the Axum terminal layer with adapter-specific…
jevansnyc Aug 25, 2026
e9cf5b9
Document the observability and access telemetry configuration surface
jevansnyc Aug 25, 2026
7942c74
Normalize telemetry method, guard zero sample rate, and mirror geo wr…
jevansnyc Aug 26, 2026
d8fcb5e
Add a local dev config envelope generator example
jevansnyc Aug 26, 2026
7cf7d86
Add JSONPaths and expression sorting key to the access datasource
jevansnyc Aug 26, 2026
600746f
Use web_time Instant on request timing paths
jevansnyc Aug 26, 2026
3d7e697
Reject opaque identifier segments in publisher route templates
jevansnyc Aug 26, 2026
38043d7
Address access telemetry review feedback
jevansnyc Aug 26, 2026
274241b
Add auction timeline offsets spec amendment
jevansnyc Aug 26, 2026
29d45e8
Add auction timeline offsets implementation plan
jevansnyc Aug 26, 2026
46911a6
Record T0-anchored auction timeline offsets in the access row
jevansnyc Aug 26, 2026
1fa9f8c
Remove generated integration wrangler config from tracking
jevansnyc Aug 26, 2026
2ef7635
Reject single-segment publisher paths in route templates
jevansnyc Aug 28, 2026
082461d
Sample access rows with real randomness at the snapshot rate
jevansnyc Aug 28, 2026
f3ee473
Drop the origin span before error-path auction telemetry
jevansnyc Aug 28, 2026
46b3c7f
Address remaining review feedback on timing surfaces and docs
jevansnyc Aug 28, 2026
a0a3af5
Merge remote-tracking branch 'origin/main' into feat/request-phase-ti…
ChristianPavilonis Aug 31, 2026
c6235b9
Enforce access telemetry body limit
ChristianPavilonis Aug 31, 2026
576ef33
Merge remote-tracking branch 'origin/feat/request-phase-timing' into …
ChristianPavilonis Sep 4, 2026
ca46e4f
Format auction timeline implementation plan
ChristianPavilonis Sep 4, 2026
6f133cc
Merge origin/main into feat/request-phase-timing
jevansnyc Sep 8, 2026
bcc1475
Bound route templates by allowlist and harden the telemetry config su…
jevansnyc Sep 8, 2026
a152367
Address round-3 telemetry robustness findings
jevansnyc Sep 8, 2026
bdb68dd
Merge feat/request-phase-timing and resolve auction timeline review f…
jevansnyc Sep 17, 2026
58f8a35
Correct the section 18 status line and give the wrangler ignore its o…
jevansnyc Sep 17, 2026
589d6cc
Merge main into feat/request-phase-timing
jevansnyc Sep 17, 2026
dae88a0
Improve GPT auction diagnostics observability
ChristianPavilonis Sep 4, 2026
30ccab8
Fix GPT auction diagnostics timing accuracy
ChristianPavilonis Sep 4, 2026
563670d
Address GPT diagnostics review feedback
ChristianPavilonis Sep 14, 2026
628ce28
Clarify GPT auction diagnostics evidence
ChristianPavilonis Sep 8, 2026
b316aee
Address GPT diagnostics review feedback
ChristianPavilonis Sep 14, 2026
9b5bb41
Align GPT diagnostics tests with current metadata
ChristianPavilonis Sep 17, 2026
54d33ad
Hide placeholder dimensions from diagnostics overlay
ChristianPavilonis Sep 17, 2026
3e8cde2
Update crates/trusted-server-js/lib/src/integrations/prebid/index.ts
ChristianPavilonis Sep 20, 2026
a7b19ca
Address remaining GPT diagnostics review feedback
ChristianPavilonis Sep 21, 2026
f951955
Fix cross-adapter GPT diagnostic timing origins
ChristianPavilonis Sep 21, 2026
0fa7dfb
Clarify Prebid diagnostics export boundaries
ChristianPavilonis Sep 25, 2026
2791ceb
Use shared EdgeZero request timing across adapters
ChristianPavilonis Sep 28, 2026
2abb8e8
Merge console timing updates into clarity
ChristianPavilonis Oct 1, 2026
3a8b8ba
Use neutral labels for unobserved server clocks
ChristianPavilonis Oct 2, 2026
74a8f44
Merge origin/main into feat/request-phase-timing
jevansnyc Oct 5, 2026
ee66fbf
Combine request timing and GPT diagnostics stack
ChristianPavilonis Oct 8, 2026
e6d51e3
Integrate main and address timing and diagnostics review
ChristianPavilonis Oct 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,7 @@ src/*.html
# leftover local build artifacts (node_modules, target, dist) that remain on disk.
/crates/js/
/crates/integration-tests/

# Wrangler config generated by the Cloudflare integration-test harness from
# wrangler.toml at run time; regenerated on every run.
Comment on lines +67 to +68

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.

⛏ nitpick: The harness renders this file from wrangler.ci.toml (crates/trusted-server-integration-tests/tests/environments/cloudflare.rs:26-27), not wrangler.toml.

Suggested change
# Wrangler config generated by the Cloudflare integration-test harness from
# wrangler.toml at run time; regenerated on every run.
# Wrangler config generated by the Cloudflare integration-test harness from
# wrangler.ci.toml at run time; regenerated on every run.

wrangler.integration.generated.toml
23 changes: 12 additions & 11 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 8 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,14 @@ cssparser = "0.36"
derive_more = { version = "2.0", features = ["display", "error"] }
directories = "5"
ed25519-dalek = { version = "2.2", features = ["rand_core"] }
edgezero-adapter-axum = { git = "https://github.com/stackpop/edgezero", rev = "683202c66948146ec360f490126f1d60f920288b", default-features = false }
edgezero-adapter-cloudflare = { git = "https://github.com/stackpop/edgezero", rev = "683202c66948146ec360f490126f1d60f920288b", default-features = false }
edgezero-adapter-fastly = { git = "https://github.com/stackpop/edgezero", rev = "683202c66948146ec360f490126f1d60f920288b", default-features = false }
edgezero-adapter-spin = { git = "https://github.com/stackpop/edgezero", rev = "683202c66948146ec360f490126f1d60f920288b", default-features = false }
edgezero-cli = { git = "https://github.com/stackpop/edgezero", rev = "683202c66948146ec360f490126f1d60f920288b" }
edgezero-core = { git = "https://github.com/stackpop/edgezero", rev = "683202c66948146ec360f490126f1d60f920288b", default-features = false }
# Temporary integration pin for EdgeZero PR #389. Before merging TS into main,
# replace all six pins with the approved EdgeZero release tag and revalidate.
edgezero-adapter-axum = { git = "https://github.com/stackpop/edgezero", rev = "ab444946fcacb1d44c020c6079abb6ba29231402", default-features = false }
edgezero-adapter-cloudflare = { git = "https://github.com/stackpop/edgezero", rev = "ab444946fcacb1d44c020c6079abb6ba29231402", default-features = false }
edgezero-adapter-fastly = { git = "https://github.com/stackpop/edgezero", rev = "ab444946fcacb1d44c020c6079abb6ba29231402", default-features = false }
edgezero-adapter-spin = { git = "https://github.com/stackpop/edgezero", rev = "ab444946fcacb1d44c020c6079abb6ba29231402", default-features = false }
edgezero-cli = { git = "https://github.com/stackpop/edgezero", rev = "ab444946fcacb1d44c020c6079abb6ba29231402" }
edgezero-core = { git = "https://github.com/stackpop/edgezero", rev = "ab444946fcacb1d44c020c6079abb6ba29231402", default-features = false }
Comment on lines +59 to +66

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.

🔧 wrench: The pinned EdgeZero rev cannot build the Fastly adapter.

ab444946 (feat/shared-request-timing, EdgeZero #389, still open with changes requested and not mergeable) has the request-timing collector but not edgezero_adapter_fastly::lifecycle, which main needs since #1179: it is 2 commits ahead of and 1 behind main's pin 683202c. Every Fastly build fails with cannot find 'lifecycle' in 'edgezero_adapter_fastly' at sandbox.rs:61, main.rs:118 and main.rs:139, which is the root of all four red checks (see the CI section in the review body).

For review I rebuilt the combination locally (683202c merged with ab444946 merges cleanly) and patched it in. clippy-fastly, test-fastly (262 adapter + 2,950 core), test-fastly-reuse, and the Axum/Cloudflare/Spin clippy and test aliases all pass against it, so the code is sound on that base. Nothing reachable from this PR builds it, though.

Merge gate, as the comment above already says: repin all six crates to an EdgeZero release, or at least a pushed commit that contains both #379 and #389, and get CI green including the integration and browser jobs. When regenerating the lock, use a scoped update and restore the unrelated edges this one rewrote (windows-sys 0.61.2 -> 0.48.0 in two places, hashbrown 0.17.1 -> 0.16.1). Until then, consider converting the PR to draft so it can't be merged by accident.

env_logger = "0.11"
error-stack = "0.6"
esi = "0.7.2"
Expand Down
5 changes: 3 additions & 2 deletions crates/trusted-server-adapter-axum/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,16 @@ path = "src/main.rs"

[dependencies]
async-trait = { workspace = true }
axum = { workspace = true }
edgezero-adapter-axum = { workspace = true, features = ["axum"] }
edgezero-core = { workspace = true }
error-stack = { workspace = true }
futures = { workspace = true }
log = { workspace = true }
reqwest = { workspace = true }
simple_logger = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread", "macros", "sync", "time"] }
tokio = { workspace = true, features = ["rt-multi-thread", "macros", "net", "signal", "sync", "time"] }
tower = { workspace = true, features = ["util"] }
trusted-server-core = { workspace = true }

[dev-dependencies]
Expand All @@ -36,4 +38,3 @@ axum = { workspace = true }
base64 = { workspace = true }
temp-env = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
tower = { workspace = true, features = ["util"] }
50 changes: 41 additions & 9 deletions crates/trusted-server-adapter-axum/src/app.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use core::future::Future;
use std::sync::Arc;

use edgezero_adapter_axum::service::EdgeZeroAxumService;
use edgezero_core::app::Hooks;
use edgezero_core::context::RequestContext;
use edgezero_core::error::EdgeError;
Expand Down Expand Up @@ -615,16 +616,10 @@ impl Hooks for TrustedServerApp {
"TrustedServer"
}

/// Returns the bare router without terminal timing; serve with
/// [`Self::dev_server_service`] to preserve `Server-Timing` emission.
fn routes() -> RouterService {
let state = match build_state() {
Ok(s) => s,
Err(ref e) => {
log::error!("failed to build application state: {:?}", e);
return startup_error_router(e);
}
};

build_router(&state)
Self::routes_with_server_timing_flag().0
}
}

Expand All @@ -646,6 +641,43 @@ impl TrustedServerApp {
Ok(build_router(&state))
}

/// The dev server's fully configured tower service: the application
/// router wrapped in the terminal timing layer
/// ([`crate::timing::TimingService`]), with `server_timing_enabled`
/// read from the same settings snapshot that built the router.
///
/// This is the standard construction path for serving this adapter.
/// [`Hooks::routes`] satisfies the `Hooks` trait contract and returns
/// the bare router without the timing layer; callers who serve traffic
/// should use this instead so `server_timing_enabled` is never
/// silently discarded.
#[must_use]
pub fn dev_server_service() -> crate::timing::TimingService<EdgeZeroAxumService> {
let (router, server_timing_enabled) = Self::routes_with_server_timing_flag();
crate::timing::TimingService::new(EdgeZeroAxumService::new(router), server_timing_enabled)
}

/// Build the router alongside whether `Server-Timing` emission is
/// enabled, read from the same settings snapshot used to build the
/// router.
///
/// The Axum dev server's terminal timing layer ([`crate::timing`]) needs
/// this flag once at startup. The Axum dev server builds its application
/// state once and reuses the same [`RouterService`] for every request.
#[must_use]
fn routes_with_server_timing_flag() -> (RouterService, bool) {
let state = match build_state() {
Ok(s) => s,
Err(ref e) => {
log::error!("failed to build application state: {:?}", e);
return (startup_error_router(e), false);
}
};

let server_timing_enabled = state.settings.observability.server_timing_enabled;
(build_router(&state), server_timing_enabled)
}

/// Build the full router with explicit settings and runtime services.
///
/// Each request receives a clone of the supplied services, allowing callers
Expand Down
3 changes: 3 additions & 0 deletions crates/trusted-server-adapter-axum/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ pub mod app;
pub mod middleware;
/// Platform-trait implementations backed by env vars and `reqwest`.
pub mod platform;
/// Terminal timing layer wrapping the Axum dev server's tower `Service`
/// boundary with the request-phase `Server-Timing` freeze point.
pub mod timing;
72 changes: 68 additions & 4 deletions crates/trusted-server-adapter-axum/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
use edgezero_adapter_axum::dev_server::{AxumDevServer, AxumDevServerConfig};
use edgezero_core::app::Hooks as _;
use std::net::SocketAddr;

use axum::Router;
use edgezero_adapter_axum::dev_server::AxumDevServerConfig;
use edgezero_adapter_axum::service::EdgeZeroAxumService;
use tokio::net::TcpListener;
use tokio::runtime::Builder as RuntimeBuilder;
use tokio::signal;
use tower::Service as _;
use tower::service_fn;
use trusted_server_adapter_axum::app::TrustedServerApp;
use trusted_server_adapter_axum::timing::TimingService;

#[allow(clippy::print_stderr)]
fn main() {
Expand All @@ -20,13 +29,68 @@ fn main() {
};

log::info!("Listening on http://{}", config.addr);
let router = TrustedServerApp::routes();
if let Err(err) = AxumDevServer::with_config(router, config).run() {
let service = TrustedServerApp::dev_server_service();
if let Err(err) = run(service, config) {
log::error!("trusted-server-adapter-axum failed: {err}");
std::process::exit(1);
}
}

/// Runs the Axum dev server with the request-phase timing terminal layer
/// ([`trusted_server_adapter_axum::timing::TimingService`]) wrapped around
/// `EdgeZeroAxumService`, ahead of `axum::serve`.
///
/// This does not use `edgezero_adapter_axum::dev_server::AxumDevServer::run`:
/// that helper only accepts a bare [`RouterService`] and builds its own
/// `EdgeZeroAxumService` and `axum::Router` internally, with no seam for an
/// outer service wrapper. Router-generated 404/405 responses bypass
/// `RouterBuilder::middleware` (see `trusted_server_adapter_axum::timing`),
/// so the freeze point has to wrap the tower `Service` boundary itself.
/// Driving `axum::serve` directly here mirrors that helper's own internal
/// bind/wrap/serve/shutdown sequence closely enough to keep behavior
/// identical for callers (`PORT` env var, ctrl-c graceful shutdown).
///
/// # Errors
///
/// Returns an error if the Tokio runtime fails to start, the listener fails
/// to bind, or the underlying serve loop errors.
fn run(
service: TimingService<EdgeZeroAxumService>,
config: AxumDevServerConfig,
) -> std::io::Result<()> {
let runtime = RuntimeBuilder::new_multi_thread().enable_all().build()?;
runtime.block_on(serve(service, config))
}

async fn serve(
service: TimingService<EdgeZeroAxumService>,
config: AxumDevServerConfig,
) -> std::io::Result<()> {
let listener = TcpListener::bind(config.addr).await.map_err(|error| {
std::io::Error::new(
error.kind(),
format!("failed to bind dev server to {}: {error}", config.addr),
)
})?;

let axum_router = Router::new().fallback_service(service_fn(move |req| {
let mut svc = service.clone();
async move { svc.call(req).await }
}));
let make_service = axum_router.into_make_service_with_connect_info::<SocketAddr>();

let server = axum::serve(listener, make_service);
if config.enable_ctrl_c {
server
.with_graceful_shutdown(async {
let _ctrl_c = signal::ctrl_c().await;
})
.await
} else {
server.await
}
}

/// Read a port number from the `PORT` environment variable.
///
/// Returns `None` when the variable is unset. Exits non-zero if the value
Expand Down
Loading
Loading