Skip to content

November release candidate (DO NOT MERGE) - #1490

Draft
ChristianPavilonis wants to merge 229 commits into
mainfrom
rc/202611
Draft

ChristianPavilonis wants to merge 229 commits into
mainfrom
rc/202611

Conversation

@ChristianPavilonis

@ChristianPavilonis ChristianPavilonis commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

November 2026 release candidate. Keep draft. DO NOT MERGE.

Summary

Starts fresh from main, not from the October RC. Merges the five remaining open PRs agreed for this RC, one at a time, preserving each audited head in the branch history.

  • Combines SDK URL ownership fixes, request timing and GPT diagnostics, Prebid EID fidelity, server-side identity providers, and CI author queues.
  • Preserves current main's mobile tracing, authenticated public Origin, consent-cookie allowlist, absolute auction deadlines, and async HTML EOF finalization.
  • Requires an unreleased EdgeZero timing candidate and includes the identity KV schema v2 migration. This is an integration branch, not deployment approval.

Release scope

Base main: 51730bdaf841.

Merge order PR Included change Audited head
1 #1405 Match SDK URLs by vendor hostname and path 1f945068ab60
2 #1261 Consolidated request timing, access telemetry and GPT diagnostics 9654deb7ac4a
3 #1416 Prebid EID fidelity from browser capture to bidder request 88cc505daecc
4 #1428 Server-side identity provider design and implementation, KV v2, writer/stager lifecycle and EID mirror 08eb99c53afd
5 #1426 Per-PR cancellation, per-author job queues, timeouts and concurrency-policy checks d79392a49d3a

The source descriptions for #1428 and #1426 lag their audited heads. In particular, #1428 is no longer docs-only. The table describes the actual included revisions.

#1261 replaces the closed #1074, #1076, #1121 and #1154 timing/diagnostics stack. Closed #1260 is not merged separately; the current #1428 implementation supplies the identity work in this RC. Source PRs retain their own review and merge processes.

Integration decisions

  • Pin all six EdgeZero dependencies to the same full revision, 511ce06ff3df59996641af2ea64cbc54cdac7ea5, from EdgeZero #389. This candidate contains released main's lifecycle and pre-dispatch ingress APIs plus shared timing. Cargo resolves all eight EdgeZero packages to this single Git identity. Replace the temporary revisions with an approved release tag and revalidate before merging this RC.
  • Keep main's dependency-major updates, toolchain pins and explicit deadline APIs. Do not restore Consolidate request timing and GPT diagnostics #1261's obsolete EdgeZero pin or the identity branch's superseded deadline capability.
  • Preserve independent trace, timing and identity-mirror response members. Both scheduler implementations use trace transport in argument three and timing diagnostics in argument four. New combined-feature tests cover those contracts.
  • Retain main's consent-cookie-only PBS forwarding, which also excludes every identity mirror cookie. Do not broaden forwarding to arbitrary publisher cookies.
  • Reuse the same identity store when adding per-request timing. Forward rate-limit pauses through decorators. Post-send identity persistence is excluded from pre-send KV timing; access telemetry starts after the identity lifecycle finishes.
  • Preserve private trace output, async EOF cancellation and original dispatched-auction deadlines. Fix the empty-plan synchronous trace regression without changing split-dispatch semantics.
  • Share one process-wide test logger across incoming identity log assertions and main's thread-local warning captures.
  • Retain the shipping-artifact separation assertions. The combined Prebid shim is 54,447 raw bytes, 18,183 gzip bytes; its measured size budget remains below 54,500 bytes. Optional Prebid diagnostics listeners stay outside the ordinary shim.

Validation

Local checks pass with Rust 1.98.1, Node 24.20.0, Viceroy 0.21.0, Spin 4.1.0, Wrangler 4.151.3 and wasm-bindgen 0.2.126.

Check Outcome
Rust formatting and all eight adapter/CLI/codegen Clippy gates Pass
Native core 3,902 passed, 2 existing ignored
Fastly core under Viceroy 3,895 passed, 6 existing ignored
Fastly adapter, default and reusable sandbox 368 passed in each mode; the 5 separately gated network tests also pass
Native Axum, Cloudflare and Spin suites Pass
Cross-adapter parity 28 passed
CLI helper, including explicitly enabled ignored browser fixtures Pass
Native build-digest tests/lint and WASM clock lint Pass
Fastly absolute-deadline network fixtures All 5 pass
HTML EOF harness tests/lint and actual Axum/Fastly EOF/auction controls Pass, including Fastly release-mode streaming
Cloudflare and Spin auction-deadline fixtures WASM builds, strict lint and local runtime probes pass
JS Build, lint, formatting and scoped type assertions pass; 3,403 tests pass
Trace asset artifacts Explicit byte-for-byte and SHA-256 checks pass; the suite's one conditional default-output-directory skip is covered by this check
Docs and repository Markdown Build, lint and formatting pass
CI policy 13 concurrency tests, policy guard and ShellCheck pass; all 30 Node-pin tests and guard pass
Documentation proposal scripts ShellCheck, 20 Node tests and shell regressions pass
Changed-line URL-host gate against main Pass

The URL-host allowlist is unchanged. Narrow, explained per-line exceptions retain shared legacy test hosts, interpolated reserved-domain fixture URLs, a negative URL parser case, the default Tinybird endpoint assertion and a one-off cited source link. That URL-host follow-up changed comments only; native core and Fastly adapter tests, Fastly Clippy, formatting and docs checks pass again afterward. Other passing checks are reused with unchanged relevant runtime inputs.

All five audited source heads and the base main commit are ancestors of this RC. The working tree is clean. The table above records pre-publication local checks, not production acceptance.

CI follow-up

All 30 GitHub checks pass at 595dffea0, including CodeQL, Rust and JavaScript tests, browser integration, all trace-runtime acceptance tests, worker deadlines, HTML EOF, formatting and docs.

  • Regenerated the trace-asset source digest and added a manifest-freshness regression test. Served JS/CSS bytes are unchanged.
  • Removed data-derived identity and Tinybird metadata from the two flagged debug logs; added fixed-reason logging coverage. No open CodeQL alerts remain on the PR merge ref.
  • Restored transport-bound ingress at the custom Axum HTTP listener before trace preflight. Real-socket tests prove matching-origin actions succeed while cross-origin actions, spoofed forwarded headers and bare services without listener metadata remain rejected. The unchanged real Chromium capture/view/export/cleanup workflow passes.
  • Updated diagnostics browser assertions for passive badge evidence and its separate accessible handle, including outside-creative geometry and keyboard activation. No production diagnostics JS changed.

Release and rollout gates

  • Identity KV schema v2 is a breaking storage change. Once this binary writes v2 rows, rollback to binaries that cannot read them is unsupported. Review the identity-provider rollout and rollback documentation before activation.
  • The __Host-ts-eids-s client mirror is default-off. Provider consent policies, writer/stager behavior, pending removals, new-visitor hold behavior and provider-specific acceptance still need operator review.
  • Access telemetry remains opt-in. Deploy the v2 datasource and scoped APPEND token, coordinate any preview schema migration, and review timing and best-effort delivery semantics before enabling it. No production Tinybird change is performed here.
  • Provider acceptance of repeated EID sources/provenance fields, live Lockr/Prebid behavior, real-browser UI geometry/navigation, hosted edge runtimes and deployment propagation are not qualified by local tests.
  • Passing CI is not release approval. This draft does not merge or close any source PR, publish a release, migrate production storage or deploy anything.

jevansnyc and others added 30 commits August 24, 2026 19:02
* Add request phase timing design spec (Server-Timing subtimings + access telemetry)

* Address review round 1: freeze point, template-cache naming, snapshot semantics, KV scope, geo carry, route template, sink confirmation, sampling and query model, config rollback

* Address review round 2: auction-wait placement modes, conservative private-only header emission, non-null sorting key with service identity, coarse publisher route template, telemetry snapshot and outage behavior, tinybird flag decoupling, adapter phase semantics

* Add request phase timing implementation plan

* Address engineer review: KV timing decorator, try_lock sampling, route metadata extension, adapter-derived env, typed template-cache state, adapter-owned emission context, per-mode delivery semantics, Axum outer wrapper
…ite-back in middleware

Three final-review fixes for access telemetry correctness:

- Normalize the HTTP method to an allowlist (GET/HEAD/POST/PUT/DELETE/
  PATCH/OPTIONS, else "other") inside access_event_row, so a client-
  controlled extension-method token can never inflate the LowCardinality
  method column, regardless of which adapter builds the row.
- Guard emit_access_telemetry_after_send against snapshots carrying a
  degraded sample_rate of 0.0 (captured on the app-state-build-failure
  fallback path), which could otherwise be sampled in by freshly reloaded
  settings and corrupt the sum(1.0/sample_rate) volume estimator.
- Mirror the geo lookup write-back from apply_entry_point_finalize_headers
  into FinalizeResponseMiddleware::handle, so a middleware-finalized
  response that resolved geo via fallback carries the resolved
  GeoLookupState for the access-telemetry snapshot instead of showing
  country "unknown".
std::time::Instant::now() panics on wasm32-unknown-unknown, so every
publisher request on the Cloudflare adapter trapped when the timing
collector was constructed, and the two auction-wait sites would trap
once an auction dispatched. web_time re-exports std's Instant on every
other target, so Fastly, Axum, and Spin behavior is unchanged.

The publisher.rs sites are qualified locally because that module's
std Instant import still serves the pre-existing template-cache sites,
which are out of scope here.
The character allowlist alone does not bound identity: [a-z0-9_-] is
exactly the alphabet UUIDs, hex ids, reset tokens, and article slugs
are built from, and truncating to 32 characters still leaves a
globally unique prefix. A first segment now rejects whole to /other/*
when it exceeds 32 characters or carries more than 7 ASCII digits,
alongside the existing charset rejection. Year archives and
hyphenated section names still pass.

Extends the adversarial tests to the publisher-fallback path with
UUID, hex-id, token, and slug shapes, and fixes the stale event_date
reference in the row-builder doc.
- Gate building the access snapshot on tinybird.enabled and
  access_enabled, threaded through SendContext: a disabled deployment
  (the default) no longer pays env reads and String allocations on
  the pre-send path. DeliveryOutcome.snapshot becomes Option and the
  emitter treats None as nothing to send.
- Classify asset-fallback responses as route_class asset with the
  operator-configured route prefix as the template, instead of
  landing in the other/unknown bucket alongside 404s.
- Pin Phase::index() to PHASE_COUNT with a uniqueness-and-bounds
  test so a future variant fails the suite instead of panicking at
  runtime.
- Drop the tautological sampled-out emission test; the 0.0-rate
  behavior is covered by sampled_in_boundary_rates_are_unconditional.
- Clarify that the local dev config env var name genuinely triples
  trusted_server_config (prefix, store, key) rather than reading as
  a find/replace mistake.
Adds section 18 to the request phase timing spec: three first-call-wins
T0 offsets (auction dispatched, resolved, committed) on RequestTimings,
emitted as additive nullable columns on access_logs_raw with auction_id
as the join key to the per-bidder auction dataset. Answers the
overlap-proof questions the two existing clocks cannot: when the
auction started relative to request entry, when the final bid landed,
and when targeting was committed toward GAM.
Implements spec section 18: three first-call-wins marks on
RequestTimings (dispatched at the DispatchAuctionOutcome::Dispatched
arm, resolved after collect at both sites, committed after
write_bids_to_state at both sites), carried through TimingSnapshot into
four additive access_logs_raw columns: auction_dispatched_ms,
auction_resolved_ms, auction_committed_ms, and auction_id as the join
key to the per-bidder auction dataset. Null offsets mean no auction
ran; a failed dispatch records nothing. FORWARD_QUERY fills the new
columns with typed defaults for pre-existing rows.

No header emission, no config surface, no adapter changes: the values
ride the existing snapshot and the tinybird.access_enabled gate.
The Cloudflare integration harness writes
wrangler.integration.generated.toml at test time; it was swept into the
previous commit by accident. Ignore it so local CI=1 runs cannot commit
it again.
The first path segment is only a section name when the path has depth:
under a /%postname%/ permalink structure every article is a
single-segment path, so keeping those segments verbatim put full
article slugs into the 30-day dataset, against spec section 9. Depth
is now required for a named template; single-segment paths, root
landing pages included, bucket to /other/*. Route slicing keeps
route_class and multi-segment templates like /news/*.
The bucket-quantized sampler truncated rates below one in a million to
a zero threshold (silently emitting nothing) and quantized other low
rates downward while rows still carried the configured rate, biasing
the sum(1.0 / sample_rate) volume estimator. Its no-rand premise was
also wrong: rand::thread_rng() is WASI-backed on this target and the
EC generation path already relies on it. The sampler is now a direct
uniform-roll comparison, and the roll gates on the rate stored in the
snapshot itself, so emission probability and the row's sample_rate
column cannot diverge; the divergence guard and its tests are removed.

Also per review: the settings-reload fallback in the post-send path
could never emit (no snapshot exists when settings were absent) and is
removed; the dead_code allow on DeliveryOutcome narrows to the one
collected-but-unemitted field; and the post-send ordering test is
narrowed to the leg it actually proves, that request_elapsed is
stamped when send returns.
On origin failure with a dispatched auction, the origin span guard
stayed alive through the emit_abandoned_auction await, so ts-origin
and origin_ms absorbed Tinybird emission time. The span now closes
when the send resolves, before either branch, with an error-path
regression test.
- Pin HEADER_PHASES against Phase::header_name() in the phase-index
  test, closing the second hand-synced list.
- Add RouteClass::Asset to the snake_case rendering test; rename the
  lowercasing test to say what it does.
- Give the Axum adapter a named, fully configured construction path
  (TrustedServerApp::dev_server_service) so server_timing_enabled is
  never silently discarded; the tuple API is private now.
- Document that Server-Timing is client-visible when enabled, in the
  configuration guide's observability section.
- Replace stale event_date references in the spec, plan, and dashboard
  guidance with the toDate(event_ts) sorting-key expression, and state
  the single-segment rejection rule in spec section 9.
…ming

# Conflicts:
#	crates/trusted-server-adapter-fastly/src/app.rs
#	crates/trusted-server-adapter-fastly/src/main.rs
#	crates/trusted-server-adapter-fastly/src/middleware.rs
#	crates/trusted-server-core/src/publisher.rs
#	crates/trusted-server-core/src/settings.rs
#	docs/guide/configuration.md
#	trusted-server.example.toml
The access emitter carried the configured body limit without enforcing it, allowing oversized rows to bypass the intended transport safeguard.
dhruv8sh and others added 25 commits October 28, 2026 18:17
…iter

A removal whose row writes all failed was lost, and a positive capture on a request that was not the writer never reached KV beyond the one UID browser ingest picked.

Both now go to pending:<ec_id> (one-day TTL, CAS append, at most 16 items). Every returning writer reads that key, merges the items above the row's new pg watermark before its own mutation, and stores the newest generation. A positive capture keeps the revision it read, so it never overwrites a record that changed since, and removed UIDs stay blocked by their ps.iv and ps.ov hashes. removal_deferred counts what went to the key; removal_lost now counts only what could not reach it. Auctions never read it.

Spec 15 questions 2 and 4 record the decision. The cost is one more KV read per writer navigation.

Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
A visitor who stayed in a single-page app waited for a full navigation before browser ingest or provider refreshes reached KV, because /_ts/page-bids never wrote.

A /_ts/page-bids call from a real browser with Sec-Fetch-Site same-origin is now the writer, at most once per ec.identity.spa_writer_interval_sec (default 300, 0 = off) since the row's new wa field, the time of its last writer flush. It writes only on a row its auction read, so it adds no row read, and runs browser ingest and after-send providers only, never hold. Spec 15 question 1 records the decision.

Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
A visitor who left after one page never got a provider ID, because no provider ran until a returning visitor's navigation.

With a hold-mode provider that does not read the EC ID configured, the Fastly publisher path now mints the EC without writing. A navigation that runs an auction dispatches those providers on the row the create will write, skipping any that read the EC ID, then creates the row with their answers before the preload and the auction, so the create stays the page load's one write and no ID reaches a bidder before its row exists. A collision fails that create rather than minting an ID the auction may already hold. Finalize creates a row its navigation never reached. Without such a provider the create runs before routing, as before. Spec 15 question 6 records the decision.

Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
ConsentPolicy::strict() required Purposes 1 and 4 and no vendor, so a resolver that forgot with_tcf_vendor sent visitor data on purpose consent alone, and the fixed purposes matched no vendor's GVL entry.

strict() now requires Purpose 1 only. ProviderDescriptorBuilder::build rejects a resolver whose policy declares no purposes with with_tcf_purposes, or names no vendor without the documented without_tcf_vendor relaxation. Capture-only providers need neither, and generic pull keeps its parity policy. The global-revoke marker now fingerprints all ten TCF purposes so it lifts on any purpose change. Test fixtures keep today's Purposes 1 and 4 through test_support::resolver_policy.

Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
… exception

The decisions log said decisions were made with "the user" and left the consent-withdrawal exception unconfirmed. Both now name the spec author, who confirmed the exception after the round 14 review.

Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Move the yq install and concurrency guard steps into format-rust so a
required check enforces them, and pin the yq download by SHA-256. The
guard now accepts expression timeouts, requires every matrix dimension
in a matrix job's group, and drops an unreachable reusable-job skip.
Add tests for the previously uncovered checks.
Resolve changelog merge conflict while preserving SDK-matching documentation and all base-branch changes.
Resolve merge conflicts while preserving SDK URL ownership matching and base-branch Lockr routing; update corpus expectations for absolute first-party URLs.

This branch has not been deployed

No deployments
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.

6 participants