Skip to content

Add device and geo provider selection with the host-signal Edge Cookie provider - #1044

Open
jwrosewell wants to merge 2 commits into
IABTechLab:mainfrom
jwrosewell:split/2-device-geo
Open

Add device and geo provider selection with the host-signal Edge Cookie provider#1044
jwrosewell wants to merge 2 commits into
IABTechLab:mainfrom
jwrosewell:split/2-device-geo

Conversation

@jwrosewell

@jwrosewell jwrosewell commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Second of five stacked PRs decomposing #838 as requested in the #986 review. Stacks on #1043 (the Edge Cookie provider seam). Compare split/2-device-geo to split/1-ec-provider to see only this PR's change.

Spec: shared with #1043: docs/superpowers/specs/2026-07-30-pluggable-providers-design.md covers the whole provider architecture, and its device and geo sections describe this PR.

What this PR does

Device classification and geolocation become selectable providers, mirroring the Edge Cookie seam.

  • [device] provider selects the classifier. The default builtin reads the User-Agent alone and makes no host call. The opt-in fastly provider strengthens the browser and bot gate with the host's TLS JA4 and HTTP/2 signals (crates/device/fastly).
  • [geo] provider selects geolocation. In this PR the host platform's lookup remains the default, matching the behavior before the selector existed, with provider = "none" as an explicit opt-out that sends no client IP to any host geo service. The flip to no-geo-by-default lands in Add the permission model with the Privacy Taxonomy vocabulary #1045 together with the permission baseline that makes a no-geo deployment viable, so this PR alone changes no deployment's behavior.
  • Every adapter (Fastly, Axum, Cloudflare, Spin) routes its host geo through the same build_geo_provider selector, so [geo] provider behaves identically everywhere rather than being a Fastly-only behavior.
  • The provider configuration sections ([device], [geo], [ec.providers.hmac], [ec.providers.host-signals]) reject unknown keys at startup, so a mistyped key fails loudly instead of silently selecting a default.
  • The host-signal Edge Cookie provider ships as an opt-in built-in. It mints from the host TLS and HTTP/2 signals plus the client IP, and with no host signals at all it defers with a warning rather than degrading to an IP-only identifier. Its identifiers are namespaced hs00~ under the provider-code registry introduced in Add a pluggable Edge Cookie provider seam with the built-in HMAC provider #1043, which fixes the identifier-collision defect the earlier review found (host-signal identifiers previously shared the HMAC grammar and keyspace). The policy question of whether host TLS/HTTP-2 processing ships in the series is put to the task force in issue Host TLS/HTTP-2 signal processing: the separate design sign-off row 22 requires #1071, the separate design sign-off row 22 asks for: our proposal is to close the row with capability opt-in, both uses permission-gated, and the policy expressed in permissions.yaml rather than compiled into the build.

How it was verified

Full local gate on this branch, all clean. cargo test-fastly, cargo test-axum, cargo test-cloudflare, cargo test-spin, the integration parity suite, cargo fmt --check, and all six per-target clippy aliases.

References #780 and #781. Decomposes #838. Spec baseline from #986.

Produced with AI assistance under James Rosewell's direction, and flagged here so reviewers know to apply the usual scrutiny.

…ider

First of five PRs decomposing the provider and permission epic. The
EdgeCookieProvider trait 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 deprecated [ec] passphrase form still starts for one release
  cycle: it maps to provider = "hmac" with a deprecation warning, and a
  configuration carrying both forms is rejected. provider = "none"
  spells explicit statelessness. A configured block that is not the
  selected provider is rejected at startup, as is a block with no
  selector.
- Global identifier bounds enforced by core at mint, read-back, and
  cookie write: the cookie-safe alphabet [A-Za-z0-9._~-] and a 256-byte
  cap. An identifier outside the bounds is rejected loudly, never
  rewritten, so the cookie value and the identity-graph key can never
  silently diverge.
- The identity graph is keyed by the provider's canonical form of the
  identifier (normalize_id_for_kv), so equivalent representations of
  one identity share one row.
- 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. A selected provider the adapter does not inject fails the
  request loudly rather than silently running stateless.
- Provider generate failures log at error level with the request
  proceeding stateless.

Edge Cookie creation and use stay gated by the existing consent context
exactly as on main, including with no provider selected; the permission
model replaces that input in the third PR of this series.

Config migration: move [ec] passphrase to [ec.providers.hmac] and set
[ec] provider = "hmac". The old form keeps working for one release with
a warning. Passphrases shorter than 32 characters are now rejected at
startup; previously they were accepted.

The design spec for this slice and the next lives at
docs/superpowers/specs/2026-07-30-pluggable-providers-design.md, the
2026-07-31 draft revised to match the implementation with a
revision-record table of every divergence.

Every provider carries a mandatory registered four-character code
(provider-code-registry.md): core mints {code}~value, checks the code
at read-back, and keys the identity graph with it, so identifiers from
different providers can never collide and a switch of provider cannot
silently adopt another provider's identities. The built-in hmac
provider mints hmac~<hash>.<suffix> and dual-reads its pre-envelope
bare form for one release cycle.
…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 signals (crates/device/fastly).
- [geo] provider selects geolocation. The host platform's lookup is the
  default, matching the behavior before the selector existed, and
  provider = "platform" spells the same choice explicitly
  (crates/geo/fastly wraps the Fastly host lookup behind the
  PlatformGeo trait). provider = "none" opts out entirely, so a client
  IP is never sent to any host geo service. The disabled-by-default
  flip ships with the permission model in the next slice, which adds
  the jurisdiction baseline that makes a no-geo deployment viable.
- Every adapter routes its host geo through the same build_geo_provider
  selector: Fastly, Axum, Cloudflare, and Spin all honor [geo] provider
  identically, so the selector is not a Fastly-only behavior.
- The provider configuration sections ([device], [geo],
  [ec.providers.hmac], [ec.providers.host-signals]) reject unknown
  keys at startup, so a mistyped key fails loudly instead of silently
  selecting a default.
- The host-signal Edge Cookie provider arrives with the capability it
  needs: the Fastly adapter injects the TLS/HTTP-2 signals as a
  HostSignals service, and the provider mints from them plus the client
  IP. With no host signals 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 host signals).
- The new crates join the fastly cargo aliases so they build, lint, and
  test in CI rather than compiling only transitively.
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.

1 participant