Skip to content

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

Open
jwrosewell wants to merge 46 commits into
feature/4-client-resolvefrom
feature/5-response-hook-docs
Open

jwrosewell wants to merge 46 commits into
feature/4-client-resolvefrom
feature/5-response-hook-docs

Conversation

@jwrosewell

@jwrosewell jwrosewell commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Changes

Area Change
docs/guide 8 files, 206 lines added, 75 removed
crates/trusted-server-core/src 3 files, 67 lines added, 20 removed
.github/workflows 1 file, 21 lines added, 4 removed
trusted-server.example.toml 1 file, 18 lines added, 7 removed
.gitignore 1 file, 3 lines added, 0 removed
crates/geo/README.md 1 file, 3 lines added, 2 removed
crates/trusted-server-js/lib 1 file, 2 lines added, 1 removed

Closes

Part of #782, which stays open until the hook itself is built.

Spec

Spec: integration-response-header-hook

Test plan

CI on this pull request at 643de85 passed all 32 checks.

  • 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 this pull request at 643de85

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 client-set Edge Cookie value path (#1346), and depends on #1343
to #1346 together, because this pull request documents what they build. It is the fifth and last of the pull requests (#1343 to #1347) that
decompose the module work of the combined change (#838), as the review of
the earlier design specs (#986) asked.

The design specs for the series are carried by the spec pull request (#1342),
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.

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

Fifth and final slice of the PR 838 decomposition:

- Documentation for the provider model: configuration reference for the
  [ec], [device], and [geo] sections (including the required
  default_country, the assume_single_jurisdiction acknowledgment, and
  the requires-signal floor on a failed lookup), the Edge Cookie guide
  rewritten around providers and the permission model (including the
  narrow withdrawal semantics, the hardened resolve endpoint, and the
  resolved-marker cookie), setup and error-reference updates, and the
  permission-model guide joins the docs navigation.
- The example configuration documents every provider selector in one
  place, with the [geo] baseline explained and acknowledged.
- The HostSignals trait moves to the top of the evidence module with
  its service framing documented.
- The Fastly EC lifecycle integration-test job joins the test workflow.

The earlier draft of this slice carried an IntegrationResponseMutator
response-header hook. It shipped with no consumer, so it is not
included: the hook returns together with the first integration that
needs it.

Two specs land with this slice: the migration and rollout spec
(docs/superpowers/specs/2026-07-30-provider-migration-rollout-design.md),
its sign-off ledger kept intact with each row the series implements
marked with its PR, and the response-header hook spec
(docs/superpowers/specs/2026-07-30-integration-response-header-hook-design.md),
retained as the design bar for the hook when its first consumer
arrives.
The rollout spec cited commit SHAs from a rebuilt branch, credited the
series with a passphrase tightening that pre-dates it, said graph rows
were keyed verbatim when the pre-series normalization already
lowercased them, attributed the Fastly-only identity endpoints to PR
1046, and claimed a CLI regression test for the provider overrides that
does not exist. The hook spec said the fastly device provider is
startup-rejected (it ships opt-in), that integrations cannot touch
response headers (request filters can name them) and that the wasm
target builds with panic = abort (it has no unwinding at all). The
configuration guide now lists every Edge Cookie and geo provider value,
the API reference documents each resolve response and the coded
identifier form, and the setup guide shows the hmac~ prefix a minted
cookie carries.
The no-client-IP test now derives identity from the request query and
cookies through `EvidenceCapturingProvider`, which left
`NoClientIpProvider` with no remaining caller. It is deleted rather than
left looking like a live fixture, and the branch builds without the
dead-code warning that denied clippy.

That test's comment claimed the provider receives the documented
unavailable value, the empty string, but the rewrite no longer observes
what the provider was handed, so nothing proved it. The comment now says
only what the test asserts, which is that a provider reading other
evidence still mints when the host can determine no client IP. Restoring
the stronger assertion would mean recording the client IP the provider
saw, which is a change to the fixture rather than to this cleanup.

The `http::HeaderMap` import in the device tests was unused, because the
one place needing the type names it in full.

Addresses: crates/trusted-server-core/src/ec/mod.rs and
crates/trusted-server-core/src/ec/device.rs, which carried a dead test
provider, a comment asserting more than the test proved, and an unused
import.
The rewrite that replaced the old no-client-IP fixture left its comment claiming
the provider receives the documented unavailable value while nothing asserted
it, so the contract went unchecked and the comment said more than the test knew.

The shared evidence-capturing provider now records the client IP it was given
alongside the query parameter and cookie it already recorded, and the test
asserts the empty string reaches it. A provider cannot decide how to behave on a
host that cannot determine a client IP without knowing what absence looks like,
so that value is part of the contract rather than an implementation detail.

Verified by falsifying the expectation and watching the assertion report the
real value it observed rather than passing regardless.

Addresses: crates/trusted-server-core/src/ec/mod.rs, a comment claiming more
than its test proved
The core library is exercised only through the WebAssembly adapter targets,
which build with panic=abort. Their harness stops at the first failing test and
reports every later one as never run, so a red build shows one failure when
there may be several, and the ones it hides are invisible until the first is
fixed and the suite is run again.

This adds a host-target run of the same crate to the job that already runs the
CLI and codegen tests natively, so every failure is reported at once. It costs
one more compilation of a crate the job has already built for other targets.

Addresses: .github/workflows/test.yml, core failures hidden behind panic=abort
The permission baseline and consent handling for a request the geo provider leaves unmatched come from the top of the permissions.yaml rules tree, so the [geo] reference no longer documents a default_country field. The deployer states the policy in that file, and the software encodes no jurisdiction's law.
The paragraph still placed the permissions file at the repository root
and referenced the retired default_country. It now points at the
config/permissions sample and the top-of-tree fallback.
Brings the permission signal work up to the documentation branch. The
merge has no conflicts.
Brings the documentation and assertion message fixes up from
split/4-client-resolve. The merge has no conflicts.
Brings upd/split/4-client-resolve at 69e7d57, which carries main at
066ea3c through split/1 to split/4, into split/5-response-hook-docs.
Five files conflicted, all in the documentation set and the example
configuration, and all for one reason. This branch documents split/1's
move of the Edge Cookie passphrase under [ec.providers.hmac] behind
provider = "hmac", still as a plaintext value, while main's #1036 made
the passphrase the name of a secret-store key. Every resolution keeps
this branch's layout with main's key-name model.

In configuration.md, ec-setup-guide.md, error-reference.md and
fastly.md, the TOML examples select provider = "hmac" with
passphrase = "ec_passphrase" under [ec.providers.hmac], and the
environment overrides use TRUSTED_SERVER__EC__PROVIDERS__HMAC__PASSPHRASE
with the key name. Three examples merged cleanly with the plaintext
placeholder still under [ec.providers.hmac] and take the key name too.
The [ec.providers.hmac] reference table gains main's note that the
passphrase is a key name, and its passphrase row says so. The security,
staging and placeholder bullets are main's, because this branch's
bullets described plaintext values in environment variables. The
validation bullets keep this branch's provider bullet and take main's
three, naming providers.hmac.passphrase.

trusted-server.example.toml keeps this branch's host-signal provider
block with a key-name placeholder, because that passphrase is also
resolved from the secret store (a75571d on split/2), and takes main's
partner comment.
The example configuration said the host-signal provider creates the
identifier from the host's TLS JA4 and HTTP/2 signals instead of the
client IP. HostSignalProvider::generate hashes the JA4 and HTTP/2
signals together with the client IP, as the provider's own
documentation says, so the comment now says so.
The configuration guide said a failed geo lookup at request time does
not fall back to default_country, a setting that no longer exists. The
sentence now says what a failed lookup does without naming it.
Brings upd/split/4-client-resolve at 2c34be4 into
split/5-response-hook-docs. That carries the split/1 review follow-ups
(0980b73, 0e7f7eb, fb1bc28, cb6f717 and 1b88e42) through
split/2, split/3 and split/4, and split/3's be0ed2d fix for the
Cloudflare and Spin startup tests, whose four settings blocks this
branch carries too.

One block conflicted, the admin lookup sentence in
docs/guide/api-reference.md. This branch added the hmac~ prefix to
main's {64 lowercase hex}.{6 alphanumeric} sentence, and split/1's
1b88e42 rewrote the same sentence to list every form the route
accepts, being an identifier from the selected provider such as the
hmac~ form, the bare legacy form the HMAC provider still reads, and
both HMAC forms when no provider is selected. That sentence covers what
this branch added, so it is kept.

Tests. The merged tree passes a native all-targets check, 2,885 core
tests, the Axum, Cloudflare and Spin adapter tests, the permission
signal crate tests, and the Fastly, Cloudflare and Spin wasm checks,
and Prettier accepts the resolved API reference.
Brings upd/split/4-client-resolve at b7dd13f into
split/5-response-hook-docs. That carries split/3's 88a7482, which makes
`tools/permissions-inspector/wasm` pass the request evidence and the signal
providers to `assemble_permissions`, and which formats the crate. This branch
carries the same inspector and the same `Permissions Inspector` workflow, so it
failed the same way.

The merge had no conflicts and touches only the four files under
`tools/permissions-inspector/wasm`.

Tests. `./scripts/build-inspector-wasm.sh` builds clean for
wasm32-unknown-unknown on this tree, and `cargo fmt --check` on the crate is
clean.
configuration guide

Brings upd/split/4-client-resolve at e17090b into
split/5-response-hook-docs, which carries split/3's a2a1dfc renaming
`config/permissions/vanilla.yaml` to `config/permissions/sample.yaml`.

This branch adds the permissions paragraph in `docs/guide/configuration.md`,
which split/3 does not have, so the merge could not correct it. That sentence
now names the new path and says the sample is for testing and evaluation only
and is neither a production policy nor legal advice, matching the file header
and the permission model guide.

The merge had no conflicts. `trusted-server.example.toml` auto-merged.

Tests. `cargo fmt --all --check` is clean and the core suite passes 2,885
tests. The docs Prettier configuration sets `proseWrap: preserve`, so the
edited sentence cannot change the formatting of the file.
…guides

Brings up the Edge Cookie provider block layout from split/1, the
host_signals rename from split/2, the permission signal provider key from
split/3 and the client_fixed rename from split/4. All four are the same
decision, which is that every provider name an operator types is
snake_case, a provider type is a top-level table, and a provider key
inside it selects what runs.

Nothing conflicted. The response hook and the documentation set this
branch carries sit beside the sections those renames touched.

The documentation needed the work the clean merge did not do. These
guides were written against the old layout, so they still told operators
to write a table that is now refused and to select providers by spellings
that now stop startup. The configuration guide, the Edge Cookie setup
guide, the error reference, the Fastly guide and the key rotation guide
all move from [ec.providers.hmac] to [ec.hmac], and the environment
override for that passphrase becomes
TRUSTED_SERVER__EC__HMAC__PASSPHRASE, which is the path the settings now
hold it at.

The configuration guide's [ec] reference needed more than a rename. Its
rule that every selection must have a matching block is no longer what
the code does, because a provider has a block only when it has settings,
so it now says which providers need one and which do not, and it explains
the implementation key that lets a block carry a label of the operator's
choosing. Its provider list and its validation notes follow the
snake_case names, and the resolve_allowed_origins key this stack added to
the [ec] section is documented alongside the rest.

The Edge Cookie guide named the host-signal and demonstration providers
by their old spellings throughout, including in a diagram label, and now
names them host_signals and client_fixed. The cargo feature that compiles
the demonstration provider in keeps the name client-fixed-demo, because a
cargo feature name is not a configuration value.

The example configuration's commented-out host-signal block carried both
the old table and the old name, and is now [ec.host_signals] selected by
provider = "host_signals".

Tests. cargo fmt --all -- --check is clean and cargo clippy -p
trusted-server-core --all-targets --all-features -- -D warnings passes.
The core suite passes 2,909 tests and 8 doc-tests with 4 ignored. The
Axum adapter passes 64 tests across its five binaries, cargo clippy-axum
is clean, and cargo check -p trusted-server-adapter-fastly --target
wasm32-wasip1 builds.
Four sentences in the guides joined their clauses with a semicolon, and
one table row grew past its column. They now read as one sentence each,
and the table is realigned, so the pages follow the house style the rest
of the guide follows.
Carries up the resolutions from split/4.

Main's documentation refresh rewrote the section table in the configuration
guide, so its version is taken with this branch's sections added in
alphabetical order. That is device and geo, which the old table listed, and
permission_signal, which it never did.
Brings main at 7a0ecb4 and the refreshed lower branches into the
documentation set. Nine of the eleven conflict blocks differed only by the
module rename and are settled as this branch's text with the rename
applied. By hand: the evidence module keeps the HostSignals trait where
this branch moved it, the setup guide keeps this branch's description of
the ec_id value ahead of main's text on grouped mappings, and the Edge
Cookie guide keeps the client_fixed paragraph and takes main's
completion-marker text for the ec_identity_store paragraph where this
branch moved it.
What an operator selects to supply a capability is a module, and provider
keeps the one meaning it has on main, an auction provider instance. This
renames the words this branch adds on top of the client resolve path,
which already says module, and only those words: every provider word was
compared with upstream main, and a word that exists there is untouched,
as is the icu_provider crate the permissions inspector's lock file names.

- The environment overrides the configuration guide and the error
  reference show read TRUSTED_SERVER__EC__MODULE,
  TRUSTED_SERVER__DEVICE__MODULE and TRUSTED_SERVER__GEO__MODULE, and the
  selectors in every example read module.
- The configuration, Edge Cookie, setup, error reference and Fastly
  guides, the example settings, the evidence module's documentation and
  the no-client-IP test's message say module wherever they meant an Edge
  Cookie, device or geo module.
Brings the fix that threads the selected module into the resolve conflict
test's context.
Brings the inspector lockfile fix, which names the icu_provider crate again.
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]`.
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.
@jwrosewell
jwrosewell removed this pull request from stack #1401 October 15, 2026 20:51
Core's build script compiles the git version in by emitting
TRUSTED_SERVER__GIT_VERSION as a rustc-env variable, and cargo also sets
that variable on the test process. The test-only from_toml_and_env read it
as a git_version setting, which Settings refuses as an unknown field, so
twelve settings tests failed in the host-target core library tests this
branch runs in CI.

The loader now builds its environment source from the process variables
without that one, and a new test loads settings with the variable set.
@jwrosewell
jwrosewell removed this pull request from stack #1408 October 18, 2026 20:15
No response carries the EC ID in a header of its own. Core reads x-ts-ec
from requests, removes it from the requests it forwards and clears it from
responses, and a module may not set an x-ts- response header, so the
identifier reaches the browser only in the ts-ec cookie, as the API
reference says. The sentence now says so, and points to the API reference
for the routes that use the cookie.
@jwrosewell
jwrosewell added this pull request to stack #1418 October 19, 2026 12:18
@jwrosewell
jwrosewell removed this pull request from stack #1418 October 19, 2026 16:30
@jwrosewell
jwrosewell added this pull request to stack #1421 October 19, 2026 17:38
@jwrosewell
jwrosewell removed this pull request from stack #1421 October 19, 2026 18:29
@jwrosewell
jwrosewell added this pull request to stack #1422 October 19, 2026 19:20
@jwrosewell
jwrosewell marked this pull request as ready for review October 19, 2026 20:22

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