Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,12 @@ jobs:
run: cargo test-fastly

test-axum:
name: cargo test (axum native)
runs-on: ubuntu-latest
name: cargo test (axum native, ${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v4

Expand Down Expand Up @@ -80,14 +84,23 @@ jobs:
run: cargo bench -p trusted-server-core --bench html_processor_bench -- --test

- name: Run host-target CLI tests
# The CLI is a host-target binary tested with an explicit host triple.
# Run it on the Linux runner only; the Windows matrix entry exists to
# verify the Axum adapter build and tests, and does not install the
# x86_64-unknown-linux-gnu target this step needs.
if: matrix.os == 'ubuntu-latest'
run: cargo test --package trusted-server-cli --target x86_64-unknown-linux-gnu

- name: Verify Fastly WASM release build
run: cargo build --package trusted-server-adapter-fastly --release --target wasm32-wasip1

test-cloudflare:
name: cargo check (cloudflare native + wasm32-unknown-unknown)
runs-on: ubuntu-latest
name: cargo check (cloudflare native + wasm32-unknown-unknown, ${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v4

Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
/spin
/spin.sig

# logs
*.log

# EdgeZero local KV store (created by edgezero-adapter-axum framework)
.edgezero/

Expand Down
81 changes: 79 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ crates/
trusted-server-adapter-cloudflare/ # Cloudflare Workers entry point (wasm32-unknown-unknown binary)
trusted-server-adapter-spin/ # Fermyon Spin entry point (wasm32-wasip1 component)
trusted-server-cli/ # Host-target `ts` operator CLI
device/
fastly/ # trusted-server-device-fastly (opt-in TLS/H2 device provider)
edgecookie/ # vendor Edge Cookie provider crates (built-in HMAC default is in core)
geo/ # vendor geo provider crates (host geo is injected by the adapter)
trusted-server-js/ # TypeScript/JS build — per-integration IIFE bundles
lib/ # TS source, Vitest tests, esbuild pipeline
```
Expand Down Expand Up @@ -57,7 +61,9 @@ fastly compute serve
# Deploy to Fastly
fastly compute publish

# Run Axum dev server (native — no Viceroy)
# Run Axum dev server (native — no Viceroy). Settings load at runtime from the
# platform config store on every adapter; publish an operator config with
# `ts config push` (see trusted-server.example.toml for the template).
cargo run -p trusted-server-adapter-axum

# Test Axum adapter only
Expand Down Expand Up @@ -138,6 +144,20 @@ cd crates/trusted-server-js/lib && node build-all.mjs
cargo install viceroy --version 0.17.0 --locked --force
```

### Windows (use WSL for the Linux-only tests)

The Rust adapter tests run natively on Windows through the cargo aliases
(`cargo test-fastly` via Viceroy, `cargo test-axum`, `cargo test-cloudflare`),
and CI runs these on both `ubuntu-latest` and `windows-latest`.

The Docker-based integration suite (`scripts/integration-tests.sh`) and the
Cloudflare worker build (`crates/trusted-server-adapter-cloudflare/build.sh`,
which uses `worker-build` + `wrangler dev`) are Linux tools. On Windows run them
inside WSL (Ubuntu) with Docker Desktop's WSL integration enabled. Provision the
WSL distro with the same toolchain as `.tool-versions` (rustup + the
`wasm32-wasip1` / `wasm32-unknown-unknown` targets, Node, Viceroy, wrangler), then
run the scripts from a clone on the WSL native filesystem for fast builds.

---

## Coding Conventions
Expand Down Expand Up @@ -265,12 +285,34 @@ impl core::error::Error for MyError {}

## Other guidelines

- Use US English spelling everywhere: code, identifiers, comments,
documentation, tests, commit messages, and configuration. For example, write
`color`, `behavior`, and `optimize`, not `colour`, `behaviour`, or `optimise`.
Where a term comes from an external source (for example the IAB TCF purpose
names), match that source's spelling even when it is not US English.
- Use only example or fictional information in comments, tests, docs, examples,
and similar non-runtime materials. (eg. for urls use: example.com domains only)
- Do not write or commit real domains, customer names, credentials,
configuration values, or other potentially sensitive real-world information in
comments, tests, docs, or examples.

### Permission model terminology

Permissions are the primitive. A provider declares the permissions it requires
(`required_permissions`) and the system decides whether each is _set_. Consent
is only one of several ways a permission may be established. Country or
jurisdiction rules (a `Granted` group baseline), legitimate interest, or
configuration can set a permission with no consent at all.

- A provider that needs nothing **requires no permission**. Never write that it
"runs without any consent".
- A gated provider **runs once its required permissions are set**, by whatever
method.
- Reserve "consent" for the consent subsystem (`consent/`, `ConsentContext`,
GDPR and TCF strings) where it genuinely means a consent signal. In the
permission layer prefer "permission", "set" / "unset", and "signal" (consent
is one kind of signal, alongside privacy and opt-out signals).

---

## Git Commit Conventions
Expand All @@ -287,6 +329,41 @@ Bad: `"fix: added feature flags"`

---

## Provider Architecture

Each vendor-differentiated capability is pluggable behind its own trait, so a
deployment selects an implementation and the core stays neutral:

| Capability | Trait | Selector | Built-in (core) | Vendor / host crates |
| --------------------- | ---------------------------------------- | ------------------- | --------------------------------------- | ---------------------------- |
| Edge Cookie identity | `EdgeCookieProvider` (`ec/provider.rs`) | `[ec] provider` | HMAC, client-fixed (opt-in, no default) | `crates/edgecookie/<vendor>` |
| Device detection | `DeviceProvider` (`ec/device.rs`) | `[device] provider` | User-Agent only (default) | `crates/device/<vendor>` |
| Geo / IP intelligence | `PlatformGeo` (`platform/traits.rs`) | `[geo] provider` | Disabled, no location (default) | `crates/geo/<vendor>` |

Principles for adding or changing a provider:

- **Core stays neutral.** The trait and the host-neutral default live in
`trusted-server-core`. Host-specific and vendor implementations live in their
own crates and are injected by the adapter (for example `build_device_provider`
and `build_geo_provider`), so core never depends on a host SDK or a vendor, and
the default request path makes no host-specific calls.
- **Providers read request evidence, not a fixed parameter set.** A provider must
be able to see everything about the request it needs (User-Agent, headers, and
host signals such as the TLS JA4 and HTTP/2 fingerprints) through an evidence
abstraction rather than a hard-coded struct of fields. Host signals come from
the host (the Fastly SDK) and are opt-in, so a neutral provider triggers no
host fingerprint calls.
- **Providers are separated by capability but composed per request, and one may
need another's output.** Geo resolves the country and region the permission
model uses, and the permission model gates whether the Edge Cookie provider
runs. Device signals gate Edge Cookie writes (the browser / bot gate). When
several vendor providers share a backend (for example a vendor's Edge Cookie,
geo, and device provider on one cloud pipeline) they share a single call per
request rather than calling independently. Give a provider the inputs and
upstream results it needs explicitly, rather than having it reach into globals.

---

## Integration System

Integrations register in Rust via:
Expand Down Expand Up @@ -320,7 +397,7 @@ IntegrationRegistration::builder(ID)
| --------------------- | ---------------------------------------------------------- |
| `edgezero.toml` | EdgeZero app/platform manifest and logical stores |
| `fastly.toml` | Fastly service configuration and build settings |
| `trusted-server.example.toml` | Source-controlled Trusted Server app-config template |
| `trusted-server.example.toml` | Source-controlled app-config template (includes the `[ec]` / `[geo]` / `[device]` provider selectors and `[geo] default_country`) |
| `trusted-server.toml` | Operator-owned app config; gitignored; `ts config push` publishes it as an EdgeZero blob envelope |
| `rust-toolchain.toml` | Pins Rust version to 1.95.0 |
| `.env.dev` | Local development environment variables |
Expand Down
39 changes: 39 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
[workspace]
resolver = "2"
members = [
"crates/device/fastly",
"crates/geo/fastly",
"crates/trusted-server-adapter-axum",
"crates/trusted-server-adapter-cloudflare",
"crates/trusted-server-adapter-fastly",
Expand Down Expand Up @@ -80,6 +82,7 @@ rustls-pemfile = "2"
scraper = "0.24.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.149"
serde_yaml_ng = "0.10"
sha2 = "0.10.9"
simple_logger = "5"
spin-sdk = { version = "~6.0", default-features = false, features = ["http", "key-value", "variables"] }
Expand All @@ -93,6 +96,8 @@ tokio-rustls = "0.26"
toml = "1.1"
tower = "0.4"
trusted-server-core = { path = "crates/trusted-server-core" }
trusted-server-device-fastly = { path = "crates/device/fastly" }
trusted-server-geo-fastly = { path = "crates/geo/fastly" }
trusted-server-js = { path = "crates/trusted-server-js" }
trusted-server-openrtb = { path = "crates/trusted-server-openrtb" }
url = "2.5.8"
Expand Down
10 changes: 10 additions & 0 deletions crates/device/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Device providers

Device-detection provider crates live here, one per vendor. The Fastly provider
(`trusted-server-device-fastly`) classifies a request with the host's TLS and
HTTP/2 fingerprints; future vendor providers (for example
`crates/device/<vendor>`) slot in alongside it.

The built-in default provider (User-Agent only) ships in `trusted-server-core`
(`ec::device`). Adapters select and inject the vendor provider via
`build_device_provider`.
17 changes: 17 additions & 0 deletions crates/device/fastly/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "trusted-server-device-fastly"
version = "0.1.0"
authors = []
edition = "2024"
publish = false
license = "Apache-2.0"

[lib]
doctest = false

[lints]
workspace = true

[dependencies]
trusted-server-core = { workspace = true }
fastly = { workspace = true }
Loading
Loading