Repository navigation
Conversation
One Fastly service can serve several publishers, each from an application
config of its own. A `__KEY` selector that contains `{host}` is a
template. The entry point puts the request's host in the placeholder's
place, lowercased and without a port or a trailing dot, before anything
reads settings, so the debug probes, the application build and the
finalizers all read the blob of the publisher the request is for.
A host with no blob is answered `421 Misdirected Request`, and the answer
is not to be stored. It is never answered from the logical store ID or
from another host's blob, because a shared fallback is how a host nobody
configured comes to be served another publisher's site. Only letters,
digits, hyphens and dots make a host name, so a `Host` header cannot be
made into the logical store ID or the key of a blob's chunk, each of
which carries an underscore. A lookup that fails is answered `500`,
because a request is not served while it is unknown which blob is its
own.
The placeholder goes in the selector that already chooses the key, and
not in a switch of its own, so that a staged version stays apart from
production. A staged deploy copies a service's runtime entries into a
store of its own and points only the key selector at
`<logical-store-id>_staging`. A separate switch would be copied with the
rest, and a staged version would then read the production blobs. The
selector is replaced, so a staged version reads the one staging blob.
A selector without the placeholder is the key as it stands and nothing is
looked up, so a service that reads one blob is unchanged.
`scripts/config-by-host-local-test.sh` pushes an app-config blob under each of two hosts and one at the logical store ID, each with a stub origin of its own, and runs the adapter under Viceroy. Without the placeholder every host reads the one blob. With it each host is answered from its own origin, in any letter case and with a port or a trailing dot, and a host with no blob, the logical store ID sent as a host and a longer name that ends in a publisher's host are each answered 421 with no publisher's page. Each response carries the sandbox's counters. In the default mode the fourth response is the first request of its sandbox, which has built one application. In `reuse` mode, built with the reusable sandbox, it is the fourth request of its sandbox, which has built two. Two publishers then shared a sandbox and never an application, which is the fault a unit test cannot show because it sits in the entry point. CI runs both modes in the Fastly job.
The configuration guide's section on the Fastly runtime config store gains "One blob for each host", with the push for each host, the selector entry, a table of what each request reads and is answered, and what the publishers on one service share. The Fastly guide points to it, and the changelog records the addition.
Core gains `build_info`, which holds the crate version, the time the build was made, the commit and the build run, each fixed when the build was compiled. A WebAssembly guest is given no custom environment variable when it runs on some hosts, so a fact read then would read as absent there. `SOURCE_DATE_EPOCH` gives the build time where it is set, so a reproducible build stays reproducible, and the time of the build stands in otherwise. `TRUSTED_SERVER_COMMIT` and `TRUSTED_SERVER_BUILD_RUN` give the other two, and each reads `unknown` without its input. The date arithmetic is written out in a file the build script and a test both compile, so the build script takes on no date dependency. Each is the builder's own report and nothing measures the binary. The attestation evidence in the next commit is what reads them.
With `[attestation]` in the settings, core answers with evidence in the sense of RFC 9334. It names the host the request arrived for, the publisher whose settings answered, the platform service, the build, the time and the caller's nonce, and it is signed with ECDSA P-256 over a configured context, one line feed and the evidence bytes. The context keeps a signature made here from passing as one the same key made for any other purpose. A module on a CDN cannot measure its own bytes, so the evidence shows who operates the deployment and not what its code is. A verifier holding the operator's public keys decides whether to trust it, and the page sends the reader there and claims nothing itself. - `[attestation]` holds `endpoint`, `operator`, `context` and `verify_url`, and refuses any other field. The endpoint defaults to `/_ts/attestation`, answers as JSON with `.json` added, and may not be an address the deployment already answers. - The host signed is the request's `Host` header. A forwarded host header is never read. - The signing keys are a schedule the build script compiles in from the file `TRUSTED_SERVER_ATTESTATION_KEYS` names, each in force from its start until the next one starts. No store the settings can reach holds one. A build given no schedule signs nothing and answers 503. - An endpoint under a prefix of its own owns that prefix. The prefix lists its pages and any other address beneath it is not found. Under the shared `/_ts` prefix only reads of the two addresses are taken. A settings validation error now carries the validator's message where it gives one, which is how a refused endpoint names the address it clashed with.
Each adapter registers the routes the settings ask for. Reads reach the handler, and other methods reach the publisher's origin unless the deployment owns the prefix. The Fastly adapter answers ahead of the Edge Cookie lifecycle and the request filters, because the pages are read only and a verifier fetching one from a server must not be turned away as a bot. The Fastly adapter reports the service, the version, the staging flag and the location from the environment variables Fastly gives every Compute service. The Axum, Cloudflare and Spin adapters report their own name. Each adapter's tests hold every fixed route it registers beneath an underscore prefix to the list an endpoint may not take, so a new fixed route cannot leave an endpoint free to register its address twice. The Fastly tests build a router for every endpoint the settings accept, and the parity tests hold the other three adapters to one answer for the owned prefix, the redirect from the default address and a deployment with no section.
`docs/guide/attestation.md` says what the evidence proves and what it does not, the addresses and answers, the settings, the key schedule, the formats, the checks a verifier makes, how replay and relay are met, and the limits. Every test it names is in the tree. The configuration guide gains the `[attestation]` section, the API reference the endpoint and its two rows in the route table, the example configuration a commented block, the docs sidebar the page, and the changelog the addition.
The build script read the key schedule itself, so nothing tested what it took and what it refused. The reading moves to `key_schedule.rs`, a file the build script and the attestation tests both compile, as the build time's arithmetic already is. A schedule's key id and key are written into Rust source as strings. The tests hold that nothing either holds can end the string it is written in, that a line of any other shape stops the build by its line number, and that the message never repeats the line, because the line holds a private key. A file saved with either line ending reads the same.
`scripts/attestation-local-test.sh` makes two signing keys, builds the Fastly adapter with their schedule compiled in and runs it under Viceroy. It then checks what the running adapter serves with OpenSSL and the public key alone, sharing no code with the adapter. The signature verifies over the context, a line feed and the payload. It fails with the other key of the schedule, under another context and once the host in the payload is changed. The key in force signs, and not the one that starts later. The evidence names the host the request named, lower case and without its port, and the publisher, the operator, the nonce, the platform, and the commit, run and build time the build was given. A forwarded host header never becomes the signed host, no nonce is signed unless one is asked for, a malformed nonce is refused, and another method is not answered with evidence. This is the one check of the schedule a build compiles in, because test builds carry no key. CI runs it last in the Fastly job, because it builds core with a schedule.
The inspector builds core from a lock file of its own, and core now uses ECDSA on P-256, so its resolution gains `ecdsa` and `rfc6979` at the versions the workspace lock holds.
`ts keys rotate` and `ts keys deactivate` do from an operator's machine what `POST /_ts/admin/keys/rotate` and `POST /_ts/admin/keys/deactivate` do from inside the running service. They drive the same rotation code in core, `KeyRotationManager`, over two store implementations that write through the `fastly` CLI with the operator's own token from `FASTLY_API_TOKEN`. The token reaches `fastly` in its environment and a private key reaches it on standard input, so neither is in an argument list another process can read. Every value is joined to its flag with `=`, so a key id beginning with `-` is never read as a flag. A key id is checked before anything is asked of `fastly`. A new key takes the rule the rotate route applies, `kid_is_creatable`. A key being retired takes the looser rule the deactivate route applies, exposed here as `kid_is_well_formed`, so that a key created under an earlier rule can still be removed. Neither command takes `current-kid` or `active-kids` for a key. Named as a key, either entry would be written over by a rotation or deleted with the key. Two things differ from the stores the service reads, and the command's stores allow for both. - A read is a call to the platform and can fail for a reason other than a missing entry. The rotation library takes any failed read for an entry that does not exist, which would write a key list without the keys already active, or let the current key leave the active set. So the stores tell a `404` from any other failure and write nothing once a read has failed. - The service's management client takes a `404` on a delete for success, which is how a delete tried again after a partial failure finishes. The stores do the same, after asking for the store, because the API answers `404` for a missing store too. `fastly_cli` holds the runner, so the next command that administers a service through the host uses the same one. Its report of a failed `fastly` names the command by its words and says what `fastly` printed, including when the process stopped before it read its input. The command comes before the removal of the two routes, so that no commit leaves an operator without a way to rotate a key.
The command called `POST /_ts/admin/cache/purge` on the publisher's domain with a shared password. It now runs `fastly service purge --key` with the operator's own token from `FASTLY_API_TOKEN`, so the credential that can empty the cache stays with the platform's access control and off the domain every reader reaches. A token with the `purge_select` scope is enough, which allows a purge by surrogate key and does not allow a purge of everything. `fastly service purge` is the Fastly CLI's name for the command since its version 14.0.0, where `fastly purge` became a deprecated alias. The repository pins 15.1.0. The surrogate key is derived in the command by `reader_url_surrogate_key`, the function the service tags a cached page with, and `--all` names `ts-template`, the key every cached object carries. Both scopes purge what they purged before. Neither runs `fastly service purge --all`, which would also flush objects the service cached without one of those keys. `--service <url>`, `--username` and `TRUSTED_SERVER_ADMIN_PASSWORD` go and `--service-id <id>` arrives. A service id that is not letters and digits is refused, because it becomes an argument of the `fastly` process. The tests that drove the command against a fixture service are replaced by tests against a fake `fastly`. The command changes before the route is removed, for the reason `ts keys` comes first. The guide's command table gains the two `ts cache` rows it lacked.
`GET /_ts/admin/ec` and `GET /_ts/admin/ec/{id}` read any reader's identity
graph record, and `GET /_ts/admin/eids` decoded the caller's EID cookies,
on the publisher's own domain behind one shared password. `GET /_ts/data`
shows a reader what is held against that reader's own Edge Cookie and asks
for no password, so the operator lookups go.
What goes with them: `ec/admin.rs`, `KvIdentityGraph::lookup_raw`, the
decoder's diagnostic view of an EID cookie, the two probes that established
handler coverage for the lookup by id, the adapters' routes and their
not-supported stand-ins, and the tests and guide sections for all of it.
What stays is the check that keeps administration paths away from the
origin. It moves to `closed_paths.rs` as `closed_path_response`, with the
matcher unchanged: every path beneath `/_ts/admin`, and the `/admin/keys`
alias beside it, as sent and after each of up to four percent-decoding
rounds. A request there that no route claims is answered `404` by the
server, so a caller's `Authorization` header and body are never forwarded.
The three removed paths are answered that way for every method, where a
method other than GET was answered `405`.
`POST /_ts/admin/keys/rotate` and `POST /_ts/admin/keys/deactivate` let anyone holding one shared password replace the keys the service signs with, over the domain every reader reaches. `ts keys rotate` and `ts keys deactivate` do the same work from an operator's machine with the operator's own token, so the two routes and their handlers go. Only Fastly served them. Axum, Cloudflare and Spin registered them to answer `501`. The `/admin/keys/rotate` and `/admin/keys/deactivate` routes go with them. They existed to refuse those paths, so that a call to one was not forwarded to the origin with its credentials. `closed_path_response` already answers every path beneath `/admin/keys` and `/_ts/admin` before the publisher fallback, on each adapter, so the four paths are still answered `404` here for every fallback method. Each adapter's `key_administration_paths_are_answered_here_and_never_proxied` holds that with the credentials a caller would send. `kid_is_creatable` and the two validators behind it stay in `request_signing/endpoints.rs`, because `ts keys` refuses a key id with them before it writes. The tests of the password challenge ask at `/_ts/admin/anything`, since the path they asked at is not a route any more, and the two probe tests take `/first-party/proxy` as their named route for the same reason. The key rotation guide's diagram had `current-kid` written before `active-kids`. The code writes `active-kids` first, and the diagram now agrees with the text beside it. `[request_signing]` still requires `config_store_id` and `secret_store_id`, which only the removed handlers read. They go in the next commit.
`[request_signing]` required `config_store_id` and `secret_store_id`, the platform ids of the two stores the keys live in. Only the removed rotate and deactivate handlers read them, to write those stores. The service finds its keys by the names the stores are linked under, `jwks_store` and `signing_keys`. With the handlers gone, the two ids were settings an operator had to supply with real values for nothing to read. They go, with the placeholder and padding checks on them in startup and deploy validation, and the auction plan's rule that enabled signing needs both. `ts keys` takes the ids on its command line. A configuration that still carries either key is refused with directions, through the refusal the removed tables use, when it is validated and when the service starts. Serializing the settings writes `enabled` alone, so a configuration the service writes is one it reads back. Fixtures whose `[request_signing]` section held only the two ids now say `enabled = false`, which is what an absent `enabled` meant.
`management_api.rs` was a client for the Fastly management API. The deployed service read an API token from a secret store named `api-keys` and used it to write its own config store and secret store, which is how the key rotation routes stored a new key. Nothing calls it now that keys are rotated with `ts keys`, so the client goes, with the `api-keys` entries in `fastly.toml` and the Viceroy template. The Fastly platform's store writes, `put`, `delete` and `create`, refuse with a message saying the service does not write its stores. They remain on the traits because `ts keys` implements them over the `fastly` CLI. `BackendConfig::from_url` and its two variants built the backend the client reached the API through. Nothing else called them, so they go as well, and the one test that parsed a URL through them states the parsed parts directly. The adapter's `base64` and `url` dependencies go with their last users. A deployment that linked an `api-keys` secret store can unlink it and revoke the token it held.
`POST /_ts/admin/cache/purge` let anyone holding one shared password empty the service's cache over the publisher's own domain, with nothing limiting how often. `ts cache purge` asks the hosting platform to purge the same surrogate keys with the operator's own token, so the route goes, with its handler in core, the stand-ins that answered `501` on Axum, Cloudflare and Spin, the harness mode and workflow step that drove it, and the parity tests of it. `auth::authenticated_username`, which named the caller in the purge log, goes too. Nothing in the service purges by surrogate key or purges everything now, so `purge_url_surrogate_key` and `purge_all` leave the template cache interface and every implementation of it. `purge_url`, which core uses to drop an entry it finds invalid, stays. The harness's purge mode was the end to end check that purging a key empties the cache. Two Fastly tests keep that evidence where it can still be had without the route: purging `ts-template` through the platform clears a stored template, and purging one page's reader key clears that page and leaves another. No admin endpoint is left, so the rule that every one must be covered by a `[[handlers]]` entry goes: `ADMIN_ENDPOINTS`, the coverage check at startup and at deploy validation, and the test that held the list against the Fastly route table. A configuration with no handler starts. Handler passwords are still checked. A request to the removed path is still answered `404` by the server and never forwarded, as for any path beneath `/_ts/admin`.
HTTP Basic authentication existed to gate the endpoints beneath `/_ts/admin`. None is left, so the gate goes: `auth.rs` in core, the `AuthMiddleware` each of the four adapters registered, the `Handler` type with its regex and its placeholder and weak password checks, and `handlers[*].password` as a declared secret. A configuration that still carries `[[handlers]]` is refused with directions when it is validated and when the service starts, through the refusal the removed tables use. Loading it and ignoring the list would leave an operator who gated a path of their own, a staging site for instance, believing it was still gated. An empty list is refused too, so is a handler given by an environment variable, and the settings are never written with one. The upgrade has an order to it. A build before this series refuses a configuration without a handler covering `/_ts/admin`, and this build refuses one with any handler, so no one configuration starts both. The new configuration and the new build go live together. The publisher path treats an `Authorization` header as bound for the origin in every case. It shared a cached page across readers when the edge had checked the header itself, and with no check at the edge that case is gone, so a request carrying the header always bypasses the shared template. The closed path check is unchanged and still answers `/_ts/admin` and `/admin/keys` with `404` before the publisher fallback, so a caller that sends a password to either never has it forwarded. Each adapter's tests of that send the credentials a caller would. What goes with the gate: - the tests of the challenge in the four adapters and the parity suite, and the tests that proved other routes were not challenged; - `[[handlers]]` in every test fixture, 52 entries; - the `handler_password` secret in `fastly.toml`, the Spin manifest, the Viceroy template, the integration fixtures, the three harnesses and the four smoke scripts, whose missing-secret cases cover the two secrets that remain; - the handlers section of the configuration guide, the Basic Authentication section of the API reference, the `Handler.password` row of the configuration guide's table of exceptional fields, and "unless an operator adds a handler" in the contracts of `/first-party/sign` and `/_ts/debug/ja4`. The configuration page's tests take their example list from `proxy.asset_routes`, having taken it from `handlers`. The page-bids alias comments named a `[[handlers]]` pattern covering `/_ts` as one reason a browser falls back to the alias. They name a gate in front of the service, which is the case that remains.
The DataDome module skipped its protection API for every path beneath `/admin/` and `/_ts/admin/`, listing them with the server's own routes. Nothing of the server's is served beneath either. `/_ts/admin` and `/admin/keys` are answered by the closed path check before any request filter runs, and everything else beneath `/admin/` is the publisher's own site, which the list left out of protection. `only_the_servers_own_paths_are_internal` holds the list both ways.
`[robots-txt] modules` names, in order, what makes the file. `refuse_all` and `allow_all` are core's own and need nothing else. Any other name is a contributor a module declared with `with_robots_txt_contributor`, and core names none of them. A contributor returns groups of records and core writes every byte. That is what lets these hold whatever a contributor returns. - With `refuse_all` selected the file is the refusal and nothing else. - A path in `always_allow` stays open to every crawler a contribution refuses the whole site to, by a group of core's own below the contributions, which are not edited. - The `Sitemap` line and the publisher's own text go around the contributions. Each contributor's answer is held in the key-value store, under a key that is a hash of the publisher, the contributor's name and the contributor's fingerprint, and the contributor is asked again when its own refresh is due. A contributor that fails has its held answer served past its age. With nothing held the file fails closed with `503`, because leaving a contributor's rules out would allow by omission what those rules refused, and a `404` or an empty file reads to a crawler as permission. A store that cannot give an entry a lifetime is written without one, so the answer is still there to serve. A contributor is asked through the module context, with the request's method, path, host and scheme and without its query or anything about who asked, because its answer is served to every crawler. `[robots-txt]` is refused when the settings load if `modules` is absent, empty or names one thing twice, if a key is not a setting, or if a block belongs to a contributor `modules` does not name. A name nothing supplies is refused when the registry is built, so the file does not fail on the first request for it.
Declared on the probe's one registration, beside its geo, Edge Cookie and device modules, so an adapter's tests can select a contributor from a crate core does not know. Its one rule names the host, path and query it was asked for, which lets a test read back what a contributor is handed.
The route is registered only when the settings carry a `[robots-txt]` section, for `GET` and `HEAD`, beside each adapter's other fixed routes. On Fastly and Axum it is a row of the named-route table, gated by `named_route_is_served`, so a request for it passes what every named route's request passes there. Every other method on the path goes to the publisher fallback. Without the section the path reaches the publisher's origin like any other, so a publisher keeping their own file is left alone. With `refuse_all` selected every response an adapter finalizes carries `X-Robots-Tag: noindex, nofollow`. It is written last when a response is finalized, and replaces any tag already there, so that neither an origin's header nor an operator's `[response_headers]` can loosen it. The comment on the Axum adapter's one preparation point now names the attestation pages as the other route outside it.
The configuration guide gains the section, its settings, how the file is made and what is refused when. The API reference gains the route and its contract. The integration guide gains the contributor extension point, and the configuration rules list `robots-txt` among the types. The example configuration carries the section commented out.
`x-served-by` names the node that answered, and `x-cache` and `x-cache-hits` say how a cache treated the request. They arrive on a response from the publisher's origin or from a cache on the way to it. `strip_diagnostic_headers` removes the three from a response unless the request was for a path in the namespace it is given. `ROUTE_NAMESPACE` names `/_ts`, the prefix of every path this server answers itself. A path that only starts with the namespace's text, `/_tsx` for one, is not in it.
The finalize middleware of each adapter takes the request's path before the handler runs, and removes the three headers from the response unless the path is beneath `/_ts`. The removal comes before the finalize step, so a header of one of those names that an operator sets in `[response_headers]` is still sent. Each adapter has three tests of its own middleware. One sends a response naming its node for a publisher's page and finds the headers gone, one does the same for a path beneath `/_ts` and finds them kept, and one finds an operator's `X-Cache` on a publisher's page.
The response headers section of the configuration guide names the three headers and says where they are kept, and the changelog records the change.
`[tinybird]` is now `[analytics.tinybird]`, and `[analytics] module = "tinybird"` selects it, which replaces the table's `enabled` line. Every other thing a deployment can switch on is selected that way, and telemetry was the one section named after its vendor and switched on by a flag. - `Settings::telemetry()` returns the module's settings only when the section selects it, and the Fastly sink is built from that, so the sink has no enabled flag of its own. - A section that selects nothing, or a module this build does not have, is refused when the settings load, and the table refuses a key it does not know. - The secret reference is `analytics.tinybird.auction_token_secret`. The references of a table nothing selects are cleared before secrets are resolved, so the refusal is what an operator sees and not a failed lookup. - A configuration that still carries `[tinybird]` is refused with directions. An earlier version wrote the table into every stored configuration, on or off, so the message says to push the configuration again with this version. - The messages about the table name it through one constant. - An empty or absent token reference is refused as the settings load, where an empty one becomes absent as they normalize, and at deploy by the declared secret field, so the deploy validation's own check of the token is removed. Both refusals are tested. The example configuration carries the new block, which a test uncomments and loads.
The configuration guide's Tinybird section becomes the analytics section, with the selector, what is refused and where `[tinybird]` went. The telemetry guide, the configuration rules and the Tinybird project's README name the settings by their new table, and the changelog records the change as breaking.
`COOKIE_MTM_PREF` sat in core's constants, and the Model Terms for Marketing signal crate is the only code that reads the cookie. The constant moves to that crate, so core names one standard less. The Axum adapter's test of the signal takes the name from the crate.
`[cmp] module = "inmobi"` selects it, with the account's Choice loader in `[cmp.inmobi] script_url` and the IAB CMP id in `cmp_id`, which is 10 unless written. Where a `[[fetch]]` entry names `cmp.inmobi`, the module's middleware writes three things into the head, in this order: the IAB TCF v2 stub, which queues every `__tcfapi` call made before the prompt arrives and answers `ping` as a stub; InMobi's GPP stub, which answers the generic `__gpp` commands at once with the CMP id and queues the rest; and the Choice loader, deferred, so a slow consent vendor never holds up the page and the loader never runs ahead of a stub the publisher's own head carries. Each stub defines nothing where the page already has its interface. The module serves no browser module, because the stubs and the Choice script are the whole client side. The prompt writes the reader's answer to the cookie the TCF permission signal reads, so the answer reaches the permission model on the next request the way every signal does. The script URL becomes a `<script src>` on every page, and a settings document reaches the service from a config store rather than from a person reading it, so it is refused when it is empty, is not a URL, is not `https`, is on a host other than `cmp.inmobi.com` or `cdn.inmobi.com`, or carries a user name or password. A host that merely contains an allowed name is refused by what it is rather than by what it looks like, and the URL is escaped again when it is written, because a URL on the right host can still carry a quote in its query. A document that selects the prompt and names it in no entry is refused, because the prompt would never be written, and the table refuses a field it does not read. The two stubs are tested in a stand-in for the browser with Node's test runner, `node --test "crates/cmp/inmobi/tests/*.test.mjs"`, which runs the scripts as the page carries them. The module's tests cover each refusal, the tag's parts and their order, that a vendor's tag named after the prompt is written after it, and the page a reader receives, recorded in `src/fixtures/page-change.recorded.html`. The stock list offers the module after Didomi and lists its middleware in the fetch phase, the example configuration names it in its fetch entry and shows its table, and the cargo aliases build and test it with the other modules. 51Degrees maintain the crate until InMobi adopt it, which its manifest states.
Core's version_header module is removed, with its call from the finalize step of the Axum, Cloudflare and Spin adapters and from the Fastly adapter's health probe and its settings fallback, so no response carries x-ts-version unless a [[serve]] entry names the service_version middleware. That middleware now writes the git version the build compiled in from TRUSTED_SERVER__GIT_VERSION or local git as X-TS-Version, when the build knows one, and the version the adapter reports for the service as X-TS-Service-Version, when it reports one. HEADER_X_TS_FASTLY_VERSION, which nothing wrote, goes with the module. The health tests of the three adapters prove the probe carries no version, the middleware's own tests prove each header is written when its version is known and nothing when neither is, and the publisher tests prove both headers reach a buffered page and neither reaches a page no entry names the middleware for. The configuration guide, the getting started guide, the EdgeZero guide and the changelog say the same.
# Conflicts: # docs/guide/configuration.md
# Conflicts: # docs/guide/configuration.md
jwrosewell
marked this pull request as ready for review
October 14, 2026 12:35
Collaborator
|
Moved into this repository as #1369, one branch of the stack, so the review continues there. This pull request is closed in its favor and its comments stay here for the record. |
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.
Stacks on the prefix module (#1315). The additions that follow the seven pull requests open here are listed in #777.
Compare
split/27-prefix-modulewithsplit/28-device-signalsto 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 ofHigh,Medium,LowandUnknown, 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 itUnknown, which is kept apart fromLowbecause a request alone cannot establish a person. Nothing reads it yet. It is written only on Fastly, the one adapter that callsdetect, andto_kv_devicedrops it, so it is not stored in the identity graph.looks_like_browsergates the Edge Cookie as it did.DeviceAttributesis 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_attributesanswers it, with a default of nothing so no existing module changes.attach_device_attributesasks the selected module after each auction request is built, at three points: the auction endpoint, the publisher's server-side path andhandle_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 asdevicetype,make,model,os,osv,wandh, each absent where nothing resolved it. A module that resolves nothing adds no device object.The attributes are attached under no permission check, as
uaandipare 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
Mediumand 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
0f4689117on 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.