Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
105 commits
Select commit Hold shift + click to select a range
4ef07d5
Add the permission model with the Privacy Taxonomy vocabulary
jwrosewell Aug 19, 2026
68f67d0
Correct the permission-model docs to what the code does
jwrosewell Aug 27, 2026
4c889b0
Give the remaining test fixtures the now-required default country
jwrosewell Aug 29, 2026
bb56ceb
Say which geo lookups can reach the requires-signal floor, and prove it
jwrosewell Aug 29, 2026
8adbb06
Scope the permission-gating claim to the provider it is true of
jwrosewell Aug 30, 2026
f446393
State that evidence is not rationed and use is
jwrosewell Aug 30, 2026
292352a
Name the IAB Tech Lab Privacy Taxonomy as the source of the vocabulary
jwrosewell Aug 30, 2026
625e816
Build the test provider code through the macro that cannot fail
jwrosewell Aug 30, 2026
0dd10fd
Documentation and house-style cleanup for the permission model PR
jwrosewell Aug 31, 2026
1a73fe6
Complete the mint and fingerprint house-style sweep after rebase
jwrosewell Aug 31, 2026
c793b23
Document how the resolved permissions reach filters and the page
jwrosewell Sep 1, 2026
e759653
Expose the request permission state on the tsjs core API
jwrosewell Sep 1, 2026
5eb8132
Pass the resolved permission state to integration request filters
jwrosewell Sep 1, 2026
e56cf0d
Carry the resolved permission state to the page
jwrosewell Sep 1, 2026
af386ca
Document the permission rules tree and retire the default country
jwrosewell Sep 1, 2026
84b97c2
Match the guide's jurisdiction values to the policy parser
jwrosewell Sep 1, 2026
7459346
Document jurisdiction as a per-node inherited attribute
jwrosewell Sep 1, 2026
76fe217
Note the unknown jurisdiction on a failed lookup and the DE codes
jwrosewell Sep 1, 2026
4c0758b
Point the GDPR page at the rules tree for jurisdiction assignment
jwrosewell Sep 1, 2026
ec86a6f
Add the permission model guide to the site navigation
jwrosewell Sep 1, 2026
1f9b03f
Restructure the permission policy into a tree of places
jwrosewell Sep 1, 2026
c681310
Give the integration environment a real place through Viceroy geoloca…
jwrosewell Sep 1, 2026
6f50829
Give the template-cache harness a real place through Viceroy geolocation
jwrosewell Sep 1, 2026
e8f0343
Anchor the harness's geo selector uncomment to the whole line
jwrosewell Sep 1, 2026
4ea7757
Repair the harness's geo selector uncomment
jwrosewell Sep 1, 2026
204393b
Model a realistic demo policy in permissions.yaml
jwrosewell Sep 2, 2026
f3f64f8
State that no policy ever ships with Trusted Server
jwrosewell Sep 2, 2026
361d934
Move the sample permissions into config/permissions
jwrosewell Sep 2, 2026
187a66a
Add the permissions inspector, driven by the real code as WebAssembly
jwrosewell Sep 2, 2026
069ec9d
Mark the inspector build script executable
jwrosewell Sep 2, 2026
9909f2e
Limit the inspector workflow token to reading contents
jwrosewell Sep 2, 2026
aa0bce0
Point the compiled-in rules doc at config/permissions
jwrosewell Sep 2, 2026
02f79c7
Put the permission signal models behind a seam
jwrosewell Sep 7, 2026
da4ea02
Evaluate permission signals in order, each amending the last
jwrosewell Sep 7, 2026
5383a36
Name the permission signal models in configuration
jwrosewell Sep 7, 2026
1c369bf
Stop treating an unreadable consent record as a model to choose
jwrosewell Sep 7, 2026
5d11c51
Open the permission signal seam to providers outside core
jwrosewell Sep 10, 2026
cce1f45
Ship the four IAB signal schemes as crates the adapters link
jwrosewell Sep 10, 2026
48919be
Document permission signals and the provider order
jwrosewell Sep 10, 2026
b752d96
Carry the terms the data is available under
jwrosewell Sep 10, 2026
c43ebef
Keep the provider rationale on the docs page
jwrosewell Sep 14, 2026
2eb8712
Say what each permission signal assertion checks
jwrosewell Sep 14, 2026
2ea1c69
Merge main into the permission model through split/2
jwrosewell Sep 14, 2026
3c9f4b0
Correct the Windows notes on CI runners and the CLI
jwrosewell Sep 14, 2026
be0ed2d
Acknowledge one jurisdiction in the adapter startup tests
jwrosewell Sep 14, 2026
e3bb4b9
Merge the Edge Cookie review follow-ups through split/2
jwrosewell Sep 14, 2026
88a7482
Run the permission signal providers in the permissions inspector
jwrosewell Sep 14, 2026
a2a1dfc
Name the permissions sample for what it is
jwrosewell Sep 14, 2026
f5b9c04
Select permission signal providers with provider, named in snake_case
jwrosewell Sep 15, 2026
5c9f27e
Merge the per-provider [ec] tables and the host_signals name from spl…
jwrosewell Sep 15, 2026
0d446be
Merge main into the permission model
jwrosewell Sep 25, 2026
edad223
Merge the test state fix from split/2
jwrosewell Sep 25, 2026
fe0b6d9
Let the shared Next.js fixture say it serves one jurisdiction
jwrosewell Sep 25, 2026
22262a0
Let the cache withdrawal test withdraw on this branch's terms
jwrosewell Sep 25, 2026
66b4bf6
Merge split/2-device-geo at 13cfa9b5e into split/3-permissions
jwrosewell Oct 6, 2026
796bb47
Say what happens when geolocation resolves no location
jwrosewell Oct 6, 2026
207d04e
Say which permissions are still waiting for a signal
jwrosewell Oct 6, 2026
c04649f
Await only what a configured module could still grant
jwrosewell Oct 6, 2026
ddceadd
Each module owns its own unreadable signal, and the state names the v…
jwrosewell Oct 6, 2026
7554c3e
The Model Terms for Marketing signal module, reading the preference c…
jwrosewell Oct 6, 2026
fc2a355
The permission model says module where it said provider
jwrosewell Oct 6, 2026
1e8b612
Run the permission signal crates' tests with the Axum adapter
jwrosewell Oct 6, 2026
511db9b
Bring the permission model's comments and tests into line
jwrosewell Oct 6, 2026
6c0d268
Acknowledge single-jurisdiction operation in the Fastly sandbox tests
jwrosewell Oct 6, 2026
4e28a68
Format the permission model and signal pages
jwrosewell Oct 6, 2026
343bd5f
Stop the inspector reporting a field core no longer answers
jwrosewell Oct 6, 2026
ac51edc
Pin the inspector's lock to the edgezero revision the workspace uses
jwrosewell Oct 6, 2026
8f6047e
Format the agent guide and the permission signal README
jwrosewell Oct 6, 2026
6489135
Merge split/2-device-geo at 6fc9cdf71 into split/3-permissions
jwrosewell Oct 7, 2026
fd51fe8
The permission signal modules are named by their crate folders
jwrosewell Oct 7, 2026
05a1cce
Merge split/2-device-geo at cec6052e2 into split/3-permissions
jwrosewell Oct 7, 2026
9da9a65
Comments and the permission model guide say what is true now
jwrosewell Oct 7, 2026
c0a0f3a
Merge split/2-device-geo at 09514b188 into split/3-permissions
jwrosewell Oct 7, 2026
613cd18
Merge split/2-device-geo at fb1d369a1 into split/3-permissions
jwrosewell Oct 7, 2026
0afd715
Merge split/2-device-geo at 47551a622 into split/3-permissions
jwrosewell Oct 8, 2026
a028c00
Merge split/2-device-geo into split/3-permissions
Jamesr51d Oct 13, 2026
90d0fc7
Merge split/2-device-geo into split/3-permissions
jwrosewell Oct 15, 2026
dae7d78
Merge split/2-device-geo into split/3-permissions
jwrosewell Oct 16, 2026
bcde751
Merge split/2-device-geo into split/3-permissions
jwrosewell Oct 16, 2026
eed2879
Merge split/2-device-geo into split/3-permissions
jwrosewell Oct 16, 2026
2db6bbd
Merge split/2-device-geo into split/3-permissions
jwrosewell Oct 18, 2026
6e70f5c
The inspector writes a policy's place codes into the headline as text
jwrosewell Oct 18, 2026
11c23ed
The inspector checks the integrity of the js-yaml script it loads
jwrosewell Oct 18, 2026
fda5142
Merge split/2-device-geo into split/3-permissions
jwrosewell Oct 18, 2026
2774277
Merge split/2-device-geo into split/3-permissions
jwrosewell Oct 19, 2026
11c3660
Merge split/2-device-geo into split/3-permissions
jwrosewell Oct 19, 2026
3c0ff33
Merge split/2-device-geo into split/3-permissions
jwrosewell Oct 19, 2026
db8e1da
The inspector workflow sets up Rust through setup-rust
jwrosewell Oct 19, 2026
af89a31
Merge split/2-device-geo into split/3-permissions
jwrosewell Oct 28, 2026
e29cedb
Add the [permissions] table and the policy file it names
jwrosewell Oct 28, 2026
577b9af
Add the policy source and Axum's folder of policy files
jwrosewell Oct 28, 2026
afd4f2f
Answer a Spin startup failure with the status of its kind
jwrosewell Oct 28, 2026
55e10cb
Read the Cloudflare blob from the config store
jwrosewell Oct 28, 2026
51e7112
Take the policy files with ts config push, validate and diff
jwrosewell Oct 28, 2026
1447dcd
Refuse a domain whose permission policy does not load
jwrosewell Oct 28, 2026
19b10c0
Decide each request under the policy its domain loaded
jwrosewell Oct 28, 2026
a9f8ce0
Check that no release WASM carries a permission policy
jwrosewell Oct 28, 2026
d0e4572
Document the permission policy file every push supplies
jwrosewell Oct 28, 2026
222e67d
CI proves the policy guard refuses the sample
jwrosewell Oct 28, 2026
b41cce3
The README quick start validates the policy file it names
jwrosewell Oct 28, 2026
eb07aa2
The Spin and Cloudflare smokes check a negative case's status where t…
jwrosewell Oct 28, 2026
d529db0
The policy sentences say no adapter build carries one
jwrosewell Oct 28, 2026
ae79f59
The Cloudflare hook router test asks every fallback method on two paths
jwrosewell Oct 28, 2026
fb7ad26
A policy already held is not read again for a second host
jwrosewell Oct 28, 2026
02ec8fa
Merge split/2-device-geo into split/3-permissions
jwrosewell Oct 28, 2026
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
6 changes: 4 additions & 2 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,12 @@ test-fastly = "test -p trusted-server-core -p trusted-server-adapter-fastly -p t
test-fastly-reuse = "test -p trusted-server-adapter-fastly --target wasm32-wasip1 --features reusable-sandbox"

