Skip to content

fix(checkout): allow cart-only checkout creation - #940

Merged
igrigorik merged 3 commits into
mainfrom
fix/cart-checkout-source
Oct 22, 2026
Merged

igrigorik merged 3 commits into
mainfrom
fix/cart-checkout-source

Conversation

@igrigorik

Copy link
Copy Markdown
Contributor

Allow a Platform to create a Checkout from a negotiated Cart without sending an ignored line_items: [] placeholder:

{"cart_id": "cart_abc123"}

PR #910 documented the workaround imposed by the base Checkout schema. This fixes the requirement at its source and makes the schema express the existing choice between inline items and Cart contents.

Changes

  • Declare cart_id as a create-only Checkout input.
  • Require line_items or cart_id using operation-scoped anyOf; accept both and preserve Cart precedence.
  • Keep legacy empty-array requests valid and reject requests with neither source.
  • Keep update, complete, and response item requirements unchanged.
  • Explicitly mark cart_id input-only, correcting unintended response exposure caused by a missing applicability annotation.
  • Update the Cart conversion example, simplify the companion prose, and add base/composed-schema regression coverage.

No new operation, transport wrapper, resolver vocabulary, or source framework. Existing REST/MCP schema references inherit the change. Cart negotiation and resource validation remain separate from request-shape validation.

Compatibility

The request relaxation preserves existing conforming request shapes, including Cart conversion with line_items: []. Platforms may omit the field only when using the updated contract; older receivers retain their old requirement.


Checklist

  • Capability: New schemas (Discovery, Cart, etc.) or extensions.
  • I have followed the Contributing Guide
  • I have updated the documentation (if applicable).
  • My changes pass all local linting and formatting checks.
  • I have added tests that prove my fix is effective or that my feature works.

@igrigorik
igrigorik requested a review from gsmith85 October 13, 2026 04:30
@igrigorik igrigorik self-assigned this Oct 13, 2026
@igrigorik igrigorik added the gov:tc-meeting Nominated for synchronous discussion at an upcoming weekly DTC meeting label Oct 13, 2026
@igrigorik igrigorik modified the milestone: 2026-12-08 Oct 13, 2026
   Cart conversion already supplies checkout items, but the base create
   schema requires callers to send an ignored line_items array.

   Require line_items or cart_id on Create Checkout using operation-scoped
   anyOf. Accept both sources and preserve existing Cart precedence and
   legacy empty-array requests. Keep update, complete, and response item
   requirements unchanged.

   Make cart_id an initialization input only, without echoing it in checkout
   responses. Update the companion prose and add regression coverage for
   base and Cart-composed schemas.

   The source alternatives remain core-defined; extensions cannot append
   another source through allOf. Introduce the new request form through a
   new negotiated release, leaving older contracts unchanged.
@igrigorik
igrigorik force-pushed the fix/cart-checkout-source branch from 8e7c4a2 to c04e875 Compare October 14, 2026 20:09
@gsmith85 gsmith85 added this to the 2026-12-08 milestone Oct 16, 2026
@damaz91 damaz91 added the area:shopping Issues and pull requests related to the Shopping vertical label Oct 16, 2026
@igrigorik
igrigorik requested a review from amithanda October 18, 2026 04:34
Comment thread source/schemas/shopping/checkout.json Resolved
Comment thread scripts/test_validate_examples.py Resolved
Cancel Checkout has no Checkout request body, so remove unsupported
cancel annotations and synthetic request-projection checks.

Omit cart_id from the Cart read projection and remove the test exception
that preserved its accidental visibility. Keep cancellation response
coverage unchanged.
Check authored keys against required-key anyOf alternatives before
scaffold data can mask missing inputs.

Restrict the empty-body shortcut to unscoped read/cancel requests.
Validate empty creates, responses and explicit selections normally,
while preserving elisions and neutral alternatives.

Add coverage and process_block regressions and update the authoring
guide to describe the validation boundary.
@igrigorik

Copy link
Copy Markdown
Contributor Author

@amithanda ty, landed updates -- ptal.

@igrigorik
igrigorik requested a review from amithanda October 20, 2026 15:19
@igrigorik
igrigorik merged commit 2bbae24 into main Oct 22, 2026
21 checks passed
@igrigorik
igrigorik deleted the fix/cart-checkout-source branch October 22, 2026 02:22
sakinaroufid pushed a commit to sakinaroufid/pr-test that referenced this pull request Oct 22, 2026
Resolves the conflict with Universal-Commerce-Protocol#940, which rewrote the same coverage walker
this branch rewrote.

scripts/validate_examples.py

  Universal-Commerce-Protocol#940 added a required-key `anyOf` check, so an authored Create
  Checkout example must name one item source before the scaffold merge
  can hide the omission. It built that on `_collect_required` and
  `_collect_properties`, the single-level `allOf` helpers this branch
  replaced with the recursive `_compose`. The check is ported onto
  `_compose` as `_required_alternatives`; the two old helpers stay gone.

  `has_object_shape` keeps both sides' terms: a node is an object if it
  declares a type, composes required fields or properties, exposes an
  open-map member schema, or offers a required-key alternative.

  The two features overlap on a variant union. Where branch selection
  pins a branch by its `const`/`enum` marker, that branch's requirements
  are merged by name, so the layer it came from is left out of the
  alternatives check. Without that, a credential object stating
  `type: pan` was told its missing `cvc` could instead be satisfied by
  `eci_value`, the token branch's field, which was never an option.

  The exclusion is keyed on the selected branch itself rather than on
  whether a layer looks marker-pinned. Branch selection reads a node's
  own `oneOf`/`anyOf` and does not descend into `allOf`, so a pinned
  `anyOf` nested under `allOf` is never selected from and keeps its
  required-key coverage; `service.json` composes `platform_schema` and
  `business_schema` that way. Skipping by appearance dropped the
  requirement there entirely.

  The real source alternatives on checkout carry no marker, so they are
  unaffected: the `buyer_only` cases still fail against both the
  resolved base and the Cart-composed schema.

scripts/test_validate_examples.py

  Both sides appended tests at the same point and interleaved. Kept
  whole from each: test_coverage_composition,
  test_coverage_variant_selection, test_coverage_respects_elision and
  test_elided_required_field_accepted from this branch;
  test_required_anyof_coverage, test_checkout_item_source_process_block
  and test_checkout_item_source from main. All 20 test functions are
  defined and called.

  Three assertions cover the overlap above: a pinned branch reports its
  requirement by name and not as a choice, and a pinned `anyOf` under
  `allOf` still reports and still clears. Each fails if the exclusion is
  dropped or widened to match by appearance.

  `process_empty_body_ok` is absent by Universal-Commerce-Protocol#940's intent, not by loss: it
  restricted the empty-body shortcut to unscoped read and cancel
  requests and replaced that assertion with
  process_empty_no_body_request_ok,
  process_empty_response_not_blindly_accepted and
  process_empty_explicit_*_not_shortcut, all of which are present.

Verified: 257 unit assertions pass, 0 fail. `ucp-schema lint source/`
checks 145 files, all pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WiZSNY1LGb9EUeWBNHsXAE
vishkaty pushed a commit to vishkaty/ucp that referenced this pull request Oct 27, 2026
Resolves the conflict in scripts/test_validate_examples.py introduced by
2bbae24 (Universal-Commerce-Protocol#940), which appended cases to the tail of
test_process_block_integration, the same place this branch appends its
three requires_escalation cases. Resolution keeps every case from main and
re-anchors the three cases from this branch after them. No line of either
side is changed. checkout.json merged without conflict.
@damaz91 damaz91 moved this to Closed / Merged in DTC Review Pipeline Oct 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:shopping Issues and pull requests related to the Shopping vertical gov:tc-meeting Nominated for synchronous discussion at an upcoming weekly DTC meeting

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants