Skip to content

TMP: preserve targeting KVs in router-authored signals_by_provider buckets #6252

Description

@ohalushchak-exadel

Summary

docs/trusted-match/router-architecture.mdx §"Context Match fan-out" step 4 says:

Enrichment signals are concatenated. Segments from all providers are combined into a single list. Targeting key-values from different providers are namespaced to prevent collisions.

That last sentence is the only mention of targeting_kv namespacing in the whole trusted-match surface. It cannot be implemented interoperably as written, and it conflicts with how the spec solves the same problem for TMPX.

Why it's unimplementable

No scheme is defined. No separator, no format, no ordering. Two conformant routers can namespace sport as acme_sport, acme.sport, or acme:sport. A publisher's ad-server line items are then not portable across routers — moving between them silently stops matching, with no error, just no fill.

It contradicts the TMPX design. For the analogous problem, the spec is emphatic that the router must never mint a name in the publisher's namespace:

Slot IDs are opaque provider-namespaced tokens, NOT ad-server macro names … so the destination namespace stays publisher-owned and the router never accepts a destination name from an untrusted provider.

Publishers resolve (provider_id, slot_id) → local destination via tmpx_macro_mapping in publisher-tmpx-config.json. A router that rewrites targeting_kvs keys is doing exactly what that design forbids — inventing a publisher-facing ad-server name.

There's no attribution to namespace with. signals.targeting_kvs entries carry only {key, value}. The router knows which provider sent each one, but the wire format has nowhere to put it, so the only way to preserve attribution today is to encode it into the key — the thing above says not to do.

It's also ambiguous. "namespaced to prevent collisions" reads either as unconditional, or as only-on-collision. Unconditional renames every key in a single-provider deployment to guard against a collision that cannot occur. Collision-triggered makes a key's name depend on which providers happened to respond, which is worse — nothing stable to target.

Where this leaves implementers

targeting_kvs is an array, so the actual defect the sentence is reaching for — one provider's key-values overwriting another's — is fixable without renaming anything: concatenate, and two providers returning sport produce two entries. That loses nothing, invents nothing, and is what adcp-go now does. But it does leave the publisher unable to tell which provider produced which pair.

Suggested resolution

Preferred, and consistent with TMPX: give signals the same per-provider attribution tmpx_providers has. Something like a signals_providers map keyed on provider_id, or a provider_id field on each targeting_kvs entry. The publisher then maps (provider_id, key) → local targeting key in their own deployment config, exactly as they already do for TMPX slots. Naming stays publisher-owned, and nothing on the wire depends on a router-chosen string.

Alternatively, if namespacing on the wire is genuinely wanted, the spec needs to pin the separator and state whether it applies unconditionally — and reconcile that with the TMPX position that the router must not name things in the publisher's namespace.

Either way, the current sentence should not stay as-is: it reads as a requirement, but no two implementations can satisfy it compatibly.

Related

  • §"Context Match fan-out" step 4 (router-architecture.mdx)
  • specification.mdx §Signals / §KeyValuePair — defines the shape, no merge rule
  • specification.mdx §"TMPX destinations are publisher-owned" — the pattern this should probably follow

Metadata

Metadata

Assignees

No one assigned

    Labels

    claude-triagedIssue has been triaged by the Claude Code triage routine. Remove to re-triage.schemaJSON Schema source-of-truth: definitions, codegen artifacts, validation, hygienesponsored-intelligenceIssue concerns the sponsored-intelligence protocol domain

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions