feat: Introduce Location Search + Lookup capabilities #545
Conversation
…ing/ to generally represent any physical location (to be referenced by Location capability).
| "id": { | ||
| "type": "string", | ||
| "description": "Unique location identifier.", | ||
| "ucp_request": { |
There was a problem hiding this comment.
Is there a ucp-schema lint error?
id and name use ucp_request: { transition: { from, to, description } }. ucp-schema lint source/ fails this file (the only failure in the PR):
error[E005]: /properties/id/ucp_request/transition - invalid ucp_request value type: expected string, got object
error[E005]: /properties/name/ucp_request/transition - invalid ucp_request value type: expected string, got object
warning[W003]: ... unknown operation "transition": expected create, update, complete, read (x2)
Can you please check?
| "description": "Geographic coordinates and geofence for the location.", | ||
| "ucp_request": "omit" | ||
| }, | ||
| "hours": { |
There was a problem hiding this comment.
Two related flags where the same input can be read two ways by different implementations. First, daily_hour/exception_hour say "when is_closed/is_24_hours is true, intervals MUST be omitted," but nothing enforces it, so a contradictory record (is_closed: true + is_24_hours: true + intervals: [...]) validates and two businesses can disagree on whether the store is open. Second, the "overnight = two entries across two days" guidance is prose-only: time_interval leaves close < open undefined (22:00-06:00 validates with no defined meaning), and 24:00 is rejected by the HH:MM pattern, so a shift ending at midnight has to be written 00:00, which is itself ambiguous.
Rather than reach for if/then schema enforcement, should we make every record deterministic with one small schema change plus two normative rules?
- Allow
24:00intime_intervalso an end-of-day/midnight close is expressible:- "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9]$" + "pattern": "^(([01][0-9]|2[0-3]):[0-5][0-9]|24:00)$" - Add a precedence rule (in the Location hours section): for a given day or date, if
is_closedis true the location is closed andis_24_hours/intervalsMUST be ignored; otherwise ifis_24_hoursis true it is open all day andintervalsMUST be ignored; otherwise it is open duringintervals(absent or empty means closed). - Add a cross-midnight rule: within one interval
closeMUST be later thanopen; a span that crosses midnight MUST be split into twodaily_hourentries (the first ending24:00, the next day starting00:00).
With 24:00 allowed, the midnight close becomes unambiguous, close > open always holds, and 22:00-06:00 is invalid (must be split) rather than undefined. Would that work?
| }, | ||
| "additionalProperties": false | ||
| }, | ||
| "geofence_point": { |
There was a problem hiding this comment.
geofence_point applies additionalProperties: false as a sibling of $ref: geo.json with no local properties. additionalProperties does not see properties reached through $ref, so it forbids the latitude/longitude that geo.json requires. Verified: {"latitude":37.42,"longitude":-122.08} fails with "Additional properties are not allowed ('latitude','longitude')," and {} fails the required check, so no instance can match. The first search example in rest.md (around line 101) sends exactly geofence_point: {latitude, longitude} and is therefore schema-invalid against search_request.
Should we drop the additionalProperties: false sibling, so the bare $ref: geo.json applies (as the distance branch already does)?
| "properties": { | ||
| "center": { | ||
| "$ref": "geo.json", | ||
| "description": "The center coordinates. If omitted, the user's course location hints in the request context MUST be used to derive the center." |
There was a problem hiding this comment.
The description says that when center is omitted, "the user's coarse location hints in the request context MUST be used to derive the center." But center is a geo (required latitude/longitude), while context composes locality.json, which only carries address_country, address_region, and postal_code, with no coordinates anywhere. As written, the one normative geo input a search can have is not expressible from the request alone, so the MUST cannot be met without an unstated geocoding step. The first REST example sidesteps this by sending explicit center coordinates, so the fallback path is never exercised.
Could we either (a) relax this to SHOULD and state that the business geocodes the coarse locality hint into a centroid, or (b) let center also accept a locality so the postal/region centroid is a first-class, schema-expressible input? Whichever we choose, could the spec name the exact field the center is derived from, so two implementations geocode the same thing?
| "type": "boolean", | ||
| "description": "Only return locations that are currently open." | ||
| }, | ||
| "open_at": { |
There was a problem hiding this comment.
open_at describes two incompatible time meanings; could we pick one?
open_at is format: date-time with the description "the specified date and time (RFC 3339) in the location's local time." Those two halves disagree. An RFC 3339 date-time carries an offset and denotes one absolute instant on the global timeline; "in the location's local time" describes a wall-clock value (2 PM at the store), which is a different quantity and is not even a valid RFC 3339 date-time (no offset). So an implementer cannot tell which is meant.
It changes results for any multi-timezone search. If a platform sends 2026-06-29T14:00:00-04:00, read as an absolute instant the business converts it to each location's local time and tests New York at 14:00 but Los Angeles at 11:00; read as "2 PM local everywhere," a single instant cannot express that at all (each timezone is a different instant). Same request, different locations returned.
Could we commit to one reading in the description? The cleaner option, matching format: date-time, is absolute-instant: "An RFC 3339 instant. The business converts it to the location's local time using location.timezone and evaluates it against hours/exception_hours (the HH:MM intervals)." If the intent is actually a local wall-clock time, then could we drop format: date-time and model it as a date plus HH:MM evaluated in each location's own timezone? Either works; the ask is just to state which, since open_now (a plain boolean evaluated "now" per location) is unambiguous but open_at currently is not.
| "properties": { | ||
| "center": { | ||
| "$ref": "geo.json", | ||
| "description": "The center coordinates. If omitted, the user's course location hints in the request context MUST be used to derive the center." |
There was a problem hiding this comment.
| "description": "The center coordinates. If omitted, the user's course location hints in the request context MUST be used to derive the center." | |
| "description": "The center coordinates. If omitted, the user's coarse location hints in the request context MUST be used to derive the center." |
| "max_distance": { | ||
| "type": "number", | ||
| "minimum": 0, | ||
| "description": "Maximum distance in RFC 7035 distance unit (meters)." |
There was a problem hiding this comment.
Is this the right RFC to link?
There was a problem hiding this comment.
This file now lives at common/types/signals.json, but its $id still reads https://ucp.dev/schemas/shopping/types/signals.json. Its sibling context.json got the correct common/ $id in the same move.
| "type": "object", | ||
| "required": ["id"], | ||
| "properties": { | ||
| "id": { |
There was a problem hiding this comment.
Undeclared cross-capability coupling.
inventory_filter.id is "product or variant ID in shopping, or dish ID in food ordering," and type well-known values are product/dish. That ties this common capability to Catalog/menu ID namespaces. There is no $ref from common/ into shopping/, so this creates a documentation gap. Could we add a short Dependencies note (IDs originate from Catalog/menu; type routes backend resolution) so integrators know how to obtain valid IDs?
Description
Defines standard interfaces for discovering, searching, and retrieving physical locations (e.g., retail stores, restaurants, warehouses, lodging properties).
It introduces two new capabilities under the
dev.ucp.commonnamespace (consistent with Shopping's Catalog capability design):dev.ucp.common.location.search): Discovery-focused endpoint for natural language query, geographic, and offerings-based filters.dev.ucp.common.location.lookup): Resolution-focused endpoints supporting single & batch lookups.Some key commerce flows it will be able to unlock:
nearby that support customer pickup and checking their operating hours & inventory availability
before selection.
or local service provider) has delivery coverage for a buyer's address.
Category (Required)
ucp-schematool (resolver, linter, validator). (Requires Maintainer approval)Related Issues
This is related to RFC #375's section 10.
Checklist
!for breaking changes).Screenshots / Logs (if applicable)