# --- Axum adapter (native dev server) ---
# Test and clippy also cover the permission signal crates, whose own tests
# run with the Axum adapter in CI.
build-axum = "build -p trusted-server-adapter-axum"
check-axum = "check -p trusted-server-adapter-axum"
clippy-axum = "clippy -p trusted-server-adapter-axum --all-targets --all-features -- -D warnings"
test-axum = "test -p trusted-server-adapter-axum"
clippy-axum = "clippy -p trusted-server-adapter-axum -p trusted-server-permission-signal-gpc -p trusted-server-permission-signal-gpp -p trusted-server-permission-signal-mtm -p trusted-server-permission-signal-tcf -p trusted-server-permission-signal-us-privacy --all-targets --all-features -- -D warnings"
test-axum = "test -p trusted-server-adapter-axum -p trusted-server-permission-signal-gpc -p trusted-server-permission-signal-gpp -p trusted-server-permission-signal-mtm -p trusted-server-permission-signal-tcf -p trusted-server-permission-signal-us-privacy"

# --- Cloudflare adapter (native host + wasm32-unknown-unknown) ---
# Build/check target the WASM runtime (requires the `cloudflare` feature);
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/deploying-trusted-server-to-fastly/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ For each shared-name store a service needs:
`fastly resource-link create --service-id <SID> --version <v> --resource-id <store-id> --name trusted_server_config`
3. Push config into the scoped store with the name override, so the write cannot
land in the shared default:
`EDGEZERO__STORES__CONFIG__TRUSTED_SERVER_CONFIG__NAME=<service>_ts_config ts config push --adapter fastly --app-config <file>`
`EDGEZERO__STORES__CONFIG__TRUSTED_SERVER_CONFIG__NAME=<service>_ts_config ts config push --adapter fastly --app-config <file> --permissions <policy file>`

