fix: replace Location inventory filtering with item availability - #766
Conversation
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.
a5c78dd to
7440bbd
Compare
| | :--- | :--- | | ||
| | [`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`). | |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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. | |
There was a problem hiding this comment.
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. | |
There was a problem hiding this comment.
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` |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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.", |
There was a problem hiding this comment.
Standard filters are
hours,amenities, anditems.
Probably should remove this sentence in case the standard filters list grows in the future?
| "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.", |
There was a problem hiding this comment.
structured
filters.hoursandfilters.amenitiespredicates, and thefilters.itemscurrent 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.
* 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>
Follow-up to #589 and #765.
Replaces Location's inventory- and status-shaped
filters.inventoryobjects 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_stockdescribes a retail inventory implementation;preorderandbackorderdescribe order acceptance and timing policies rather than the same kind of stock state;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.itemsis 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:
The contract is deliberately small:
Why this shape
The alternatives each introduced the wrong semantics or the wrong level of abstraction:
filters.inventoryexposes an implementation concept rather than the cross-vertical commerce fact.availability_statusinvites a shared status vocabulary that mixes stock, purchase policy, lifecycle, timing, and presentation without a corresponding item-result surface.filters.availablestates 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.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.filters.itemsis 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:
context.location, evaluate item-level fulfillment methods for that Location.These boundaries have concrete consequences:
filters.itemsandfilters.hours;hours.open_atdoes not change the availability evaluation instant.filters.itemswith 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.End-to-end examples
Retail store finder
A Platform starts with two Catalog variant identifiers for a phone and case.
distanceandfilters.items: ["phone_variant", "case_variant"]to shortlist nearby stores that can currently provide both items.filters.hours.open_at. If it adds a pickup amenity, that proves only that the store generally supports pickup.context.locationidentifies the variants and evaluates their item-level fulfillment methods there.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.
filters.items: ["dish_id", "side_id"]and eitherdistanceorservesto find branches that can currently provide both offerings.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.