Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f59fd85
Add gam_unit_path template parser
prk-Jr Jul 23, 2026
9a71f55
Add request-path section derivation
prk-Jr Jul 23, 2026
7575873
Add section_root, unit-template compile/render, and startup validation
prk-Jr Jul 23, 2026
860ed0b
Render gam_unit_path template per request across initial and SPA paths
prk-Jr Jul 23, 2026
21a3523
Add spec and plan for per-section gam_unit_path
prk-Jr Jul 23, 2026
070fd94
Document per-section gam_unit_path templating
prk-Jr Jul 23, 2026
20f5f8e
Use fictional example values in tests and docs
prk-Jr Jul 23, 2026
dbdaea7
Merge branch 'main' into 954-per-section-gam-unit-path
prk-Jr Jul 23, 2026
f0a275b
Address review feedback on gam_unit_path templating
prk-Jr Jul 24, 2026
bfc1cb9
Merge remote-tracking branch 'origin/954-per-section-gam-unit-path' i…
prk-Jr Jul 24, 2026
92faf6b
Merge branch 'main' into 954-per-section-gam-unit-path
prk-Jr Jul 29, 2026
4bedcd7
Address review feedback on per-section gam_unit_path
prk-Jr Jul 29, 2026
367dc04
Fix doc lint
prk-Jr Jul 29, 2026
7c9d4f4
Merge branch 'main' into 954-per-section-gam-unit-path
prk-Jr Jul 29, 2026
7a3dec4
Document PR 957 review resolution design
prk-Jr Aug 3, 2026
2e0acb3
Plan PR 957 review resolution
prk-Jr Aug 3, 2026
3cb971d
Make dynamic GAM templates fail legacy rollback
prk-Jr Aug 3, 2026
b71d16d
Validate GAM network IDs only when consumed
prk-Jr Aug 3, 2026
b7f491c
Bound dynamic GAM slot rendering
prk-Jr Aug 3, 2026
d7003f8
Test dynamic GAM path byte boundaries
prk-Jr Aug 3, 2026
b3256ac
Document dynamic GAM template safeguards
prk-Jr Aug 3, 2026
349e905
Clarify default GAM section segment
prk-Jr Aug 3, 2026
86d107c
Clarify GAM rollback compatibility
prk-Jr Aug 3, 2026
66b1db0
Remove PR review resolution notes
prk-Jr Aug 3, 2026
5263041
Merge remote-tracking branch 'origin/main' into 954-per-section-gam-u…
prk-Jr Aug 3, 2026
0f5086b
Merge branch 'main' into 954-per-section-gam-unit-path
aram356 Aug 4, 2026
59935de
Filter unrenderable GAM slots before auction
prk-Jr Aug 4, 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Added the default-true `[auction].rewrite_creatives` option. Setting it to `false` preserves mandatory creative sanitization across `POST /auction` and publisher SSAT/page-bids delivery while skipping first-party resource/click URL rewriting; it also skips creative TSJS injection on `POST /auction`.
- `creative_opportunities.slot.gam_unit_path` is now a template supporting `{network_id}`, `{slot_id}`, and `{section}`, so a publisher whose ad unit varies by site section expresses it in one slot rule instead of one per (slot × section). `{section}` derives from the request path: `[creative_opportunities].section_segment` selects which path segment names the section (0-based, default `0`; set `1` for locale-prefixed URLs), and `section_root` supplies the value for paths with no such segment. `section_root` is required when a template uses `{section}`. Existing static and absent `gam_unit_path` configs are unchanged. Startup rejects a blank `gam_network_id` only when an absent/default path or `{network_id}` template consumes it. Trusted Server conservatively caps whole rendered dynamic paths at 100 UTF-8 bytes, informed by Google's 100-character per-ad-unit-code limit; an over-limit request-specific path omits that slot without failing the response. During typed/startup finalization, every placeholder-bearing template that omits `section_segment` materializes `section_segment = 0`, so an older binary rejects the blob loudly. Static and absent paths remain legacy-schema compatible only when both `section_root` and `section_segment` are omitted. Before rolling back below this feature, replace or remove dynamic paths, remove both keys, re-push and finalize the config, then roll back the binary.
- Added Osano consent mirror integration docs and public enablement guidance.
- Implemented basic authentication for configurable endpoint paths (#73)
- Added integrations guide with example `testlight` integration
Expand Down
71 changes: 71 additions & 0 deletions crates/trusted-server-core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,46 @@ mod tests {
use super::*;
use crate::test_support::tests::crate_test_settings_str;

#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
#[allow(dead_code)]
struct LegacyCreativeOpportunitiesConfig {
gam_network_id: String,
#[serde(default)]
auction_timeout_ms: Option<u32>,
#[serde(default)]
price_granularity: serde_json::Value,
#[serde(default)]
slot: Vec<serde_json::Value>,
}

fn serialized_creative_opportunities(gam_unit_path: Option<&str>) -> serde_json::Value {
let mut toml = crate_test_settings_str();
toml.push_str(
r#"

[creative_opportunities]
gam_network_id = "99999"

[[creative_opportunities.slot]]
id = "example-slot"
page_patterns = ["/*"]
formats = [{ width = 300, height = 250 }]
"#,
);
if let Some(gam_unit_path) = gam_unit_path {
toml.push_str(&format!("gam_unit_path = {gam_unit_path:?}\n"));
}

let app_config: TrustedServerAppConfig =
toml::from_str(&toml).expect("should deserialize app config wrapper");
serde_json::to_value(app_config)
.expect("should serialize app config wrapper")
.get("creative_opportunities")
.cloned()
.expect("should contain creative opportunities")
}

fn valid_settings() -> Settings {
let mut settings =
Settings::from_toml(&crate_test_settings_str()).expect("should parse test settings");
Expand Down Expand Up @@ -254,6 +294,37 @@ mod tests {
);
}

#[test]
fn dynamic_gam_unit_templates_are_rejected_by_legacy_schema() {
for gam_unit_path in ["/{network_id}/example", "/example/{slot_id}"] {
let creative_opportunities = serialized_creative_opportunities(Some(gam_unit_path));
let err =
serde_json::from_value::<LegacyCreativeOpportunitiesConfig>(creative_opportunities)
.expect_err("should reject dynamic GAM unit template");

assert!(
err.to_string().contains("section_segment"),
"legacy error should name section_segment: {err}"
);
}
}

#[test]
fn static_gam_unit_template_is_accepted_by_legacy_schema() {
let creative_opportunities = serialized_creative_opportunities(Some("/99999/example/home"));

serde_json::from_value::<LegacyCreativeOpportunitiesConfig>(creative_opportunities)
.expect("should accept static GAM unit template");
}

#[test]
fn absent_gam_unit_template_is_accepted_by_legacy_schema() {
let creative_opportunities = serialized_creative_opportunities(None);

serde_json::from_value::<LegacyCreativeOpportunitiesConfig>(creative_opportunities)
.expect("should accept absent GAM unit template");
}

#[test]
fn deploy_validation_rejects_placeholders() {
let settings = Settings::from_toml(
Expand Down
Loading
Loading