At runtime the service opens `trusted_server_config` and gets the linked physical
store, so no other service is touched. Apply the same physical-name-per-service
Expand Down
20 changes: 0 additions & 20 deletions .github/actions/setup-integration-test-env/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ name: Setup integration test environment
description: Prepare toolchains, Viceroy, and Docker images for integration test jobs.

inputs:
origin-port:
description: Fixed origin port baked into the WASM binary for integration tests.
required: true
install-viceroy:
description: Install the Viceroy binary used by integration tests.
required: false
Expand Down Expand Up @@ -73,23 +70,11 @@ runs:
- name: Build WASM binary
if: ${{ inputs.build-wasm == 'true' }}
shell: bash
env:
TRUSTED_SERVER__PUBLISHER__ORIGIN_URL: http://127.0.0.1:${{ inputs.origin-port }}
TRUSTED_SERVER__PUBLISHER__PROXY_SECRET: integration-test-proxy-secret
TRUSTED_SERVER__EC__PASSPHRASE: integration-test-ec-secret-padded-32
TRUSTED_SERVER__EC__PARTNERS: >-
[{"name":"Integration Test Partner","source_domain":"inttest.example.com","bidstream_enabled":true,"api_token":"integration-test-token-alpha-32-bytes-ok"},{"name":"Integration Test Partner 2","source_domain":"inttest2.example.com","bidstream_enabled":true,"api_token":"integration-test-token-bravo-32-bytes-ok"}]
TRUSTED_SERVER__PROXY__CERTIFICATE_CHECK: "false"
run: cargo build --package trusted-server-adapter-fastly --release --target wasm32-wasip1

