Skip to content

feat: Introduce Location Search + Lookup capabilities #545

Open
jingyli wants to merge 9 commits into
Universal-Commerce-Protocol:mainfrom
jingyli:location-feat
Open

feat: Introduce Location Search + Lookup capabilities #545
jingyli wants to merge 9 commits into
Universal-Commerce-Protocol:mainfrom
jingyli:location-feat

Conversation

@jingyli

@jingyli jingyli commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

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.common namespace (consistent with Shopping's Catalog capability design):

  1. Location Search (dev.ucp.common.location.search): Discovery-focused endpoint for natural language query, geographic, and offerings-based filters.
  2. Location Lookup (dev.ucp.common.location.lookup): Resolution-focused endpoints supporting single & batch lookups.

Some key commerce flows it will be able to unlock:

  • Local Pickup Discovery: Finding locations like retail stores or restaurant branches
    nearby that support customer pickup and checking their operating hours & inventory availability
    before selection.
  • Fulfillment Area Verification: Checking if a specific location (e.g., utility depot, restaurant,
    or local service provider) has delivery coverage for a buyer's address.

Category (Required)

  • Core Protocol: Changes to the base communication layer, global context, or breaking refactors. (Requires Technical Council approval)
  • Governance/Contributing: Updates to GOVERNANCE.md, CONTRIBUTING.md, or CODEOWNERS. (Requires Governance Council approval)
  • Capability: New schemas (Discovery, Cart, etc.) or extensions. (Requires Maintainer approval)
  • Documentation: Updates to README, or documentations regarding schema or capabilities. (Requires Maintainer approval)
  • Infrastructure: CI/CD, Linters, or build scripts. (Requires DevOps Maintainer approval)
  • Maintenance: Version bumps, lockfile updates, or minor bug fixes. (Requires DevOps Maintainer approval)
  • SDK: Language-specific SDK updates and releases. (Requires DevOps Maintainer approval)
  • Samples / Conformance: Maintaining samples and the conformance suite. (Requires Maintainer approval)
  • UCP Schema: Changes to the ucp-schema tool (resolver, linter, validator). (Requires Maintainer approval)
  • Community Health (.github): Updates to templates, workflows, or org-level configs. (Requires DevOps Maintainer approval)

Related Issues

This is related to RFC #375's section 10.

Checklist

  • 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.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.
  • (For Core/Capability) I have included/updated the relevant JSON schemas.
  • I have regenerated Python Pydantic models by running generate_models.sh under python_sdk.

Screenshots / Logs (if applicable)

location_ucp_doc

@jingyli jingyli added this to the Working Draft milestone Jun 24, 2026
@jingyli jingyli added TC review Ready for TC review WIP labels Jun 24, 2026
@jingyli jingyli marked this pull request as ready for review June 29, 2026 17:33
@jingyli jingyli requested review from a team as code owners June 29, 2026 17:33
@jingyli jingyli removed the WIP label Jun 29, 2026
"id": {
"type": "string",
"description": "Unique location identifier.",
"ucp_request": {

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.

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": {

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.

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?

  1. Allow 24:00 in time_interval so 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)$"
    
  2. Add a precedence rule (in the Location hours section): for a given day or date, if is_closed is true the location is closed and is_24_hours/intervals MUST be ignored; otherwise if is_24_hours is true it is open all day and intervals MUST be ignored; otherwise it is open during intervals (absent or empty means closed).
  3. Add a cross-midnight rule: within one interval close MUST be later than open; a span that crosses midnight MUST be split into two daily_hour entries (the first ending 24:00, the next day starting 00: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": {

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.

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."

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.

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": {

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.

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."

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.

Suggested change
"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)."

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.

Is this the right RFC to link?

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.

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": {

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.

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

TC review Ready for TC review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants