Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
2 changes: 1 addition & 1 deletion adcp/schemas/.bundle-sha256
Original file line number Diff line number Diff line change
@@ -1 +1 @@
7eecf9826aab38b6574cb89e5e99cc80ee860ecda04013144620dcd831633d5f
9fc36017a839e117a1079e8304f13e4818c0aadbdc81c132e9edf707eafc1e83
2 changes: 1 addition & 1 deletion adcp/schemas/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.1.0
3.2.0
17 changes: 9 additions & 8 deletions adcp/seller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ func TestBuildCapabilitiesDefaults(t *testing.T) {
require.NotNil(t, caps.ADCP)
assert.Equal(t, 86400, caps.ADCP.Idempotency.ReplayTTLSeconds)
assert.Equal(t, []int{3}, caps.ADCP.MajorVersions)
assert.Equal(t, []string{"3.0", "3.1"}, caps.ADCP.SupportedVersions)
assert.Equal(t, "3.1", caps.AdcpVersion)
assert.Equal(t, []string{"3.0", "3.1", "3.2"}, caps.ADCP.SupportedVersions)
assert.Equal(t, "3.2", caps.AdcpVersion)
assert.Equal(t, 3, caps.AdcpMajorVersion)
assert.Contains(t, caps.SupportedProtocols, "media_buy")
}
Expand Down Expand Up @@ -177,8 +177,8 @@ func TestCapabilitiesResponseWireShape(t *testing.T) {
idem, ok := adcp["idempotency"].(map[string]any)
require.True(t, ok, "adcp.idempotency must be present as an object (required in 3.0)")
assert.EqualValues(t, 86400, idem["replay_ttl_seconds"])
assert.Equal(t, []any{"3.0", "3.1"}, adcp["supported_versions"])
assert.Equal(t, "3.1", wire["adcp_version"])
assert.Equal(t, []any{"3.0", "3.1", "3.2"}, adcp["supported_versions"])
assert.Equal(t, "3.2", wire["adcp_version"])
assert.EqualValues(t, 3, wire["adcp_major_version"])

mb, ok := wire["media_buy"].(map[string]any)
Expand Down Expand Up @@ -315,8 +315,9 @@ func TestRegisteredCapabilitiesNegotiatesVersionPins(t *testing.T) {
}{
{name: "explicit 3.0", args: map[string]any{"adcp_version": "3.0", "adcp_major_version": 3}, want: "3.0"},
{name: "explicit 3.1", args: map[string]any{"adcp_version": "3.1", "adcp_major_version": 3}, want: "3.1"},
{name: "legacy major", args: map[string]any{"adcp_major_version": 3}, want: "3.1"},
{name: "default", args: map[string]any{}, want: "3.1"},
{name: "explicit 3.2", args: map[string]any{"adcp_version": "3.2", "adcp_major_version": 3}, want: "3.2"},
{name: "legacy major", args: map[string]any{"adcp_major_version": 3}, want: "3.2"},
{name: "default", args: map[string]any{}, want: "3.2"},
}

for _, tt := range tests {
Expand All @@ -329,7 +330,7 @@ func TestRegisteredCapabilitiesNegotiatesVersionPins(t *testing.T) {
assert.EqualValues(t, 3, wire["adcp_major_version"])
adcpBlock, ok := wire["adcp"].(map[string]any)
require.True(t, ok)
assert.Equal(t, []any{"3.0", "3.1"}, adcpBlock["supported_versions"])
assert.Equal(t, []any{"3.0", "3.1", "3.2"}, adcpBlock["supported_versions"])
})
}
}
Expand Down Expand Up @@ -375,7 +376,7 @@ func TestRegisteredCapabilitiesRejectsUnsupportedVersion(t *testing.T) {
assert.Equal(t, "VERSION_UNSUPPORTED", errPayload["code"])
details, ok := errPayload["details"].(map[string]any)
require.True(t, ok)
assert.Equal(t, []any{"3.0", "3.1"}, details["supported_versions"])
assert.Equal(t, []any{"3.0", "3.1", "3.2"}, details["supported_versions"])
}

