Skip to content

[Change] § 5.6.2 — hold_status needs refused and re-entry to requested #66

Open

Description

@KonstantinMirin

What kind of change?

Add or change an enum value

Which section?

5.6 — Commercial terms

Current text

| Key | Meaning |
| :--- | :--- |
| `hold_available` | Whether this line item may be held |
| `hold_duration` | ISO-8601 duration the hold runs for |
| `hold_scope` | `line_item` \| `proposal` |
| `state` | `none` \| `requested` \| `held` \| `expired` \| `released` \| `converted` |
| `hold_expires_at` | Absolute timestamp, set when the hold is granted |

Transitions: `none` → `requested` → `held` → (`expired` | `released` |
`converted`). `converted` is terminal and occurs when the line item reaches
`agreed`.

Proposed text

`hold_terms` offers a hold. It is `seller-set`, and it is inside the binding
record (#63). `hold` is live state, outside the record, and is absent on a
shared proposal (#26, #65).

**`HoldTerms`** keys:

| Key | Type | Required | Meaning |
| :--- | :--- | :--- | :--- |
| `available` | Boolean | REQUIRED | Whether this line item may be held |
| `duration` | Duration | OPTIONAL | How long a hold runs from its grant |

**`Hold`** keys:

| Key | Type | Required | Meaning |
| :--- | :--- | :--- | :--- |
| `status` | HoldStatus | REQUIRED | Moved by the transitions below |
| `version` | Integer | REQUIRED | The `version` the hold is on (#64) |
| `requested_terms` | LineItemWrites | OPTIONAL | The composition the buyer asks to hold, which is not a `commit`. A field it doesn't name reads as § 3.1 and § 3.2 put in effect |
| `held_availability` | Availability | OPTIONAL | What the hold reserves, as the seller states it on a grant or a re-offer. It can be less than `requested_terms` asks |
| `position` | Integer | OPTIONAL | Seller-set, 1 for first in line, in the order granted. A later hold moves up only when the one ahead expires, is released or converts. Holds on the same inventory reserve it in that order, so a later hold reserves only what the holds granted ahead of it leave (#67) |
| `valid_until` | Instant | REQUIRED when `status` is `held` | The grant time plus `hold_terms.duration`, or the cap in #65 if that comes first. With no `duration`, the seller sets it within the cap. It MAY remain on `expired` and `released` |

**`HoldStatus`** is `requested` \| `held` \| `declined` \| `expired` \| `released` \|
`converted`.

These transitions are exhaustive:

- No `hold`, or `declined` | `expired` | `released` → `requested`: the buyer asks,
  writing `version` and any `requested_terms`. `hold_terms.available` MUST be `true`.
  The ask replaces any earlier `hold`.
- `requested` → `held`: the seller grants, writing `valid_until` and any
  `held_availability`.
- `requested` → `declined`: the seller declines, `hold_terms.available` becomes
  `false`, or the line item leaves `published` or `under_review` other than to
  `agreed`.
- `held` → `held`: the seller re-offers a hold its revision makes stale, moving
  `version` to the new one, writing any `held_availability`, and lowering
  `valid_until` to the new version's cap where that comes first (#65, rule 2).
- `held` → `expired`: `valid_until` passes.
- `held` → `released`: the buyer releases, or the seller releases a hold its
  revision makes stale (#65, rule 2).
- `requested` | `held` → `converted`: the line item reaches `agreed`.
  `converted` is terminal.

While `status` is `held`, the held terms are the offered terms of `hold.version`,
which #64 freezes; a seller change is a new `version` under #65, rule 2.

A seller's answer MUST set `status` to `held` or `declined`. A repeated request
while `requested` or `held` MUST NOT start a second hold. Under
`linked_execution: all`, the holds on the proposal's line items move as a unit: each
transition applies to all of them, and `valid_until` is the earliest any of them
allows.

Hold transitions do not change the line item's `status` (§ 5.1) and do not
append to `negotiation_history[]` (§ 4.1).

The holds PR carries this text. It takes LineItemWrites from the buyer writes PR, Availability from the pricing PR, and linked_execution from @Sirajmx's dependencies PR (#70), which the lifecycle PR narrows to all.

Why

§ 5.6.2 hold_status gives a seller no way to refuse a hold request, and a buyer no way to ask again after a hold lapses. A hold is something the seller grants to one buyer; we ask for declined and re-entry. We also tie the held terms to the held version (our held-terms follow-up), so a seller revision can't reprice them in place. The text also adds the hold position @premesh proposed, as hold.position, per open question 5. It splits hold_status into hold_terms, an offered term, and hold, live state, and drops hold_scope: under linked execution, holds move as a unit.

As drafted (§ 5.6.2):

stateDiagram-v2
    none --> requested
    requested --> held
    held --> expired
    held --> released
    held --> converted
    converted --> [*]
Loading

Proposed:

stateDiagram-v2
    [*] --> requested
    requested --> held
    requested --> declined
    requested --> converted
    held --> held
    held --> expired
    held --> released
    held --> converted
    converted --> [*]
    declined --> requested
    expired --> requested
    released --> requested
Loading

Our commitment acceptance change request (#67) stops the seller declining a held buyer's commitment for lack of availability. Our lifecycle change request (#65) caps hold.valid_until at the offer's end and the earliest price_valid_until. A hold is on a version, and our versioning change request (#64) makes a published version immutable, so the held terms are frozen by construction: they are the held version's terms. A seller change to the held terms is a new version under #65 rule 2, which releases the hold or re-offers it at the new version. The buyer's commit at the held version goes stale (#65 rule 1). The freeze is therefore not a separate rule. The seller can still decline under #67 for other reasons, price included, but can't reprice the held version. Under a firm entry (the lifecycle PR), only the other grounds it allows are left.

Knock-on effects

§ 6.3 CTV line item (#37): the example shows a held hold that expires as the offer opens. Show hold_terms: { available: false } only: the line item is private_auction and reserves nothing, and hold is absent on a shared proposal. The closing sentence states the same rule on status and history as our companion lifecycle filing (§ 4.3). One buyer's hold lives on the proposal issued to it (#26, our reply there).

Open questions for the working group:

  1. Answered in our lifecycle change request (#65) and the lifecycle PR. Should the freeze cover proposal-level terms, such as offer_window and description, and the sibling line items under linked execution, or only the held line item's own terms? The held terms are the whole held version's offered terms, proposal-level terms and sibling line items included, because [Change] § 4.1 — Freeze a version's offered terms; changes create version n+1 and a revised entry #64 freezes every value the seller writes. Under the lifecycle PR, a hold goes stale only when the binding record of the line items it names changes, proposal-level terms included. Outside linked execution, a new version that touches only another line item leaves the hold as it is.
  2. Answered in our lifecycle change request (#65). Should the freeze sit in § 5.6.2 hold_status as proposed here, or should who moves each status (#65), whose rule 2 bars revision while an ask is open, treat a running hold as an open ask? Neither: [Change] § 4, § 5.1 — Add § 4.3 Lifecycle: who moves each status #65 rule 2 covers a running hold as well as an open ask, and the freeze follows from [Change] § 4.1 — Freeze a version's offered terms; changes create version n+1 and a revised entry #64, so § 5.6.2 needs only the sentence that ties the held terms to the held version.
  3. Answered in our lifecycle change request (#65). Should the seller instead stay free to revise a held line item, provided a commit at the held version isn't rejected as stale (rule 1 of who moves each status (#65)) until the hold ends? The seller stays free to revise, but the revision is a new version: a commit at the held version goes stale, and the seller releases the hold or re-offers it at the new version ([Change] § 4, § 5.1 — Add § 4.3 Lifecycle: who moves each status #65 rule 2).
  4. Answered in part in our lifecycle change request (#65) and by @EvgenyAndroid, with @premesh: Should the freeze apply under every delivery type, or only where a hold reserves availability (guaranteed and share_of_voice in our delivery type change request)? That change request asks what a hold on a non_guaranteed line item firms. The held terms are the held version's terms under every delivery type, because [Change] § 4.1 — Freeze a version's offered terms; changes create version n+1 and a revised entry #64 freezes every published version. held always means reserved inventory, and a later hold reserves behind the holds ahead of it. A seller that reserves nothing offers no hold (hold_terms.available: false, or no hold_terms) and states the expiry in pricing[].price_valid_until. That date bars a late commit ([Change] § 4, § 5.1 — Add § 4.3 Lifecycle: who moves each status #65). As @EvgenyAndroid proposed, until then the seller can't change that pricing[] entry. Nor can it decline a commit under it except on the grounds a firm entry allows (the lifecycle PR). Still open, in the rules issue: whether holds apply only where inventory is reserved, and which form a seller that reserves nothing uses.
  5. Answered by @premesh. Should concurrent holds on the same inventory be ranked (first right of refusal, or a challenge to commit or release), or is ranking seller policy outside 3.0? Rank them with a seller-set hold.position, 1 for first in line, in the order granted. A later hold moves up only when the one ahead expires, is released or converts. "There's no challenge", so this machine needs no sub-state. The position is hold status, outside [Change] § 4.1 — Freeze a version's offered terms; changes create version n+1 and a revised entry #64's offered terms, so a queue move creates no version (#64, @EvgenyAndroid, @premesh). First refusal on renewal is a separate, negotiated line-item term, which @premesh calls renewal_right.
  6. Should hold carry a seller-issued hold reference, so a buyer can reconcile a hold against the seller's systems, as @EvgenyAndroid asks? It would sit in hold, outside [Change] § 4.1 — Freeze a version's offered terms; changes create version n+1 and a revised entry #64's offered terms, as @EvgenyAndroid places it.
  7. Answered in part in the holds PR: Should hold carry the held quantity, so a buyer can tell a partial hold from a full one, as @EvgenyAndroid asks? The same placement applies. Our commitment acceptance change request (#67) asked whether a hold reserves a quantity, and points to hold.held_availability. That key states what the hold reserves, and it can be less than hold.requested_terms asks. Still open, in the rules issue: whether a grant MUST state it.
  8. Should a hold stay live under a commit the buyer makes during it? Otherwise a seller can let the hold run out while the commit waits, then decline it for lack of availability. As proposed, held → expired runs on hold.valid_until alone, and the buyer can end the wait by withdrawing its commit. The rules issue lists this with the other hold rules we left out of the text.

Would this break an implementation already built to this draft?

No — additive or editorial

You are commenting as

Other — IAB Tech Lab member (Postindustria).

Before you submit

  • I have read CONTRIBUTING.md and agree my contribution may be published under this repository's licence.
  • This contains no confidential or commercially sensitive information.

Details and examples: hold_status state machine: full rationale and examples

Revised 2026-11-03: hold_status splits into hold_terms and hold, refused becomes declined, none and hold_scope go, and hold_position becomes hold.position. Open question 1 is answered, 7 in part, and 8 is new.

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

Activity

  1. KonstantinMirin commented on Oct 22, 2026

    @KonstantinMirin
    Author

    One addition: a hold has to freeze the held terms, or a seller revision can reprice them.

    #67 stops the seller declining a held buyer's commitment for lack of availability. #65 caps hold_expires_at at the earliest price_valid_until. Neither stops the seller revising a held line item: a hold isn't an ask, so #65 rule 2 misses it. The revision creates a new version (#64), so the buyer's commit goes stale (#65 rule 1). The hold keeps availability, not the terms.

    Proposed, after the transition list:

    While state is held, the seller MUST NOT revise the offered terms (#64) of any line item the hold covers, proposal-level terms included, except to answer an open ask (#65 rule 2).

    The seller can still decline under #67 for other reasons, price included, but can't reprice.

    As drafted (§ 3, § 5.6.2), read with #64 and #65:

    stateDiagram-v2
        held --> held : seller revises terms, version n+1
        held --> held : commit at version n rejected as stale
        held --> converted : commit at version n+1 accepted, line item agreed
        held --> expired
        held --> released
    
    Loading

    Proposed:

    stateDiagram-v2
        held --> converted : commit at the held terms accepted, line item agreed
        held --> expired : terms may change after
        held --> released : terms may change after
        note right of held : seller MUST NOT revise offered terms, except to answer an ask
    
    Loading

    Open questions:

    1. Should the freeze cover proposal-level terms, such as valid_until and description, and the sibling line items of a hold_scope: proposal hold, or only the held line item's own terms?
    2. Should this rule sit in § 5.6.2 hold_status as proposed here, or should who moves each status (#65), whose rule 2 bars revision while an ask is open, treat a running hold as an open ask?
    3. Should the seller instead stay free to revise a held line item, provided a commit at the held version isn't rejected as stale (rule 1 of who moves each status (#65)) until the hold ends?
    4. Should the freeze apply under every delivery type, or only where a hold reserves availability (guaranteed and share_of_voice in our delivery type change request)? That change request asks what a hold on a non_guaranteed line item firms.
    5. Should concurrent holds on the same inventory be ranked (first right of refusal, or a challenge to commit or release), or is ranking seller policy outside 3.0?

    Details: Delivery type and hold terms: full rationale

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

  2. EvgenyAndroid commented on Oct 27, 2026

    @EvgenyAndroid

    Supporting refused and re-entry. Two further gaps:

    1. A hold has no reference or quantity. Nothing identifies the seller's reservation or says how much is held, so a buyer can't reconcile a hold against the seller's systems, or tell a partial hold from a full one. A seller-issued hold reference and the held quantity would close that.
    2. Reserving inventory isn't the same as honouring terms. A seller can promise to honour the offered terms until a date without reserving any inventory. As drafted, held could mean either, and to a buyer they're different guarantees. Either say held always means reserved inventory, or add a way to state that a commitment is terms-only.
  3. premesh commented on Oct 27, 2026

    @premesh

    On ranking concurrent holds: from the OOH side, yes, expose the rank, but keep the model this proposal already has. The buyer requests, the seller grants (held) or refuses, and a hold ends converted, expired or released. Until it converts it isn't a commitment, and once contracted the contract terms govern. That is how OOH works today, and I'd keep it seller-granted for agents too.

    Holds exist because client approval is slow, and agents don't change that. What agents change is that asking costs nothing: an agent can request holds on hundreds of units to keep options open. Because the seller grants, the seller can refuse speculative holds, and that matters more than any rule about ranking.

    What's missing for OOH:

    • hold_position: seller-set integer, 1 = first in line. Several holds often sit on a popular unit in the order granted. A later hold moves up only when the one ahead expires, is released or converts. There's no challenge.
    • hold_available: false should be a normal answer on some products (programmatic, rotary), where a firm offer with an expiry does the job.

    First refusal on renewal is a different thing: the current advertiser's right to the next cycle before it opens up. I'd put it on the line item (renewal_right: first_refusal | none), not in the hold queue.

  4. EvgenyAndroid commented on Oct 27, 2026

    @EvgenyAndroid

    @premesh hold_available: false plus a firm offer with an expiry answers the second point I raised above, and the spec already has the expiry: pricing[].price_valid_until. So I'd settle it this way: held always means reserved inventory, and a seller that only honours terms says so with hold_available: false and price_valid_until.

    One catch with hold_position, read with #64. #64 counts every seller-written value as an offered term, and its exclusion list names only hold_status.state and hold_status.hold_expires_at. A seller-set hold_position would therefore be an offered term. Each time a hold ahead expires and the queue moves, the seller would mint a new version, and a buyer's commit at the old version would go stale under #65 rule 1. If hold_position goes in, it belongs in hold_status and on #64's exclusion list, along with the hold reference and held quantity from my earlier comment.

  5. premesh commented on Oct 27, 2026

    @premesh

    @EvgenyAndroid agreed on both. hold_available: false with price_valid_until is a cleaner answer than anything new.

    And yes, hold_position is status, not a term: the buyer isn't agreeing to a position, it's just where they stand while the hold runs. It belongs in hold_status and on #64's exclusion list. Otherwise every expiry ahead of you makes your commit stale, which is the opposite of what a queue is for.

    renewal_right is different. It's negotiated, so it should stay an offered term and version like one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions