Skip to content

[Change] § 4.1, § 5.6.1 — Commitments must carry proposal_id and version; stale versions are rejected #24

Open

Description

@Kishore-2572

What kind of change?

Change a normative requirement (MUST / SHOULD / MAY)

Which section?

Whole document / more than one section

Current text

§ 4.1:


| `version` | Integer | derived | Monotonically increasing revision number |


§ 5.6.1: n/a, new keys.

Proposed text

**§ 4.1, `version`:**


| `version` | Integer | derived | Monotonically increasing revision number. Each revision while status is `published` increments it |


**§ 5.6.1, two new keys:**


| `proposal_id` | REQUIRED | The proposal the commitment is made against |
| `version` | REQUIRED | The proposal `version` the commitment was priced against |


**§ 5.6.1, add after the idempotency paragraph:**


The seller MUST reject a commitment whose `version` is not the current
version, whose proposal is past `valid_until`, or whose
`pricing[].price_valid_until` has passed. The rejection MUST return the current
version number, so the buyer can fetch it again.

This check MUST be deterministic, as for `idempotency_key`.


**§ 4.1, `negotiation_history[]`:** no change to the row. Entries already carry `version`.

**§ 6, example commitment:**


commitment:
  proposal_id: "examplepub-us-q4-2026-omni"
  version: 4
  basis: spend
  amount: 160000.00
  currency: USD
  flight_start: "2026-10-06"
  flight_end:   "2026-12-21"
  idempotency_key: "agency-a-camp1-weather-app-ctv-3b7e0d52"

Why

§ 4.1 already has version, derived and monotonic, but nothing says a commitment must name the version it was priced against, or what happens if the buyer's copy is stale. A buyer agent that cached an old version could commit at old terms.

Raised on the working calls: track by proposal_id and version, given the risk of buyers holding expired versions.

Out of scope: per-version performance statistics (served, inquiries, win rate) are the seller's private analytics in its proposal store, not object fields.

Knock-on effects

  • § 4.1: the version description states that each revision while published increments it.
  • § 5.6.1: two REQUIRED keys and the rejection rules.
  • Appendix A / B: reviewed; the new keys sit within commitment, so no new rows.
  • § 6: an example commitment showing proposal_id and version.
  • Breaking: a commitment without proposal_id and version would be rejected.

Would this break an implementation already built to this draft?

Yes — breaking

Alternatives you considered and rejected

  • Accepting a stale commitment at current terms: the buyer would commit to terms it did not see.

You are commenting as

Standards body / working group participant

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.

Activity

  1. aleksUIX commented on Oct 14, 2026

    @aleksUIX

    Could the version and expiry checks explicitly exclude replay of an already processed commitment?

    Section 5.6.1 currently requires a previously seen idempotency_key to return the original result. Suppose the seller accepts a commitment immediately before price_valid_until, but the response is lost. The buyer retries the identical request after that time. The existing rule requires the accepted result; the proposed expiry rule requires rejection.

    Suggested addition:

    For an authenticated replay of the same previously processed commitment attempt, the seller MUST return the original result without creating another commitment. The current-version and expiry checks apply to a new attempt, not to returning a stored result. Reusing the same idempotency key with different commitment terms MUST produce an explicit conflict and MUST NOT create or modify a commitment.

    This preserves rejection of new stale commitments while allowing a buyer to recover the outcome of a timed-out request. The same distinction applies when the current published version changes between acceptance and retry.

    This is a source-review counterexample, not a defect reproduced in an implementation.

    Commenting in a personal capacity.

  2. KonstantinMirin commented on Oct 22, 2026

    @KonstantinMirin

    Postindustria (IAB Tech Lab member) supports pinning the version a commitment is made against, and @aleksUIX's replay distinction. Two gaps remain. #24 makes every seller-set revision (§ 3) increment version, so correcting one line item rejects commitments on another. And an equal version doesn't prove equal bytes (#35). Our coming PRs close both.

    Bind content, not the counter. The buyer's commitment entry in assent[] carries version and record_digest, the digest of the binding record of the line items it names (our binding-record filing). The seller recomputes it over those line items as it stores them, plus the buyer's writes. An equal value means the buyer read the same terms, so the seller MAY accept at the entry's version. Our PR on what is signed states this test, and our lifecycle PR defines staleness the same way.

    sequenceDiagram
        Seller->>Buyer: version 4, line items A and B
        Note over Seller: seller revises, version 5
        Buyer->>Seller: commit on A at version 4, with record_digest
        Note over Seller: recompute over stored A plus the writes
        alt equal
            Seller-->>Buyer: MAY accept at version 4
        else A changed
            Seller-->>Buyer: rejected (stale_version)
        else buyer read other bytes
            Seller-->>Buyer: rejected (record_mismatch)
        end
    
    Loading

    One replay rule. #73 states it in line with the replay rule in IAB's agentic primitives (FD-12), and our PR on what is signed keeps it. A replay under the same key gets the result as it stands, without re-running the checks. A changed Commit under that key gets idempotency_conflict, a code our errors and extensions PR defines.

    Take idempotency_key and the pin out of commitment. Both describe one commit, not terms. Our PR on what is signed puts them on the buyer's assent[] entry, outside the digest.

    Details: Commitment digest and replay: full rationale and examples

    Open questions:

    1. How long does a seller keep a stored rejection? An acceptance keeps its key in assent[], but a rejection writes no entry, so the seller keeps it apart. @EvgenyAndroid suggests on #128 a floor at valid_until (offer_window.end in our PRs). Our PRs leave replay beyond § 5.6.1 to the AAMP wire and lifecycle contract (§ 1.2). Should that contract set this floor?

    Revised 2026-11-03: Rewritten against our coming PRs: the seller's own recompute replaces offer_digest, and idempotency_key moves to the assent[] entry. We withdraw the valid_until floor our #128 reply quoted, pending open question 1.

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

  3. Kishore-2572 commented on Oct 22, 2026

    @Kishore-2572
    Author

    Thanks @aleksUIX. As the filer, I agree that the version and expiry checks should apply to a new commitment attempt, not to replaying the stored result of one already processed. Otherwise a buyer cannot recover the outcome of a timed-out request. I will include your suggested wording in the draft PR for this change request, including the explicit conflict when the same idempotency_key is reused with different terms, for the working group to consider at adjudication.

  4. Kishore-2572 commented on Oct 22, 2026

    @Kishore-2572
    Author

    Thank you, @KonstantinMirin. Three points in reply, as the filer:

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