Skip to content

Improve GPT auction diagnostics observability - #1121

Closed
ChristianPavilonis wants to merge 5 commits into
spec/auction-timeline-offsetsfrom
feature/ts-console-improvements
Closed

ChristianPavilonis wants to merge 5 commits into
spec/auction-timeline-offsetsfrom
feature/ts-console-improvements

Conversation

@ChristianPavilonis

@ChristianPavilonis ChristianPavilonis commented Sep 4, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Link every creative badge to the same stable Ad #N in the TS Console panel and export.
  • Add SSAT, SPA Trusted Server, client-side, and genuinely competing-auction classification with winning bidder and bucketed price facts.
  • Surface session-gated server auction timings alongside clearly named GAM callback timings, and clean up 1×1 and size terminology.

This is stacked on #1076, which is stacked on #1074. Retarget this PR to main after those dependencies merge.

EdgeZero dependency and release gate

The latest review feedback is addressed by stackpop/edgezero#389 and TS commit 2791ceb46908e6af04ccaae0c10b29d35336a9f4.

All six EdgeZero dependencies temporarily pin exact candidate commit 9c03cc59300363ae5339fc970dded08ede563e98 for integration testing. The lockfile resolves one EdgeZero core identity, with no local path overrides.

Do not merge this PR into main until a proper EdgeZero release is published. Replace all six temporary revision pins with that release tag, regenerate/review Cargo.lock, and rerun tag-backed verification first. Neither repository has been merged or released by this update.

Shared timing migration

  • EdgeZero owns the generic collector and shared attachment middleware. TS retains typed phase enums, auction facts, Server-Timing rendering and privacy policy.
  • One concrete request-extension handle carries the same clock and mutex across all four adapters. Removed the Cloudflare/Spin middleware copies without changing sanitization order or health method policy.
  • Axum now preserves a preinstalled collector through handler access and terminal header rendering. The regression failed before the fix and passes afterward.
  • Fastly's clock, streaming and header-finalization boundaries remain unchanged. The separate initial-document missing-collector follow-up is not included.

Verification of the migration

Independent joint reviews found no remaining issues. All 14 GitHub checks passed at 2791ceb46908e6af04ccaae0c10b29d35336a9f4, including full integration and browser jobs. Final local validation was tied to the exact committed source tree:

  • All four target-matched Rust adapter suites, CLI, cross-adapter parity, format and six native/WASM clippy aliases passed.
  • Core timing fixtures and the Axum preinstalled-origin regression passed. Full local integration and Fastly EC lifecycle gates passed.
  • Vitest: 899 passed. GPT diagnostics browser suite: 3 passed. Complete Next.js and WordPress suites: 20 and 10 passed, with only existing framework-selection skips.
  • JS build/format and docs format passed. EdgeZero candidate CI and local native/WASM timing verification passed.

Browser setup failures from regenerated Prebid artifacts and a temporary cache override were corrected without source changes or weakened assertions; the complete unchanged suites then passed. Runtime evidence uses local Viceroy/workerd/headless-browser harnesses, not deployed providers. TS Spin has native and WASM compile/lint evidence, not a deployed runtime test.

Changes

File Change
crates/trusted-server-core/src/publisher.rs Serialize initial and SPA auction timing facts, preserve generation safety, and gate SPA diagnostics on the active console session.
crates/trusted-server-js/lib/src/integrations/gpt/ Carry immutable auction facts through the initial scheduler and SPA page-bids path.
crates/trusted-server-js/lib/src/integrations/gpt_diagnostics/ Validate, retain, classify, clone, and present auction diagnostics with stable slot numbering.
crates/trusted-server-js/lib/test/ and browser integration tests Cover timing propagation, session gating, winner validation, classification, stable numbering, terminology, and export isolation.
docs/guide/integrations/gpt-diagnostics.md Document auction labels, timing origins, winner/privacy boundaries, size behavior, and browser callback semantics.

Closes

Closes #1081

