Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
6d49771
docs: specify LiveRamp integration
prk-Jr Aug 21, 2026
22fef99
docs: clarify LiveRamp config ownership
prk-Jr Aug 21, 2026
675cbdd
docs: preserve preconfigured Prebid user IDs
prk-Jr Aug 21, 2026
0c93b3b
docs: plan LiveRamp integration implementation
prk-Jr Aug 21, 2026
bf213ff
Configure LiveRamp for managed Prebid
prk-Jr Aug 21, 2026
73f75d9
Manage LiveRamp IdentityLink configuration
prk-Jr Aug 21, 2026
aee4396
Cover LiveRamp Prebid bundle and EID behavior
prk-Jr Aug 21, 2026
4724a63
Document managed LiveRamp RampID setup
prk-Jr Aug 21, 2026
a3a1adf
Format LiveRamp API documentation for clippy
prk-Jr Aug 21, 2026
42c23bb
Guard managed LiveRamp merge configuration
prk-Jr Aug 21, 2026
930a099
Clarify managed LiveRamp config APIs
prk-Jr Aug 21, 2026
83c3684
Align LiveRamp plan with public config guards
prk-Jr Aug 21, 2026
7663357
Cover LiveRamp merge behavior in real bundle
prk-Jr Aug 21, 2026
54cff90
Merge branch 'main' into issue-355-liveramp-integration
prk-Jr Aug 21, 2026
54640d8
Merge branch 'main' into issue-355-liveramp-integration
prk-Jr Aug 24, 2026
3e2c14a
Enforce the TCF consent signal in the generated Prebid bundle
prk-Jr Aug 24, 2026
d38c602
Reject LiveRamp configuration that omits its Prebid User ID module
prk-Jr Aug 24, 2026
f346767
Harden the managed LiveRamp Prebid configuration wrapper
prk-Jr Aug 24, 2026
f5c68a6
Drop a duplicated auction EID test and widen the consent gating test
prk-Jr Aug 24, 2026
47e774d
Build the injected LiveRamp payload through a From implementation
prk-Jr Aug 24, 2026
e6cc341
Document LiveRamp consent enforcement, network use, and storage
prk-Jr Aug 24, 2026
8f18102
Add host-target clippy aliases for the CLI and OpenRTB codegen
prk-Jr Aug 24, 2026
fb4b9db
Match the LiveRamp envelope host exactly in the consent enforcement test
prk-Jr Aug 24, 2026
6f38ba3
Clarify LiveRamp config and consent corrections
prk-Jr Aug 24, 2026
fe9c1ee
Plan LiveRamp review corrections
prk-Jr Aug 24, 2026
f293f71
Clarify LiveRamp consent and config behavior
prk-Jr Aug 24, 2026
8b70964
Record LiveRamp review completion
prk-Jr Aug 24, 2026
1f9cae2
Record LiveRamp PR refresh
prk-Jr Aug 24, 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
7 changes: 7 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ run_cli_macos = "run --package trusted-server-cli --target aarch64-apple-darwin
test_cli_linux = "test --package trusted-server-cli --target x86_64-unknown-linux-gnu"
test_cli_macos = "test --package trusted-server-cli --target aarch64-apple-darwin"

# --- Host-target lint gates that no adapter alias covers ---
# CI lints these two crates explicitly (see .github/workflows/format.yml), but
# pins the Linux triple, so there was no command a developer could run locally
# to reproduce them. These omit --target and therefore build for the host.
clippy-cli = "clippy -p trusted-server-cli --all-targets --all-features -- -D warnings"
clippy-codegen = "clippy -p trusted-server-openrtb-codegen --all-targets -- -D warnings"

# When a wasm binary IS built, run it under Viceroy.
[target.'cfg(all(target_arch = "wasm32"))']
runner = "viceroy run -C ../../fastly.toml -- "
8 changes: 7 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,12 @@ cargo clippy-cloudflare-wasm
cargo clippy-spin-native
cargo clippy-spin-wasm