func TestRegisteredCreateMediaBuyStampsVariants(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions adcp/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ type MediaBuyCapabilities struct {
ReportingDeliveryMethods []string `json:"reporting_delivery_methods,omitempty"`
OfflineDeliveryProtocols []string `json:"offline_delivery_protocols,omitempty"`
SupportsProposals *bool `json:"supports_proposals,omitempty"`
GovernanceAware *bool `json:"governance_aware,omitempty"`
PropagationSurfaces []string `json:"propagation_surfaces,omitempty"`
CreativeApprovalMode string `json:"creative_approval_mode,omitempty"`
Features map[string]any `json:"features,omitempty"`
Expand Down
24 changes: 12 additions & 12 deletions adcp/types_gen.go

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

6 changes: 4 additions & 2 deletions adcp/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,22 @@ const (
ADCPProtocolVersion30 = "3.0"
// ADCPProtocolVersion31 is the 3.1 release-precision wire version.
ADCPProtocolVersion31 = "3.1"
// ADCPProtocolVersion32 is the 3.2 release-precision wire version.
ADCPProtocolVersion32 = "3.2"
// ADCPMajorVersion3 is the legacy major-version value for all AdCP 3.x releases.
ADCPMajorVersion3 = 3
)

// SupportedADCPVersions returns the 3.x release-precision versions this SDK
// supports on the wire. Callers receive a fresh slice.
func SupportedADCPVersions() []string {
return []string{ADCPProtocolVersion30, ADCPProtocolVersion31}
return []string{ADCPProtocolVersion30, ADCPProtocolVersion31, ADCPProtocolVersion32}
}

// DefaultADCPVersion returns the highest 3.x release-precision version this
// SDK emits when a request does not pin adcp_version.
func DefaultADCPVersion() string {
return ADCPProtocolVersion31
return ADCPProtocolVersion32
}

// VersionEnvelopeFor returns a request/response version envelope for a
Expand Down
9 changes: 5 additions & 4 deletions adcp/version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,9 @@ func TestNegotiateADCPVersion(t *testing.T) {
}{
{name: "explicit 3.0", requestVersion: "3.0", want: "3.0", ok: true},
{name: "explicit 3.1", requestVersion: "3.1", want: "3.1", ok: true},
{name: "legacy major", requestMajor: 3, want: "3.1", ok: true},
{name: "default highest", want: "3.1", ok: true},
{name: "explicit 3.2", requestVersion: "3.2", want: "3.2", ok: true},
{name: "legacy major", requestMajor: 3, want: "3.2", ok: true},
{name: "default highest", want: "3.2", ok: true},
{name: "downshift", requestVersion: "3.1", supported: []string{"3.0"}, want: "3.0", ok: true},
{name: "pre release uses matching stable", requestVersion: "3.1-rc.3", supported: []string{"3.0", "3.1"}, want: "3.1", ok: true},
{name: "ga buyer can use only matching pre release seller", requestVersion: "3.1.0", supported: []string{"3.1-rc.3"}, want: "3.1-rc.3", ok: true},
Expand All @@ -74,11 +75,11 @@ func TestNegotiateADCPVersionMajorPresence(t *testing.T) {
want string
ok bool
}{
{name: "omitted major defaults highest", req: adcpVersionRequest{}, want: "3.1", ok: true},
{name: "omitted major defaults highest", req: adcpVersionRequest{}, want: "3.2", ok: true},
{name: "explicit zero major is invalid", req: adcpVersionRequest{major: 0, majorProvided: true}, ok: false},
{name: "negative major is invalid", req: adcpVersionRequest{major: -1, majorProvided: true}, ok: false},
{name: "unsupported positive major is invalid", req: adcpVersionRequest{major: 4, majorProvided: true}, ok: false},
{name: "supported major selects highest matching release", req: adcpVersionRequest{major: 3, majorProvided: true}, want: "3.1", ok: true},
{name: "supported major selects highest matching release", req: adcpVersionRequest{major: 3, majorProvided: true}, want: "3.2", ok: true},
}

for _, tt := range tests {
Expand Down
3 changes: 2 additions & 1 deletion internal/generate/go-overlays.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@
"Offer.creative_manifest": {"type": "json.RawMessage", "pointer": true},
"Offer.price": {"type": "OfferPrice", "pointer": true},
"ProviderRegistration.status": {"type": "ProviderStatus"},
"ErrorResponse.code": {"type": "ErrorCode"}
"ErrorResponse.code": {"type": "ErrorCode"},
"IdentityMatchResponse.tmpx_providers": {"type": "map[string]TmpxProviderEntry"}
},
"refs": {
"/schemas/latest/core/property-id.json": "string",
Expand Down
2 changes: 1 addition & 1 deletion internal/generate/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ func LoadSchemas(schemaDir, enumDir, mergeDir, overlayPath string) (*IR, error)
// Infer the $id or construct ref path from filename.
refPath := s.ID
if refPath == "" {
refPath = "/schemas/tmp/" + e.Name()
refPath = "/schemas/trusted-match/" + e.Name()
}
ctx.structReg[refPath] = goName
}
Expand Down
46 changes: 39 additions & 7 deletions router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -620,29 +620,58 @@ func mergeContextResponses(requestID string, responses []contextResult, logger *
// same `package_id` in multiple providers' eligible lists — we log a warning
// naming all reporting providers and emit the union (the spec's "must be in
// both" rule collapses to union when only yes-responses are observable).
// TMPX: collected from whichever provider returned it (last wins; in
// practice only one provider mints the token).
//
// TMPX collection per the spec §"TMPX collection":
// - Each agent's TmpxMacros[] is folded into tmpx_providers[provider_id] so
// per-provider attribution survives the fan-out.
// - The legacy singular `tmpx` field stays populated on the merged response
// for back-compat with consumers that haven't moved to tmpx_providers
// (deprecated, removed in 4.0). Source order: prefer the first provider's
// first macro value when TmpxMacros[] is present; otherwise fall back to
// the agent's legacy `tmpx` field (legacy-only agents during transition).
// - The router MUST NOT carry tmpx_macros[] at the root of the outbound
// response — that field is the agent → router carrier only; leaking it
// alongside tmpx_providers would give the publisher no schema signal for
// which to read.
func mergeIdentityResponses(requestID string, providerIDs []string, responses []*tmproto.IdentityMatchResponse, logger *slog.Logger) *tmproto.IdentityMatchResponse {
eligibleSet := make(map[string]struct{})
pkgProviders := make(map[string][]string) // package_id -> distinct provider IDs that listed it
providerRepeats := make(map[string]map[string]bool) // package_id -> set of providers that repeated it within their own response
minServeWindowSec := -1
var tmpx string
var legacyTmpx string
tmpxProviders := make(map[string]tmproto.TmpxProviderEntry)

for i, resp := range responses {
if resp == nil {
continue
}
if resp.Tmpx != "" {
tmpx = resp.Tmpx
}
if minServeWindowSec < 0 || resp.ServeWindowSec < minServeWindowSec {
minServeWindowSec = resp.ServeWindowSec
}
providerID := ""
if i < len(providerIDs) {
providerID = providerIDs[i]
}
// TMPX: prefer the new TmpxMacros[] carrier — collect into the
// per-provider map. Skip empty providerID entries (defensive: a
// fan-out with mis-aligned parallel slices would otherwise stash
// macros under "", which any consumer would treat as garbage).
if len(resp.TmpxMacros) > 0 && providerID != "" {
tmpxProviders[providerID] = tmproto.TmpxProviderEntry{
Macros: append([]tmproto.TmpxMacro(nil), resp.TmpxMacros...),
}
if legacyTmpx == "" {
legacyTmpx = resp.TmpxMacros[0].Value
}
} else if resp.Tmpx != "" {
// Legacy-only agent during transition — preserve in legacy
// carrier so single-string consumers keep working. No
// tmpx_providers entry: the router does not synthesize names
// from registration metadata in this version.
if legacyTmpx == "" {
legacyTmpx = resp.Tmpx
}
}
// Track DISTINCT providers per package_id and remember if any single
// provider repeats a package_id within its own response — eligible
// arrives raw off the wire with no dedup, so a within-provider repeat
Expand Down Expand Up @@ -719,7 +748,10 @@ func mergeIdentityResponses(requestID string, providerIDs []string, responses []
RequestID: requestID,
EligiblePackageIDs: eligible,
ServeWindowSec: minServeWindowSec,
Tmpx: tmpx,
Tmpx: legacyTmpx,
}
if len(tmpxProviders) > 0 {
merged.TmpxProviders = tmpxProviders
}
return merged
}
Expand Down
Loading
Loading