Skip to content

Support open-ended hosted duration ranges#5323

Merged
bokelley merged 1 commit into
mainfrom
inventory-sources-durations
Jun 4, 2026
Merged

Support open-ended hosted duration ranges#5323
bokelley merged 1 commit into
mainfrom
inventory-sources-durations

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

@bokelley bokelley commented Jun 4, 2026

Summary

  • Allows hosted video_hosted and audio_hosted duration_ms_range endpoints to be null for one-sided ranges.
  • Rejects [null, null] by requiring at least one bounded endpoint.
  • Clarifies the duration model as two mechanisms only: duration_ms_exact for fixed durations and duration_ms_range for bounded or one-sided ranges.
  • Adds composed schema coverage for open-ended ranges, exact-plus-range precedence compatibility, and [null, null] rejection.

Why

Issue #5322 raised that bare min/max fields would create a third duration vocabulary. One-sided ranges express “up to X” and “at least Y” without adding separate min-only or max-only fields.

Issue #5321 was withdrawn and is intentionally not included.

Validation

  • npm run build:schemas
  • npm run test:schemas
  • npm run test:composed
  • npm run test:canonical-conventions
  • npm run test:schema-utf8
  • npm run test:json-schema -- --file docs/creative/canonical-formats.mdx
  • git diff --check
  • precommit: unit tests, dynamic import lint, callApi state-change lint, typecheck
  • pre-push: version sync, changeset present, schema link convention, Mintlify broken-links check

Expert Review

  • Protocol review: no blocking issues.
  • Docs review: wording issues addressed before publishing.

@mintlify
Copy link
Copy Markdown

mintlify Bot commented Jun 4, 2026

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

Project Status Preview Updated (UTC)
adcp 🟢 Ready View Preview Jun 4, 2026, 10:27 AM

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

Copy link
Copy Markdown
Contributor

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

Choose a reason for hiding this comment

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

Schema mechanism is the right shape. Changeset categorization is wrong and ships a wire change without a version signal.

Block is on one line of frontmatter. Everything else is a non-blocking nit or a scoped follow-up.

Must fix

  • .changeset/5322-open-ended-hosted-duration.md:1-2 — empty frontmatter on a protocol-spec change. Per .agents/playbook.md:289-304,327-332: --empty is for non-protocol changes (UI, docs, infra, migrations, tooling). patch/minor/major is for schema/task-def/API-ref. Widening duration_ms_range.items to accept null in two published canonical formats is the JSON Schema analog of "add new enum value" — textbook minor. Prior art: .changeset/2260-creative-retention-outlasts-campaigns.md:1-3 and .changeset/2911-mcp-envelope-flat-serialization.md:1-3 both declare "adcontextprotocol": minor for schema-surface changes. Fix:

    ---
    "adcontextprotocol": minor
    ---
    

Things I checked

  • Schema mechanism: items.anyOf: [{integer,minimum:0},{null}] + array-level contains: {type: integer, minimum: 0} + minItems/maxItems: 2 correctly enforces "each endpoint is integer≥0 or null, at least one must be integer." Rejects [null, null]; accepts [X, null], [null, Y], [X, Y].
  • Walker: new anyOf is inside items, not top-level. scripts/audit-oneof.mjs only walks oneOf arrays — no baseline ratchet needed.
  • Schema-vs-docs coherence: new "Duration constraint precedence" section in docs/creative/canonical-formats.mdx:990-1010 matches both audio_hosted.json:22 and video_hosted.json:42 description prose, and the updated core/product-format-declaration.json description prose. MUST/SHOULD verbs are used consistently.
  • Backward compat: producers emitting closed [5000, 30000] continue to validate. Existing closed-range examples in docs/creative/canonical-formats-migration.mdx:81,376 unaffected. Additive widening, not breaking — minor is correct.
  • Test coverage: three new cases in tests/composed-schema-validation.test.cjs cover the three relevant branches (open-bounded accepted, exact+range coexistence, [null,null] rejection).
  • Precedence wording on audio_hosted.json upgraded to the long-form version that video_hosted.json already carried — buyers MUST validate against the exact value when both ship. Right call, brings the two hosted formats into alignment.

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

  • VAST/DAAST coverage asymmetry. static/schemas/source/formats/canonical/video_vast.json:40-46 and audio_daast.json:19-25 still carry the un-widened items: {integer, minimum 0} and the older terse precedence wording. A VAST product wanting to say "any creative up to 30s" now has no way to express it without the very _min_only / _max_only fields this PR rejects. Was the hosted-only scope deliberate, or worth folding tag-served formats in?
  • docs/media-buy/product-discovery/media-products.mdx:266 still references "a duration range" without acknowledging one-sided ranges. Worth updating to keep the canonical-formats.mdx ↔ media-products.mdx pair in sync.
  • min ≤ max not enforced. [5000, 3000] validates. Pre-existing behavior — not a regression — but worth a backlog issue if endpoint ordering is meant to be a wire-shape invariant.

Minor nits (non-blocking)

  1. video_hosted.json:42 description dropped the advisory-metadata clause. Previous wording included "The range is treated as advisory metadata in that case (e.g., for UI display showing the broader product family)." The PR removes it for symmetry with audio_hosted. Not load-bearing — the canonical-formats.mdx section preserves the same intent — but if the symmetry was incidental, the clause was useful for implementers.
  2. Negative-case test gap. No assertion that [3000, 60000.5] or [3000, "60000"] is rejected. The items.anyOf handles it correctly today; a one-line case would lock that in.

Fix the frontmatter and this is ready. The --empty reaches for the docs carve-out in the playbook, but the changes that aren't docs are exactly the changes the playbook reserves minor for.

Copy link
Copy Markdown
Contributor

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

Choose a reason for hiding this comment

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

LGTM. The right shape — null-endpoint widening with contains: integer forecloses [null, null] at validation time, and keeping the vocabulary to two modes (duration_ms_exact, duration_ms_range) avoids minting a third min/max scalar pair as #5322 set out to avoid.

Things I checked

  • JSON Schema mechanics on static/schemas/source/formats/canonical/audio_hosted.json:16-28 and video_hosted.json:34-47: items.anyOf[{integer ≥0},{null}] + minItems/maxItems: 2 + contains: {integer ≥0} correctly admits [int,int], [int,null], [null,int] and rejects [null,null]. Draft-07 contains is AJV-native and already used elsewhere in the source schemas (signal-definition.json, sync-plans-request.json).
  • Existing [int,int] data continues to validate. Producer-side this is additive, so minor is defensible — and matches the precedent in .changeset/5076-5078-contract-clarifications.md for a similar nullable widening.
  • Schema-vs-docs coherence: new "Duration constraint precedence" section in docs/creative/canonical-formats.mdx:990-1002 matches the per-canonical descriptions on both hosted files. The two-mode framing explicitly forecloses the "why not duration_ms_min/duration_ms_max" question. product-format-declaration.json:5 description reads cleanly after the inline edits.
  • Tests at tests/composed-schema-validation.test.cjs:200-340 cover both one-sided shapes on both hosted canonicals, exact+range coexistence, and [null, null] rejection.
  • Changeset present, correctly typed minor, body names the wire change.

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

  • Containment math with null is unspecified. docs/creative/canonical-formats.mdx:999 says a request satisfies a product when every permitted value falls inside the accepted range. With null endpoints, the natural reading is "null = unbounded on that side," but the spec doesn't say so. Two conformant SDKs could disagree on whether [null, 30000] satisfies [5000, 60000]. One sentence in the new precedence section closes the gap.
  • Narrowing rule at docs/creative/canonical-formats.mdx:1134 is arithmetic. "v2's lower bound ≥ v1's lower bound AND v2's upper bound ≤ v1's upper bound" doesn't say what happens when either side is null. Same fix.
  • VAST/DAAST asymmetry. static/schemas/source/formats/canonical/video_vast.json and audio_daast.json carry the same duration_ms_range field with the integer-only shape. PR scopes itself to hosted, but the rationale isn't written down. Either extend the change or add a sentence in the new precedence section explaining that hosted is the only kind that admits open-ended ranges. Silent divergence on a shared field name is a spec smell.
  • Examples table at docs/creative/canonical-formats.mdx:1003-1008 has no one-sided row. Integrators get the rule in prose but no worked containment example. One row covering [null, 30000] (accept) and one covering [15000, null] against an out-of-range exact (reject) would do more for clarity than the prose section alone.

Minor nits (non-blocking)

  1. Range/interval term inconsistency at docs/creative/canonical-formats.mdx:999. "...falls within the product's accepted range; overlap alone is insufficient. An exact value... satisfies a range when that exact value falls inside the accepted interval." Two terms for the same thing in adjacent sentences. "Range" reads better — duration_ms_range literally has the word in its name.
  2. items.anyOf[0].minimum: 0 on audio_hosted.json:19 and video_hosted.json:36 admits [0, null] and [0, 0]; duration_ms_exact uses minimum: 1. A 0-ms hosted spot is meaningless. Pre-existing inconsistency but the PR is touching exactly this keyword, so it's a clean place to align.

Approving.

@bokelley bokelley merged commit 68039f7 into main Jun 4, 2026
32 checks passed
@bokelley bokelley deleted the inventory-sources-durations branch June 4, 2026 10:33
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