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
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