Skip to content

fix: replace Location inventory filtering with item availability - #766

Merged
jingyli merged 2 commits into
feat/locationfrom
fix/location-availability-filter
Aug 25, 2026
Merged

fix: replace Location inventory filtering with item availability#766
jingyli merged 2 commits into
feat/locationfrom
fix/location-availability-filter

Conversation

@igrigorik

@igrigorik igrigorik commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #589 and #765.

Replaces Location's inventory- and status-shaped filters.inventory objects with one cross-vertical discovery predicate:

{
  "filters": {
    "items": ["item_1", "item_2"]
  }
}

A candidate Location matches only when the Business can currently provide every referenced item at that Location. The same filter applies to Location Search and Lookup. Search discovers candidate Locations; Lookup resolves requested Location identifiers and then refines the resolved set using the same availability semantics.

How/why per-item status exposed the underlying problem

The previous request shape paired each opaque item identifier with an optional open status string:

{
  "filters": {
    "inventory": [
      {
        "id": "item_1",
        "availability_status": "in_stock"
      }
    ]
  }
}

Trying to make that status interoperable exposed several different concepts hiding behind one field:

  • in_stock describes a retail inventory implementation;
  • preorder and backorder describe order acceptance and timing policies rather than the same kind of stock state;
  • fulfillment timing answers when or how an accepted item can be fulfilled, not whether a Location can currently provide it;
  • unfamiliar status strings need explicit consumer behavior, not merely an open JSON Schema string.

The old filter could establish aggregate predicate truth: a returned Location implied that every requested item/status predicate matched. But the Location-shaped response could not report an authoritative current status for each item or identify which item caused an omitted Location to fail.

Why availability, not inventory

Availability is an existing and generic cross-vertical concept because it standardizes only that question—whether the Business can currently provide the referenced item at the Location—and leaves the computation to the Business. The identifiers remain opaque and Business-scoped, so the applicable item-owning domain determines whether a reference denotes a product variant, dish, appointment-based service, ticket, or another orderable item; Location does not standardize the item's internal model -- inventory is a more loaded term, by contrast.

Design

filters.items is a nonempty array of distinct, nonempty, opaque, Business-scoped item identifiers:

{
  "distance": {
    "center": {
      "latitude": 40.707,
      "longitude": -74.011
    },
    "max": 10000
  },
  "filters": {
    "items": [
      "item_id_phone_15_pro",
      "item_id_phone_15_pro_case_black"
    ]
  }
}

For each candidate Location:

within distance.max of distance.center
AND can currently provide item_id_phone_15_pro
AND can currently provide item_id_phone_15_pro_case_black

The contract is deliberately small:

  • all item identifiers combine with AND;
  • availability is evaluated from the Business's current data while processing the request;
  • unknown, unavailable, and non-evaluable items make the candidate Location fail the filter;
  • the Business must not ignore an item identifier and return broadened results;
  • ordinary availability non-matches return an empty or refined Location result, not a request error;
  • the Location response remains Location-shaped and does not echo per-item status or diagnostics;
  • returned results are provisional and must be revalidated as the commerce flow acquires configuration, quantity, method, and transaction context.

Why this shape

The alternatives each introduced the wrong semantics or the wrong level of abstraction:

  • filters.inventory exposes an implementation concept rather than the cross-vertical commerce fact.
  • availability_status invites a shared status vocabulary that mixes stock, purchase policy, lifecycle, timing, and presentation without a corresponding item-result surface.
  • filters.available states the predicate but is broad enough to read as applying to another Location fact, including whether an amenity is available.
  • filters.availability: [id] names a data domain but gives it only a bare identifier array.
  • a root availability: {"items": [...]} wrapper reserves a namespace whose likely future siblings—quantity, method, status, and time—belong to other capabilities and have no distinct Location processing or response contract.
  • OR or configurable match modes would return a Location without revealing which items matched, recreating the missing item-result problem. The base filter therefore guarantees that every supplied identifier matches.

filters.items is intentionally operand-centric: the field supplies the item identifiers, while its normative schema and prose definition supply the current-availability predicate. This trades some local readability for a key that cannot be mistaken for availability of amenities or other Location facts. It does not weaken or broaden the availability contract.

