Skip to content

§ 5.4 — Formats and assets: pinned specs, what the buyer supplies, and how an asset was made (#87) - #152

Open
KonstantinMirin wants to merge 95 commits into
IABTechLab:mainfrom
KonstantinMirin:op30-formats
Open

KonstantinMirin wants to merge 95 commits into
IABTechLab:mainfrom
KonstantinMirin:op30-formats

Conversation

@KonstantinMirin

@KonstantinMirin KonstantinMirin commented Nov 4, 2026 •

Copy link
Copy Markdown

What this changes

Draft 1 doesn't say whether specs holds a format's spec or only points at a registry entry, and a format id carries no version. A registry edit could change what an agreed line item requires. Nor can a line item say what the buyer supplies, who built an asset, or how it was made. This PR keeps each spec in the record, pinned to one version, and adds those three.

  • § 5.4: specs becomes format_specs, a map from FormatId to FormatSpec. A FormatSpec holds the whole spec, and a spec_ref when the spec comes from a registry.
  • § 5.4: spec_ref is an EntryRef (§ 2.2), so it names one exact version. A pinned spec equals that version's content, and the format id SHOULD match its <collection>/<entry_id>.
  • § 5.4: once the proposal leaves draft, every offered format has an entry.
  • § 5.4: a FormatId starts with the line item's channel.
  • § 5.4: a spec MAY state asset, an open vocabulary for what the buyer supplies: audio_file, video_file, image_file, markup, or script.
  • § 5.4: an Asset MAY state provenance: how it was made and how it's marked or disclosed.
  • § 5.4: assets[] also records seller-built creative, through built_by.
  • § 5.4: approval and creative_policy are prose. creative_policy moves here from § 5.6.
  • § 4.2: specifications becomes media_kit_detail.
  • § 2.5 and § 2.7 treat a spec as open text the counterparty wrote.
  • § 8 question 4 and OQ-4 state the registry gap.

Related issue

Closes #87, a change request from Postindustria (IAB Tech Lab member).

Refs #5 and #32.

Builds on

This PR builds on our video and audio PR and our audience PR.

Through them it builds on our placement PR (#120) and these PRs of ours:

Through our PRs it carries #17, #18, and #70 to #72 by @Sirajmx, #73 to #76 by @Kishore-2572, and #117 and #118 by @aleksUIX. Review the last three non-merge commits: the formats change, then provenance, then built_by. The last two can be left out. The one before them writes the line items into proposal.yaml. The first carries small fixes from our earlier PRs and changes no rule.

Type

  • Editorial — no normative effect
  • Normative — changes what the spec requires
  • Breaking — would break an implementation already built to this draft
  • Examples / schema / docs only

Checklist

  • The body section is updated — § 2.5, § 2.7, § 4.1.1, § 4.2, § 5.4, § 5.6, § 6.3, § 7, and § 8 question 4, with OQ-4 in docs/open-questions.md. The second commit updates § 6.1, and the first rewords § 2.6, § 5.8, § 6.6, § 8 question 3, and OQ-3
  • Appendix A (field index) matches the body. The media_kit_detail, formats, format_specs, and approval rows take the new names and types. creative_policy moves to § 5.4. scripts/check_appendix.py passes: 96 field names, 98 rows
  • Appendix B (fields by marker, and the counts) matches the body. The renamed fields keep seller-set, and no count changes: 5 requestable, 26 of 98 touched by the buyer
  • Any affected example in examples/ is updated and still parses. Every offered format has a whole spec. The CTV example pins two formats and defines ctv/instream-6s itself, and seller-defined entries state asset. Both record_digest values are recomputed. scripts/check_examples.py checks each format id against its channel, an entry per offered format, one spec per spec_ref across examples, and the EntryRef form. In the second commit, proposal.yaml holds its line items, the checker compares the extracts with them, and manifest.json is regenerated. The first commit widens the checker. Each commit passes scripts/check_appendix.py, scripts/check_examples.py, and scripts/check_pricing_fixtures.py, 60 of 60
  • RFC 2119 keywords used deliberately — MUST / SHOULD / MAY are not synonyms
  • The no-inheritance rule (§ 2) still holds — format_specs, approval, and creative_policy sit on the line item; provenance on the asset
  • Anything selectable declares available[] and included[]; anything settable declares bounds (§ 3.1, § 3.2) — formats declares both; no new selectable or settable fields
  • No confidential or commercially sensitive content

Knock-on effects

Open questions

  1. Which registry can a spec_ref name, and how does a reader fetch an entry? This is [Change] § 5.4 — Pin each format's spec to a version and keep it in the agreed record #87 question 3, tracked on OQ-4 — The format registry has no entries for audio/*, ctv/* or dooh/* #5.
  2. A draft line item on a published proposal needs entries, because the condition reads the proposal's status (§ 4.3.3). Should it read the line item's status instead?
  3. Nothing limits format_specs keys to formats.available[]. Should it?
  4. Two sellers can give one seller-defined id different specs. Is the copy in format_specs enough, or does such an id need a namespace beyond its channel?
  5. A seller-built entry has no field for the buyer's approval, because approval_status is the seller's review. Our #32 comment proposes approvals[]. Should that land here or with [Change] § 5.4 — Validate assets[].format_ref against the resolved formats selection at submission, not at creative review #32?

By opening this pull request I agree my contribution may be published under this
repository's licence, as set out in CONTRIBUTING.md.

Analyzed and generated with the help of Claude, fully reviewed by me.

Sirajmx and others added 30 commits October 6, 2026 16:28
§ 5.2 properties can carry apps[] (platform, bundle, storeurl, domain),
keyed as the OpenRTB 2.6 app object where one exists. Each property
must carry domains, apps or both. The CTV example carries an app.

Refs IABTechLab#15
§ 5.6 defines floor as the seller's offer floor at proposal time, not
the downstream auction floor and not a guarantee that bids will clear.
Editorial: the meaning of the field is unchanged.

Refs IABTechLab#16
Without apps[].domain a buyer has nothing to run the app-ads.txt check
against. RECOMMENDED (SHOULD) rather than a conditional REQUIRED, as the
object has no field in which a seller claims app-ads.txt authorisation.

Refs IABTechLab#15
A proposal can say its line items must be bought together
(linked_execution, § 4.1), and a line item can name the line items or
spend that must be in the same commitment (requires, § 5.6.3). A bonus
line is a line item with requires and its own price. The § 2 "binding
terms" hook now points at a field, and the Summary Representation
carries has_requires and linked_execution.

Adds a CTV bonus line example (§ 6.5) and linked_execution: none to the
proposal example.

Refs IABTechLab#23
The seller declares which exclusion types it accepts and how it
enforces each (exclusions_accepted); the buyer supplies its lists
(exclusions). Blocked apps carry their store, { platform, bundle },
so one store id is never applied across stores. Exclusions are
recompute inputs (§ 3.3) and are never disclosed to other parties
(§ 5.8). The display example declares and supplies lists.

Refs IABTechLab#25
A line item whose rate differs by market can declare geo with the
selectable structure of § 3.1: available[], included[], pricing deltas
and max_select. A geo that declares only allowed[] stays settable, so
existing payloads are unchanged. When several priced markets are
selected, the highest delta applies. The audio example is priced by
market.

Refs IABTechLab#14
A commitment names the proposal and version it was priced against
(§ 5.6.1). The seller rejects a new attempt on a stale version, or past
valid_until or price_valid_until, and returns the current version. An
authenticated replay with the same idempotency_key and terms returns the
stored result without re-running those checks; the same key with
different terms is a conflict. Keys are opaque and scoped to one buyer
and seller pair. § 4.1 states that each revision while published
increments version.

The display, CTV and audio examples carry proposal_id and version.

Refs IABTechLab#24
§ 4.1 adds an optional, immutable proposal_template_id linking an
issued proposal to the seller's template. Each issued proposal has its
own status, version and negotiation_history[], and buyers never need
the template id to transact. § 7 adds a non-normative note separating
seller-side templates from the issued proposals buyers commit against.

Appendix A gains one row; Appendix B counts immutable 6 → 7 and
72 fields. The proposal example carries a proposal_template_id.

Refs IABTechLab#26
§ 5.8 adds terms_ref ({ url, sha256, version }), REQUIRED when
terms_acceptance_required is true, hash-pinned and frozen at agreed, so
assent binds to an exact document. § 5.10 adds agreement_ref, an
informative pointer to the master or annual agreement a line item is
priced under, and documents the io and agreement roles on
external_references[]. IO generation is stated to be a connector
concern, and § 7 adds a non-normative note on relationships.

agreement_ref is seller-set; a buyer-held agreement id travels as an
external_references[] entry with role: agreement, so the field keeps
one marker (§ 3).

Appendix A gains two rows; Appendix B counts seller-set 29 → 31 and
73 fields. The display example carries terms_ref.

Refs IABTechLab#27
A new § 2.3 lets a seller publish its public standard line items at
/.well-known/openproposal.json: seller_id, generated_at, spec_version
and, per proposal, proposal_id, version, the Summary Representation of
each line item and a full_url. ETag / Last-Modified are required, only
line items with visibility public appear, and entries must match the
current version. Buyers discover from the manifest, then fetch the Full
Representation of shortlisted line items.

Adds examples/manifest.json (§ 6.5) and visibility: public to the CTV
and audio examples; the registry_gated display line item is not listed.

Refs IABTechLab#29
The specification does not define authentication yet (WS1; see IABTechLab#62),
so the replay rule now applies to a replay from the same buyer rather
than an authenticated replay.

Refs IABTechLab#24
agreement_ref.parties used buyer_id, which the specification does not
define. parties[] now names the seller by its seller_id and the buyer
by the id it holds in eligible_bidders[] (section 5.8).

Refs IABTechLab#27
The rule that a hold on a linked_execution: all proposal uses
hold_scope: proposal came from a comment on IABTechLab#23, not the change request
as filed. It moves to the PR description as a point for the working
group, so this PR carries only the filed change.

Refs IABTechLab#23
IABTechLab#25 names the content-category list iab_content_category[], a list of
IAB Content Taxonomy ids. The earlier commit renamed it content_category
with a version-pinned taxonomy_ref, which nobody proposed. This restores
the filed name and shape; which taxonomy version to reference is left
to the working group.

Refs IABTechLab#25
IABTechLab#14 doesn't say how deltas combine when a buyer selects more than one
priced market, and neither did any comment on it. The earlier commit
said the highest delta applies; that is a design choice nobody
proposed, so it moves to the PR description as a question.

Refs IABTechLab#14
Read the media condition from the ad served when an impression accepts
several media, name the seller as the party that serves, place linear TV
outside the table, and state environments as (distribution channel,
device type) pairs. Insert environments.available[] into the summary
list without rewrapping it, shorten the changelog entry and drop
audio_streaming from OQ-7 in docs/open-questions.md.

Refs IABTechLab#90
Move the summary rule out of the § 2.1 key list, drop skip from the CTV example, and mark the CHANGELOG entry as breaking.
time_zone is the line item's clock for daypart windows and
frequency_cap.period: an IANA time zone name or viewer_local. A window
is { days[], start, end } in civil time with an exclusive end later
than start, up to 24:00; a window that crosses midnight is two windows.
List the `days[]` values once in the `daypart` row, as `monday` | ... |
`sunday`. Write HH:MM, America/New_York, and UTC as plain text, since
they're a format and examples, not field names. The meaning is
unchanged.
"At least one of" an empty list is never satisfied, so the bonus line
would fail a literal check. An absent key sets no condition. Only the
examples change; the any_of[] row and the rules stay as filed in IABTechLab#23.

Refs IABTechLab#23
The floor row now says it "neither constrains nor describes the
downstream auction floor", as IABTechLab#16 asked, and names that floor: the
per-impression floor the exchange applies at auction time (OpenRTB 2.6
Imp.bidfloor).

Refs IABTechLab#16
- Rename apps[].domain to developer_domain, as IABTechLab#15 filed it. It is the
  developer domain from the store listing that app-ads.txt uses, not
  OpenRTB 2.6 app.domain, which is the domain of the app.
- Define platform as the store that issued bundle, since a bundle is
  unique only within its store.
- List apps[].platform and apps[].bundle in the Summary Representation
  (section 2.1), as IABTechLab#15 asked.

Refs IABTechLab#15
AudienceOption, AudienceSelectable, BuyerSupplied, BuyerList, and
AddressableScale get key tables. combine is a Selectable of any and all,
so included[] names the operator in effect, and a buyer names the options
it excludes in excluded[]. An audience option states its taxonomy, data
label, and sensitive categories, and no longer carries addressable_scale.
match_rate_expected becomes expected_match_rate, a Fraction.
expected_match_rate changes without a new version.

accepted_exclusions maps each exclusion type to its enforcement and
max_entries. exclusions is one map keyed by type, and each value is an
array of entries or a DocumentRef, which replaces list_ref.
activation_route names seller_side, a categories entry is a
TaxonomyCodes, and a domains or categories entry covers only itself. The
disclosure rule moves to section 5.5 and admits the systems and vendors
that enforce or verify the lists. The CTV example accepts keywords only
as best_effort.

The checkers read the new tables, Map keys, and removal paths below a
typed field.

Closes IABTechLab#91
Closes IABTechLab#97
Refs IABTechLab#39, IABTechLab#25, IABTechLab#71
Drop the undefined workstream from section 8 question 3 and from OQ-3.
Add assent[] to the mass nouns of section 2.6, and name ApprovalStatus
as a list that keeps another standard's spelling. Say in section 6.6
that the bonus line item is registry_gated too. Reflow the Object
paragraph of section 2.6 and the not_eligible paragraph of section 5.8,
and add the blank line before section 5.2.9.

Widen scripts/check_examples.py. It reads the closed lists, core terms,
open types, and one-key types from the spec, and parses YAML under the
1.2 core schema. It checks the status rollup, the history, eligibility,
requires, the value tables, the Type cells, the yaml blocks in the
spec, the hold cap, and venue_type. scripts/check_appendix.py names ext
as a second field that legitimately repeats.

This changes no rule.
Write the line items of examples/proposal.yaml inline, and say in
section 6.1 which line item examples are extracts of it and which show
a buyer's composed record. Its description and specifications now name
the video and audio it sells, and the manifest is regenerated.

scripts/check_examples.py checks that each line item file with no
commitment equals its entry in proposal.yaml, and that proposal.yaml
carries no buyer state. It builds the manifest from those line items,
and the finding it held as known is gone.

The display line item no longer holds the buyer's seats, which a shared
proposal can't carry, and its materials are due one day before start.
In proposal-issued.yaml, visibility and eligible_buyers move below the
buyer's writes, which leaves its record_digest as it was.
Rename specs to format_specs and specifications to media_kit_detail.
Map each format id to a FormatSpec that holds the whole spec and, for a
registry entry, an EntryRef spec_ref that names one exact version. Make
approval and creative_policy prose fields of section 5.4.

Let a spec state what the buyer supplies, as an open vocabulary, and
check that term in the examples.

State in section 8 and the open questions that the draft names no way
to fetch a registry entry. Give every example a whole spec for each
offered format, and check format ids against the channel.

Closes IABTechLab#87
Refs IABTechLab#5, IABTechLab#32
Give an asset an optional provenance record: whether its content is
synthetic, what generated it, the voice consent, how it is marked or
disclosed, and a digest of its bytes. The record states what the party
that delivers the asset says, and is not itself a marking or disclosure.

Show voice and pronunciation_guide in the host-read example, as keys
the seller states in its spec.

Refs IABTechLab#5
Add built_by to an asset, so assets[] can hold a seller-built read and
its provenance. The seller writes the supplied keys of such an entry,
and adding one needs no new version. A live read has no file to record,
so it has no entry.

Refs IABTechLab#5, IABTechLab#32
@KonstantinMirin KonstantinMirin changed the title § 5.4 — Formats: each format spec pinned to one version (#87) § 5.4 — Formats and assets: pinned specs, what the buyer supplies, and how an asset was made (#87) Nov 4, 2026
Make commitment.advertisers[] REQUIRED, so a commitment says whom the
spend is for. Make ground REQUIRED on a decline entry. Require an answer
to a commit under a firm entry while the entry holds, so that silence
can't work as a decline on no ground.

Refs IABTechLab#65, IABTechLab#126, IABTechLab#153
State that held means reserved inventory. Keep a hold live while a
commit made during it is open, so a seller can't leave the commit
unanswered until the hold lapses and then decline it for lack of
availability. Add the seller's hold reference and renewal_right, an
offered term for the right to the next cycle.

Refs IABTechLab#66
Add seller_domain to a VersionRef outside its proposal, because a
proposal_id is unique only within a seller's domain. Let a Deals API
push carry the baseline in ext.baseline, so a DSP can join the deal to
its record.

Refs IABTechLab#31, IABTechLab#132
State the sign of agreed_rate and have the checker flag a spend
commitment with no rate. Let a counter of list_rate also change
included[], for a counter on a composed rate. Add location to
separation, and have a demographic term state how it treats co-viewers
and unknown age. Correct the discussion document's unit wording.

Refs IABTechLab#38, IABTechLab#98, IABTechLab#117
A commitment now names whom the spend is for.
A commitment now names whom the spend is for.
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.

[Change] § 5.4 — Pin each format's spec to a version and keep it in the agreed record

4 participants