Skip to content

feat!: buyer consent with per-segment extensibility#451

Merged
igrigorik merged 9 commits into
mainfrom
feat/consent_segments
Jun 12, 2026
Merged

feat!: buyer consent with per-segment extensibility#451
igrigorik merged 9 commits into
mainfrom
feat/consent_segments

Conversation

@igrigorik

@igrigorik igrigorik commented May 19, 2026

Copy link
Copy Markdown
Contributor

Evolves the buyer_consent extension to support per-purpose and per-segment consent capture under a uniform object shape. Each consent decision carries the current granted state, the source identifying who asserted it (business default vs. platform-captured buyer preference), and human-readable context — keyed by reverse-DNS identifiers at both purpose and segment levels. This generalizes the per-channel consent capture explored in #407 into a primitive that supports open extensibility for purposes and segments without further schema changes.

Businesses advertise the current state of every supported consent option in cart/checkout responses:

"buyer": {
  "consent": {
    "dev.ucp.consent.marketing": {
      "granted": false,
      "source": "business",
      "description": "Promotional communications across all channels",
      "links": [{ "type": "privacy_policy", "url": "https://example.com/privacy" }],
      "segments": {
        "dev.ucp.consent.marketing.email": {
          "granted": true,
          "source": "platform",
          "description": "Promotional emails and exclusive offers"
        },
        "dev.ucp.consent.marketing.sms": {
          "granted": false,
          "source": "business",
          "description": "Marketing text messages"
        }
      }
    },
    "dev.ucp.consent.analytics": {
      "granted": true,
      "source": "business",
      "description": "Site analytics and performance measurement"
    }
  }
}

This is a breaking change: the wire shape and field names differ from the prior allowed boolean. granted is a past-participle state-adjective ("consent is granted") that reads correctly in all four {state × source} combinations: business defaults and platform-captured buyer preferences alike. The source field carries causation; granted carries the state.

The same map shape carries both directions:

Direction Who populates Fields populated
Advertise (response) Business granted, source, description; optional links and segments
Confirm (request) Platform granted, source; segments when present

Platforms submit the current state of every advertised purpose and segment back to the business:

"buyer": {
  "consent": {
    "dev.ucp.consent.marketing": {
      "granted": true,
      "source": "platform",
      "segments": {
        "dev.ucp.consent.marketing.email": { "granted": true, "source": "platform" },
        "dev.ucp.consent.marketing.sms":   { "granted": true, "source": "platform" }
      }
    },
    "dev.ucp.consent.analytics": { "granted": true, "source": "business" }
  }
}

Per-purpose/segment data requirements (e.g., SMS marketing needs buyer.phone_number) are business-specific and not enforced by UCP. Missing dependencies surface via the standard message pattern with asymmetric severity: warning on cart/checkout create/update, error on complete_checkout (the business MUST NOT transition to completed while consent data dependencies are unmet).

{
  "messages": [
    {
      "type": "warning",
      "code": "missing_consent_data",
      "content": "Phone number is required for SMS marketing.",
      "path": "$.buyer.phone_number"
    }
  ]
}

Checklist

  • Capability: New schemas (Discovery, Cart, etc.) or extensions.
  • I have followed the Contributing Guide (including Conventional Commits title requirements and ! for breaking changes).
  • I have updated the documentation (if applicable).
  • My changes pass all local linting and formatting checks.

@igrigorik igrigorik added the TC review Ready for TC review label May 19, 2026

@jamesandersen jamesandersen left a comment

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.

Another worthy alternate design alongside that from @amithanda on #407 ... this one going after all forms of consent. I can get behind this one also if we want to tackle the broader scope of consent (not just marketing) ... though honestly I'd recommend just dropping our pre-existing consent categories in the process if we go this route. @amithanda , @vixdug thoughts?

Comment thread source/schemas/shopping/buyer_consent.json Outdated
"type": "object",
"description": "A single consent segment within a category. Businesses populate `description`, `links`, and current `allowed` state when advertising. Platforms populate `allowed` with the captured decision when confirming.",
"required": ["description", "allowed"],
"properties": {

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.

If we were to add a dev.ucp.consent.mobile_app_push the business would need to collect some kind os specific push token that wouldn't be drawn from the existing cart/checkout request body. Should we consider some flexible "metadata" dict here e.g. businesses can look for additional platform derived data where applicable in this dict and report via messages if a needed token is not found / invalid.

I'm just pressure testing a bit here; this could also wait until the need is more clear

Comment thread source/schemas/shopping/buyer_consent.json Outdated
Comment thread source/schemas/shopping/buyer_consent.json Outdated
Comment thread docs/specification/buyer-consent.md Outdated
@amithanda

Copy link
Copy Markdown
Contributor

Hi @igrigorik, @jamesandersen

Thanks for putting this together. Evolving buyer_consent to support granular, per-segment consent is a very clean way to address regulatory requirements without locking the schema into a fixed set of channels.

I’d like to highlight 3 core architectural topics for us to discuss and align on, building upon some of @jamesandersen's points:

1. Polymorphism (Boolean vs. Segment Map)

As @jamesandersen pointed out, since this is already a breaking change, we should evaluate if keeping the hybrid boolean | object modeling is the right path.

  • Implications: Allowing a property to accept either a primitive boolean or a complex object introduces type polymorphism. This complicates runtime parsing and often generates messy/ambiguous union types in standard client-side model-generation tools.
  • Discussion Point: If we want to support granular consent, should we completely deprecate the raw boolean values and standardise on a single object-based configuration? Furthermore, should we dispense with the intermediate strict category hierarchy (marketing, analytics, etc.) and instead let businesses express a flat map/array of consent segments directly under buyer.consent?

2. Consent Flow and Lifecycle (update vs. complete & PII)

I want to echo @jamesandersen’s recommendation to allow the buyer object on checkout complete (making "complete": "optional").

  • The Latency Problem: Surfacing shipping, contact, and consent forms on the final screen is a standard checkout layout. If the buyer object is omitted on complete, the platform is forced to run a blocking update_checkout API call to save consent states right before triggering the final payment. This blocking round-trip at the conversion point adds unnecessary latency.
  • The PII Disclosure Boundary: For privacy-centric platforms (e.g., assistants/wallets that withhold buyer contact PII until the user actively confirms the order), submitting buyer.phone_number or buyer.email is reserved for the final complete call. If consent dependencies (like SMS requiring a phone number) are validated during update, these privacy flows are broken. Allowing buyer and consent states to be submitted atomically on complete resolves both latency and PII boundary conflicts.

3. Default Consent Values & Jurisdictional Gaps

Building on @jamesandersen’s note that "some jurisdictions should be modeled as opt-in, some as opt-out and it's the business' responsibility to own that decision":

  • The Compliance Gap: If a US-based merchant defaults a checkbox to checked ("allowed": true), but the platform renders the checkout for a user in the EU (where pre-checked marketing boxes are illegal under GDPR), the platform must override the merchant default and show the box unchecked.
  • Lack of Intent Signaling: The current schema only communicates the final allowed state. The merchant has no way of knowing if a false value represents a user actively opting out (unchecking a box) vs. a platform default override (GDPR opt-in rendering where the user just ignored the box).
  • Discussion Point: Should we:
    1. Add explicit metadata to the segment payload (e.g., consent_model: "opt_in" | "opt_out") so the platform knows how to legally render the defaults?
    2. Introduce a signal like explicit_choice: boolean or user_interacted: boolean on confirm requests to help merchants audit and verify active user consent decisions?

@igrigorik

Copy link
Copy Markdown
Contributor Author

@jamesandersen, @amithanda, @wsbrunson ty for the feedback!

Updated to allow consent on complete (dfc5619).

The big design question is on nesting. I like the idea of a flat namespace and spent good chunk of time trying to iterate through how that would look. A key outcome and realization from running this exercise...

Consent > Purpose > Segment is load-bearing structure.

A consent decision is about a purpose (marketing, analytics, etc) that may be qualified by a channel / segment / vendor / program. One way or another, this relationship needs to be captured, and the question is by whom, as that determines where the grouping lives. Let's step through a few examples.

Flat shape forces agent to own grouping

"consent": {
  "dev.ucp.consent.marketing.email": { ... },
  "dev.ucp.consent.marketing.sms":   { ... },
  "com.chatapp.marketing":           { ... },
  "dev.ucp.consent.analytics":       { ... },
  "com.merchant.purpose_or_channel": { ... }
}

Above yields a simple flat structure but, notably, it defers any semantics over purpose to the description of each object and there is no easy or logical grouping unless agent leans on substring matches (not guaranteed to work reliably) or spends LLM tokens to group and present a coherent choice matrix: as a user I may want to opt out from all marketing, or only consent to a particular segment. This places the burden on the agent to present and get the choice matrix right, which is an antipattern that lands everyone (agent, business, user) in non-deterministic (bad) outcome.

Channels / segments at top level are footguns

As an example, dev.ucp.consent.email at top level carries a footgun. If buyer says no, does that mean that order confirmation and update emails are blocked? With segmen keys at the top, description becomes load-bearing to capture purpose. This is fragile and adds another complication on top of above concerns.

(Recommended) nested shape: Purpose > (optional) Segments

We can normatively spec that businesses must provide purpose > segment structure...

"dev.ucp.consent.marketing": {
  "allowed": false,
  "description": "Promotional communication...",
  "links": [...],
  "segments": {
    "dev.ucp.consent.marketing.email": { "allowed": true, "description": "...", links: [] },
    "dev.ucp.consent.marketing.sms":   { "allowed": false, "description": "...", links: [] },
    "com.chatapp.channel.marketing":  { "allowed": false, "description": "...", links: [] }
  }
}

The agent walks the tree. UI structure IS protocol structure: render purpose description, nest segments under with appropriate state and toggles. Channels / vendors nest naturally -- ChatApp owns the segment identifier, merchant does the work of logically grouping under a purpose key, which itself carries a description and links. No guessing for the agent required, and this mirrors structure that agents already model in their other channels and UIs. Further, the choice of how it's presented and group is material.

In effect, protocol carries the grouping the user-facing UI needs, so the agent doesn't reconstruct it from heuristics.


This structure yields roughly...

"buyer": {
  "consent": {
    "dev.ucp.consent.marketing": {
      "allowed": false,
      "description": "Promotional communications across all channels",
      "links": [...],
      "segments": {
        "dev.ucp.consent.marketing.email": { "allowed": true,  "description": "Promotional emails and offers" },
        "dev.ucp.consent.marketing.sms":   { "allowed": false, "description": "Marketing text messages" },
        "com.chatapp.channel.marketing":  { "allowed": false, "description": "Marketing via ChatApp" }
      }
    },
    "dev.ucp.consent.analytics": {
      "allowed": true,
      "description": "Site analytics and performance measurement",
      "segments": {
        "com.google.analytics": { "allowed": false, "description": "Google Analytics tracking" }
      }
    },
    "dev.ucp.consent.preferences":     { "allowed": true,  "description": "Remember preferences and personalize experience" },
    "dev.ucp.consent.sale_or_sharing": { "allowed": false, "description": "Sale or sharing of personal data with third parties" }
  }
}

UCP-curated "well-known" purposes:

  • dev.ucp.consent.marketing
  • dev.ucp.consent.analytics
  • dev.ucp.consent.preferences
  • dev.ucp.consent.sale_or_sharing (renamed from sale_of_data)

Businesses are free to define own purpose buckets using rDNS convention, under which they slot optional segments. The top level contract of buyer.consent > purpose.description > [segment.description] allows agent to faithfully render the choice matrix without any guessing or heuristics.

@jamesandersen's to your question of "are these 4 right?". AFAIK, unfortunately there is no universally agreed enumeration we can lean on. But this is an extensible list, so our job is not to enumerate exhaustive list but to spec a well-known subset, similar to how we do in many places across the spec with well-known codes, etc. Absence of an agreed standard/enumeration should not stop us from solving the common 80%.

Thoughts, reactions?

@amithanda

Copy link
Copy Markdown
Contributor

Hi @igrigorik,

This nested Consent > Purpose > Segment design is a huge improvement! It successfully eliminates the type polymorphism from the previous draft (replacing the boolean | object unions with uniform objects) and provides a clear, structural roadmap for platforms to render the checkbox groupings.
I have two suggestions to build upon this nested shape that we can discuss:

1. Enforcing Ordered Arrays for Consent Segments (Compliance)

Should we design this as an array of objects for segments instead of a map.

  • Why ordering is legally critical: Under GDPR, CCPA/CPRA, and TCPA, the
    Business (merchant) carries 100% of the data controller liability. Choice
    architecture is legally scrutinized. For example, California CPRA mandates
    "equal prominence" for opt-outs, and TCPA requires SMS consent to be
    "clear and conspicuous" and positioned adjacent to its disclosure text.
    Shuffling consent options (which is common when parsing unordered JSON maps)
    can create non-compliant layouts, immediately exposing merchants to severe
    regulatory penalties and litigation.
    *. Support for Nested Hierarchy: Inline with your argument around avoiding platforms
    to guess layout groupings, ordering etc via heuristics. Using a map within
    the nested segments still fails to guarantee key ordering. Standardizing
    on a nested array structure resolves both: the parent purpose defines
    the logical grouping, and the child array enforces the rendering order.
  • Consistency: While maps are preferred for programmatic registries, UCP
    consistently uses arrays for display-critical transactional sequences (such
    as totals and line_items). Using an array for consent segments aligns
    with this existing protocol convention.

2. Hierarchical Opt-In / Opt-Out Metadata (Jurisdictional Compliance)

Different channels are governed by different regulations. For example, in the
US, email marketing is legally opt-out (under CAN-SPAM), while SMS
marketing is strictly opt-in (under TCPA, where pre-checked SMS boxes
are illegal).

  • The Solution: We should support the consent_model attribute at both the
    Purpose and Segment levels, using an inheritance model. The Purpose
    defines a fallback default, and nested segments can explicitly override it
    to ensure precise, legally compliant rendering on the platform.

Suggested Payload Shapes

Business Advertise (Response)

"buyer": {
  "consent": {
    "dev.ucp.consent.marketing": {
      "allowed": true, 
      "description": "Promotional communications across all channels",
      "links": [{"type": "privacy_policy", "url": "https://example.com/privacy"}],
      "consent_model": "opt_out", // Fallback default for marketing segments
      
      // Changed from a map to an array of objects to guarantee UI rendering order
      "segments": [
        {
          "id": "dev.ucp.consent.marketing.email",
          "allowed": true,
          "description": "Promotional emails and offers",
          "consent_model": "opt_out" // Inherited from parent; platform can pre-check
        },
        {
          "id": "dev.ucp.consent.marketing.sms",
          "allowed": false,
          "description": "Marketing text messages",
          "links": [{"type": "tcpa_disclosure", "url": "https://example.com/sms-terms"}],
          "consent_model": "opt_in" // Overrides parent; platform must render unchecked
        }
      ]
    },
    "dev.ucp.consent.sale_or_sharing": {
      "allowed": true,
      "description": "Do not sell or share my personal information",
      "consent_model": "opt_out" // No segments; parent-level model is authoritative
    }
  }
}

Platform Confirm (Request)

When sending decisions back, the platform omits descriptive metadata
(description, links, and consent_model are marked as ucp_request: "omit"
in the schema) and only returns the captured choices:

"buyer": {
  "consent": {
    "dev.ucp.consent.marketing": {
      "allowed": true, // User selected general marketing
      "segments": [
        {
          "id": "dev.ucp.consent.marketing.email",
          "allowed": true // User opted in to email
        },
        {
          "id": "dev.ucp.consent.marketing.sms",
          "allowed": false // User opted out of SMS
        }
      ]
    }
  }
}

3. Parent-Child Inheritance & Override Semantics (Prose Clarification)

We should normatively specify how parent and child values interact in the spec
prose to prevent logical conflicts:

  • Consent State (allowed):
    • Parent-level false (Master Block): Overrides all nested segments. A
      general opt-out blocks all channels, regardless of individual segment
      states.
    • Parent-level true (Fallback): Acts as general consent for the
      purpose; individual segment-level toggles (e.g., sms: false) act as
      explicit overrides for those specific channels.
  • Consent Type (consent_model):
    • Parent-level fallback: Defines the default consent type (opt_in /
      opt_out) for the category.
    • Segment-level override: Individual segments can explicitly define
      their own consent_model, overriding the parent fallback for that
      channel.

Let's discuss and take a call.

@jamesandersen

Copy link
Copy Markdown
Contributor

@igrigorik / @amithanda I feel like this is close to the finish line and appreciate support from both of you!

Here's my quick view:

  • updated the schema to make buyer optional on create, update, and complete
  • rationale for keeping the "purpose" layer of the structure - pressure-tested and sound reasoning for keeping it ... now with room to extend more easily as needed
  • Boolean / object polymorphism - It looks like the samples from both of the recent comments have modeled allowed as a property of the purpose level which feels like it accomplishes the "blanket" consent for purpose without the need for actual polymorphism complexity
  • regarding the hypothetical case of a platform needing to provide some additional data to accompany the consent e.g. a mobile app push token ... consent_segment is an object we could extend later if this is deemed important but I'm content ignoring this for now

Outstanding:

  • Best representation of Business intent - I second @amithanda proposed tweak from map to array to allow businesses to more clearly indicate the consent ordering as there could be some regulatory implications; both of you have highlighted the importance of letting the business express more clearly the intended UX display. However, I also think we need to be careful with this ... a business ordering and grouping of segments alone doesn't imply or complete the compliance the business from its responsibilities. In fact the split nature of requesting (business) and rendering (platform) these consents makes reasoning about compliance even more challenging IMO. Perhaps something like the following in the docs would be appropriate:

UCP carries the business's consent configuration to the platform. Ordering of segments, descriptions, and links reflect the business's expressed intent and SHOULD be preserved by the platform in rendering. Compliance with applicable privacy and consent regulations (GDPR, TCPA, CCPA/CPRA, etc.) is the responsibility of the implementing business and platform, not of the protocol. Businesses are responsible for the consent options they advertise, including applicable default states, disclosures, and segment ordering. Platforms are responsible for faithfully presenting those options to buyers.

  • Consent Model - @igrigorik I didn't see this in your latest comment but also think this is important to include (as in @amithanda 's last comment) for a business to control the consent default experience which can vary by jurisdiction

Replace the previous category/boolean consent shape with a nested
Consent > Purpose > Segment model keyed by reverse-DNS identifiers.

- make consent purpose and segment values uniform objects with `allowed`
- advertise response-only `description` and `links`
- allow buyer consent on checkout complete
- define well-known consent purposes and marketing channel segments
- document advertise/confirm flow, data dependencies, and segment override rules
- update core concepts and playground capability metadata
@igrigorik igrigorik force-pushed the feat/consent_segments branch from 7400301 to a7145c7 Compare May 30, 2026 15:10
@igrigorik igrigorik marked this pull request as ready for review May 30, 2026 15:27
@igrigorik igrigorik requested review from a team as code owners May 30, 2026 15:27
@igrigorik

Copy link
Copy Markdown
Contributor Author

@amithanda @jamesandersen updated the draft to capture the shape we reviewed above. PTAL.

@wsbrunson

Copy link
Copy Markdown

This looks great and would still solve the problems we had when we opened this proposal: #407

At the time we weren't sure how we could extend the marketing consent channels options to the other types of consents like analytics. What you have here works really well and would hopefully be the last breaking change to buyer.consent. It should be simple for Platforms and Businesses to add new types of consents and new options for collecting that consent.

The one thing I'm unsure of: Does it make sense for Businesses to send an allowed value? I could see an issue where there was confusion from the Business on what value to send and they accidentally opt-in a buyer. If we had two fields, one for a Buyer's selection and one for a Business's preference, there would be no confusion. Consent would always be flowing from Buyer -> Platform -> Business.

This would add more complexity to something that may or may not be an issue. Whatever we decide here, I'm good with this proposal 👍.

@jamesandersen

jamesandersen commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

@igrigorik I'm feeling really good about where this is headed. I just left a few smaller comments.

The one slightly challenging area left (as per our discussion and @wsbrunson highlighting again) is how the platform is expected to handle allowed (in the absence of the consent model proposal from @amithanda above). I'm trying to get comfortable with the approach you've modeled ... but just want to be super clear about this so bear with the following consideration of this example:

"dev.ucp.consent.marketing": {
  "allowed": false,
  "description": "Promotional communication...",
  "links": [...],
  "segments": {
    "dev.ucp.consent.marketing.email": { "allowed": true, "description": "...", links: [] },
    "dev.ucp.consent.marketing.sms":   { "allowed": false, "description": "...", links: [] }
  }
}

Just for extreme clarity - is this how you expect a platform to interpret this in light of the drafted normative language requiring (i.e. MUST) the platform to display any segments the business provides:

  1. Email segment appears with allowed: true (why would a business request consent it already feels it has?)

    • By including the segment at all (rather than omitting it), the business is indicating it does want to present an explicit consent choice to the user
    • By setting allowed: true the business is indicating it already feels it has this consent (e.g. default opt-out regulatory jurisdiction)
    • It would be appropriate for the platform to present this as "opt-out" (e.g. checked checkbox) to the user
  2. Sms consent is allowed: false

  • By inclusion of the segment, business is seeking explicit user consent for this segment
  • By setting allowed: false the business is indicating it doesn't have this consent (e.g. default opt-in regulatory jurisdiction)
  • It would be appropriate for the platform to present this as "opt-in" (e.g. unchecked checkbox) to the user

For both cases, unless for some reason the business needs to re-validate consent, it would omit these segments on subsequent cart/checkout transactions so the platform doesn't continually re-prompt for the same consent. This could be as a result of the business...

  • Receiving affirmative consent from the user
  • Prompting X time in Y days and not receiving buyer consent ... thus deciding to backoff on this consent prompt for a period of time ... business side logic (not platform)

Comment thread docs/specification/buyer-consent.md Outdated
Comment thread source/schemas/shopping/buyer_consent.json
igrigorik added 2 commits June 4, 2026 17:01
   Replace single `allowed` boolean with `(checked, source)` pair to
   distinguish business-defaults from platform-captured buyer decisions.
   Resolves reviewer ambiguity about how to interpret an advertised value
   without baking jurisdiction or regulatory framing into the protocol.

   - Rename `allowed` → `checked`, mirroring the HTML input element. The
     field is intentionally neutral: the value carries only binary state
     and does not itself suggest a treatment, allowing the same shape to
     model all forms of consent contracts
   - Add `source: "business" | "platform"` to purpose and segment,
     required in both directions
   - Remove opt-in/opt-out and jurisdiction-specific references from
     normative requirements; policy reasoning stays the business's
     responsibility
   - Replace error-message ambiguity on `complete_checkout` with an
     explicit state-machine rule (MUST NOT transition to completed with
     unmet consent dependencies); defer the mechanism to standard error
     handling
   - Establish full-replace confirm semantics consistent with the checkout
     resource-replace pattern: `consent` field is optional, but when
     submitted MUST include every advertised key with both `checked` and
     `source`
   - Add normatives for source attribution (requires buyer-stated
     preference) and per-business scoping (decisions don't transfer across
     businesses)
@igrigorik

Copy link
Copy Markdown
Contributor Author

@jamesandersen @wsbrunson ty both. PTAL at the updated shape: e51a4fd. Taking a different approach and I believe it addresses your feedback cleanly.

Comment thread docs/specification/buyer-consent.md
Comment thread docs/specification/buyer-consent.md
Comment thread docs/specification/buyer-consent.md Outdated
   - Reframe clause 1: business advertises the complete set; platform
     decides which choices to present and when; the MUST applies to
     fidelity when presenting, not to presenting every advertised choice
   - Drop `source` from clause 1's render list — it informs platform
     decisions but is not user-facing content
   - Scope the "opaque handles, MUST NOT infer semantics" guard to
     identifiers outside the `dev.ucp.consent.*` namespace; UCP-defined
     identifiers carry this spec's semantics so platforms can map them
     to known buyer preferences
   - Tighten source semantics: platforms MAY suppress re-presentation of
     `source: "platform"` choices, removing the implication that the
     full advertised set must be rendered every transaction

@jamesandersen jamesandersen left a comment

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.

nit only: "checked" seems a bit anchored in a particular UX representation ... would "consented" (or something similar?) work?

Comment thread docs/specification/buyer-consent.md
@amithanda

Copy link
Copy Markdown
Contributor

nit only: "checked" seems a bit anchored in a particular UX representation ... would "consented" (or something similar?) work?

I think it is a good point, checked is heavily anchored in visual UI representations and may be semantically awkward for future non UX based scenarios e.g voice/chat yes or no etc.
Maybe rename it to consented as @jamesandersen suggested or granted - here are some reasons:

  • Modality Neutral: "Granted" fits both visual checkboxes and verbal agent interactions ("Consent is granted").
  • Legally Consistent: Consent is either "granted" (true) or "denied/withdrawn" (false).
  • Resolves Opt-Out Mapping Confusion: For some tricky consent types e.g. dev.ucp.consent.sale_or_sharing (Sale or sharing of data), granted: false means consent to share data is not granted. This maps directly to the semantic state of the purpose, avoiding the logical inversion confusion that arises when trying to map a visual "Do Not Sell" checkbox state to a field named checked.

Comment thread source/schemas/shopping/buyer_consent.json Outdated
Comment thread docs/specification/buyer-consent.md
igrigorik added 4 commits June 9, 2026 23:25
   Adds a teaching example before the comprehensive advertise example,
   showing the two key composition patterns side by side: purpose-level
   capture (analytics) and parent-with-segments override (marketing
   parent off, marketing.sms on per the buyer's consent).

   Per @jamesandersen review feedback on PR #451.
   - Drop `additionalProperties: false` from `consent_purpose` and
     `consent_segment` per the Schema Authoring Guide's open-objects
     convention; keyed extensibility already lives on the parent maps
   - Source semantics: `source: "platform"` MAY persist across
     subsequent transactions with the same business
   - Add clause 7 (Buyer review and revocation): platforms MUST provide
     a way for buyers to audit and revoke or change prior consent
     decisions

   Addresses PR #451 review feedback from @amithanda.
   Field-name choice is grammatical:
   - `granted` is the past participle of a transitive verb and functions
     natively as a state-adjective ("permission is granted", "consent is
     granted") — exactly the function a boolean state field on a consent
     record needs.
   - `consented` is past-tense of an intransitive verb and reads as
     event ("the buyer consented"), which creates a semantic
     contradiction in `source: "business"` cells where no buyer action
     occurred.

   `granted` reads correctly across all four {state × source} cells.
   The `source` field carries causation; `granted` carries the state.
@igrigorik

Copy link
Copy Markdown
Contributor Author

@amithanda @jamesandersen updated, ptal! 🤞🏻

Comment thread docs/specification/buyer-consent.md Outdated
   Clause 7 previously stated a free-floating `MUST provide a way for buyers
   to audit and revoke prior consent decisions`, with the related `MAY
   persist` and `MAY suppress re-presentation` discretions sitting in the
   explanatory "Advertise and confirm" section. The two halves were not
   visibly coupled, so the MUST read as a standalone-UI mandate.

   Restructured as a conditional in one place:

   - Platforms MAY persist prior buyer preferences and MAY suppress
     re-presentation of unchanged values.
   - Where preferences are persisted, platforms SHOULD give the buyer
     the ability to change them.
@amithanda

Copy link
Copy Markdown
Contributor

Nice work on this, the extension reads much cleaner than the prior iterations. A few things I think turned out really well:

  • Unifying on a single {granted, source, description, links, segments} object keyed by reverse-DNS, and dropping the old boolean | object polymorphism, is the right call. It makes purposes and segments open-extensible without future schema changes, and it resolves the polymorphism concern raised earlier in the thread.
  • source separating authorship from state is a clean model
  • Folding persistence and reversibility and the additionalProperties cleanup both look good.

One side effect worth recording (which @jamesandersen's comment touched upon), not a blocker: because the complete: "optional" annotation sits on the whole buyer ref, it also reopens first_name, last_name, email, and phone_number for changes & submission at complete, where base checkout omits the buyer object entirely.

Why I think this is fine: those fields are already submittable on create and update, so this opens no new field and no new data sink. The only practical change is that a client can now also send or overwrite buyer PII on the complete call. That seems acceptable.

Approving. Thanks for the careful iteration on this one.

@igrigorik igrigorik added this to the Working Draft milestone Jun 12, 2026
@igrigorik igrigorik merged commit bc4414f into main Jun 12, 2026
15 checks passed
@igrigorik igrigorik deleted the feat/consent_segments branch June 12, 2026 14:30
@github-actions github-actions Bot added the enhancement New feature or request label Jun 12, 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.

5 participants