Add the client-set Edge Cookie value path - #1046
Open
jwrosewell wants to merge 4 commits into
Open
Conversation
This was referenced Aug 19, 2026
…ider First slice of the PR 838 decomposition (one PR per feature, Edge Cookie provider first). Introduces the EdgeCookieProvider trait and routes Edge Cookie minting, cookie read-back, and KV keying through the selected provider, so a vendor identifier round-trips verbatim instead of being dropped by the built-in shape check. - [ec] provider selector with per-provider [ec.providers.<key>] blocks; the hmac block carries the passphrase that previously lived on [ec]. With no provider selected, no Edge Cookie is generated. - Built-in provider: hmac (HMAC over client IP, preserves today's identity). The host-signal provider follows in the device slice, which supplies the host fingerprints it needs. - Request evidence abstraction (crate::evidence) giving providers read access to the client IP, headers (including cookies), URL path, and query parameters. - Adapter injection seam: RuntimeServices carries an optional vendor provider, so a vendor provider lives in its own crate and core never names it. - Provider-declared identifier semantics: accepts_id gates cookie read-back and withdrawal, normalize_id_for_kv controls the KV key, so an opaque identifier survives byte for byte as both cookie value and storage key. Tests cover the verbatim round-trip with a non-default provider, evidence access at generate time, and opaque KV persistence. Edge Cookie creation and use stay gated by the existing consent context exactly as before, including with no provider selected; the permission model replaces that input in a later slice. The client-set resolve path and the response-header hook are later slices. Config migration: [ec] passphrase is rejected as an unknown field; move it to [ec.providers.hmac] and select provider = "hmac" to keep minting.
jwrosewell
force-pushed
the
split/4-client-resolve
branch
from
August 20, 2026 01:47
4971aa6 to
e221cda
Compare
…e provider Second slice of the PR 838 decomposition. Device classification and geolocation become selectable providers, mirroring the Edge Cookie provider seam: - [device] provider selects the classifier. The built-in default reads the User-Agent alone and makes no host call; the opt-in fastly provider strengthens the browser/bot gate with the host's TLS JA4 and HTTP/2 fingerprints (crates/device/fastly). - [geo] provider selects geolocation. No provider is the default and resolves no location; provider = "platform" opts into the host's lookup (crates/geo/fastly wraps the Fastly host lookup behind the PlatformGeo trait). - The host-signal Edge Cookie provider arrives with the capability it needs: the Fastly adapter injects the TLS/HTTP-2 fingerprints as a HostSignals service, and the provider mints from them plus the client IP. With no fingerprint at all it defers with a warning rather than degrading to an IP-only identifier. - Device signals move to a field-based DeviceSignals derived in the adapter (derive_ua_only for hosts without fingerprints). - The new crates join the fastly cargo aliases so they build, lint, and test in CI rather than compiling only transitively.
Third slice of the PR 838 decomposition. Permissions become the primitive that gates identity features; consent is one of several ways a permission is established: - permissions.rs resolves a per-request PermissionState from the country/region baseline in permissions.yaml augmented by the session's signals (TCF, GPP, GPC, US Privacy). Permission names follow the Privacy Taxonomy Data Uses. - The Edge Cookie gate moves from raw consent to the permission model: a provider declares required_permissions() and core executes it only when every one is set. With no provider selected the gate stays closed and identifiers are treated as absent, matching the stateless contract established in the first slice. - Withdrawal separates from mere not-permitted: ec_storage_withdrawn (an explicit signal) expires the cookie and writes tombstones, while a pre-consent or fail-closed state only strips EC response headers. - Bidstream EIDs gate on the permission model (gate_eids_by_permissions) instead of raw TCF consent. - [geo] default_country becomes required: it names the permissions.yaml rule that applies when the geo provider leaves a request unmatched, validated at startup, so there is always a defined permission baseline. - The consent module keeps building the ConsentContext; its EC-specific gating helpers move behind the permission model.
Fourth slice of the PR 838 decomposition. A provider that cannot derive an identifier at the edge can now defer, run its own page script, and mint from the value the page posts back: - POST /_ts/api/v1/ec/resolve accepts the client-produced value, runs it through the selected provider's resolve_from_client (which must verify it before trusting it), and sets the ts-ec cookie from the result. The endpoint enforces the provider's required permissions before the provider runs. - ClientFixedProvider is the built-in demonstration provider: it defers at the edge, its page script (ec_client_fixed) posts a fixed known word, and the endpoint mints when the word verifies. The script rides the tsjs bundle only when the provider is selected. - A test-only TestIdProvider models a client-minted UUID identifier and proves the full scenario: organic request defers, the client posts a value, the cookie is set, and the identifier round-trips verbatim on the next request. - EcContext keeps the injected provider and host signals for the request, so the resolve path rebuilds the provider with the same services as the organic path.
jwrosewell
force-pushed
the
split/4-client-resolve
branch
from
August 20, 2026 02:22
e221cda to
88f96f8
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fourth of five PRs decomposing #838 (see #1043 for the series description). Stacked on #1045: until predecessors merge, this PR displays the cumulative diff, so review the top commit only.
What this PR contains
POST /_ts/api/v1/ec/resolveaccepts a client-produced value, runs it through the selected provider'sresolve_from_client(which must verify it before trusting it), and sets thets-eccookie from the result. The endpoint enforces the provider's required permissions before the provider runs.ClientFixedProvideris the built-in demonstration provider: it defers at the edge, its page script (ec_client_fixed) posts a fixed known word, and the endpoint mints when the word verifies. The script rides the tsjs bundle only when the provider is selected.TestIdProviderproves the full scenario end to end: an organic request defers, the client posts a value, the cookie is set, and the identifier round-trips verbatim on the next request.Verification
Full gate green on this branch: fmt, clippy x6 with
-D warnings, all four adapters' test suites, cross-adapter parity, and the JS build and tests for the new page script.Produced with AI assistance under James Rosewell's direction; needs human review.