Test plan

  • cargo test-fastly && cargo test-axum
  • cargo test-cloudflare
  • cargo clippy-fastly && cargo clippy-axum && cargo clippy-cloudflare
  • cargo fmt --all -- --check
  • JS tests: cd crates/trusted-server-js/lib && npx vitest run (899 passed, no type errors)
  • JS lint/build: npm run lint && npm run build
  • JS format: cd crates/trusted-server-js/lib && npm run format
  • Docs format/lint/build: cd docs && npm run format && npm run lint && npm run build
  • WASM build: cargo build --package trusted-server-adapter-fastly --release --target wasm32-wasip1
  • Other: focused Next.js Playwright GPT diagnostics suite (3 passed)

Checklist

  • Changes follow CLAUDE.md conventions
  • No unwrap() added in production code
  • Logging conventions preserved; no println! / eprintln! added
  • New code has tests
  • No secrets or credentials committed

@prk-Jr prk-Jr 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

Solid, well-tested addition: stable Ad #N identity, auction classification, and server auction timings all land behind the existing activation gate, with a normalization boundary and tests for the malformed cases. No correctness, security, or WASM-compatibility problem found; all 14 CI checks pass. The findings below are all non-blocking — the substantive ones are about the SPA timing anchor and label accuracy in a tool whose value is precise facts.

1 of the inline comments below carries a one-click GitHub suggestion — use Commit suggestion to apply it as a commit on the PR branch. The remaining comments describe the fix in prose because the change touches test files, spans more than one hunk, or is a design choice rather than a mechanical edit.

Non-blocking

🤔 thinking

  • SPA auctionDispatchedMs is always 0 by construction — see inline at crates/trusted-server-core/src/publisher.rs:6748
  • Navigation T0 overstates the anchor — see inline at crates/trusted-server-js/lib/src/integrations/gpt_diagnostics/overlay.ts:246
  • The store infers ssat when auction facts are absent or malformed — see inline at crates/trusted-server-js/lib/src/integrations/gpt_diagnostics/store.ts:1129
  • 1x1 suppression conflates "GPT reported 1x1" with "GPT reported nothing" — see inline at crates/trusted-server-js/lib/src/integrations/gpt_diagnostics/presentation_helpers.ts:9

♻️ refactor

  • set_auction_diagnostics rebuilds the script cell and drops a debug prefix — see inline at crates/trusted-server-core/src/publisher.rs:3209 (suggestion)
  • Placement wire string has two sources of truth — see inline at crates/trusted-server-core/src/publisher.rs:6767

⛏ nitpick

  • Duplicated 5-arg / 6-arg recorder call — see inline at crates/trusted-server-js/lib/src/integrations/gpt/index.ts:1113
  • Browser spec traded away its only non-1x1 fill-size case — see inline at crates/trusted-server-integration-tests/browser/tests/nextjs/gpt-diagnostics.spec.ts:223

👍 praise

  • Stable Ad #N on re-entry, and fail-closed dispatch gating — see inline at crates/trusted-server-js/lib/src/integrations/gpt_diagnostics/store.ts:1201

Cross-cutting / body-level findings

  • 📝 Stacked base — this targets spec/auction-timeline-offsets (stacked on #1076 → #1074). Retarget to main after those land, as the description says. Nothing in the diff depends on that ordering beyond the base itself.
  • 📝 Coverage of the server write path is complete — write_bids_to_state has exactly two production call sites (collect_non_html_auction, collect_stream_auction) and both are now paired with set_auction_diagnostics, so there is no document path that commits bids without the timing facts. Verified by grep rather than assumed.

CI Status

  • browser integration tests: PASS
  • integration tests: PASS
  • integration tests (Fastly EC lifecycle): PASS
  • prepare integration artifacts: PASS
  • cargo test: PASS
  • cargo test (axum native): PASS
  • cargo test (ts CLI, native): PASS
  • cargo test (cross-adapter parity): PASS
  • cargo check (cloudflare native + wasm32-unknown-unknown): PASS
  • cargo check/build/test (spin native + wasm32-wasip1): PASS
  • cargo fmt: PASS
  • vitest: PASS
  • format-typescript: PASS
  • format-docs: PASS

gh pr checks --required returned no names for this base, so none of the above are annotated as branch-protection-required; all of them are gates CLAUDE.md treats as PR gates, and all pass.

Comment thread crates/trusted-server-core/src/publisher.rs Outdated
Comment thread crates/trusted-server-core/src/publisher.rs Outdated
Comment thread crates/trusted-server-core/src/publisher.rs
Comment thread crates/trusted-server-js/lib/src/integrations/gpt_diagnostics/overlay.ts Outdated
Comment thread crates/trusted-server-js/lib/src/integrations/gpt_diagnostics/store.ts Outdated
Comment thread crates/trusted-server-js/lib/src/integrations/gpt/index.ts

@prk-Jr prk-Jr 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.

Verdict: APPROVE

Follow-up to the detailed review above, correcting its verdict: none of its findings are blocking (no 🔧 wrench, no ❓ question), and all 14 CI checks pass, so this should have been submitted as an approval rather than a comment.

Every inline comment there stands as written and remains worth reading — 4 🤔 thinking, 2 ♻️ refactor (one as a one-click suggestion), 2 ⛏ nitpick, 1 👍 praise — but each is a merge-can-proceed observation. The two most substantive, if you want to pick any of them up here rather than in a follow-up:

  • SPA auctionDispatchedMs is a structural 0 (the clock is read on the line after it starts), which also makes auctionWaitMs a duplicate of auctionResolvedMs — crates/trusted-server-core/src/publisher.rs:6748.
  • Navigation T0 in the panel is the edge request-receipt offset, not the browser's navigationStart — crates/trusted-server-js/lib/src/integrations/gpt_diagnostics/overlay.ts:246.

Approved on ffc0438dd5f872e68c3ccad36d5c35bde47101d2.

@aram356

aram356 commented Sep 8, 2026 •

Copy link
Copy Markdown
Collaborator

@ChristianPavilonis please assign issue to this PR

@ChristianPavilonis ChristianPavilonis linked an issue Sep 11, 2026 that may be closed by this pull request
@aram356 aram356 added this to the 202609 milestone Sep 11, 2026
@aram356
aram356 self-requested a review September 12, 2026 00:24

@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

Solid, well-tested expansion of the GPT diagnostics console: stable Ad #N identity across badge/panel/export, auction classification, winner and bucketed-price facts, and server auction timings. The privacy boundary is careful — bounded string lengths, a validated price-bucket shape, and timing normalization at the diagnostics boundary.

Two blocking issues: the SPA timing origin can be derived wrongly after a failed or superseded navigation, labelling navigation-T0 offsets as SPA-auction offsets; and the browser spec loses its only normal-fill-size coverage.

None of the inline comments below carry a one-click GitHub suggestion. Every fix either lands outside the diff hunks or spans a second file, so each is described in prose with the proposed code.

Blocking

🔧 wrench

  • Stale navigation-T0 timings relabelled as SPA-auction timings — see inline at crates/trusted-server-js/lib/src/integrations/gpt/index.ts:1467
  • Browser spec loses all normal-fill-size coverage — see inline at crates/trusted-server-integration-tests/browser/tests/nextjs/gpt-diagnostics.spec.ts:223

Non-blocking

🤔 thinking

  • auctionDispatchedMs is structurally always 0 on the page-bids path — see inline at crates/trusted-server-core/src/publisher.rs:6748
  • Page-bids hardcodes auctionWaitPlacement as a bare string literal — see inline at crates/trusted-server-core/src/publisher.rs:6766
  • browser_session_active drops more eligibility guards than it needs to — see inline at crates/trusted-server-core/src/integrations/gpt_diagnostics.rs:296

♻️ refactor

  • Redundant if/else around recordTrustedServerOpportunity — see inline at crates/trusted-server-js/lib/src/integrations/gpt/index.ts:1115-1132

📝 note

  • Dead timing-origin fallback in the overlay — see inline at crates/trusted-server-js/lib/src/integrations/gpt_diagnostics/overlay.ts:239

Cross-cutting / body-level findings

  • 📝 Verified during review, no action needed — the following were checked against a scratch worktree at this head and are correct: slot-number reuse across eviction produces no slotOrder/slotActivityOrder duplicates at the MAX_DIAGNOSTIC_SLOTS boundary; set_auction_diagnostics cannot clobber the auction debug prefix (prepend_auction_debug_comment runs after it in collect_stream_auction, and collect_non_html_auction has no prepend); stripping the console cookie before handle_request_cookies is correctly ordered for the stated privacy goal; the added prepare_request call in handle_page_bids is genuinely idempotent via the extension cache, and all four adapters already call it pre-routing; the normalization boundary correctly rejects -1.00, 1e3, 1., .5 for priceBucket and negatives / NaN / > u32::MAX for timings; and elapsed_millis saturation (~49.7 days) is not a practical edge concern.

CI Status

  • browser integration tests: PASS
  • cargo check (cloudflare native + wasm32-unknown-unknown): PASS
  • cargo check/build/test (spin native + wasm32-wasip1): PASS
  • cargo fmt: PASS
  • cargo test: PASS
  • cargo test (axum native): PASS
  • cargo test (cross-adapter parity): PASS
  • cargo test (ts CLI, native): PASS
  • format-docs: PASS
  • format-typescript: PASS
  • integration tests: PASS
  • integration tests (Fastly EC lifecycle): PASS
  • prepare integration artifacts: PASS
  • vitest: PASS

Branch protection reports no required checks on this branch, so none of the above are marked (required).

Comment thread crates/trusted-server-js/lib/src/integrations/gpt/index.ts
Comment thread crates/trusted-server-core/src/publisher.rs Outdated
Comment thread crates/trusted-server-core/src/publisher.rs Outdated
Comment thread crates/trusted-server-js/lib/src/integrations/gpt/index.ts Outdated
Comment thread crates/trusted-server-core/src/integrations/gpt_diagnostics.rs
@ChristianPavilonis
ChristianPavilonis force-pushed the feature/ts-console-improvements branch from a758e16 to 563670d Compare September 17, 2026 16:17

@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

Second pass, against 563670df9 (the branch was rebased and Address GPT diagnostics review feedback added since the last review at ffc0438dd).

Five of the seven earlier findings are fixed, two were declined with sound reasoning, and I verified each outcome against the new head rather than taking the replies at face value. The 1×1 browser coverage came back stronger than what I proposed, and dropping ?? 'ssat' from the store so malformed direct facts fail closed was a good unprompted improvement.

Two new blocking findings, both consequences of the timing fix rather than pre-existing issues: the guide now describes a clock anchor the code does not use, and two of the four adapters silently fall back to a different anchor than the other two.

Verification of the previous round

Finding Outcome
🔧 Stale navigation-T0 timings relabelled as SPA-auction timings Fixed, re-verified. The original repro now passes: after a failed page-bids fetch ts.auctionDiagnostics is undefined, and the superseded-navigation path clears too. spa_hook.test.ts pins both.
🤔 auctionDispatchedMs structurally always 0 Partly fixed — now a real offset on Fastly and Axum, still structurally zero on Cloudflare and Spin. See finding B.
🤔 Hardcoded auctionWaitPlacement wire literal Fixed. Shared auction_wait_placement_wire mapper, and page-bids routes AuctionWaitPlacement::PreHeader through it.
♻️ Redundant if/else around the recorder call Fixed as proposed; the arity-sensitive assertions now pin { auctionType: 'ssat' } rather than a bare trailing undefined.
🔧 Browser spec lost all normal-fill-size coverage Fixed, and better than proposed. 300×250 is exercised and exported again, a separate 1×1 cycle proves the raw fact survives to snapshot/export while staying out of the rendered UI, and captureClosedShadowRoots is a tidy way to read the closed root.
📝 Dead timing-origin fallback in the overlay Declined — reasonable, it is harmless.
🤔 browser_session_active drops prefetch and bot guards Declined, and the rebuttal checks out. publisher.rs:6863 gates the auction on !is_bot && !is_prefetch, so the diagnostics block is unreachable for a bot or prefetch. Withdrawing it.

Local verification at this head: JS suite 899 passed / 0 failed, no type errors; the previous round's repro re-run against the new code; adapter RequestTimings wiring grepped across all four crates.

Blocking

🔧 wrench

  • Guide contradicts the code on the SPA clock anchor — see inline at docs/guide/integrations/gpt-diagnostics.md:156
  • Cloudflare and Spin silently fall back to a handler-entry clock — see inline at crates/trusted-server-core/src/publisher.rs:6689

CI Status

  • browser integration tests: PASS
  • cargo check (cloudflare native + wasm32-unknown-unknown): PASS
  • cargo check/build/test (spin native + wasm32-wasip1): PASS
  • cargo fmt: PASS
  • cargo test: PASS
  • cargo test (axum native): PASS
  • cargo test (cross-adapter parity): PASS
  • cargo test (ts CLI, native): PASS
  • format-docs: PASS
  • format-typescript: PASS
  • integration tests: PASS
  • integration tests (Fastly EC lifecycle): PASS
  • prepare integration artifacts: PASS
  • vitest: PASS

Branch protection reports no required checks on this branch, so none are marked (required).

Comment thread docs/guide/integrations/gpt-diagnostics.md Outdated
Comment thread crates/trusted-server-core/src/publisher.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

Third pass, against f951955f5. The two blocking findings from the previous round are both genuinely fixed, and I verified each against this head rather than from the replies: the guide and panel labels now describe the adapter server-request clock that the code actually uses, Cloudflare and Spin attach the collector, and handle_page_bids fails closed when an adapter omits it. The pre-aged-collector test closes the coverage gap I raised, and the timing design spec was updated unprompted so it does not go stale.

What remains is the shape of the fix rather than its correctness. RequestTimingMiddleware landed as two byte-identical copies in two adapter crates, which is the fourth instance of a duplication pattern this repo has been carrying, and the new middleware plus the collector it installs are framework-level concerns that belong in EdgeZero rather than in Trusted Server's adapters.

Verification performed at this head

Local runs: cargo test-cloudflare (22 passed), cargo test-spin (44 + 37 passed), cargo test-axum (17 + 1 + 25 passed), core page_bids filter (25 passed, including the new fail-closed test), page_bids_response_includes_auction_id_only_for_winning_bids (passed, confirming auctionDispatchedMs > 0 under the extension path), and the full JS suite (899 passed, no type errors). The first round's stale-timing repro still comes back clear, and both timing origins map to the corrected labels.

git diff 563670df9 f951955f5 is exactly the nine files addressing the previous round, with no incidental drift, so the surfaces cleared in earlier passes are unchanged.

Things that looked like problems and are not: the hardcoded "/health" matches the existing literal in the Fastly and Axum paths rather than introducing it; Spin's second router at app.rs:441 is the degraded-state 503 fallback where no auction runs; and the 2 ms thread::sleep in the async test has ample margin for a > 0 millisecond assertion with nothing else pending on that runtime.

Blocking

🔧 wrench

  • Move the request-timing middleware into EdgeZero instead of duplicating it per adapter — see inline at crates/trusted-server-adapter-cloudflare/src/middleware.rs:73

Non-blocking

🌱 seedling

  • The fail-closed timing invariant is enforced on only one of the two handlers that depend on it — see inline at crates/trusted-server-core/src/publisher.rs:6689

Cross-cutting / body-level findings

  • 📌 The other three adapter middlewares carry the same duplication — SanitizeRequestMiddleware, FinalizeResponseMiddleware, and AuthMiddleware are each duplicated across trusted-server-adapter-cloudflare/src/middleware.rs and trusted-server-adapter-spin/src/middleware.rs in the same way, and predate this PR. They are not this PR's to fix, but they are the reason the new middleware should not become a fourth instance: whatever mechanism carries RequestTimingMiddleware into shared code is the one that should eventually carry these too. Worth a follow-up issue so the pattern stops growing.

  • 📝 Where the generic/domain boundary falls, for whoever picks up the extraction — I mapped request_timing.rs against edgezero-core to check the move is actually possible. Generic and movable: new, record, span, mark_headers_ready, mark_request_elapsed, set_resp_bytes, server_timing_value, snapshot, and PhaseSpan. Domain-specific and staying in Trusted Server: Phase's eight variants (EcKv, AuctionWait, Origin, TemplateCacheLookup and the rest), AuctionWaitPlacement, record_auction_wait, set_auction_id, and the three mark_auction_* marks. edgezero-core already carries http, web-time, and async-trait, so uuid (used only by set_auction_id, which stays behind anyway) is the one dependency question, and it resolves itself once the auction marks stay on this side.

CI Status

  • browser integration tests: PASS
  • cargo check (cloudflare native + wasm32-unknown-unknown): PASS
  • cargo check/build/test (spin native + wasm32-wasip1): PASS
  • cargo fmt: PASS
  • cargo test: PASS
  • cargo test (axum native): PASS
  • cargo test (cross-adapter parity): PASS
  • cargo test (ts CLI, native): PASS
  • format-docs: PASS
  • format-typescript: PASS
  • integration tests: PASS
  • integration tests (Fastly EC lifecycle): PASS
  • prepare integration artifacts: PASS
  • vitest: PASS

Branch protection reports no required checks on this branch, so none are marked (required).

Comment thread crates/trusted-server-adapter-cloudflare/src/middleware.rs Outdated
Comment thread crates/trusted-server-core/src/publisher.rs
aram356 added a commit that referenced this pull request Sep 24, 2026
# Conflicts:
#	crates/trusted-server-core/src/publisher.rs
#	crates/trusted-server-js/lib/src/integrations/gpt_diagnostics/overlay.ts
#	crates/trusted-server-js/lib/test/integrations/gpt_diagnostics/overlay.test.ts
#	docs/guide/integrations/gpt-diagnostics.md

@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

Fourth pass, against 2791ceb46. The refactor I asked for last round is done, and done better than I sketched it: RequestTimings is now a thin typed facade over a generic edgezero_core::request_timing::RequestTimings<PHASE_COUNT, AuctionTimingData>, RequestTimingMiddleware is a type alias over the shared EdgeZero middleware, and both adapter copies are gone. The generic/domain boundary landed where it should — I grepped the new EdgeZero collector for auction|bid|publisher|ec_kv|trusted and it is clean, with phases reduced to slot indices and the auction facts carried as the payload type that stays on this side.

The one blocking problem is not the code, it is the dependency edge. All six EdgeZero crates are pinned to rev = "9c03cc59…", the head of EdgeZero PR #389, which is open, not an ancestor of main, and currently CHANGES_REQUESTED / BLOCKED with two API-shape items outstanding against exactly this surface. The PR cannot merge until that lands and the pin becomes a release tag.

Verification performed at this head

Local runs: cargo test-cloudflare (19 + 22 passed), cargo test-spin (44 + 37 passed), cargo test-axum (18 + 1 + 25 passed — the extra case is the new preinstalled-collector test), and the full JS suite (899 passed, no type errors).

Refactor-safety checks, since a rewrite this broad is where silent drift happens:

  • TimingSnapshot's public fields are byte-identical to the previous head, so the diagnostics payload contract is unchanged.
  • No stale get::<RequestTimings>() lookups remain anywhere in the workspace. Extensions now consistently hold RequestTimingHandle, nothing inserts the wrapper, and every reader goes through from_extensions.
  • The infallible contract survived the move to a Result-returning API: every call site discards TimingError with let _ =, so lock contention still drops the sample rather than panicking. The one production expect (span, line 166) is unreachable by construction — PHASE_COUNT is 8, Phase has exactly 8 variants, and index() is an exhaustive match — and carries a # Panics doc.
  • Axum now reuses a preinstalled handle instead of overwriting it, with a test asserting both the carried-over phase and the preserved origin. That matters because the collector can now be installed by middleware running earlier.
  • All three earlier rounds' fixes are still in place: the stale-timing clear (gpt/index.ts:1431), the fail-closed gate (publisher.rs:6685, used at 6924), and the corrected anchor labels (overlay.ts:247).

Blocking

🔧 wrench

  • Six crates pinned to an unmerged EdgeZero revision with outstanding requested changes — see inline at Cargo.toml:58

Non-blocking

🌱 seedling

  • The fail-closed timing invariant is still enforced on only one of its two handlers — see inline at crates/trusted-server-core/src/publisher.rs:4266

📝 note

  • Health-exclusion semantics now differ by adapter, with no observable consequence — see inline at crates/trusted-server-adapter-axum/src/timing.rs:84

CI Status

  • browser integration tests: PASS
  • cargo check (cloudflare native + wasm32-unknown-unknown): PASS
  • cargo check/build/test (spin native + wasm32-wasip1): PASS
  • cargo fmt: PASS
  • cargo test: PASS
  • cargo test (axum native): PASS
  • cargo test (cross-adapter parity): PASS
  • cargo test (ts CLI, native): PASS
  • format-docs: PASS
  • format-typescript: PASS
  • integration tests: PASS
  • integration tests (Fastly EC lifecycle): PASS
  • prepare integration artifacts: PASS
  • vitest: PASS

Branch protection reports no required checks on this branch, so none are marked (required). Note that CI is green against the pinned EdgeZero revision, so it validates a dependency state that is expected to change before this can merge.

Comment thread Cargo.toml
Comment on lines +58 to +65
# 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 = "9c03cc59300363ae5339fc970dded08ede563e98", default-features = false }
edgezero-adapter-cloudflare = { git = "https://github.com/stackpop/edgezero", rev = "9c03cc59300363ae5339fc970dded08ede563e98", default-features = false }
edgezero-adapter-fastly = { git = "https://github.com/stackpop/edgezero", rev = "9c03cc59300363ae5339fc970dded08ede563e98", default-features = false }
edgezero-adapter-spin = { git = "https://github.com/stackpop/edgezero", rev = "9c03cc59300363ae5339fc970dded08ede563e98", default-features = false }
edgezero-cli = { git = "https://github.com/stackpop/edgezero", rev = "9c03cc59300363ae5339fc970dded08ede563e98" }
edgezero-core = { git = "https://github.com/stackpop/edgezero", rev = "9c03cc59300363ae5339fc970dded08ede563e98", default-features = false }

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 — This pins six crates to an unmerged EdgeZero revision that has outstanding requested changes, so the PR cannot merge as it stands.

The comment above is honest that the pin is temporary, and stacking a cross-repo change this way is the right shape. Two facts make it blocking rather than a bookkeeping note, though:

9c03cc59 is the head of EdgeZero PR #389, which is open and not an ancestor of main (git merge-base --is-ancestor 9c03cc59 origin/main fails). So a cargo build from a clean checkout resolves a revision that exists only on an unmerged branch.

More importantly, #389 currently reports reviewDecision: CHANGES_REQUESTED and mergeStateStatus: BLOCKED, with two 🔧 items outstanding against exactly the surface this PR consumes:

  • TimingError is not #[non_exhaustive], inconsistent with EdgeError / KvError / ConfigStoreError, and adding it after a tag is a breaking change.
  • t0 should be hoisted out of the mutex so elapsed() becomes infallible.

Neither of those touches Trusted Server's call sites at this revision, so the code here is compatible today. But both change the published API shape, which means the revision this PR pins is expected to be rewritten before it can be tagged. Merging now would land a dependency edge that is known to be going away.

What I would ask for, in preference order:

  1. Land Convert VCL into Rust code in TS to not require extra CDN VCL hop and increase reponse times #389 with those two items addressed, cut the release tag, and repin all six entries to it. That is the end state the comment already describes, and it is the only one where this PR is self-consistently mergeable.
  2. If you would rather not block this PR on an EdgeZero release, the alternative is to split: merge the diagnostics work with the middleware hosted in trusted-server-core (a pure move, no API decisions, no external pin), and let the EdgeZero extraction land separately once Convert VCL into Rust code in TS to not require extra CDN VCL hop and increase reponse times #389 settles. I raised that option last round and it remains available.

Either way, please keep the pin visible in the PR description or as a merge checklist item rather than only in this comment — a rev = pin is easy to lose track of, and the failure mode is silent (a stale rev keeps resolving long after the tag exists).

Apply manually — repinning depends on an EdgeZero release that does not exist yet, so there is no replacement text to suggest.

.get::<RequestTimings>()
.cloned()
.unwrap_or_default();
let timings = RequestTimings::from_extensions(req.extensions()).unwrap_or_default();

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.

🌱 seedling — Still carrying the asymmetry from the last round, now that the refactor has touched this exact line.

handle_page_bids keeps its has_request_timings gate (line 6685, used at 6924), so a missing collector there yields no timing facts. This handler still takes whatever unwrap_or_default() produces, with no equivalent check, so the initial-document path would publish handler-local offsets under the Initial document request T0 label if an adapter ever stopped attaching the collector.

I flagged this as non-blocking last round and it stays non-blocking: I re-confirmed every adapter attaches the collector at this revision (Fastly via main.rs before routing, Axum via its tower wrapper, Cloudflare and Spin via the shared middleware at their router registration sites), so nothing reaches the unguarded path today.

Recording it again only because the refactor rewrote this statement and the moment has passed twice now. With the collector installed by shared middleware rather than per-adapter code, the set of ways it can go missing is no longer obvious from reading any single crate — which is exactly when an invariant wants to be structural. Pushing the check down into set_auction_diagnostics, so both callers inherit it from one place, would be a smaller change than the guard at each call site and would close it permanently. A follow-up issue is fine; it does not belong in this PR.

// carries timing data, on any adapter.
// Bypass installation and finalization for `/health`, regardless of method.
// A preinstalled collector remains available to the inner service.
if req.uri().path() == HEALTH_PATH {

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.

📝 note — Worth recording what this costs in cross-adapter consistency, because the answer is "nothing observable" and that took checking.

Fastly's health bypass is GET-only and ignores the query string, and the new test at main.rs pins that. The shared middleware excludes by path for every method, and the Axum wrapper does the same, which the comment there states plainly ("regardless of method").

So on three adapters a POST /health skips timing entirely, while on Fastly it is timed and routed. I checked whether that difference is reachable: every adapter registers /health as GET-only (app.rs in Axum and Spin, the short-circuit in Fastly's main.rs), so a non-GET request to that path is a 405 everywhere and carries no timing worth collecting either way.

No action needed. Flagging it only so the divergence is a recorded decision rather than something a future reader has to re-derive, and because with_excluded_paths(&["/health"]) now repeats the literal at each registration site — which is the right call for policy that belongs to the adapter, but does mean four places to update if the probe path ever changes.

@ChristianPavilonis

Copy link
Copy Markdown
Collaborator Author

Superseded by #1261, which consolidates #1074 → #1076 → #1121 → #1154 and the review fixes. Closing this PR as requested; its branch and history are preserved. The EdgeZero dependency integration/release remains a blocker on the replacement PR.

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.

Improvements to TS_CONSOLE for ad observability

3 participants