Skip to content

feat: eligibility claims & verification contract#250

Merged
igrigorik merged 8 commits intomainfrom
feat/context-eligibility
Mar 13, 2026
Merged

feat: eligibility claims & verification contract#250
igrigorik merged 8 commits intomainfrom
feat/context-eligibility

Conversation

@igrigorik
Copy link
Copy Markdown
Contributor

Context: #214 (comment). Supersedes #214, closes #137.

Introduce context.eligibility — buyer claims about eligible benefits (loyalty membership, payment instrument perks, etc.) that Businesses can act on across the shopping lifecycle.

Processing model:

  • Platform provides claims via context.eligibility on any request
  • Business MAY act on recognized claims (adjust pricing, product access, provisional discounts); MUST ignore unrecognized claims
  • At checkout completion, all claims that influenced the checkout MUST be resolved: verified against proof, or rescinded by Platform
  • Unresolved claims block completion (invalid_eligibility error)
  • Business MUST NOT mutate checkout on verification failure

Layering:

  • context.json: eligibility array with reverse-domain $ref validation
  • checkout.md: normative verification contract (core obligation)
  • discount.json: provisional + eligibility fields on applied_discount for structured attribution when extension is active
  • catalog/index.md: MAY adjust price/list_price for eligible claims
  • error_code.json: invalid_eligibility standard error

Eligibility lives on context (not PaymentInstrument) enabling full-funnel coverage from catalog through checkout. Verification is a core checkout concern; the discount extension adds attribution.


Checklist

  • New feature (non-breaking change which adds functionality)
  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings

@igrigorik igrigorik requested a review from maximenajim March 10, 2026 23:25
@igrigorik igrigorik self-assigned this Mar 10, 2026
@igrigorik igrigorik requested review from a team as code owners March 10, 2026 23:25
@igrigorik igrigorik added the TC review Ready for TC review label Mar 10, 2026
Comment thread docs/specification/checkout.md Outdated
Comment thread docs/specification/checkout.md
Comment thread docs/specification/checkout.md Outdated
Copy link
Copy Markdown
Contributor

@ACSchil ACSchil left a comment

Choose a reason for hiding this comment

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

This LGTM. I like the direction we ended up taking; it works elegantly within the broader spec.

Comment thread docs/specification/checkout.md Outdated
Comment thread docs/specification/checkout.md
Comment thread docs/specification/checkout.md Outdated
Comment thread docs/specification/catalog/index.md
@douglasborthwick-crypto
Copy link
Copy Markdown

The context.eligibility + provisional model here is clean. One gap worth flagging: the verification contract at completion says claims "MUST be resolved: verified against proof, or rescinded by Platform" — but the spec doesn't define what "proof" looks like for claims that aren't derivable from the payment credential.

For instrument-bound claims (RedCard BIN check), the Business can verify locally. For non-instrument claims — loyalty tiers, on-chain holdings, membership status — the Business needs something to verify against.

A minimal option: let context.eligibility entries optionally carry a companion attestation (sig + kid) that the Business can check against a published JWKS. That keeps verification deterministic without requiring the Business to call back to the Platform or the claim source. More detail in #214.

igrigorik added a commit that referenced this pull request Mar 12, 2026
  Terminology:
  - "claims that influenced the checkout" → "accepted claims" throughout,
    aligning with the accepted/not-accepted partition (maximenajim, ACSchil)
  - "not applied" → "not accepted" for consistency

  Verification semantics:
  - Clarify that verification failure MUST only affect the messages array,
    not checkout state (line items, totals, discounts, etc.) (maximenajim)
  - Add path field to eligibility_invalid example for machine-readable
    partial failure identification (ACSchil)

  Messages contract:
  - Add MAY use type: "info" to explain effects of accepted claims,
    complementing SHOULD warn on rejection (maximenajim)

  Catalog:
  - Add non-binding pricing contract to "Relationship to Checkout" section:
    catalog responses are not transactional commitments, checkout is
    authoritative, responses SHOULD NOT be reused across sessions
    without re-validation (maximenajim)
@igrigorik
Copy link
Copy Markdown
Contributor Author

@maximenajim ty, great feedback, ptal at updated draft.

@douglasborthwick-crypto we're intentionally leaving the verification mechanism open — "UCP does not prescribe how verification occurs" — because the proof model varies by claim type:

  • Payment instrument claims: the credential itself is the proof, no additional mechanism needed
  • Authenticated session claims (loyalty, membership): can be validated via provided identity / linking
  • Third-party attestation: this is where a signed proof would operate

A companion attestation mechanism (sig + kid against a published JWKS) is a great candidate for a capability extension that complements eligibility. Additive and non-breaking: platforms that can provide attestations send them alongside the claim; businesses that require them check, others ignore.

