fix!: discriminate destinations at the method level - #689
Merged
igrigorik merged 2 commits intoAug 6, 2026
Merged
Conversation
…ly in requests A fulfillment method's type selects the shape of its entire subtree, destinations included: a shipping method has shipping-address destinations, a pickup method has business-location destinations, and extension-defined method types define their own. Polymorphism is resolved at the parent, so request destinations need no per-object discriminator. - fulfillment_method branches per method type; the generic fulfillment_destination union is no longer referenced by schemas (kept for response documentation). - Destination type is required in responses, optional in requests. - destinations under pickup is response-only (ucp_request omit inside the pickup branch): under strict resolution the Platform cannot write business locations. selected_destination_id is the sole selection channel and accepts any Business-scoped Location ID the Business recognizes for the method, including IDs not yet enumerated (#589 handoff). - dependentRequired: a request that writes destinations[] must carry the method's type. - Removed explicit additionalProperties:true from fulfillment_method (behavior-neutral in open validation; lets strict sealing work). - Existing request wire shapes are unchanged; responses gain the required type field. Assisted-By: devx/296664b9-53b6-409a-989a-ace9d3348247
Fulfillment responses always self-describe with a required destination type, while Platform requests follow the enclosing method's authorship contract. Clarify that untyped destinations under the well-known shipping method default to Shipping Destination, pickup destinations are Business-authored and selected through selected_destination_id, and other method types define their own request shape and Platform writability. Remove the unsupported suggestion that an alternate destination type can be selected under core shipping. An ID-only saved or provider-held address remains a Shipping Destination; provider provenance or additional fields require a negotiated extension contract.
igrigorik
marked this pull request as ready for review
August 5, 2026 21:52
igrigorik
merged commit Aug 6, 2026
455ab32
into
feat/typed-fulfillment-destinations
4 of 5 checks passed
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Counter-proposal for the request side of #688; response-side typing is unchanged. Context: Slack thread.
Problem
#688 discriminates destinations per-object in both directions. Requiring
typeon request destinations invalidates every deployed Platform integration — requests that validate today fail the moment the requirement lands. Response-sidetypeis additive: producers add a field; consumers ignore unknown fields under the open-world model. The two directions carry incomparable migration costs.The per-object tag also leaves the agreed authority boundary unenforced.
{ "type": "business_location", "id": ... }remains a schema-valid Platform-written destination, so a written destination andselected_destination_idcan select different locations with no precedence rule — the conflict #688 sets out to remove.Solution
Move the discriminator up one level: a fulfillment method's
typeselects the shape of its entire subtree, destinations included. Ashippingmethod has Shipping Destinations; apickupmethod has Business Location Destinations; extension-defined method types define their own. Polymorphism resolves at the parent, so request destinations need no per-object tag, and the request wire format Platforms send today remains valid unchanged.Plain draft-2020
if/then+$ref— no direction- or context-specific request schemas.Design decisions:
ucp_request: "omit"inside the pickup branch removesdestinationsfrom the request projection; under strict resolution a Platform-written location is rejected as unevaluated.selected_destination_idis the sole selection channel and accepts any stable, Business-scoped Location ID the Business recognizes for the method, including IDs not yet enumerated indestinations[]— the feat!: Introduce Location Search + Lookup capabilities #589 handoff. One channel, one authority.dependentRequired: {"destinations": ["type"]}. Methodtypeis update-optional (target byid), so a request that writesdestinations[]must carry the methodtypefor branch dispatch. Updates touching onlyselected_destination_idorselected_option_idare unaffected.typeand remains self-describing.typestays optional in requests for reference disambiguation (e.g., anid-only destination naming one of several address sources).Before / After — strict request validation
destinations[]destinations[]withoutmethod.typedependentRequired)Resolver change
ucp-schema --strictseals item schemas referenced from base properties but does not descend intoif/then/else; branch item schemas are never sealed. One match arm inclose_additional_properties_inner(resolver.rs):Verified against a patched build: the full matrix above holds on unmodified resolver output; resolver unit tests pass (153/153). Diagnostic trade-off: a failed
thencontributes no annotations, so some rejections surface as a root-level unevaluated-destinationserror rather than an item-level message — verdicts unchanged, messages less precise.Trade-offs
schema_fieldsmacro does not collect branch-scoped properties, sodestinationsno longer renders in the Fulfillment Method field table (covered in the Destinations section). Either the macro learnsthen.properties, or destinations are documented per method type.fulfillment_destination.jsonis no longer schema-referenced; retained as the response-side documentation entity, or foldable into docs.