Conversation
Bookable resources — products held for a window of time instead of counted
in stock — had no coverage in any skill. Two storefront builds (Tools
Universe rentals, and the rental work in Car Parts Universe) had to
discover the whole surface from the schema, and three of its failure modes
are silent.
New skill `bookable-resources`, with references for the Core setup, the
Shop API flow and modelling.
Verified on 2026-09-24 against the live Core API and Shop API on the
tools-universe tenant, including two test reservations taken and released.
Confirmed directly:
- Policy durations are seconds. `heavy-equipment` reads back as
advanceWindow 10368000 = 120 days, cancellationWindow 172800 = 2 days,
pendingHoldDuration 900 = 15 minutes. `humanized` gives the same
values as { value, unit }, which is how you catch a wrong unit.
- `bookingPolicies` is a connection, and `stats.referencingProductCount`
is what blocks a delete.
- A pool is units or capacity, never both; unit `meta` carries depot,
serial and service data, and Discovery serves it back on the item.
- `checkBooking` requires `language` and returns { ok, reason }, not a
union. `bookSkuItem` returns Cart | ReservationConflict | NotBookable |
InvalidRange | InvalidUnitId — refusals are results, not errors.
- The reservation id is on the cart LINE's meta (`reservationIds`),
alongside a `booking.window` the API writes itself. The line's unitId
is not returned, so the caller has to store it.
- `cancelReservation(cartId, reservationId)` applies the cancellation
window to a hold that was never bought: a booking for tomorrow under a
two-day window answers CancellationWindowClosed, so a shopper cannot
empty their own basket. Re-hydrating without the line releases it, and
the reservation then reads null.
- `confirmCartBooking` answers Cart | NotPlaced | NothingToConfirm |
ReservationNoLongerHeld.
The one thing a storefront cannot get right by reading the schema is the
double confirmation: `confirmCartBooking` writes `orderId` onto the
reservations only if the cart already has one, and the cart is linked in
the background a few hundred ms after `createFromCart` returns. A single
confirm leaves { state: CONFIRMED, orderId: null } and the admin shows
"No order — not checked out" for a booking that was paid for. The skill
documents polling the cart and confirming again, as Tools Universe does.
Version to 3.7.0 in the three manifests. mcp-servers/crystallize was left
on 3.5.0 by #3 and is brought back into lockstep.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- No feature flag gates bookings; RBAC does. Replace the ExperimentalFeaturesNotAvailableError probe with the FORBIDDEN case. - setBookable, reapplyBookablePolicy and clearBookable write the draft; each needs a publish before the Shop sees it. - BookablePoolKindChangeError only fires capacity -> units while a capacity pool is published; document clear, publish, drain, set. - bookableProducts lists published configurations only; the pool is shared by every language of the product. - The first confirmCartBooking commits the slot (CONFIRMED never expires); mark it optional, place payment in the sequence, and show the /booking/admin cancel for a failed payment. - Expired holds become EXPIRED and are re-taken on hydrate; placed carts refuse hydrate, cancel and rebook. - Add the hydrate shape for a booking line (lineId, window, unit), the exact cancellation rule, why orderId must be polled, and that bookSkuItem ignores group and type. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bookable resources are in no skill at all. An agent asked to build a rental store today has to discover policies, pools, holds and the confirmation dance from the schema — and three of the failure modes are silent. Two storefront builds went through that: Tools Universe (24 rental machines across five depots, sold by day/weekend/week/4 weeks) and the rental work in Car Parts Universe.
Adds
bookable-resources:SKILL.mdplusreferences/policies-and-pools.md(Core setup),references/booking-flow.md(Shop API) andreferences/modelling.md.The one thing you cannot get from the schema
confirmCartBookingwritesorderIdonto the reservations only if the cart already has one — and the cart is linked in the background a few hundred milliseconds aftercreateFromCartreturns. The obvious order (bookSkuItem→place→confirmCartBooking→createFromCart) therefore leaves every reservation at{ state: CONFIRMED, orderId: null }, and the admin shows "No order — not checked out" for a booking that was paid for.The skill documents the flow Tools Universe ships: keep the first confirm (it proves the holds still stand before the shopper is charged), then poll
cart(id) { orderId }and confirm again. If that timing is considered a bug rather than the contract, this is the page to change once it is fixed.Verified against the live APIs
On the
tools-universetenant, 2026-09-24 — including two test reservations taken and released:heavy-equipment:advanceWindow10368000 →humanized120 days;cancellationWindow172800 → 2 days;pendingHoldDuration900 → 15 minutesbookingPoliciesis a connection, deletes are blocked while in usetotalCount: 3,stats.referencingProductCount15 / 4 / 5metacarries identityBookableUnitPool,poolSize: 5, units withdepot,serial,hours,lastService— served back by DiscoverycheckBookingneedslanguage, returns{ ok, reason }{ ok: true }bookSkuItemrefusals are results, not errorsCart,ReservationConflict,NotBookable,InvalidRange,InvalidUnitIdmeta: { reservationIds: "18a6b9d4-…", "booking.window": "2026-10-01T08:00:00.000Z/2026-10-01T16:00:00.000Z" }— and the line'sunitIdis not returned, so the caller must store itcancelReservationapplies the cancellation window to a hold that was never boughtCancellationWindowClosed, i.e. the shopper cannot remove their own basket line. Re-hydrating without the line releases it (reservation→null)confirmCartBookingresultCart | NotPlaced | NothingToConfirm | ReservationNoLongerHeldhydratetakes the cart id insideCartInput,cancelReservationtakesreservationIdBoth test carts were emptied afterwards; the holds are gone.
Also in the skill
ExperimentalFeaturesNotAvailableErrorfrombookingPolicies, so match the type rather than a message.NotBookableon a product that looks configured usually means it is not published, or the query'slanguageis wrong.reapplyBookablePolicy; products carry apolicySnapshotwith itsversion.hydrateis the whole cart: a booking line you leave out is cancelled, which is the supported way to remove one from the basket, and re-hydrating slides the hold's expiry.fulfillaftercreateFromCart, consistent with fix(skills): correct five places where the skills contradict the API #8.Version
3.7.0 in
plugin.json,marketplace.jsonandmcp-servers/crystallize/package.json. The last one was left on 3.5.0 by #3 and is brought back into lockstep.oxfmt --checkpasses onuse-crystallize/skillsandREADME.md.🤖 Generated with Claude Code