@igrigorik igrigorik requested a review from maximenajim March 12, 2026 16:42
@douglasborthwick-crypto
Copy link
Copy Markdown

That's the right split — prescribing verification in core would overfit to one claim type. The capability extension model keeps eligibility clean.

Happy to draft that extension. The shape I have in mind:

{
  "context": {
    "eligibility": [{
      "type": "token_holder",
      "label": "USDC holder discount",
      "attestation": {
        "sig": "<base64 ECDSA signature>",
        "kid": "insumer-attest-v1",
        "jwks_uri": "https://insumermodel.com/.well-known/jwks.json",
        "condition_hash": "<SHA-256 of evaluated condition>",
        "attested_at": "2026-03-12T16:40:00Z",
        "expires_at": "2026-03-12T17:10:00Z"
      }
    }]
  }
}

The Business verifies the signature against the JWKS, checks condition_hash matches the claim, confirms expires_at hasn't passed. No callback to the Platform or the attestation provider needed — fully offline verification.

This is already the pattern in #203 (com.insumermodel.attestation) and the x402 cold-start spec. I'll put together a capability extension draft against the current eligibility schema once #250 lands.

Copy link
Copy Markdown
Contributor

@lemonmade lemonmade left a comment

Choose a reason for hiding this comment

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

I really like where you landed with this 👏

