Skip to content

test(creative): universal_macro_translation cross-SDK conformance vectors#5650

Merged
bokelley merged 1 commit into
mainfrom
bokelley/universal-macro-translation-vectors
Jun 25, 2026
Merged

test(creative): universal_macro_translation cross-SDK conformance vectors#5650
bokelley merged 1 commit into
mainfrom
bokelley/universal-macro-translation-vectors

Conversation

@bokelley

Copy link
Copy Markdown
Contributor

Adds static/test-vectors/universal-macro-translation.json — language-neutral conformance vectors pinning the universal_macro_translation helper contract so the JS (@adcp/sdk) and Python (adcp) implementations cannot drift.

Why

The helper's substitution is the seller's internal pixel-translation step — it is never serialized on any AdCP wire surface, so no conformance storyboard can observe it (see the split discussion on #5646). Its only enforcement is unit tests. And the conformance storyboard only ever exercises alphanumeric IDs, which encode identically under every encoder — so a Python port that diverges on reserved characters (e.g. urllib.quote() defaults leaving / unescaped) would pass everything while silently producing different pixels. This fixture is the cross-SDK source of truth that catches that.

Contract pinned

Each vector's expected is the full helper return { url, dropped_params, unmapped_macros }:

  • value entries → RFC-3986 unreserved-whitelist percent-encoded
  • native entries → inserted raw (ad-server token survives)
  • unmapped-macro param → dropped + reported
  • already-minted param → untouched
  • single-pass: a {MACRO} inside a substituted value is encoded as data, never re-expanded
  • scope: query params only (path/fragment left raw)

Matches the envelope of the existing static/test-vectors/catalog-macro-substitution.json. Single location (not mirrored into static/compliance/source/test-vectors/) because — unlike the catalog fixture — this is consumed by SDK unit tests, not the expect_substitution_safe storyboard runner.

Refs adcontextprotocol/adcp-client#2263, adcontextprotocol/adcp-client-python#956, and the Live Integration RFC #5649.

🤖 Generated with Claude Code

… vectors

Pins the universal_macro_translation helper contract (value RFC-3986
unreserved-encoded, native raw, unmapped-macro params dropped, minted
params untouched, single-pass no re-expansion) as language-neutral test
vectors. Both @adcp/sdk and the Python adcp SDK validate against this
file so the two encoders cannot drift — the conformance storyboard only
exercises alphanumeric IDs, so cross-language drift is otherwise invisible.

Refs adcontextprotocol/adcp-client#2263, adcontextprotocol/adcp-client-python#956

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mintlify

mintlify Bot commented Jun 20, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
adcp 🟢 Ready View Preview Jun 20, 2026, 5:28 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@aao-release-bot aao-release-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean additive fixture. Right call to pin a helper that no conformance storyboard can observe — the only enforcement is unit tests, and alphanumeric-ID storyboards make cross-language encoder drift invisible.

Things I checked

  • All 7 vectors verified by hand against the RFC-3986 unreserved whitelist (ALPHA / DIGIT / - . _ ~). value-unreserved-encoded is correct: space→%20, /%2F, &%26, =%3D gives mb%20spring%2F2025%26x%3D1. This is exactly the urllib.quote()-leaves-/-unescaped trap the PR body calls out — the vector catches it.
  • no-re-expansion-of-substituted-value: {PACKAGE_ID}%7BPACKAGE_ID%7D, braces encoded, underscore survives. Single-pass injection defense holds.
  • native-inserted-raw: %%CACHEBUSTER%% passes through verbatim. Correct — encoding it would break GAM/Kevel/Xandr literal replacement.
  • unmapped-macro-param-dropped and already-minted-param-untouched have the right dropped_params/unmapped_macros shapes; pkg_123 is not re-encoded.
  • Scope vectors (no-query-string-url-unchanged, fragment-left-untouched) match the documented query-params-only limitation; the mapped {CREATIVE_ID} in the fragment is correctly NOT reported in unmapped_macros.
  • No changeset required: static/test-vectors/** is not a wire surface (not static/schemas/source/** or docs/reference/**), and the PR body's own argument — the helper is never serialized on any AdCP surface — holds. No spec drift to schemas.
  • Envelope matches the sibling static/test-vectors/catalog-macro-substitution.json. Single-location rationale (SDK unit tests, not the expect_substitution_safe storyboard runner) is sound.

Follow-ups (non-blocking — file as issues)

  • Dangling spec_reference anchor. The fixture points at docs/creative/universal-macros.mdx#implementing-translation-with-the-sdk, but that anchor does not exist — the doc has ### Macro Translation Approach / ### Translation Examples and never documents the universal_macro_translation helper, its { url, dropped_params, unmapped_macros } return, or the value/native mapping. The contract this fixture pins is currently SDK-internal with no normative doc home. Either add the section the anchor names or repoint spec_reference at #macro-translation-approach. Worth resolving before a third SDK ports against a pointer that goes nowhere.

LGTM. Follow-up noted.

@bokelley bokelley merged commit fbf0477 into main Jun 25, 2026
15 checks passed
@bokelley bokelley deleted the bokelley/universal-macro-translation-vectors branch June 25, 2026 15:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant