Skip to content

fix!: define deterministic operating hours for Location service - #687

Merged
jingyli merged 4 commits into
feat/locationfrom
fix/location-schemaorg-hours
Aug 12, 2026
Merged

fix!: define deterministic operating hours for Location service#687
jingyli merged 4 commits into
feat/locationfrom
fix/location-schemaorg-hours

Conversation

@igrigorik

@igrigorik igrigorik commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

The current Location PR (#687) 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

   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`
@igrigorik igrigorik added this to the 2026-08-14 milestone Aug 5, 2026
@igrigorik
igrigorik requested a review from jingyli August 5, 2026 14:06
@igrigorik igrigorik self-assigned this Aug 5, 2026
@igrigorik igrigorik added the TC review Ready for TC review label Aug 5, 2026
Comment thread docs/specification/location/lookup.md
Comment thread docs/specification/location/lookup.md

@jingyli jingyli left a comment

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.

Thanks @igrigorik for the enhancement on hours representation! Overall the proposed shape LGTM but added a couple of inline comments (would especially love to get some reaction on the request filter front).

Comment thread docs/specification/location/search.md Outdated
instant expressed with `Z` or a numeric offset, and can include
extension-defined fields. Before sending a Buyer-local wall time, a Platform
**MUST** resolve it to an exact instant for `open_at`. When asking which
Locations are open now, a Platform **MUST** supply the current instant. No

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.

Just a naive thought: but I feel like replacing open_now boolean filter with this approach may not be ideal in the sense that current instant is extremely difficult to enforce in practice.

Platforms can issue the request at Z instant, but because there is always some intrinsic delay before business receives the request at Z' instant, it always look like platform has issued a search request with an instant in the past. I'd imagine there would be other legitimate reasons for businesses wanting to implement some logic to invalidate platform requests searching for stores that are "opened in the past", if this case is mixed in there, it introduces a rather grey area.

Instead, if we separate out the use case with open_now, then the boolean can clearly indicate that business should use Z' when processing the search request and can also easily invalidate all requests where open_at < Z'.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clarified in c23dc7e, ptal.

open_at identifies the instant relevant to the buyer—such as expected arrival or pickup time—rather than making a freshness claim tied to when the Business receives it. We don't need to chase false precision and request/response timing here.

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.

Thanks! The clarification works well for the scheduled/future-looking intent but would love to still dig a bit deeper into this thread as I personally think the correctness/precision remains important when it comes to hyper local goods that need an immediate representation that doesn't depend on the platform's clock. Related, this data contract will require 2 additional translations on both platform & business sides to express immediate - so it complicates implementation and sacrifices precision..

The things I feel are still unresolved:

  • If the business receives a platform request where open_at is in the past relative to the business's clock, it must decide whether to allow it or reject it now through leveraging a "tolerance window".
  • If platform's clock is misconfigured or agent hallucinates when it attempts to translate a user's "immediate" intention to an absolute local instant, then the response will be incorrect.

Comment thread docs/specification/location/index.md Outdated
Comment thread docs/specification/location/index.md Outdated

### Evaluation

Timed intervals ordinarily include the opening time and exclude the closing

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.

Given this is documentation for location, I'm wondering if we should call out explicitly that the intervals used here are actually inclusive of both ends instead (also consistent of how we plan to represent the exception valid date ranges) of this standard half open representation?

The current way will result in a very unnatural way of representation opening hours. Example: a store that opens from 10am to 5pm. Under this rule, businesses would need to return [10:00, 17:01) vs. the more humanly understandable format of [10:00, 17:00].

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think half-open is right contract here. 10:00–17:00 is open immediately before 17:00 and closed at 17:00, matching the ordinary meaning of "closes at 5" without requiring 17:01. A value of 17:02 means the Location closes exactly at 17:02; it is not rounded to 17:00.

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 actually think we are agreeing on the same thing conceptually 😃 but the wording of the sentence here remains the issue...

exclude the closing time seems to suggest that the closing time (value in closes) should be discarded and naively reads more like a [10:00, 17:01) and there is no further clarification sentence in this paragraph talking about why for location specifically we are leveraging a closed interval - the only exception builds on top of the half-open semantics for entire date.

Can I suggest rewording this sentence instead to call out that time intervals in Location capability deviates from the ordinary half-open timed intervals and instead enforces a closed interval where opening and closing times are both included?

Comment thread docs/specification/location/index.md
Comment thread docs/specification/location/index.md
Comment thread source/schemas/common/types/location.json
Comment thread source/schemas/common/types/time_interval.json Outdated
Comment thread source/schemas/common/types/time_interval.json Outdated
   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.
Comment thread docs/specification/location/index.md Outdated

### Evaluation

Timed intervals ordinarily include the opening time and exclude the closing

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 actually think we are agreeing on the same thing conceptually 😃 but the wording of the sentence here remains the issue...

exclude the closing time seems to suggest that the closing time (value in closes) should be discarded and naively reads more like a [10:00, 17:01) and there is no further clarification sentence in this paragraph talking about why for location specifically we are leveraging a closed interval - the only exception builds on top of the half-open semantics for entire date.

Can I suggest rewording this sentence instead to call out that time intervals in Location capability deviates from the ordinary half-open timed intervals and instead enforces a closed interval where opening and closing times are both included?

Comment thread docs/specification/location/index.md
Comment thread docs/specification/location/search.md Outdated
instant expressed with `Z` or a numeric offset, and can include
extension-defined fields. Before sending a Buyer-local wall time, a Platform
**MUST** resolve it to an exact instant for `open_at`. When asking which
Locations are open now, a Platform **MUST** supply the current instant. No

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.

Thanks! The clarification works well for the scheduled/future-looking intent but would love to still dig a bit deeper into this thread as I personally think the correctness/precision remains important when it comes to hyper local goods that need an immediate representation that doesn't depend on the platform's clock. Related, this data contract will require 2 additional translations on both platform & business sides to express immediate - so it complicates implementation and sacrifices precision..

The things I feel are still unresolved:

  • If the business receives a platform request where open_at is in the past relative to the business's clock, it must decide whether to allow it or reject it now through leveraging a "tolerance window".
  • If platform's clock is misconfigured or agent hallucinates when it attempts to translate a user's "immediate" intention to an absolute local instant, then the response will be incorrect.

   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.
@jingyli

jingyli commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Thanks @igrigorik for the latest update! All my outstanding concerns are now addressed through 8bb2b74. Proceeding with the merge back into the feature branch.

@jingyli
jingyli merged commit 8b9d8b3 into feat/location Aug 12, 2026
3 of 4 checks passed
@jingyli
jingyli deleted the fix/location-schemaorg-hours branch August 12, 2026 23:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants