Skip to content

Add the module documentation set and finish the decomposition - #1047

Open
jwrosewell wants to merge 231 commits into
IABTechLab:mainfrom
jwrosewell:split/5-response-hook-docs
Open

jwrosewell wants to merge 231 commits into
IABTechLab:mainfrom
jwrosewell:split/5-response-hook-docs

Conversation

@jwrosewell

@jwrosewell jwrosewell commented Aug 19, 2026 •

Copy link
Copy Markdown
Collaborator

This description was updated on 7 October 2026, and on 8 October for the merge of main at 182fdf4. The branch now carries
the refreshed client-set Edge Cookie value path (#1046 at 9a37728) and,
through it, main at 182fdf4. Since the last version of this text:

  1. What an operator selects is a module, and a module is named by its
    crate folder. The guides and the example settings read [ec] module,
    [device] module, [geo] module and [permission-signal] modules, the
    environment override examples read TRUSTED_SERVER__EC__MODULE,
    TRUSTED_SERVER__DEVICE__MODULE and TRUSTED_SERVER__GEO__MODULE, and
    the prose says module wherever it meant an Edge Cookie, device, geo or
    permission signal module. The configuration guide states the name rule
    (a name is parts joined by ., each of lower case letters, digits, _
    or -, and a module from a crate is named by its folder below crates/,
    written in full, as edgecookie.<name>, or with that type folder left
    off), and the permission signals guide lists the modules by those names,
    gpc, gpp, us-privacy, tcf and mtm.
  2. The guides describe what the refreshed pull requests below now do: a
    resolve_allowed_origins entry must be a bare origin and is refused
    otherwise, the old client-fixed spelling is no longer refused by name,
    the batch-sync ec_id field is the full value as issued
    (hmac~{64hex}.{6alnum}, with the bare form still accepted), and the
    Edge Cookie guide carries main's text on grouped batch-sync mappings
    and on the completion markers that keep a stale read from rewriting a
    withdrawal.
  3. The evidence module keeps the HostSignals trait at the top of the file,
    as this pull request moved it, and the no-client-IP test says module.
  4. The Edge Cookie guide describes the permission model as it is. It
    still said that an unknown country falls back to a configured default
    country and that only the storage permission is resolved from a TCF
    signal. It now says that the baseline on the top node of the rules tree
    applies, that every permission is resolved from that baseline and the
    signal modules in the configured order, and that a failed location
    lookup resolves at the requires-signal floor. The setup guide describes
    the bare identifier form as a value with no module prefix.

The commits since the last version are 3c77e44 (the merge of #1046 at
ff56a5f), caccf8d (the documentation set says module), ecb5fb7 (the
merge of #1046's conflict-test fix at 17fd681) and 30fbc6d (the merge of
#1046's inspector lockfile fix at e3aeeba, which names the icu_provider
crate again), then on 7 October 833089e (the merge of #1046 at 9a37728,
which brings the module name rule), 93af3ea (the configuration guide
states the module name rule), 327211c (the merge of #1046 at 6b9f52b),
f220687 (the Edge Cookie guides describe the permission model as it is)
and 1f31ba5 (the merge of #1046 at 59660c4, which brings #1043's wording
of the identity graph's comments as device records, not person records).

Stacks on the client-set Edge Cookie value path (#1046), and depends on #1043
to #1046 together, because this pull request documents what they build. The
stack has six pull requests (#1043, #1044, #1045, #1046, #1047, #1094), each
targeting main, with this one fifth and the last of the five that decompose
the module work in #838, as requested in the #986 review.
Compare split/4-client-resolve with split/5-response-hook-docs
to see only this pull request's change.

The design specs for the series are carried by the spec pull request (#1084),
including the two this pull request used to carry:

What this pull request does

  • The IntegrationResponseMutator response-header hook that an earlier version
    of this pull request added is not in the series. The hook had no consumer,
    and the spec set's own rule is against speculative surface. The hook returns
    with the first integration that needs one, and its spec is the starting bar
    for that design.
  • docs/guide/configuration.md
    gains the [ec] module selector with its [ec.hmac] and
    [ec.host_signals] tables, the [device] and [geo] selectors, the
    implementation line, the assume_single_jurisdiction acknowledgment, the
    requires-signal floor on a failed geo lookup, the bare-origin rule for
    resolve_allowed_origins, and a section on the country and region rules in
    permissions.yaml. It also says what stops a deployment, being a table the
    selector does not name, a name this build does not have, a name that is
    not a module name, and a setting the module does not know.
  • The Edge Cookie guide
    (docs/guide/ec-setup-guide.md)
    is rewritten around modules and the permission model, including the narrow
    withdrawal rules, the hardened resolve endpoint and the resolved-marker
    cookie. The setup guide, API reference, error reference, Fastly guide and
    key-rotation guide are updated to match.
  • trusted-server.example.toml
    adds a commented [ec.host_signals] block and rewrites the [device] and
    [geo] comments to say what each default does and how to override it.
  • CI runs the Axum and Cloudflare jobs in
    .github/workflows/test.yml
    on windows-latest as well as ubuntu-latest, and the Axum job also runs
    the core library's unit tests natively, because on the WebAssembly targets
    the test harness stops at the first failing test and reports the rest as
    never run.
  • Two small code changes come with the documentation. A test in
    crates/trusted-server-core/src/ec/mod.rs
    checks that a module receives the empty string as the client IP when the
    host cannot determine one, and
    crates/trusted-server-core/src/evidence.rs
    corrects its module documentation and moves the HostSignals trait to the
    top of the file.

What happens to #838

Once the five pull requests that decompose #838 merge, #838 is closed. It stays
open as a draft reference for the review period only.

How it was verified

The head this pull request shows now is 3c7ef00e7, which merges #1046 at e7623de78 and, through it, main at 182fdf4 into 1f31ba53e. The merge brings the ts dev lint domains and ts dev install-hooks commands (#733) and a design document (#930), and changes none of this pull request's own code. No dependency of core or of an adapter changed version, and the one line of core the merge touches gains a comment. On 3c7ef00e7 these ran locally on Windows: cargo fmt --all --check, a locked dependency check (cargo tree --locked) and the core suite natively (3,140 tests).

1f31ba53e adds to 93af3ea2b
two merges of #1046, which bring comment and guide wording, the identity
graph's comments, one doc comment and one test, and one commit to two guide
pages. On 1f31ba53e these ran
locally on Windows: cargo fmt --all --check, Clippy with warnings denied on
all four adapters and the five permission signal crates, the core suite
natively (3,140 tests), the five permission signal crates' own tests
(55 tests), and the docs lint, Prettier (inside docs/ and over the Markdown
outside it) and VitePress build. The wider set last ran locally on
9a3772871, the head of #1046 that the earlier merge brought, being the core
suite under Viceroy (3,377 across the test binaries the Fastly alias runs),
the Axum (123 tests, the five permission signal crates included), Cloudflare
(56) and Spin (90) adapter suites, the cross-adapter parity suite (17) and
the permissions inspector's wasm build, and CI runs all of them on this
head. The CLI tests, the browser integration tests and the Fastly Edge
Cookie lifecycle test run only in CI.

CI on 3c7ef00e7: every check passes, being Run Tests, Run Format, Integration Tests, Permissions Inspector and CodeQL Advanced, with the Fastly Edge Cookie lifecycle test in the integration run. There are 22 checks rather than the 20 the earlier
pull requests run because this one puts the Axum test job and the Cloudflare
check job on a [ubuntu-latest, windows-latest] matrix.

References #777 and #778. Decomposes #838. Spec baseline from #986.

@jwrosewell
jwrosewell force-pushed the split/5-response-hook-docs branch 3 times, most recently from 7ebce99 to 700c913 Compare August 25, 2026 10:51
@jwrosewell jwrosewell changed the title Add the integration response-header hook and the provider documentation set Add the provider documentation set and finish the decomposition Aug 25, 2026
@jwrosewell
jwrosewell force-pushed the split/5-response-hook-docs branch 4 times, most recently from c17a7ea to 5b63f48 Compare August 27, 2026 05:37
@jwrosewell
jwrosewell force-pushed the split/5-response-hook-docs branch from 5b63f48 to 0bab4c0 Compare August 27, 2026 15:10
jwrosewell added a commit to jwrosewell/trusted-server that referenced this pull request Aug 27, 2026
The five-PR series (IABTechLab#1043 to IABTechLab#1047) opens the identity, device and geo
seams. The nine vendor integrations already in core sit behind the
integration registry instead, which is a private table, so none of them
can move out until that table is opened.

This spec defines the one core change that opens it: public registration
builders with a second input on IntegrationRegistry, browser JavaScript
carried on the registration, startup validation as a hook, the same
treatment for auction providers and the bid renderer contract, and
neutral replacements for the two places where a vendor reaches into
core. It then sets out the migration of all nine existing integrations,
one PR each. The change is complete in itself: after it, no vendor move
needs a core change.

Written against the series' tree with the file and line references for
every claim about the current code. Documentation only.
@jwrosewell
jwrosewell force-pushed the split/5-response-hook-docs branch 2 times, most recently from 3cfe393 to 45acb97 Compare August 31, 2026 12:50
jwrosewell added a commit to jwrosewell/trusted-server that referenced this pull request Aug 31, 2026
The five-PR series (IABTechLab#1043 to IABTechLab#1047) opens the identity, device and geo
seams. The nine vendor integrations already in core sit behind the
integration registry instead, which is a private table, so none of them
can move out until that table is opened.

This spec defines the one core change that opens it: public registration
builders with a second input on IntegrationRegistry, browser JavaScript
carried on the registration, startup validation as a hook, the same
treatment for auction providers and the bid renderer contract, and
neutral replacements for the two places where a vendor reaches into
core. It then sets out the migration of all nine existing integrations,
one PR each. The change is complete in itself: after it, no vendor move
needs a core change.

Written against the series' tree with the file and line references for
every claim about the current code. Documentation only.
jwrosewell added a commit to jwrosewell/trusted-server that referenced this pull request Aug 31, 2026
The review of IABTechLab#1043 asked that spec changes land before the code that
implements them, so a divergence is a decision taken in review rather
than a ratification of something already merged. PRs IABTechLab#1043 to IABTechLab#1047
each carried the design document for their own step, and IABTechLab#1043 carried
a 607-line spec describing device providers, geo providers, the
permission model and the browser resolve endpoint, none of which is in
that PR.

Move all six series documents here, so this PR carries the complete
normative set and no code:

- 2026-07-30-pluggable-providers-design.md (from IABTechLab#1043)
- provider-code-registry.md (from IABTechLab#1043)
- 2026-07-30-permission-model-design.md (from IABTechLab#1045)
- 2026-07-30-client-cycle-ec-resolve-design.md (from IABTechLab#1046, later
  revised by IABTechLab#1047)
- 2026-07-30-integration-response-header-hook-design.md (from IABTechLab#1047)
- 2026-07-30-provider-migration-rollout-design.md (from IABTechLab#1047)

Each file is taken verbatim at the tip of the stack, so the later
revisions are preserved: the provider-switching continuity section, the
geo requires-signal floor, and the code-envelope paragraph IABTechLab#1047 added
to the client-cycle spec. The revision-record tables are unchanged. No
document's substance was edited.

The only edits are to this spec's own status line, which said the PR
adds one document and that the series specs land with IABTechLab#1047, and a
revision-record row recording the move.
@aram356

aram356 commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

The sequencing discussion for this series is on #1084. This PR is superseded rather than rejected. The design in §3.6 is accepted and most of the provider work carries over onto the reordered base. See #1084.

jwrosewell added a commit to jwrosewell/trusted-server that referenced this pull request Sep 1, 2026
The four series specs (client-cycle EC resolve, permission model,
pluggable providers, migration and rollout) each carried a Status line
saying they were implemented. The code they describe is only in PRs
IABTechLab#1043 to IABTechLab#1047 and none of those is merged, so the line read as shipped
behavior. Each now says Proposed, names the PR that carries the
implementation and states that it is not yet on main, keeping the
existing revision dates and notes.

The integration provider seam spec carried counts and line references
that do not hold on main at d516a9e. Corrected against that commit:

- Section 4 said migration_guards.rs embeds "the thirteen vendor
  files". The directory holds 23 .rs files (2 infrastructure, 6 in
  nextjs/, 2 in datadome/, 13 top-level integration modules), the guard
  embeds 20 of them and 9 of those 20 belong to the nine vendors, with
  osano.rs and the two datadome/ files absent. builders() registers 13
  integrations, which is a different 13 from the file count.
- Section 3.5 gave no counts for the prepare and finalize calls. There
  are nine production prepare_request call sites across the four
  adapters and a tenth in core, and the single production
  finalize_response call site is in core rather than in any adapter.
- Section 8 item 3 described a proxy resolving geo twice, which does
  not happen on main. The real double resolution is the adapter EC
  context build against handle_auction on POST /auction.
- Section 8 item 5 understated the Spin gap and misdescribed
  Cloudflare. Cloudflare covers every route it registers and has no
  health route, while Spin skips its first-party bindings as well as
  its inline admin stubs.
- Line references: settings.rs:166 to :215, auction/mod.rs:49 to the
  list at :51 to :53, publisher.rs:4361 to :4369.

Section 6 now requires the round trip to be proven on the Fastly
adapter, the primary deployment target, rather than on any adapter,
because Fastly has no library target and the round trip otherwise only
runs on the Axum dev server.
@jwrosewell
jwrosewell force-pushed the split/5-response-hook-docs branch 6 times, most recently from 98764db to d48c98d Compare September 1, 2026 23:04
Brings the naming module, so a module is named by its crate folder, and the
Edge Cookie side resolving a written name within its type folder. The
host-signal module's key and the type folder sit side by side.
Brings the naming module from the Edge Cookie seam, so a module is named by
its crate folder.
Each of the five modules takes its name from its crate folder through
`module_name!()`, so `crates/permission-signal/gpp` is `gpp` and
`crates/permission-signal/us-privacy` is `us-privacy`, where the two had
called themselves `gpp_sale_opt_out` and `us_privacy` by hand. The page is
told a signal's source by the short name, without the type folder.

The section is `[permission-signal]`, named exactly as its folder, and its
key is `modules`, a list, because signals compose where `[ec]`, `[geo]` and
`[device]` each name one module. A name in the list may be written in full,
as `permission-signal.gpp`, or with the type folder left off, and the
refusal of a name this build does not offer lists the short names. A
configuration still carrying `[permission_signal]`, a `module` key or the
retired `sources` key is refused with directions to the new spelling.
Brings the naming module and the permission signal modules named by their
crate folders, with [permission-signal] modules. The Edge Cookie module
file keeps the client_fixed key beside the type folder, and the agent
guide's capability table keeps this branch's rows with the permission
signal selector as [permission-signal] modules.
Brings the naming module, the permission signal modules named by their
crate folders and [permission-signal] modules, with their guides.
A name written in `[ec] module`, an `[ec.<name>]` block or an
`implementation` line is parts joined by `.`, each of lower case letters,
digits, `_` or `-`, in place of snake_case, and a module from a crate may be
written in full, as `edgecookie.<name>`, or with that type folder left off.
The sections table names `[permission-signal]`.
Comments on the adapters and two test notes in core described what the code
did before it changed. Each now says what the code does and why.

- The resolved Edge Cookie module held on the Cloudflare, Fastly and Spin
  application state is resolved when the state is built, because resolving
  reads no request data.
- The three tests that a selected module the adapter cannot build fails the
  request say what a default context would do.
- The Spin state builder reads its settings from the config store, and its
  test says why the failure has to be the absence of a config store.
- The test that keeps the internal header list in step with the Edge Cookie
  response headers, and the test for an unknown key in a module block, say
  what they guard.

No code changes.
- The Cloudflare region test says why the region header has to reach the
  privacy outcome.
- The Fastly geo module's re-export and the geo selector's default are
  described as they are.
- The integration fixture's note on the geo selector no longer promises a
  setting that a later change does not add.

No code changes.
- The integration fixture carried two notes on its geo selector that
  contradicted each other. The one that stays says that Viceroy maps the
  loopback client to US/CA, so the platform geo module resolves a real place.
- That fixture, the Viceroy template, the template cache harness and the
  permission model guide no longer mention a default country setting, which
  no released version had.
- Five code comments say what the code does without saying what it did
  before.

No code changes.
Core builds the identity-graph key from the module's code and whatever
`normalize_id_for_kv` returns, so the method decides what two visits must
have in common to be treated as the same visitor. Its documentation
described normalization only and advised a module with a case-sensitive
identifier to return the value unchanged. That is right for a stable
identifier and wrong for one that carries a signature, a nonce or a
timestamp, because each reissue then gets a row of its own and the identity
does not survive it.

The trait documentation now says that the returned value is the identity two
visits must share, and that a module whose identifier has a part reissued
each time must return the stable part. It also says that core asks
`accepts_id` about the returned string as well as about the identifier as
issued, so a module must accept its own canonical form, or no row is read or
written for it.

`two_issues_of_one_identity_share_one_identity_graph_key` drives a fixture
module whose identifier is a stable part and a part that changes on each
issue through `AcceptedModules::canonical_kv_key`. Two identifiers that
differ only in the reissued part reach one key, and a different stable part
reaches another. The test fails for a module that returns the value
unchanged.

No behavior changes.
The Edge Cookie guide still said that an unknown country falls back to a
configured default country, and that only the storage permission is resolved
from a TCF signal. Neither is how the permission model works.

- When no country is identified, or neither the region nor the country has a
  rule, the baseline on the top node of the rules tree applies.
- Every permission is resolved from that baseline and the signals, each
  signal read by its own permission signal module in the configured order,
  with the Model Terms for Marketing preference among them.
- A location lookup that fails resolves at the requires-signal floor.

The setup guide describes the bare identifier form by what it is, a value
with no module prefix, rather than by when it was issued.
…ords

`KvNetwork` said a low cluster count "indicates an individual or
household". A count of connections is a fact about a network, and reading
a low one as an individual describes a device record as a record about a
person. It now says a small network, such as a home connection.

`KvEntry::ids` now says what it holds, which is each partner's identifier
for the same browser on the same device, never an identifier for a person.

Comments only, with no change in behavior.

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.

3 participants