Comment thread docs/specification/checkout.md
Comment on lines +69 to +72
"eligibility": {
"$ref": "../ucp.json#/$defs/reverse_domain_name",
"description": "The eligibility claim accepted by the Business for this discount. Corresponds to a value from context.eligibility. Omitted for code-based and non-eligibility automatic discounts."
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Something doesn’t quite sit right with me about this. I think it’s that we are providing details about the source of one kind of automatic discount with a top-level field. If we wanted to indicate the source of other automatic discounts (from identity linking, based on the line items, etc), we would need additional fields. Did you consider any design for this that broadened this field a bit to leave space for more detail on other discount types?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good prompt. There is potential for "provenance proliferation" here, but so far I haven't spotted a generic schema. If you squint, codes and eligibility are both string[] — but they carry different lifecycle contracts that you'd want to capture and distinguish, so a generic source would still need type-specific semantics. All we'd have done is push the modeling complexity down one layer.

Open to suggestions.

@igrigorik
Copy link
Copy Markdown
Contributor Author

@douglasborthwick-crypto 👍 modulo one gotcha on example you shared...

context.eligibility is string[] with reverse-domain validated items. Your example changes items from strings to objects, which breaks the contract. The attestation should live separately from eligibility not inline, e.g...

  {
    "context": {
      "eligibility": ["com.insumermodel.token_holder"],
      "attestations": {
        "com.insumermodel.token_holder": {
          "sig": "<base64>",
           ...
        }
      }
    }
  }

^ either that or on cart/checkout object similar to discount extension.

igrigorik and others added 5 commits March 12, 2026 21:37
  Introduce `context.eligibility` — buyer claims about eligible benefits
  (loyalty membership, payment instrument perks, etc.) that Businesses
  can act on across the shopping lifecycle.

  Processing model:
  - Platform provides claims via context.eligibility on any request
  - Business MAY act on recognized claims (adjust pricing, product
    access, provisional discounts); MUST ignore unrecognized claims
  - At checkout completion, all claims that influenced the checkout
    MUST be resolved: verified against proof, or rescinded by Platform
  - Unresolved claims block completion (invalid_eligibility error)
  - Business MUST NOT mutate checkout on verification failure

  Layering:
  - context.json: eligibility array with reverse-domain $ref validation
  - checkout.md: normative verification contract (core obligation)
  - discount.json: provisional + eligibility fields on applied_discount
    for structured attribution when discount extension is active
  - catalog/index.md: MAY adjust price/list_price for eligible claims
  - error_code.json: invalid_eligibility standard error

  Key design decision: eligibility lives on context (not PaymentInstrument)
  enabling full-funnel coverage from catalog through checkout. Verification
  is a core checkout concern; the discount extension adds attribution.
Co-authored-by: Alex Schillinger <alexcschillinger@gmail.com>
Co-authored-by: Alex Schillinger <alexcschillinger@gmail.com>
  Terminology:
  - "claims that influenced the checkout" → "accepted claims" throughout,
    aligning with the accepted/not-accepted partition (maximenajim, ACSchil)
  - "not applied" → "not accepted" for consistency

  Verification semantics:
  - Clarify that verification failure MUST only affect the messages array,
    not checkout state (line items, totals, discounts, etc.) (maximenajim)
  - Add path field to eligibility_invalid example for machine-readable
    partial failure identification (ACSchil)

  Messages contract:
  - Add MAY use type: "info" to explain effects of accepted claims,
    complementing SHOULD warn on rejection (maximenajim)

  Catalog:
  - Add non-binding pricing contract to "Relationship to Checkout" section:
    catalog responses are not transactional commitments, checkout is
    authoritative, responses SHOULD NOT be reused across sessions
    without re-validation (maximenajim)
  Standardize codes for the three eligibility message types:
  eligibility_not_accepted (warning), eligibility_accepted (info),
  and eligibility_invalid (error at completion).
@igrigorik igrigorik force-pushed the feat/context-eligibility branch from c0fc5ee to f8e70b4 Compare March 13, 2026 04:38
Comment thread docs/specification/checkout.md
Comment thread source/schemas/shopping/discount.json Outdated
Copy link
Copy Markdown
Contributor

@raginpirate raginpirate left a comment

Choose a reason for hiding this comment

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

Looks great! This matches my initial reaction to first model this above payments before we take it into payments.

And the path of associating a discount to an eligibility signal highlights how we can take this to other independent primitives across ucp in the future; payments can eventually adopt this connective tissue too if we find the use case benefits people beyond this simple version.

  Per TC discussion, this is already captured via messages flow.
Copy link
Copy Markdown
Contributor

@amithanda amithanda left a comment

Choose a reason for hiding this comment

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

Looks great, thanks for addressing all the feedback.

@igrigorik igrigorik merged commit 49aee52 into main Mar 13, 2026
6 checks passed
@igrigorik igrigorik deleted the feat/context-eligibility branch March 13, 2026 21:52
douglasborthwick-crypto added a commit to douglasborthwick-crypto/ucp that referenced this pull request Mar 13, 2026
Adds a capability extension that complements eligibility (Universal-Commerce-Protocol#250) with
cryptographic attestation proofs. Platforms relay signed attestations
alongside eligibility claims; Businesses verify offline via JWKS.

Follows the discount extension pattern (allOf on Cart/Checkout).
Discussed in Universal-Commerce-Protocol#203 and Universal-Commerce-Protocol#250 — igrigorik invited this as a companion
extension ("a great candidate, additive and non-breaking").

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
douglasborthwick-crypto added a commit to douglasborthwick-crypto/ucp that referenced this pull request Mar 13, 2026
Adds a capability extension that complements eligibility (Universal-Commerce-Protocol#250) with
cryptographic attestation proofs. Platforms relay signed attestations
alongside eligibility claims; Businesses verify offline via JWKS.

Follows the discount extension pattern (allOf on Cart/Checkout).
Discussed in Universal-Commerce-Protocol#203 and Universal-Commerce-Protocol#250 — igrigorik invited this as a companion
extension ("a great candidate, additive and non-breaking").
richmolj pushed a commit that referenced this pull request Mar 16, 2026
* feat: eligibility claims & verification contract

  Introduce `context.eligibility` — buyer claims about eligible benefits
  (loyalty membership, payment instrument perks, etc.) that Businesses
  can act on across the shopping lifecycle.

  Processing model:
  - Platform provides claims via context.eligibility on any request
  - Business MAY act on recognized claims (adjust pricing, product
    access, provisional discounts); MUST ignore unrecognized claims
  - At checkout completion, all claims that influenced the checkout
    MUST be resolved: verified against proof, or rescinded by Platform
  - Unresolved claims block completion (invalid_eligibility error)
  - Business MUST NOT mutate checkout on verification failure

  Layering:
  - context.json: eligibility array with reverse-domain $ref validation
  - checkout.md: normative verification contract (core obligation)
  - discount.json: provisional + eligibility fields on applied_discount
    for structured attribution when discount extension is active
  - catalog/index.md: MAY adjust price/list_price for eligible claims
  - error_code.json: invalid_eligibility standard error

  Key design decision: eligibility lives on context (not PaymentInstrument)
  enabling full-funnel coverage from catalog through checkout. Verification
  is a core checkout concern; the discount extension adds attribution.

* align error code to noun_adjective pattern

* fix: address PR #250 review feedback

  Terminology:
  - "claims that influenced the checkout" → "accepted claims" throughout,
    aligning with the accepted/not-accepted partition (maximenajim, ACSchil)
  - "not applied" → "not accepted" for consistency

  Verification semantics:
  - Clarify that verification failure MUST only affect the messages array,
    not checkout state (line items, totals, discounts, etc.) (maximenajim)
  - Add path field to eligibility_invalid example for machine-readable
    partial failure identification (ACSchil)

  Messages contract:
  - Add MAY use type: "info" to explain effects of accepted claims,
    complementing SHOULD warn on rejection (maximenajim)

  Catalog:
  - Add non-binding pricing contract to "Relationship to Checkout" section:
    catalog responses are not transactional commitments, checkout is
    authoritative, responses SHOULD NOT be reused across sessions
    without re-validation (maximenajim)

* add eligibility message codes for warning and info

  Standardize codes for the three eligibility message types:
  eligibility_not_accepted (warning), eligibility_accepted (info),
  and eligibility_invalid (error at completion).

* remove display MUST from provisional

  Per TC discussion, this is already captured via messages flow.

---------

Co-authored-by: Alex Schillinger <alexcschillinger@gmail.com>
alexpark20 added a commit to alexpark20/ucp that referenced this pull request Mar 17, 2026
Add 6 missed post-release changes discovered during full audit of 58 PRs
merged after the 2026-01-23 branch was cut:

  Universal-Commerce-Protocol#16 — Optional request/response signing headers (PR Universal-Commerce-Protocol#156) [Additive]
  Universal-Commerce-Protocol#17 — Eligibility claims & provisional discounts (PR Universal-Commerce-Protocol#250) [High/Breaking]
  Universal-Commerce-Protocol#18 — Optional signals field on cart and checkout (PR Universal-Commerce-Protocol#203) [Additive]
  Universal-Commerce-Protocol#19 — Fulfillment method id/type optional on update (PR Universal-Commerce-Protocol#143/Universal-Commerce-Protocol#196) [Low]
  Universal-Commerce-Protocol#20 — intent field added to context (PR Universal-Commerce-Protocol#95) [Additive]
  Universal-Commerce-Protocol#21 — available_instruments added to payment handler (PR Universal-Commerce-Protocol#187) [Additive]

Also updates PR_URLS map and changes/visible counts from 15 → 21.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
alexpark20 added a commit to alexpark20/ucp that referenced this pull request Mar 17, 2026
Add 6 missed post-release changes discovered during full audit of 58 PRs
merged after the 2026-01-23 branch was cut:

  Universal-Commerce-Protocol#16 — Optional request/response signing headers (PR Universal-Commerce-Protocol#156) [Additive]
  Universal-Commerce-Protocol#17 — Eligibility claims & provisional discounts (PR Universal-Commerce-Protocol#250) [High/Breaking]
  Universal-Commerce-Protocol#18 — Optional signals field on cart and checkout (PR Universal-Commerce-Protocol#203) [Additive]
  Universal-Commerce-Protocol#19 — Fulfillment method id/type optional on update (PR Universal-Commerce-Protocol#143/Universal-Commerce-Protocol#196) [Low]
  Universal-Commerce-Protocol#20 — intent field added to context (PR Universal-Commerce-Protocol#95) [Additive]
  Universal-Commerce-Protocol#21 — available_instruments added to payment handler (PR Universal-Commerce-Protocol#187) [Additive]

Also updates PR_URLS map and changes/visible counts from 15 → 21.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
douglasborthwick-crypto added a commit to douglasborthwick-crypto/ucp that referenced this pull request Mar 18, 2026
Adds a capability extension that complements eligibility (Universal-Commerce-Protocol#250) with
cryptographic attestation proofs. Platforms relay signed attestations
alongside eligibility claims; Businesses verify offline via JWKS.

Follows the discount extension pattern (allOf on Cart/Checkout).
Discussed in Universal-Commerce-Protocol#203 and Universal-Commerce-Protocol#250 — igrigorik invited this as a companion
extension ("a great candidate, additive and non-breaking").
douglasborthwick-crypto added a commit to douglasborthwick-crypto/ucp that referenced this pull request Mar 19, 2026
Adds a capability extension that complements eligibility (Universal-Commerce-Protocol#250) with
cryptographic attestation proofs. Platforms relay signed attestations
alongside eligibility claims; Businesses verify offline via JWKS.

Follows the discount extension pattern (allOf on Cart/Checkout).
Discussed in Universal-Commerce-Protocol#203 and Universal-Commerce-Protocol#250 — igrigorik invited this as a companion
extension ("a great candidate, additive and non-breaking").
douglasborthwick-crypto added a commit to douglasborthwick-crypto/ucp that referenced this pull request Mar 20, 2026
Adds a capability extension that complements eligibility (Universal-Commerce-Protocol#250) with
cryptographic attestation proofs. Platforms relay signed attestations
alongside eligibility claims; Businesses verify offline via JWKS.

Follows the discount extension pattern (allOf on Cart/Checkout).
Discussed in Universal-Commerce-Protocol#203 and Universal-Commerce-Protocol#250 — igrigorik invited this as a companion
extension ("a great candidate, additive and non-breaking").
@wry-ry wry-ry added the enhancement New feature or request label Mar 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request TC review Ready for TC review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat]: Allow tender-based discounts via payment instrument qualifiers in Checkout Session Update

9 participants