diff --git a/examples/line-item-display.yaml b/examples/line-item-display.yaml index 808af24..e515e05 100644 --- a/examples/line-item-display.yaml +++ b/examples/line-item-display.yaml @@ -89,6 +89,9 @@ reporting: visibility: registry_gated required_credentials: [{ type: dsp_seat, scheme: jwt_keypair, issuer: "dsp-a", id_required: true }] +terms_acceptance_required: true +terms_ref: { url: "https://examplepub.example/legal/io-terms-2026.pdf", + sha256: "", version: "2026.1" } buying_route: PUBLISHER_AD_SERVER transaction_mechanism: { available: ["PROGRAMMATIC_GUARANTEED", "PREFERRED_DEAL"], diff --git a/spec/CHANGELOG.md b/spec/CHANGELOG.md index 1dfacef..29f01b3 100644 --- a/spec/CHANGELOG.md +++ b/spec/CHANGELOG.md @@ -5,6 +5,11 @@ issue that caused it, so the comment record and the spec history line up. ## [Unreleased] +- § 5.8, § 5.10, § 7 — `terms_ref` for the exact terms accepted at assent, + `agreement_ref` for the agreement a line item is priced under, and `io` and + `agreement` roles on `external_references[]`. Breaking + ([#27](https://github.com/IABTechLab/OpenProposal/issues/27)). + ## [3.0-draft-1] — 2026-09-22 Initial draft published for public comment. Ported from the working-group diff --git a/spec/openproposal-3.0.md b/spec/openproposal-3.0.md index 6940958..a315fd1 100644 --- a/spec/openproposal-3.0.md +++ b/spec/openproposal-3.0.md @@ -330,6 +330,9 @@ Transitions: `none` → `requested` → `held` → (`expired` | `released` | | `required_credentials[]` | Array[Object] | seller-set | `{ type: iab_registry \| dsp_seat \| custom, scheme: api_key \| oauth2_client_credentials \| jwt_keypair \| oidc_federated \| none, issuer, id_required }` | | `eligible_bidders[]` | Array[String] | seller-set | Permitted seat or buyer ids | | `terms_acceptance_required` | Boolean | seller-set | Whether legal terms must be accepted before assent | +| `terms_ref` | Object | seller-set | OPTIONAL; REQUIRED when `terms_acceptance_required` is `true`. `{ url, sha256, version }`: the legal terms or IO template the buyer accepts at assent. Hash-pinned, so assent (§ 4.1) binds to an exact document | + +`terms_ref` MUST be hash-pinned and frozen at `agreed`. ### 5.9 Execution @@ -346,11 +349,16 @@ Transitions: `none` → `requested` → `held` → (`expired` | `released` | | Field | Type | Mutability | Description | | :--- | :--- | :--- | :--- | -| `external_references[]` | Array[Object] | derived | `{ system, namespace, id, role }`. Replaces platform-specific key names. A buyer-owned reference such as a Prisma `io_id` is supplied | +| `external_references[]` | Array[Object] | derived | `{ system, namespace, id, role }`. Replaces platform-specific key names. A buyer-owned reference such as a Prisma `io_id` is supplied. Documented `role` values: `io`, an IO id generated by an order management system and carried back on the line item; `agreement`, a buyer-held agreement id | +| `agreement_ref` | Object | seller-set | OPTIONAL. Reference to a master or annual agreement the line item is priced under: `{ agreement_id, parties[], type: upfront \| annual \| master_services \| other, valid_from, valid_until }`, where `parties[]` names the seller by its `seller_id` and the buyer by the id it holds in `eligible_bidders[]` (§ 5.8). An informative pointer; the agreement itself is outside this specification. A buyer-held agreement id travels as an `external_references[]` entry with `role: agreement` | | `correlation_id` | String | derived | One identity for this line item's commitment, carried across every system it touches | | `billing` | Object | seller-set | `{ supplier_id, billing_supplier_id, payment_terms, invoicing_party }`. Who delivers is not always who invoices | | `supported_order_systems[]` | Array[String] | seller-set | Declared up front so the agent knows it will reconcile | +This specification does not define IO generation. IO creation is a connector +concern. OpenProposal supplies the agreed record, `terms_ref` and +`correlation_id` a connector needs. + --- ## 6. Examples @@ -396,6 +404,14 @@ See [`examples/line-item-audio.yaml`](../examples/line-item-audio.yaml). satisfaction rather than negotiation. Only genuinely out-of-envelope asks require the counterparty to respond. +**Relationships, agreements and IOs (non-normative).** Buyer–seller +relationships are expressed as issued proposals (`visibility: named`, +`eligible_bidders[]` set to that buyer, with the negotiated `pricing[]`) plus +the buyer agent's business rules. "Preferred partner" weighting is a buyer-side +ranking rule, not part of this specification. `agreement_ref` and `terms_ref` +(§ 5.10, § 5.8) point at the agreement and terms a line item is executed under; +IO generation happens through connectors. + --- ## 8. Open questions @@ -494,6 +510,7 @@ body is normative.** | `required_credentials[]` | 5.8 Transaction eligibility | Array[Object] | seller-set | | `eligible_bidders[]` | 5.8 Transaction eligibility | Array[String] | seller-set | | `terms_acceptance_required` | 5.8 Transaction eligibility | Boolean | seller-set | +| `terms_ref` | 5.8 Transaction eligibility | Object | seller-set | | `buying_route` | 5.9 Execution | Enum | seller-set | | `transaction_mechanism` | 5.9 Execution | Object | selectable | | `serving_mode` | 5.9 Execution | Enum | seller-set | @@ -501,6 +518,7 @@ body is normative.** | `buyer_requirements` | 5.9 Execution | Object | mixed | | `serving_platform` | 5.9 Execution | String | seller-set | | `external_references[]` | 5.10 External references and settlement | Array[Object] | derived | +| `agreement_ref` | 5.10 External references and settlement | Object | seller-set | | `correlation_id` | 5.10 External references and settlement | String | derived | | `billing` | 5.10 External references and settlement | Object | seller-set | | `supported_order_systems[]` | 5.10 External references and settlement | Array[String] | seller-set | @@ -512,7 +530,7 @@ body is normative.** | Marker | Count | Meaning | Fields | | :--- | :--- | :--- | :--- | | immutable | 6 | Set once at creation. Neither party may change it. | `proposal_id`, `seller_id`, `seller_name`, `type`, `line_item_id`, `channel` | -| seller-set | 29 | Seller sets and may revise while `draft` or `published`. | `valid_from` · `valid_until`, `description`, `specifications`, `best_for[]`, `not_suitable_for[]`, `seasonality_notes`, `content_detail`, `specs`, `third_party_tags`, `creative_delivery`, `approval`, `pricing[].cost_method`, `pricing[].gross_rate`, `pricing[].seller_rate`, `pricing[].price_valid_until`, `pricing[].currency`, `pricing[].floor`, `commitment_bounds`, `min_spend`, `creative_policy`, `visibility`, `required_credentials[]`, `eligible_bidders[]`, `terms_acceptance_required`, `buying_route`, `serving_mode`, `serving_platform`, `billing`, `supported_order_systems[]` | +| seller-set | 31 | Seller sets and may revise while `draft` or `published`. | `valid_from` · `valid_until`, `description`, `specifications`, `best_for[]`, `not_suitable_for[]`, `seasonality_notes`, `content_detail`, `specs`, `third_party_tags`, `creative_delivery`, `approval`, `pricing[].cost_method`, `pricing[].gross_rate`, `pricing[].seller_rate`, `pricing[].price_valid_until`, `pricing[].currency`, `pricing[].floor`, `commitment_bounds`, `min_spend`, `creative_policy`, `visibility`, `required_credentials[]`, `eligible_bidders[]`, `terms_acceptance_required`, `terms_ref`, `buying_route`, `serving_mode`, `serving_platform`, `agreement_ref`, `billing`, `supported_order_systems[]` | | selectable | 12 | Buyer chooses from a declared set. Resolves without assent. | `properties`, `environments`, `audiences`, `activation_route`, `formats`, `production`, `cancellation_policy`, `exclusivity`, `measurement_source`, `shortfall_remedy`, `reporting`, `transaction_mechanism` | | settable | 5 | Buyer sets a value within declared bounds. Resolves without assent. | `geo`, `device`, `daypart`, `frequency_cap`, `commitment` | | supplied | 3 | Buyer provides data or an artifact. | `brief_ref`, `buyer_supplied`, `assets[]` | @@ -522,8 +540,8 @@ body is normative.** ### What the distribution says -Of 71 fields, **23 are touched by the buyer** — 12 `selectable`, 5 `settable`, 3 -`supplied` and 3 `requestable`. The remaining 48 are `seller-set`, `immutable`, +Of 73 fields, **23 are touched by the buyer** — 12 `selectable`, 5 `settable`, 3 +`supplied` and 3 `requestable`. The remaining 50 are `seller-set`, `immutable`, `derived` or `mixed`. Only the 3 `requestable` fields require a round trip. Everything else the buyer