- name: Build Axum native binary
if: ${{ inputs.build-axum == 'true' }}
shell: bash
env:
TRUSTED_SERVER__PUBLISHER__ORIGIN_URL: http://127.0.0.1:${{ inputs.origin-port }}
TRUSTED_SERVER__PUBLISHER__PROXY_SECRET: integration-test-proxy-secret
TRUSTED_SERVER__EC__PASSPHRASE: integration-test-ec-secret-padded-32
TRUSTED_SERVER__PROXY__CERTIFICATE_CHECK: "false"
run: cargo build -p trusted-server-adapter-axum

- name: Build WordPress test container
Expand All @@ -116,9 +101,4 @@ runs:
- name: Build Cloudflare Workers bundle
if: ${{ inputs.build-cloudflare == 'true' }}
shell: bash
env:
TRUSTED_SERVER__PUBLISHER__ORIGIN_URL: http://127.0.0.1:${{ inputs.origin-port }}
TRUSTED_SERVER__PUBLISHER__PROXY_SECRET: integration-test-proxy-secret
TRUSTED_SERVER__EC__PASSPHRASE: integration-test-ec-secret-padded-32
TRUSTED_SERVER__PROXY__CERTIFICATE_CHECK: "false"
run: bash crates/trusted-server-adapter-cloudflare/build.sh
23 changes: 23 additions & 0 deletions .github/workflows/inspector.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: "Permissions Inspector"

on:
push:
branches: [main]
pull_request:

permissions:
contents: read

jobs:
build-inspector-wasm:
name: build inspector wasm
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Rust toolchain
uses: ./.github/actions/setup-rust
with:
target: wasm32-unknown-unknown
cache-key: cargo-inspector
- name: Build the inspector engine
run: ./scripts/build-inspector-wasm.sh
7 changes: 3 additions & 4 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@ jobs:
- name: Set up shared integration build environment
uses: ./.github/actions/setup-integration-test-env
with:
origin-port: ${{ env.ORIGIN_PORT }}
install-viceroy: "false"
build-cloudflare: "true"

- name: Check that no permission policy is compiled into the Cloudflare WASM
run: ./scripts/check-no-compiled-policy.sh crates/trusted-server-adapter-cloudflare/build/index_bg.wasm

- name: Generate integration Viceroy configs
run: ./scripts/generate-integration-viceroy-configs.sh
env:
Expand Down Expand Up @@ -67,7 +69,6 @@ jobs:
id: shared-setup
uses: ./.github/actions/setup-integration-test-env
with:
origin-port: ${{ env.ORIGIN_PORT }}
install-viceroy: "true"
build-wasm: "false"
build-axum: "false"
Expand Down Expand Up @@ -126,7 +127,6 @@ jobs:
id: shared-setup
uses: ./.github/actions/setup-integration-test-env
with:
origin-port: ${{ env.ORIGIN_PORT }}
install-viceroy: "true"
build-wasm: "false"
build-test-images: "false"
Expand Down Expand Up @@ -170,7 +170,6 @@ jobs:
id: shared-setup
uses: ./.github/actions/setup-integration-test-env
with:
origin-port: ${{ env.ORIGIN_PORT }}
install-viceroy: "true"
build-wasm: "false"
build-test-images: "false"
Expand Down
27 changes: 18 additions & 9 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,21 @@ jobs:
- name: Verify Fastly WASM release build
run: cargo build --package trusted-server-adapter-fastly --release --target wasm32-wasip1

- name: Check that no permission policy is compiled into the Fastly WASM
run: ./scripts/check-no-compiled-policy.sh target/wasm32-wasip1/release/trusted-server-adapter-fastly.wasm

# The sample carries both markers the guard looks for, so a guard that
# stops finding them passes the sample and fails the job here. Exit 1 is
# the refusal; exit 2 means the guard could not run.
- name: Check that the policy guard refuses a file that carries a policy
run: |
status=0
./scripts/check-no-compiled-policy.sh config/permissions/sample.yaml || status=$?
if [ "$status" -ne 1 ]; then
echo "the guard should refuse config/permissions/sample.yaml with exit 1, and exited $status" >&2
exit 1
fi

test-cloudflare:
name: cargo check (cloudflare native + wasm32-unknown-unknown)
runs-on: ubuntu-latest
Expand Down Expand Up @@ -153,17 +168,11 @@ jobs:
run: cargo check-spin

- name: Build Spin adapter release WASM
# Mirror the Fastly release-build overrides so the artifact embeds usable
# settings instead of the trusted-server.toml placeholders, which startup
# rejects (falling back to the generic 503 router). This proves the
# documented Spin artifact can boot, not just compile.
env:
TRUSTED_SERVER__PUBLISHER__ORIGIN_URL: http://127.0.0.1:8080
TRUSTED_SERVER__PUBLISHER__PROXY_SECRET: integration-test-proxy-secret
TRUSTED_SERVER__EC__PASSPHRASE: integration-test-ec-secret-padded-32
TRUSTED_SERVER__PROXY__CERTIFICATE_CHECK: "false"
run: cargo build --package trusted-server-adapter-spin --target wasm32-wasip1 --features spin --release

- name: Check that no permission policy is compiled into the Spin WASM
run: ./scripts/check-no-compiled-policy.sh target/wasm32-wasip1/release/trusted_server_adapter_spin.wasm

- name: Run Spin adapter tests (native host)
run: cargo test-spin

Expand Down
117 changes: 108 additions & 9 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ 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 module)
edgecookie/ # vendor Edge Cookie module crates (built-in HMAC module is in core)
geo/ # vendor geo module crates (host geo is injected by the adapter)
permission-signal/ # permission signal module crates, one per scheme (gpc, gpp, tcf, us-privacy); core links none
trusted-server-js/ # TypeScript/JS build — per-integration IIFE bundles
lib/ # TS source, Vitest tests, esbuild pipeline
```
Expand Down Expand Up @@ -63,7 +68,11 @@ fastly compute serve
EDGEZERO_MANIFEST="<release-root>/edgezero.toml" \
ts deploy --adapter fastly --service-id <service-id> --application-release "<release-root>"

# 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 --permissions <policy file>` (see
# trusted-server.example.toml for the template). Axum reads the policy file
# from the folder TRUSTED_SERVER_PERMISSIONS_DIR names.
cargo run -p trusted-server-adapter-axum

# Test Axum adapter only
Expand Down Expand Up @@ -162,6 +171,24 @@ cd crates/trusted-server-js/lib && node build-all.mjs
cargo install viceroy --version 0.21.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 them on `ubuntu-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.
The CLI crate (`trusted-server-cli`) does not build on a Windows host either,
because its dependency `edgezero-adapter-fastly` uses a standard library
feature that is unstable on Windows, so run its tests and the template cache
harness (`scripts/template-cache-local-test.sh`), which builds it, in WSL too.

---

## Coding Conventions
Expand Down Expand Up @@ -289,6 +316,11 @@ 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. (e.g. use example.com for URLs by default)
- Do not write or commit real domains, customer names, credentials,
Expand All @@ -302,6 +334,37 @@ impl core::error::Error for MyError {}
under the [allowlist maintenance policy](docs/superpowers/specs/2026-05-18-check-domains-design.md#allowlist-maintenance-policy).
Per-line suppressions also need review.

### Permission model terminology

Permissions are the primitive. A module declares the permissions it requires
(`required_permissions`) and the system decides whether each is _set_. Consent
is only one of many 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 module that needs nothing **requires no permission**. Never write that it
"runs without any consent".
- A gated module **runs once its required permissions are set**, by whatever
method.

**Evidence is not rationed, use is.** Every module and every integration sees
all the evidence available for a request, including host signals such as the TLS
JA4 and HTTP/2 signals. The core never decides which vendor may see what,
because withholding a signal from one vendor and not another discriminates
between them, and the core stays neutral. What a vendor may _do_ with the
evidence is governed by the permissions it declares and the system sets. Access
is universal, use is gated.

The practical consequence: never "fix" a vendor's access to a signal by hiding
the signal. If a use needs controlling, express it as a permission. A change
that removes evidence from a module's reach is working against the
architecture, not protecting it.

- 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 @@ -318,6 +381,42 @@ Bad: `"fix: added feature flags"`

---

## Module 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 | `EdgeCookieModule` (`ec/module.rs`) | `[ec] module` | HMAC, client-fixed (opt-in, no default) | `crates/edgecookie/<vendor>` |
| Device detection | `DeviceModule` (`ec/device.rs`) | `[device] module` | User-Agent only (default) | `crates/device/<vendor>` |
| Geo / IP intelligence | `PlatformGeo` (`platform/traits.rs`) | `[geo] module` | Disabled, no location (default) | `crates/geo/<vendor>` |
| Permission signals | `PermissionSignalModule` (`permission_signal/mod.rs`) | `[permission-signal] modules` (an ordered list) | None, and with no module every permission stays at its country and region baseline | `crates/permission-signal/<scheme>` |

Principles for adding or changing a module:

- **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_module`
and `build_geo_module`), so core never depends on a host SDK or a vendor, and
the default request path makes no host-specific calls.
- **Modules read request evidence, not a fixed parameter set.** A module 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 signals) 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 module triggers no
host signal calls.
- **Modules 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 module
runs. Device signals gate Edge Cookie writes (the browser / bot gate). When
multiple vendor modules share a backend (for example a vendor's Edge Cookie,
geo, and device module on one cloud pipeline) they share a single call per
request rather than calling independently. Give a module 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 @@ -347,14 +446,14 @@ IntegrationRegistration::builder(ID)

## Configuration Files

| File | Purpose |
| ----------------------------- | ------------------------------------------------------------------------------------------------- |
| `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.toml` | Operator-owned app config; gitignored; `ts config push` publishes it as an EdgeZero blob envelope |
| `rust-toolchain.toml` | Pins Rust version to 1.98.1 |
| `.env.dev` | Local development environment variables |
| File | Purpose |
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `edgezero.toml` | EdgeZero app/platform manifest and logical stores |
| `fastly.toml` | Fastly service configuration and build settings |
| `trusted-server.example.toml` | Source-controlled app-config template, which carries the `[ec]`, `[geo]` and `[device]` module selectors and the `[permission-signal] modules` list |
| `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.98.1 |
| `.env.dev` | Local development environment variables |

---

Expand Down
Loading
Loading