# The CLI and the OpenRTB codegen crate are host-target members that no adapter
# alias covers. CI lints both with the Linux triple pinned; these aliases omit
# `--target` so they reproduce it on any host.
cargo clippy-cli
cargo clippy-codegen

# Check compilation (per-target aliases — bare `cargo check` fails at the workspace root)
cargo check-fastly && cargo check-axum && cargo check-cloudflare

Expand Down Expand Up @@ -336,7 +342,7 @@ IntegrationRegistration::builder(ID)
Every PR must pass:

1. `cargo fmt --all -- --check`
2. `cargo clippy-fastly && cargo clippy-axum && cargo clippy-cloudflare && cargo clippy-cloudflare-wasm && cargo clippy-spin-native && cargo clippy-spin-wasm`
2. `cargo clippy-fastly && cargo clippy-axum && cargo clippy-cloudflare && cargo clippy-cloudflare-wasm && cargo clippy-spin-native && cargo clippy-spin-wasm && cargo clippy-cli && cargo clippy-codegen`
3. `cargo test-fastly && cargo test-axum && cargo test-cloudflare && cargo test-spin`
4. `cargo test --manifest-path crates/trusted-server-integration-tests/Cargo.toml --test parity`
5. JS build and test (`cd crates/trusted-server-js/lib && npx vitest run`)
Expand Down
98 changes: 98 additions & 0 deletions crates/trusted-server-cli/src/prebid_bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ pub(crate) type CliResult<T> = Result<T, String>;

const NODE_MODULES_MISSING_HELP: &str = "Prebid bundling dependencies are missing. Run `cd crates/trusted-server-js/lib && npm ci`, then retry `ts prebid bundle`.";

/// Prebid User ID module that backs `[integrations.prebid.liveramp]`.
///
/// Without it in the bundle the managed `identityLink` entry the server injects
/// has no submodule to drive, so `LiveRamp` silently resolves nothing.
const IDENTITY_LINK_USER_ID_MODULE: &str = "identityLinkIdSystem";

#[derive(Debug, clap::Args)]
pub(crate) struct PrebidBundleArgs {
/// Trusted Server config path.
Expand Down Expand Up @@ -235,6 +241,22 @@ pub(crate) fn load_bundle_config(config_path: &Path) -> CliResult<PrebidBundleCo
));
}

// A pinned module list that omits `identityLinkIdSystem` while LiveRamp is
// configured builds a bundle that cannot serve that configuration. The
// only runtime signal is a browser-side diagnostic, so fail here instead.
// An absent list uses the generator's default preset, which includes it.
if prebid.get("liveramp").is_some()
&& let Some(modules) = user_id_modules.as_ref()
&& !modules
.iter()
.any(|module| module == IDENTITY_LINK_USER_ID_MODULE)
{
return cli_error(format!(
"{} configures [integrations.prebid.liveramp] but integrations.prebid.bundle.user_id_modules omits {IDENTITY_LINK_USER_ID_MODULE}",
config_path.display()
));
}

let external_bundle_url = prebid
.get("external_bundle_url")
.and_then(toml::Value::as_str)
Expand Down Expand Up @@ -608,6 +630,82 @@ adapters = ["rubicon"]
assert_eq!(config.user_id_modules, None);
}

#[test]
fn bundle_config_loader_rejects_liveramp_without_its_user_id_module() {
let (_temp, path) = write_config(
r#"
[integrations.prebid]
enabled = true
server_url = "https://prebid.example.com/openrtb2/auction"

[integrations.prebid.liveramp]
placement_id = "999"

[integrations.prebid.bundle]
adapters = ["rubicon"]
user_id_modules = ["sharedIdSystem"]
"#,
);

let error = load_bundle_config(&path).expect_err("should reject the unusable combination");

assert!(
error.contains("omits identityLinkIdSystem"),
"should name the missing module: {error}"
);
}

#[test]
fn bundle_config_loader_accepts_liveramp_with_its_user_id_module() {
let (_temp, path) = write_config(
r#"
[integrations.prebid]
enabled = true
server_url = "https://prebid.example.com/openrtb2/auction"

[integrations.prebid.liveramp]
placement_id = "999"

[integrations.prebid.bundle]
adapters = ["rubicon"]
user_id_modules = ["sharedIdSystem", "identityLinkIdSystem"]
"#,
);

let config = load_bundle_config(&path).expect("should load bundle config");

assert_eq!(
config.user_id_modules,
Some(vec![
"sharedIdSystem".to_string(),
"identityLinkIdSystem".to_string()
])
);
}

#[test]
fn bundle_config_loader_allows_liveramp_with_the_default_module_preset() {
// An absent list means the generator's default preset, which already
// contains identityLinkIdSystem.
let (_temp, path) = write_config(
r#"
[integrations.prebid]
enabled = true
server_url = "https://prebid.example.com/openrtb2/auction"

[integrations.prebid.liveramp]
placement_id = "999"

[integrations.prebid.bundle]
adapters = ["rubicon"]
"#,
);

let config = load_bundle_config(&path).expect("should load bundle config");

assert_eq!(config.user_id_modules, None);
}

#[test]
fn bundle_config_loader_rejects_missing_prebid_block() {
let (_temp, path) = write_config("[publisher]\ndomain = \"example.com\"\n");
Expand Down
32 changes: 31 additions & 1 deletion crates/trusted-server-core/src/consent/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -703,7 +703,7 @@ mod tests {
use super::{
ConsentPipelineInput, allows_ec_creation, apply_expiration_check,
apply_tcf_conflict_resolution, build_consent_context, build_context_from_signals,
consent_allows_server_side_auction, has_explicit_ec_withdrawal,
consent_allows_server_side_auction, gate_eids_by_consent, has_explicit_ec_withdrawal,
};
use crate::consent::jurisdiction::Jurisdiction;
use crate::consent::types::{
Expand Down Expand Up @@ -1080,6 +1080,36 @@ mod tests {
TcfBuilder::new().with_storage(has_storage).build()
}

#[test]
fn gate_eids_by_consent_strips_every_eid_when_personalization_is_denied() {
let context = ConsentContext {
jurisdiction: Jurisdiction::Gdpr,
gdpr_applies: true,
tcf: Some(
TcfBuilder::new()
.with_storage(true)
.with_personalized_ads(false)
.build(),
),
..ConsentContext::default()
};

// Gating is all-or-nothing across sources; LiveRamp is included here as
// the case that motivated this coverage, not as a special case.
let gated = gate_eids_by_consent(
Some(vec![
("liveramp.com", "opaque-test-envelope"),
("sharedid.org", "shared-test-id"),
]),
Some(&context),
);

assert!(
gated.is_none(),
"should remove every EID when personalization consent is denied"
);
}

#[test]
fn ec_allowed_gdpr_with_storage_consent() {
let ctx = ConsentContext {
Expand Down
23 changes: 23 additions & 0 deletions crates/trusted-server-core/src/ec/prebid_eids.rs
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,29 @@ mod tests {
);
}

#[test]
fn ingest_liveramp_eid_cookie_preserves_the_opaque_envelope() {
let registry = make_registry(vec![("liveramp", "liveramp.com")]);
let cookie = encode_json(&json!([
{
"source": "liveramp.com",
"uids": [{"id": "opaque-test-envelope", "atype": 3}]
}
]));
let writer = RecordingWriter::default();

ingest_eid_cookies_with_writer(Some(&cookie), None, "ec-id", &writer, &registry);

let calls = writer.calls.borrow();
assert_eq!(calls.len(), 1, "should perform one bulk writer call");
assert_eq!(calls[0].len(), 1, "should write one LiveRamp partner ID");
assert_eq!(
calls[0][0],
PartnerIdUpdate::new("liveramp.com", "opaque-test-envelope"),
"should preserve the opaque envelope without decoding it"
);
}

#[test]
fn ingest_eid_cookies_sharedid_cookie_overrides_prebid_sharedid_update() {
let registry = make_registry(vec![("sharedid", "sharedid.org")]);
Expand Down
Loading
Loading