feat: eligibility claims & verification contract#250
Conversation
ACSchil
left a comment
There was a problem hiding this comment.
This LGTM. I like the direction we ended up taking; it works elegantly within the broader spec.
docs/specification/checkout.md
Outdated
| such as loyalty membership, payment instrument perks, and similar. These are | ||
| claims, not verified facts. Businesses **MAY** act on recognized claims during | ||
| the session (adjusting pricing, granting product access, applying provisional | ||
| discounts), but all claims that influenced the checkout **MUST** be resolved |
There was a problem hiding this comment.
The contract hinges on “all claims that influenced the checkout MUST be resolved”, but “influenced” (and even “recognized”) isn’t defined precisely enough to guarantee interoperable behavior.
In practice, platforms need a deterministic way to know which specific context.eligibility[] entries the Business actually used (pricing, access gating, etc.) so they can remediate without guesswork. Right now the only guidance is “SHOULD notify… when a claim is not applied,” which doesn’t cover the applied case.
Suggestion: define “influenced” as “reflected in the Business response” and require the Business to explicitly echo applied/active claims (e.g., a new response field like eligibility_applied: [...] / eligibility_pending_verification: [...], or a standardized messages entry per accepted claim). For discount-related effects, discounts.applied[].eligibility covers this, but there’s no equivalent for non-discount effects like product access or catalog price/list_price adjustments.
There was a problem hiding this comment.
Good observation. Tightened "influenced" to "accepted" in the latest revision — the partition is accepted vs. not-accepted, and Businesses SHOULD warn when a claim is not accepted.
The checkout state itself is the effect of accepted claims — pricing, availability, line items all reflect what the Business did with them. When a structured extension exists (like the discount extension's applied[].eligibility), it provides explicit attribution — I would keep this contract, instead of trying to model a generic eligibility_applied. For the general case, the Business MAY use messages with type: "info" to explain the effects of accepted claims — integrated into the spec text.
|
The 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 |
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)
|
@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:
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. |
|
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 This is already the pattern in #203 ( |
lemonmade
left a comment
There was a problem hiding this comment.
I really like where you landed with this 👏
| "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." | ||
| }, |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
|
@douglasborthwick-crypto 👍 modulo one gotcha on example you shared...
^ either that or on cart/checkout object similar to discount extension. |
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).
c0fc5ee to
f8e70b4
Compare
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:
Layering:
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