Skip to content

feat: extend warning to support disclosure contract#267

Merged
amithanda merged 7 commits intomainfrom
feat/disclosure-message
Mar 17, 2026
Merged

feat: extend warning to support disclosure contract#267
amithanda merged 7 commits intomainfrom
feat/disclosure-message

Conversation

@igrigorik
Copy link
Contributor

@igrigorik igrigorik commented Mar 16, 2026

Resolves #222

Extends message_warning with a presentation field and two new optional fields (image_url, url). When presentation: "disclosure", the warning carries a notice — safety warnings, allergen declarations, compliance content, etc. — that platforms MUST display in proximity to the referenced component and MUST NOT hide, collapse, or auto-dismiss.

The presentation field defines the rendering contract axis for warnings. Two values are defined today:

  • "notice" (default) — current warning behavior: MUST display, MAY dismiss
  • "disclosure" — MUST display in proximity to path, MUST NOT dismiss, MUST escalate via continue_url if cannot honor

Why extend warning rather than a new message type or line_item.disclosures[]

This is an alternative to both line_item.disclosures[] (#222) and a separate type: "disclosure":

  • messages[] already works across all surfaces (checkout, cart, catalog_lookup, catalog_search)
  • Path-based targeting enables product-level inheritance: a disclosure on $.products[0] applies to all variants
  • Graceful degradation: platforms that don't recognize the presentation field still render the underlying warning
  • Preserves the established error/warning/info model without setting a precedent for type proliferation

Why presentation (string) rather than disclosure (boolean)

  • code = what the warning is about (allergens, prop65, etc.)
  • presentation = how the platform must render it (notice, disclosure)

Provides a natural home for future rendering contracts. For example, an acknowledge value could fill the gap between display-only (disclosure) and full escalation (requires_buyer_review) — where the platform natively collects buyer confirmation without handing off to merchant UI. That value isn't needed today, but the axis is ready for it without migration.

Examples

Catalog — allergen disclosure targeting a product (all variants inherit):

{
  "ucp": { "version": "2026-01-11", "status": "success" },
  "products": [
    {
      "id": "prod_nut_butter",
      "title": "Artisan Nut Butter Collection",
      "variants": [
        {
          "id": "var_almond",
          "title": "Almond Butter",
          "price": { "amount": 1299, "currency": "USD" },
          "availability": { "available": true }
        },
        {
          "id": "var_cashew",
          "title": "Cashew Butter",
          "price": { "amount": 1499, "currency": "USD" },
          "availability": { "available": true }
        }
      ]
    }
  ],
  "messages": [
    {
      "type": "warning",
      "code": "allergens",
      "path": "$.products[0]",
      "content": "**Contains: tree nuts.** Produced in a facility that also processes peanuts, milk, and soy.",
      "content_type": "markdown",
      "presentation": "disclosure",
      "image_url": "https://merchant.com/allergen-tree-nuts.svg",
      "url": "https://merchant.com/allergen-info"
    }
  ]
}

The path targets $.products[0] — both Almond Butter and Cashew Butter variants inherit the disclosure without duplication. This is the key advantage over line_item.disclosures[], which would require repeating the disclosure on every variant.

Checkout — disclosure co-existing with a recoverable error:

{
  "ucp": { "version": "2026-01-11", "status": "success" },
  "id": "chk_abc123",
  "status": "incomplete",
  "currency": "USD",
  "line_items": [
    {
      "id": "li_1",
      "item": { "id": "item_456", "title": "Artisan Nut Butter Collection", "image_url": "https://merchant.com/nut-butter.jpg" },
      "quantity": 1,
      "totals": [{ "type": "subtotal", "amount": 1299 }]
    }
  ],
  "totals": [{ "type": "total", "amount": 1299 }],
  "messages": [
    {
      "type": "error",
      "code": "field_required",
      "path": "$.buyer.email",
      "content": "Buyer email is required",
      "severity": "recoverable"
    },
    {
      "type": "warning",
      "code": "allergens",
      "path": "$.line_items[0]",
      "content": "**Contains: tree nuts.** Produced in a facility that also processes peanuts, milk, and soy.",
      "content_type": "markdown",
      "presentation": "disclosure",
      "image_url": "https://merchant.com/allergen-tree-nuts.svg",
      "url": "https://merchant.com/allergen-info"
    }
  ],
  "links": []
}

Key design decisions

  • presentation on warning, not a new type — preserves error/warning/info model; graceful degradation over schema purity
  • String, not boolean — extensible without migration; code carries the "what", presentation carries the "how"
  • Business resolves jurisdiction — businesses use buyer-provided data (context and other inputs) and product attributes to determine applicable disclosures; platforms do not affect or resolve disclosure applicability
  • image_url and url on warning only — other message types (error, info) don't need these fields
  • path is SHOULD, not MUST — kept optional in schema; when omitted, disclosure applies to response as a whole
  • Disclosure is a presentation mechanism — it does not gate the checkout flow; when buyer acknowledgment is also required, business MAY combine with escalation mechanisms from the checkout status lifecycle
  • MUST-escalate fallback — platforms that cannot honor the disclosure rendering contract MUST escalate to merchant UI via continue_url rather than silently downgrading

Checklist

  • New feature (non-breaking change which adds functionality)
  • Documentation update
  • 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 added the TC review Ready for TC review label Mar 16, 2026
@sinhanurag
Copy link

sinhanurag commented Mar 16, 2026

A question on the approach (apologies in case I missed the discussion):

An evolutionary side effect of adding a new message type can be fostering a pattern that we will start evolving the type as needed for future requirements as well. IMHO the types defined today error, info, warning are pretty broad and generic to the type of the message and disclosure seems like more of a derivative of either info or warning with the mandate from the business that the agent has to display it and the associated content.

To that effect an alternate approach would be to evolve message structure to have some of these explicit signals (content, content_type, image_url etc.) and maybe a mandate signal from the business to display it that the agent has to honor?

This alternate model can have some cons though such as making the interpretation more nested however I was curious if that is still a better choice than adding new types some of which might not be at the same level of abstraction?

  Extends message_warning with disclosure: true flag, image_url, and url
  fields. When disclosure is set, platforms MUST display the warning in
  proximity to the path-referenced component and MUST NOT hide or dismiss it.

  This preserves the existing three-type message model (error/warning/info)
  while giving disclosures a prescribed rendering contract. Platforms that
  don't recognize the flag still render the underlying warning — graceful
  degradation by design.
@igrigorik igrigorik force-pushed the feat/disclosure-message branch from 7770730 to f3a115a Compare March 16, 2026 17:02
@igrigorik
Copy link
Contributor Author

@sinhanurag fair, pushed an update so we can look at them side by side..

Flag approach forfeits schema-level distinction. With a separate type: "disclosure", code generators produce a distinct type, validators can enforce disclosure-specific fields, and platforms switch(message.type) with an explicit case.

On the other hand, flag approach gains graceful degradation. A platform that doesn't recognize disclosure: true still renders the warning — the buyer sees the content, just without the proximity and persistence guarantees. It also preserves the established error/warning/info model without proliferating new types.

On balance, I'm 👍 on warning + disclosure flag.

@amithanda
Copy link
Contributor

I like the warnings+disclosure flag based approach better as well.

@igrigorik igrigorik marked this pull request as ready for review March 16, 2026 17:33
@igrigorik igrigorik requested review from a team as code owners March 16, 2026 17:33
@igrigorik igrigorik changed the title feat: add disclosure message type feat: extend warning to support disclosure contract Mar 16, 2026
@igrigorik
Copy link
Contributor Author

Looks like we're converging on extending warning. @amithanda @sinhanurag updated PR title & description to match.

  Maxime flagged that platform requirements say omitted path means "applies
  to the whole response" while business requirements say MUST use path —
  a contradiction.

  Three options:
  1. Require path via JSON Schema if/then when disclosure: true. Valid
     2020-12 but adds conditional complexity that many validators handle
     poorly in practice.
  2. Require path in prose, use root JSONPath ($) for response-wide
     disclosures. Elegant but introduces a convention nobody uses in
     the spec today — practically a novelty implementers need to learn.
  3. Keep path optional, downgrade business requirement to SHOULD, define
     fallback behavior explicitly. When path is omitted, disclosure applies
     to the response as a whole.

  Going with (3): no schema gymnastics, no novel conventions, and the
  platform requirement already defines the fallback behavior, which
  addresses the concern about divergent client rendering.
  1. Disclosure vs acknowledgment: add section clarifying that disclosure
     is a presentation mechanism. When buyer acknowledgment is also needed,
     business MAY combine with escalation mechanisms from the checkout
     status lifecycle.

  2. Escalation fallback: platforms that cannot honor the disclosure
     rendering contract MUST escalate to merchant UI via continue_url
     rather than silently downgrading to a regular warning.
@igrigorik igrigorik requested a review from maximenajim March 16, 2026 19:51
Removed "item-anchored" qualifier from disclosure descriptions in checkout.md
and catalog/index.md — the rendering contract in Platform Requirements already
specifies proximity to the path-referenced component.
  Pivots from `disclosure: true` (boolean) to `presentation: "disclosure"`
  (string) on warning messages, based on converging feedback from three
  independent reviewers (Chris Sauve, ACSchil, Alex/Maxime's team) who
  each independently suggested a more extensible field.

  Design options:
  1. Boolean `disclosure: true` — simple, fully validatable, but three
     reviewers flagged boolean proliferation risk. No migration path
     without adding a new field and deprecating the old one.

  2. `subtype: "disclosure"` (ACSchil) — extensible, but names the
     content category ("what") rather than the rendering behavior.
     Content category is already served by `code`.

  3. `presentation: "notice" | "disclosure"` (inspired by Chris Sauve's
     `presentation_policy` suggestion) — names the rendering behavior
     dimension ("how"), not the content category. Two concrete points
     on the axis: `notice` describes existing warning behavior, `disclosure`
     describes the new contract.

  Going with (3) because it correctly separates the two axes:
  - `code` = what the warning is about (allergens, prop65, etc.)
  - `presentation` = how the platform must render it (notice, disclosure)

  This also provides a natural home for future rendering contracts.
  For example, an `acknowledgment` value could fill the gap between
  display-only (disclosure) and full escalation (requires_buyer_review) —
  where the platform can natively collect buyer confirmation without
  handing off to merchant UI. That value isn't needed today, but the
  axis is ready for it without migration.

  Schema: `presentation` is open string defaulting to `"notice"`.
@igrigorik
Copy link
Contributor Author

@ACSchil @lemonmade having sat on it for some more, how about: e4be6c4 - h/t to Chris for suggestion. I think it works and fits the contract we're after well.

@amithanda amithanda merged commit ad4d8c3 into main Mar 17, 2026
7 checks passed
@amithanda amithanda deleted the feat/disclosure-message branch March 17, 2026 13:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

TC review Ready for TC review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add line_items[].disclosures[] for item-level regulatory disclosures / disclaimers (Prop 65, allergens, gift card scam warnings, etc.)

6 participants