Skip to content

Let a device module score a person and describe the device - #1369

Draft
jwrosewell wants to merge 17 commits into
feature/27-prefix-modulefrom
feature/28-device-signals
Draft

jwrosewell wants to merge 17 commits into
feature/27-prefix-modulefrom
feature/28-device-signals

Conversation

@jwrosewell

Copy link
Copy Markdown
Collaborator

Summary

Changes

Area Change
crates/trusted-server-core/src 12 files, 505 lines added, 3 removed
crates/trusted-server-adapter-axum/tests 1 file, 47 lines added, 0 removed
crates/testing/seam-probe 1 file, 27 lines added, 1 removed
crates/auction/prebid-server 1 file, 9 lines added, 0 removed
docs/guide 2 files, 8 lines added, 7 removed
CHANGELOG.md 1 file, 1 line added, 0 removed
crates/ad-server/mock 1 file, 1 line added, 0 removed

Closes

Closes #1286

Test plan

CI: 19 of 19 checks green, the fork's CI on b680d07 (jwrosewell#23).

  • cargo test-fastly && cargo test-axum
  • cargo clippy-fastly && cargo clippy-axum
  • cargo fmt --all -- --check
  • JS tests: cd crates/trusted-server-js/lib && npx vitest run
  • JS format: cd crates/trusted-server-js/lib && npm run format
  • Docs format: cd docs && npm run format
  • WASM build: cargo build --package trusted-server-adapter-fastly --release --target wasm32-wasip1
  • Manual testing via fastly compute serve
  • Other: the full workflow set of this repository, run on the pull request named above

Checklist

  • Changes follow AGENTS.md conventions
  • No unwrap() in production code (clippy denies unwrap_used in CI)
  • Uses log macros as AGENTS.md asks (not println!)
  • New code has tests
  • No secrets or credentials committed

Stacks on the prefix module (#1315). The additions that follow the seven pull requests open here are listed in #777.

Compare split/27-prefix-module with split/28-device-signals

to see only this pull request's change.

What this pull request does

Two answers a device module may give beyond the coarse browser-or-bot signals the Edge Cookie gate uses.

DeviceSignals::human_confidence, one of High, Medium, Low and Unknown, is how confident the module is that a person is looking at the page. A module that scores the request writes it and every other module leaves it Unknown, which is kept apart from Low because a request alone cannot establish a person. Nothing reads it yet. It is written only on Fastly, the one adapter that calls detect, and to_kv_device drops it, so it is not stored in the identity graph. looks_like_browser gates the Edge Cookie as it did.

DeviceAttributes is what a bidder is told about the device, being the OpenRTB device type, make, model, operating system and version, and screen size, every field optional because every source is partial. The module gives the device type as OpenRTB's own code, so core holds no detection vendor's names. DeviceModule::advertising_attributes answers it, with a default of nothing so no existing module changes. attach_device_attributes asks the selected module after each auction request is built, at three points: the auction endpoint, the publisher's server-side path and handle_page_bids. The consent-denied path makes no outbound call and does not ask. The answer goes on the request's device object, which the OpenRTB request carries as devicetype, make, model, os, osv, w and h, each absent where nothing resolved it. A module that resolves nothing adds no device object.

The attributes are attached under no permission check, as ua and ip are and unlike EIDs.

The signals answer whether the request looks like a browser. The attributes answer what a bidder prices and are stored nowhere.

The seam probe's device module scores the request Medium and answers fixed attributes naming a tablet. The Axum adapter's test calls the module directly and drives no auction, so it proves the probe answers through the call core hands a device module, not that the answers reach a bid request through core. Core's own tests carry the attributes to the OpenRTB device object.

Why

A device module that reaches a detection service knows far more than whether the request looks like a browser, and a bidder prices inventory on the device it is told about. Without a place for that answer in core, a module's knowledge stops at the gate, and a deployment that wants its bid requests to carry the device has to patch core.

How it was verified

CI passed on 0f4689117 on the fork. Not exercised: no module outside the stock build scores a person or resolves a device. The probe's module is the one answer, and the Axum test calls it directly and drives no auction.

References #1286.

jwrosewell and others added 17 commits October 11, 2026 20:31
`DeviceSignals` gains `human_confidence`, one of `High`, `Medium`, `Low`
and `Unknown`, which a device module that scores the request writes and
every other module leaves `Unknown`. `Unknown` is kept apart from `Low`
because a request alone cannot establish a person, so a first-time
visitor is not called a machine. Nothing in core gates on the answer;
`looks_like_browser` still gates the Edge Cookie as it did, and the
answer is there for a module that reads it.
`DeviceAttributes` carries what a bidder is told about the device, being
the OpenRTB device type, make, model, operating system and version, and
screen size, every field optional because every source is partial. It is
kept apart from `DeviceSignals`, which answer whether the request looks
like a browser and are stored in the identity graph, because these
answer what a bidder prices and are stored nowhere.

`DeviceModule::advertising_attributes` answers them from the module call
`detect` is handed, with a default of nothing so no existing module
changes. `attach_device_attributes` asks the selected module after each
auction request is built, on the auction endpoint and the publisher's
server-side path and not the consent-denied path, which makes no outbound
call. A module that resolves nothing adds no device object, because an
absent object and an empty one say the same to a bidder and the absent
one is smaller. The OpenRTB device object carries the attributes, each
absent where nothing resolved it, and `device_type_from_name` maps a
detection name to the OpenRTB code and maps nothing for a name it does
not know, because a wrong type is worse for a bidder than an absent one.
The probe's device module scores the request `Medium` and answers fixed
attributes, a tablet by name, so the Axum adapter proves from outside
core that the module `[device] module` resolves answers both through the
call it is handed, and that a tablet maps to OpenRTB device type 5.
The integration guide's table of what a module is handed gains the
device module's second answer, the attributes for the bid request, and
the configuration guide's device section says a module may score the
request and tell a bidder what the device is. The changelog records the
addition.
Core no longer maps one vendor's device type names, so a device module gives the OpenRTB code itself. The unused as_str and is_machine are deleted, the scoring narrative and the claim of one lookup are cut, and the reasoning for attaching device attributes is stated once. The configuration guide no longer contradicts itself about bid enrichment.
human_confidence is written where the device module runs, is not stored in
the identity graph with the other signals, and nothing reads it yet. The
guide, the changelog and the field's own doc say so, and the guide says
that the redirect middlewares acting on it follow this set of changes.
…nal/28-device-signals

# Conflicts:
#	docs/guide/configuration.md

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.

2 participants