Skip to content

fix(schemas): url-asset url.format uri → uri-template for macro support#2801

Merged
bokelley merged 3 commits into
mainfrom
bokelley/fix-sync-dpa-creative
Apr 22, 2026
Merged

fix(schemas): url-asset url.format uri → uri-template for macro support#2801
bokelley merged 3 commits into
mainfrom
bokelley/fix-sync-dpa-creative

Conversation

@bokelley

@bokelley bokelley commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Pivots PR #2801 based on protocol-expert review. The fixture was right; the schema was wrong. This change relaxes core/assets/url-asset.json to accept RFC 6570 URI templates, which is exactly what AdCP's universal macros produce at sync time.

Background

The original PR encoded {SKU}%7BSKU%7D in the sales-social/sync_dpa_creative fixture to satisfy format: uri on url-asset.json. Expert review caught that this contradicted the spec: `docs/creative/universal-macros.mdx:575-585` explicitly says:

❌ WRONG: `https://track.com/imp?device=%7BDEVICE_ID%7D\`
✅ CORRECT: `https://track.com/imp?device={DEVICE_ID}\`

No need to URL-encode macro placeholders. The ad server handles encoding of actual values automatically.

The percent-encoded form is never supposed to be on the wire — at sync time it's a non-conformant template; at impression time the braces are gone after substitution. The 60 lint errors were the signal, not the noise.

Fix

  • url-asset.json url.format changed uriuri-template (RFC 6570). uri-template accepts both plain URIs and level-1 macro templates ({var}), which is exactly what AdCP universal macros produce.
  • Description on the `url` property updated to document the sync-time-raw / impression-time-encoded split so future fixture authors don't pre-encode.
  • Fixture in `sales-social/catalog_driven_dynamic_ads/sync_dpa_creative` reverts to raw braces (the spec-canonical form).

Other asset schemas with `format: uri` (image-asset, video-asset, audio-asset, vast-asset, daast-asset, webhook-asset) are left as strict `uri` — those fields are for real asset delivery URLs that shouldn't carry macros. Only url-asset is for tracker/click URLs that conventionally do.

Ratchet

Allowlist unchanged at 12. Same outcome as the previous commit, now via the correct path. The remaining 12 entries are all blocked on upstream WG issues #2774-#2776.

Changeset

Marked as patch on adcontextprotocol since this is a published protocol schema change. The change is backward-compatible at the wire level: uri-template is a superset of uri in this context (plain URIs still validate; templated URIs now also validate).

Test plan

  • npm run test:storyboard-sample-request-schema — passes (12 grandfathered, 0 new drift, 0 stale)
  • npm run test:schemas — 7/7 pass
  • npm run test:examples — 31/31 pass
  • npm run test:unit — 631 passed
  • npm run typecheck — clean

🤖 Generated with Claude Code

bokelley and others added 2 commits April 21, 2026 22:27
The `sales-social/catalog_driven_dynamic_ads/sync_dpa_creative` fixture's
tracker URLs used raw `{SKU}` / `{GTIN}` / `{MEDIA_BUY_ID}` macros. The
step's own narrative already documents the canonical form:

  "values substituted into URL contexts MUST be percent-encoded such that
   only RFC 3986 unreserved characters remain unescaped" (per #2620)

Encoding `{` → `%7B` / `}` → `%7D` satisfies `format: uri` AND matches what
a conformant platform actually receives over the wire at impression time
(the platform decodes then substitutes). Raw braces were failing
`format: uri` on every branch of `creative-manifest.assets`' anyOf,
cascading to 60 spurious lint errors. The fix is one character-for-character
substitution.

Allowlist shrinks 13 → 12. The remaining 12 entries are all blocked on
upstream WG issues #2774-#2776.
…pport

Pivots PR #2801 from the wrong direction (percent-encode the fixture)
to the right direction (relax the schema to accept AdCP macros at sync
time).

docs/creative/universal-macros.mdx:575-585 explicitly says buyers MUST
submit raw `{SKU}` / `{DEVICE_ID}` / `{MEDIA_BUY_ID}` templates at sync
time; the ad server URL-encodes substituted values at impression time.
Strict `format: uri` rejected those templates, contradicting the spec.

`format: uri-template` (RFC 6570) accepts both plain URIs and level-1
macro templates, which is exactly what AdCP universal macros produce.
Description on the `url` property updated to document the sync-time-raw /
impression-time-encoded split.

Fixture in `sales-social/catalog_driven_dynamic_ads/sync_dpa_creative`
reverts to raw braces (the spec-canonical form) from the wrong
percent-encoded fix. Allowlist stays at 12 entries (same outcome via
the correct path).

Protocol change — changeset is a `patch` on `adcontextprotocol`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bokelley bokelley changed the title fix(storyboards): percent-encode DPA macros in sync_dpa_creative URLs fix(schemas): url-asset url.format uri → uri-template for macro support Apr 22, 2026
…K note

Protocol-expert review of PR #2801 flagged two non-blocking improvements:

1. universal-macros.mdx needed an explicit note that AdCP uses RFC 6570
   Level 1 only — without that, an overeager implementer could ship
   `{+SKU}` or `{?SKU}` and still validate as a URI template even though
   AdCP's literal-substitution contract doesn't support operators.

2. The changeset should call out the SDK-side implication: any buyer-side
   SDK that defensively percent-encodes `{`/`}` at sync time should stop.

Both added here. No schema or fixture changes this commit — just docs
scoping and migration-note prose.
@bokelley bokelley merged commit bd7ee99 into main Apr 22, 2026
14 checks passed
bokelley added a commit that referenced this pull request Apr 22, 2026
…l-asset templates)

Folds origin/main into the release-docs branch. Adds:

- Item 18: asset_type discriminator (#2776), refine[] prefixed ids + optional action (#2775), SI context→intent rename (#2774), governance plan scoping contract (#2777).
- Item 19: url-asset url.format relaxed from uri to uri-template (#2801) so AdCP macros like {SKU}/{DEVICE_ID}/{MEDIA_BUY_ID} validate at sync time.
- Breaking-changes table: asset_type const, refine[] product_id/proposal_id, SI context→intent, url-asset uri-template.
- Migration notes: asset_type on payloads, refine rename, SI field rename, url-asset no-op guidance.

Local compliance-assertion modules superseded by bundled @adcp/client 5.9 defaults (#2769) — accepted the upstream delete on merge.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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