The array shape is capability-local. Location binds candidate Locations, so the request supplies the item identifiers whose availability must hold at each candidate. An item-oriented capability binds candidate items and can express availability as a Boolean constraint over those candidates. This PR does not introduce or constrain a Catalog filter shape.


Capability boundaries contract

The filter establishes a staged discovery contract rather than transaction truth:

Capability Responsibility
Location Find or refine places where the Business can currently provide every referenced item.
Catalog Identify and describe the items. With Fulfillment active and a shortlisted Location supplied in context.location, evaluate item-level fulfillment methods for that Location.
Cart Add the Buyer's concrete item configurations and quantities.
Checkout + Fulfillment Revalidate the actual basket, including grouping and splits, destination, method, windows and options, prices, eligibility, and terms.

These boundaries have concrete consequences:

  • Operating hours are independent. A closed Location may still report that it can currently provide an item. A Platform that needs both facts supplies both filters.items and filters.hours; hours.open_at does not change the availability evaluation instant.
  • Fulfillment methods are independent. Item availability at a Location does not establish pickup, delivery, dine-in, or another mode.
  • Amenities do not create item-method correlation. Combining filters.items with a pickup amenity means that the items are available at the Location and that the Location generally supports pickup. It does not prove that those items are available through pickup.
  • Quantity is cart/checkout responsibility. Location does not evaluate a Platform-selected quantity or assert that all requested items and quantities can be transacted together.
  • Selection is out of scope. A matching Location is not a reservation, destination selection, fulfillment-method selection, or guarantee of checkout success.

End-to-end examples

Retail store finder

A Platform starts with two Catalog variant identifiers for a phone and case.

  1. It calls Location Search with distance and filters.items: ["phone_variant", "case_variant"] to shortlist nearby stores that can currently provide both items.
  2. If the Buyer needs a store that is open on arrival, it separately adds filters.hours.open_at. If it adds a pickup amenity, that proves only that the store generally supports pickup.
  3. For a shortlisted Location, Catalog with Fulfillment active and that Location in context.location identifies the variants and evaluates their item-level fulfillment methods there.
  4. Cart captures the selected configurations and quantities.
  5. Checkout with Fulfillment revalidates whether the complete basket can be grouped or split, whether pickup is actually available for those lines at the selected Location, the applicable window or option, and the final price and terms.

A Location Search match keeps the first step fast and cross-vertical without pretending to answer steps three through five.

Restaurant branch discovery

A Platform has opaque menu-item identifiers for a dish and side.

  1. It calls Location Search with filters.items: ["dish_id", "side_id"] and either distance or serves to find branches that can currently provide both offerings.
  2. The Business may compute that answer from menu state, ingredients, equipment, and kitchen capacity; none of those implementation details become protocol vocabulary.
  3. Catalog identifies and describes the dish, side, and available option choices. Cart carries the Buyer's selected size, substitutions, add-ons, and quantities.
  4. Checkout with Fulfillment determines whether the configured basket can actually be accepted together and whether pickup, delivery, or another applicable method is available on the final terms.

The same Location predicate therefore works for a store stocking products and a kitchen preparing dishes while preserving domain-specific item and transaction logic downstream.

@igrigorik igrigorik added this to the 2026-08-24 milestone Aug 24, 2026
@igrigorik igrigorik self-assigned this Aug 24, 2026
@damaz91 damaz91 added the status:needs-triage Signal that the PR is ready for human triage label Aug 24, 2026
Base automatically changed from fix/location-review-followup to feat/location August 24, 2026 20:55
The inventory filter mixed stock, order-acceptance, lifecycle, and timing
semantics in an open per-item status vocabulary, while Location returns only
aggregate Location matches.

Replace it with `filters.items`, a nonempty array of distinct,
Business-scoped item identifiers shared by Search and Lookup. A Location
matches only when every item is available there under the Business's current
data; unknown, unavailable, or non-evaluable items are ordinary non-matches.

Keep hours, quantity, and fulfillment methods outside this predicate. Catalog
with Fulfillment describes item-level methods for a shortlisted Location, Cart
carries configuration and quantity, and Checkout with Fulfillment revalidates
basket feasibility and final terms.
@igrigorik
igrigorik force-pushed the fix/location-availability-filter branch from a5c78dd to 7440bbd Compare August 24, 2026 21:35
| :--- | :--- |
| [`dev.ucp.common.location.search`](search.md) | Search for locations using free-text queries, explicit spatial relations (`distance`, `serves`), and filters (`hours`, `amenities`, and `inventory`). |
| [`dev.ucp.common.location.lookup`](lookup.md) | Retrieve full details for one or more locations by identifier. |
| [`dev.ucp.common.location.search`](search.md) | Search for Locations using free-text queries, explicit `distance` and `serves` relations, and filters (`hours`, `amenities`, and `items`). |

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: Revert it back to the older "explicit spatial relations (distance, serves)" so it matches more with filters?

2. **Inventory-Based Store Finder**: Platforms can use Location Search with the `filters.inventory`
predicate to locate nearby stores that have a specific item available, bridging the gap between
online catalog browsing and physical store visits.
2. **Separation of Discovery Concerns**: Each capability answers one narrow

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.

Similar concern as the one raised in #755, this feels like we are referencing a lot of Shopping specific capabilities in the overarching documentation for Location.

Instead, can we simplify this to just:

2. **Separation of Discovery Concerns**: Each capability answers one narrow question,
and later stages revalidate earlier signals. Location answers which places can currently
provide a set of referenced items (the [`items` filter](search.md#item-availability-filter)).

[Search Filters](search.md#search-filters).
Location is returned only when it satisfies every supplied relation and
filter. The relations and predicates use the same schemas and semantics as
Search — see [Spatial Relations](search.md#spatial-relations) and

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.

see Spatial Relations and Search Filters, including the Item Availability Filter.

wording nit:

see [Spatial Relations](search.md#spatial-relations),
[Search Filters](search.md#search-filters), and
[Item Availability Filter](search.md#item-availability-filter).

| :--- | :--- | :--- |
| `search_locations` | [Search](search.md) | Search for locations using text, spatial relations, and filters. |
| `lookup_locations` | [Lookup](lookup.md) | Batch lookup one or more Locations by identifier. |
| `search_locations` | [Search](search.md) | Search for Locations using text, explicit relations, and filters. |

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: explicit spatial relations instead of explicit relations?

| `distance` | A relation between a candidate Location and an explicit Platform-supplied center point and inclusive radius. |
| `serves` | A relation between a candidate Location and one explicit Platform-supplied service target. |
| `filters` | Predicates over inherent or current Location facts: `hours`, `amenities`, and `inventory`. |
| `filters` | Predicates over inherent or current Location facts: standard `hours`, `amenities`, and `items`, plus extension-defined filters. |

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: Remove standard from the bullet here?

satisfies a fact. The two relations are independent: either can anchor a
request by itself, they can use different points, and neither inherits an
operand from the other.
`distance` and `serves` are independent spatial relations, while `filters`

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.

I don't think this paragraph should be updated - the original paragraph outlines the principle on when a dimension should be placed into filters vs. nested flat in the request and the edits removed that principled explanation.

**MUST** revalidate transaction-specific availability, quantities, methods,
and terms later in the commerce flow; see
[Relationship to Other Capabilities](index.md#relationship-to-other-capabilities)
for how Catalog, Cart, and Checkout divide that work.

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.

Related to the comment above - given we want to ensure the scope limits to Location capability and not over-reference Shopping specific capabilities, can we remove for how Catalog, Cart, and Checkout divide that work here?

"$id": "https://ucp.dev/schemas/common/types/location_filter.json",
"title": "Location Filter",
"description": "Filter criteria to narrow location search and lookup results. All specified filters combine with AND logic.",
"description": "Filter criteria to narrow Location Search and Lookup results. Standard filters are `hours`, `amenities`, and `items`. All supplied filters combine with AND.",

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.

Standard filters are hours, amenities, and items.

Probably should remove this sentence in case the standard filters list grows in the future?

Comment thread source/services/common/mcp.openrpc.json Outdated
"name": "search_locations",
"summary": "Search for physical locations",
"description": "Search for physical locations (e.g., retail stores, restaurants) using query text, explicit `distance` and `serves` relations, and structured filters (e.g., hours, amenities, inventory).",
"description": "Search for physical locations (e.g., retail stores, restaurants) using query text, explicit `distance` and `serves` relations, structured `filters.hours` and `filters.amenities` predicates, and the `filters.items` current item-availability predicate.",

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.

structured filters.hours and filters.amenities predicates, and the filters.items current item-availability predicate.

Same here - probably don't want to explicitly list out the filter fields in the description in case it grows in the future. Same comment to the other reference below on line 69 and in rest.openapi.json

   Restore the distinction between root spatial relations and filters over
   Location facts, and keep Common Location guidance independent of
   Shopping-specific handoffs.

   Use consistent spatial terminology and stop enumerating open filter fields in
   shared schema and service descriptions, reducing drift without changing
   filters.items behavior.
@jingyli
jingyli merged commit ea95b86 into feat/location Aug 25, 2026
3 of 4 checks passed
@jingyli
jingyli deleted the fix/location-availability-filter branch August 25, 2026 00:08
igrigorik added a commit that referenced this pull request Aug 25, 2026
* Extend retail_location.json with more fields and move it out of shopping/ to generally represent any physical location (to be referenced by Location capability).

* Add appropriate transition annotation to set proper expectation on field presence.

* Add common location capability.

* Minor wording updates in documentation and also fix rendering issues.

* Minor fix on fulfillment retail location reference.

* Fix on rendering links.

* More fixes on rendering links.

* Fix location_filter file name.

* Clean up remaining broken links and minor style updates.

* Address comments that involve minor schema changes.

* Address rest of the comments from feedback.

* Fix broken doc build link.

* fix(location): mark custom filters as extensible

Location search intentionally permits Business-defined filters, but the schema
relied on JSON Schema's implicit open-object default while the prose named
additionalProperties as the extension mechanism.

Declare the extension point explicitly so schema readers and generated
documentation can distinguish intentional extensibility from omission. Strict
resolution remains a caller-selected closed-world override.

* docs(location): remove catalog copy-paste

Location documentation inherited Catalog-specific descriptions, rendering
contexts, and a severity policy that Location never defined. It also documented
a singular REST path and a filter name that do not exist in the binding/schema.

Use Location-specific llms.txt descriptions and render scopes, remove the
unsupported severity claim, and align the visible endpoint and filter names with
their canonical definitions.

* Add location into UCP glossary to resolve feedback on PR#642.

* Add security & privacy considerations.

* Address feedback on simplifying service area representation on location responses and finetune request filters.

* Update hours representation to be more consistent with standard schema representation.

* Relax exact quantity based search and instead leverage availability status based coarse search during discovery phase.

* Standardize amenities vocabulary and also restructure filtering model between it and dynamic inventory filter.

* Address bounded location representation problem and clean up misc/unused ucp annotation for fulfillment related files.

* Fix broken reference rendering.

* Fix example on REST to follow proper exception_hour representation.

* Clean up legacy field description.

* Fix signature definition in MCP JSONRPC transport schema definition.

* Add validation rule for timezone in location.json.

* Loosen language on what is being considered as a search inputs to give more flexible combination of request inputs.

* Add some implementation guidance on how to deal with context-only search requests.

* Add more validation on the filter schema and tighten up prose around how business should handle contextual hints fallback.

* fix!: define deterministic operating hours for Location service (#687)

* define deterministic operating hours

   The current Location PR introduces weekly and exceptional operating hours,
   but leaves several wire and evaluation semantics ambiguous. In particular,
   closures rely on an artificial midnight interval, `open_now` depends on an
   implicit server clock, exception date bounds are unclear, and the specification
   does not define timezone, overnight, DST, overlap, or precedence behavior.

   Close those gaps with a UCP-native schedule model informed by Schema.org's
   OpeningHoursSpecification:
   https://schema.org/OpeningHoursSpecification

   Schema.org is design input only. UCP owns the field names, values, and
   evaluation rules defined here.

   Make weekly intervals explicit and reusable:

       "hours": [
         {
           "day": "tuesday",
           "opens": "09:00",
           "closes": "12:00"
         },
         {
           "day": "tuesday",
           "opens": "13:00",
           "closes": "21:00"
         }
       ]

   Rename `open` and `close` to `opens` and `closes`, and define `day` as a
   stable UCP weekday identifier rather than localized display text. Multiple
   entries for one day represent split shifts, and an interval whose closing time
   is earlier than its opening time continues into the next local date.

   Refactor the shared time interval schema so `opens` and `closes` are an
   optional but inseparable pair. Weekly hours require both fields, while
   exception hours may omit both to represent a full closure. Reject the ambiguous
   `00:00` to `00:00` pair and reserve `00:00` to `23:59` as the full-local-day
   sentinel.

   Replace the previous exception shape:

       {
         "from": "2026-11-26",
         "through": "2026-11-27",
         "label": "Thanksgiving",
         "open": "00:00",
         "close": "00:00"
       }

   with inclusive local-date bounds and an actual closure representation:

       {
         "title": "Thanksgiving",
         "valid_from": "2026-11-26",
         "valid_through": "2026-11-26"
       }

   Rename `from`, `through`, and `label` to `valid_from`, `valid_through`, and
   `title`. Treat `title` as optional presentation metadata that does not affect
   schedule evaluation. Allow timed exceptions with paired `opens` and `closes`,
   including multiple entries with identical bounds for split shifts.

   Define every returned schedule in the Location's Business-owned IANA timezone.
   Require `timezone` whenever regular or exception hours are present, and keep
   the canonical schedule independent of the requesting Platform or Buyer's
   timezone.

   Specify deterministic evaluation:

   - convert an exact instant into each Location's local date, weekday, and time
   - use half-open timed intervals, except for the reserved full-day sentinel
   - let overnight intervals carry into the following local date
   - replace regular hours with exception hours at local midnight
   - treat omitted weekdays as having no interval starting that day
   - treat absent schedules as unknown rather than closed
   - evaluate DST gaps and folds pointwise without shifting nonexistent times
   - reject equal time pairs and intersecting non-identical exception ranges as
     Business conformance errors where JSON Schema cannot express the constraint

   Remove the redundant `open_now` filter. It makes results depend on an implicit
   processing clock and creates undefined precedence when combined with
   `open_at`. Require one caller-supplied RFC 3339 instant instead:

       "filters": {
         "hours": {
           "open_at": "2026-05-18T17:00:00Z"
         }
       }

   Require `open_at` to include `Z` or a numeric offset. The offset identifies the
   instant only; the Business still evaluates that instant using each candidate
   Location's authoritative IANA timezone. Keep the nested hours filter open so
   extensions can add qualifiers without changing the standard predicate.

   Move complete Search and Lookup examples into the transport-neutral capability
   documents. Cover hours with serviceability and amenities, inventory with
   distance, split shifts, full closures, and partial Lookup success there.

   Reduce REST and MCP examples to equivalent binding envelopes that link to the
   same canonical payload examples. This keeps both transports on equal footing,
   avoids duplicating domain semantics, and prevents one binding's examples from
   becoming more complete or authoritative than the other. Preserve MCP's
   required `meta["ucp-agent"].profile` contract while separating protocol
   metadata from the Location request.

   This is a breaking correction to the Location PR's draft wire shape:

   - `open` becomes `opens`
   - `close` becomes `closes`
   - `from` becomes `valid_from`
   - `through` becomes `valid_through`
   - `label` becomes `title`
   - `open_now` is removed
   - full closures omit both time fields instead of using `00:00` to `00:00`

* s/weekday/day of week

* clarify operating-hours semantics

   Define `open_at` as the caller-selected instant relevant to the request, such
   as an expected arrival or pickup time. This avoids framing it as a request for
   the Business's receipt-time notion of "now": normal request latency does not
   change the question, and the Business evaluates the supplied instant against
   each Location's schedule.

   Describe operating hours more directly as local dates and clock times
   interpreted using the Location's IANA timezone. Clarify that temporary closures
   retain the regular `hours` schedule and override it with a date-bounded
   `exception_hours` entry that omits `opens` and `closes`.

   Mirror omitted-schedule semantics in the Location schema for implementers who
   read generated references:

   - an omitted day has no regular interval beginning that day
   - an interval from the preceding day may still carry into it
   - omission of the entire `hours` property means the schedule is unknown

   Make `time_interval` genuinely reusable by limiting it to generic `HH:MM`
   opening and closing fields. Location-specific recurrence and timezone
   interpretation remain with the containing daily, exception, and Location
   schemas.

   Remove the schema check that rejected only `00:00`–`00:00`. The actual
   authoring rule rejects every pair where `opens` equals `closes`, but standard
   JSON Schema cannot compare sibling values; enforcing one special case would
   misleadingly imply that other equal pairs are valid. Continue enforcing paired
   field presence and time formatting mechanically, while keeping unequal times
   as a normative Business conformance requirement and requiring Platforms not to
   infer openness from invalid schedule data.

* define authority for hours filtering

   The TC discussion converged on keeping one `open_at` filter, but left open
   whether both the Platform and Business could apply timing tolerance when
   interpreting immediate intent.

   After further consideration, assign that flexibility to one side only. The
   Platform owns the interpretation of Buyer intent and selects the instant to
   query. It may use its current time, choose an expected arrival, pickup, or
   order-acceptance time, and round or adjust that choice to the granularity
   appropriate to the interaction. Once encoded, however, `open_at` identifies one
   specific RFC 3339 instant.

   Require the Business to evaluate that instant exactly as supplied using each
   Location's authoritative timezone. It must not round, shift, substitute request
   receipt time, or otherwise reinterpret the value. Allowing both parties to
   apply independent tolerance would make the evaluated question unknowable and
   could produce different matches for identical requests near an opening or
   closing boundary.

   Apply normal positive-match filter semantics: return a Location only when the
   Business can establish that it is open at `open_at`. Missing, invalid,
   out-of-range, or otherwise unusable schedule data is a non-match rather than a
   reason to guess or adjust the requested instant.

   Clarify that the numeric offset in `open_at` identifies the queried instant,
   not the Location's timezone. The Business converts that instant using the
   Location's authoritative IANA timezone before evaluating its local schedule.

   State closing-boundary behavior concretely: a `10:00`–`17:00` interval is open
   immediately before `17:00` and closed at `17:00`. This avoids ambiguity over
   whether `HH:MM` values represent exact boundaries or minute-sized buckets.

   Keep exception payloads useful for planning without accumulating stale history.
   Businesses should remove entries once they cannot affect any current or future
   instant and publish known future exceptions through the horizon for which their
   schedule is authoritative.

   Remove the request-language localization recommendation for exception `title`.
   The field remains optional presentation metadata, but this capability does not
   define a localization guarantee for it.

* Cleanup incorrectly placed signature headers in meta object definition.

* Address feedback on existing contracts consistency.

* Remodel amenities as reverse-DNS string arrays.

* Fix documentation examples.

* Fix serves(target) contextual fallback algorithm.

* Revert back the changes to retail_destination.json to decouple the scope.

* Remove transition annotation from location_base.json as this is now being treated as a net new schema type.

* fix!: Location spatial relations and Lookup correlation (#753)

* separate location spatial relations from filters

   The previous filters.geo shape mixed predicates about a Location with
   relations to Platform-supplied points and addresses. That made context
   fallback ambiguous and coupled proximity with serviceability even when
   they need different operands.

   Promote distance and serves to independent request-root relations in Search
   and Lookup. Keep filters for inherent or current Location facts, preserve
   its open extension model, and combine every explicit relation and predicate
   with AND. Keeping the relations separate allows a request to measure
   distance from one point while testing serviceability to another, without
   hidden operand inheritance.

   Require distance.center plus inclusive distance.max in meters and define
   matching against the unrounded shortest WGS 84 ellipsoidal geodesic. Reject
   unsupported radii rather than silently clamping, substituting operands, or
   falling back to context, signals, or IP-derived locality.

   Model serves as exactly one point, coarse locality, or negotiated
   reverse-domain target. Treat a match as provisional evidence that at least
   one currently available method can serve the target, without exposing
   coverage geometry or promising checkout success. Reject targets that cannot
   be evaluated rather than ignoring them or broadening results.

   Keep query and contextual hints non-authoritative: they may influence ranking
   or bounded selection but cannot create or relax spatial proof. Preserve
   empty, hint-only, pagination-only, and filters-only Search requests as
   bounded browse forms, with the existing default page size of 10.

* mirror Catalog correlation in Location Lookup

   Location Lookup supports secondary identifiers and aliases, but returning only
   the canonical Location.id makes unordered batch results ambiguous when inputs
   converge on one Location or fan out to several.

   Mirror Catalog's lookup API shape by requiring inputs[] on every returned
   Location. Each entry preserves one identifier exactly as requested; a Location
   resolved by multiple inputs is returned once with all correlations, while one
   input may resolve to multiple Locations. Requiring inputs for direct ID matches
   adds minor payload overhead but preserves one uniform, schema-enforceable rule
   across Catalog and Location.

   Keep the correlation record inline and omit Catalog's match classification
   because Location has no product-to-featured-variant resolution distinction.
   Businesses must support canonical Location.id values and may additionally
   support aliases or secondary identifiers.

   Apply batch limits after deduplication. Process the first N distinct identifiers
   in request order and return a successful partial response with a
   batch_limit_applied informational message, leaving the omitted suffix retryable
   rather than reporting it as unresolved or failing at the transport layer.

* drop geo disclosure for distance matches

* Minor tweaks to language & remove unnecessary ucp_request annotation from fields that are already required in the enclosing type.

* make pagination defaults Business-defined

   The shared pagination schema fixed `limit.default` at 10, while Catalog
   and Location described different omission behavior. This made one shared
   type carry divergent semantics and could cause clients or generated SDKs
   to materialize 10 before the Business could apply its own policy.

   Require every Business to apply a default page size when `limit` is
   omitted, recommend 10 without making it a fixed value or supported floor,
   and allow the Business to choose another default. Requested and default
   page sizes remain targets rather than guaranteed result counts, so a
   Business may return fewer results when enforcing its maximum and a
   Platform must not assume count equality. Platforms that need a particular
   page size can continue to send an explicit `limit`.

   Remove the JSON Schema `default` annotation so the wire schema does not
   misrepresent a Business-specific policy. Align Catalog and Location on
   the shared contract, and have REST and MCP conformance link to the
   operation-level pagination rules instead of duplicating a default value.

   Keep Location's spatial semantics independent: pagination flexibility
   never permits reducing or silently clamping an explicit `distance.max`.

* clarify Location spatial constraint wording

   Attach the context-signal prohibition to the Business actor and separate
   that normative obligation from the factual consequence: contextual hints
   may influence ranking or bounded selection, but prove neither proximity
   nor serviceability and cannot replace explicit spatial operands.

   Remove the self-referential "`max` is not an alias" sentence from the
   distance schema. `max` is already the canonical required property, and
   documenting abandoned candidate names adds design-history noise without
   strengthening the contract.

   Preserve the existing requirements to evaluate the supplied radius
   exactly and reject requests whose radius cannot be honored rather than
   clamping or substituting another value.

---------

Co-authored-by: Jing Li <jingyli@google.com>

* Refactor Location specification documentation to follow the same pattern #723 is enforcing.

* Fix broken links.

* Address outstanding feedback around using cosolidated location_summary.json representation and normative amenity language.

* Fix outstanding broken references.

* Minor tweaks to the language to maintain consistency.

* Fix rendering link issues.

* Add location response schame in ucp.json and correct the last missing deeplink to lookup location.

* Fix one more typo on the rendering.

* Address feedback.

* fix!: make Location amenities self-describing (#765)

* make Location amenities self-describing

   Amenity identifiers use an open reverse-DNS vocabulary, so Platforms cannot
   derive buyer-facing text or safely present unfamiliar Business-defined
   amenities from the key alone.

   Replace amenity arrays with maps whose values require a short, buyer-facing
   description. Define exact-key filter matching and require filtered results to
   disclose requested keys so Platforms can verify each match.

   The presentation contract recommends presenting every returned amenity from
   Business-provided text, permits enhanced UX for known identifiers, and
   prohibits allowlist filtering or inferred semantics for unknown identifiers.

* clarify Location fulfillment handoff

   A Location ID identifies a destination, not a fulfillment mode. Treating it
   as a pickup signal conflated destination selection with the method contract
   and could make discovery results appear binding.

   Document that Platforms submit Location IDs only on applicable methods, whose
   type determines the mode. Businesses revalidate current availability and
   terms; recognizing an ID neither reserves inventory nor guarantees
   eligibility.

* repair Location routes and generated references

   Location documentation moved under specification/common/location, but
   discovery examples and MkDocs metadata still referenced the old paths. The
   REST binding also linked to a Lookup Location entity that it did not render.

   Point capability links and route metadata at the canonical Location paths.
   Add the generated Lookup Location section so its anchor and schema fields are
   available, and refresh route summaries to match the current Location model.

* align Location terminology and metadata

   Location documentation and bindings mixed legacy actor names with stale
   schema metadata. The rich Location schema also described a removed Base
   Location layer, while location destinations retained metadata used only by a
   deleted generator.

   Use glossary actor terms consistently across documentation and service
   bindings, normalize Location and Lookup naming, and make independent
   capability adoption an explicit BCP 14 permission.

   Describe the rich Location as composing Location Summary and remove the final
   retired ucp_shared_request marker.

* remove idempotency from read-only Location tools

   Location Search and Lookup are read-only operations, but their MCP metadata
   exposed an optional idempotency key borrowed from mutating operations. That
   implied retry-deduplication and replay semantics the capability does not
   define.

   Remove the parameter so retries follow ordinary read-only request semantics.
   The REST binding already carries no equivalent idempotency requirement.

* review feedback on the self-describing amenities change:

   - Amenity presentation (index.md): adopt Platform presentation
     autonomy — a Platform MAY decide whether and where amenities appear;
     the contract binds only when it presents them. Keep identifier-based
     suppression at MUST NOT (review proposed SHOULD NOT): self-description
     exists so unrecognized identifiers carry no presentation penalty, and
     weakening it would make extension amenities second-class — the footgun
     this PR closes. Adopt the reviewer's "solely because unrecognized"
     scoping, which permits uniform policies such as truncation.

   - Stable Identifiers (index.md): stop restating Fulfillment's contract
     in Location docs. The bullet is now informative — a Location ID
     selects only the physical entity — and defers to fulfillment.md's
     Selection and Location Identity, which owns selected_destination_id
     semantics including revalidation.

   - Amenity filter rejection (search.md): reword the Platform rejection
     sentence for flow; semantics unchanged.

   - Search example prose (search.md): group the hours sentences by moving
     the custom-amenity note after the Operating Hours reference; the
     sentence stays because the example deliberately shows a namespaced
     custom amenity remaining presentable.

* fix: replace Location inventory filtering with item availability (#766)

* fix!: replace Location inventory filtering with item availability

The inventory filter mixed stock, order-acceptance, lifecycle, and timing
semantics in an open per-item status vocabulary, while Location returns only
aggregate Location matches.

Replace it with `filters.items`, a nonempty array of distinct,
Business-scoped item identifiers shared by Search and Lookup. A Location
matches only when every item is available there under the Business's current
data; unknown, unavailable, or non-evaluable items are ordinary non-matches.

Keep hours, quantity, and fulfillment methods outside this predicate. Catalog
with Fulfillment describes item-level methods for a shortlisted Location, Cart
carries configuration and quantity, and Checkout with Fulfillment revalidates
basket feasibility and final terms.

* tighten Location availability framing

   Restore the distinction between root spatial relations and filters over
   Location facts, and keep Common Location guidance independent of
   Shopping-specific handoffs.

   Use consistent spatial terminology and stop enumerating open filter fields in
   shared schema and service descriptions, reducing drift without changing
   filters.items behavior.

* Remove unused wording (geofence) from the custom-words ignore list as part of final cleanup.

* Final reference fix post refactoring merge.

---------

Co-authored-by: Ilya Grigorik <ilya@grigorik.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status:needs-triage Signal that the PR is ready for human triage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants