@voyant-travel/trips) owns the composition layer that turns several independent commitments into one customer experience. A real itinerary is rarely a single booking: it is a tour plus a hotel stay plus a flight plus a transfer, each supplied, confirmed, taxed, and cancelled on its own terms. Trips groups those parts into a single Trip Envelope so the customer sees one itinerary, one price, one checkout, one set of documents, and one cancellation preview, without erasing the lifecycle boundaries underneath.
It is a deterministic composer, not a new commitment primitive. It aggregates pricing, runs a reserve workflow across components, hands off to checkout, and previews and executes per-component cancellations. Checkout and cancellation are dependency-injected, so the app and runtime keep owning the payment provider, bank transfer, storefront URL, supplier, and staff-remediation policy. The trip itself does not become a Booking; it references the component bookings that are.
Key concepts
customer-facing aggregate
A customer-facing aggregate that groups one or more component bookings (and provider or source order refs) into one itinerary, checkout, support, document, and cancellation-preview experience. It is not necessarily one Booking, and it does not erase the lifecycle boundaries of its components. See Trip Envelope.
independently committed part
One independently committed part of a Trip Envelope, with its own supplier or provider reference, cancellation rules, tax treatment, fulfillment state, and operational owner. A component is independent: cancelling one does not implicitly cancel its siblings. See Component Booking.
individually assembled itinerary
An individually composed Trip Envelope assembled from independent commitments such as a product, stay, flight, transfer, cruise, or staff-confirmed placeholder. This is the bespoke, build-it-yourself itinerary, distinct from a single packaged product. See Composed FIT Trip.
dependent child line
A child line that modifies or extends a component booking and shares that component’s lifecycle closely enough to be cancelled, fulfilled, taxed, and supported with it. Product-internal bundles and dependent extras stay inside their component booking; they do not become siblings. See Extra.
vertical-specific extension
A cruise-specific pre or post-cruise hotel or land program. Its offer definition can be shared across cruises and sailings; a selected extension is an Extra when cruise-owned and lifecycle-dependent, or a sibling component booking when independently supplied and confirmed. The module ships
./cruise-extension helpers for the link and the selection representation. See Cruise Extension.frozen composition state
A trip snapshot freezes the composed itinerary for reserve and provenance; a trip reservation plan captures what reserve secured (and what failed) before the component bookings are created. A booking’s Booking Origin can point back at the trip snapshot.
How independent commitments group into one itinerary
The load-bearing rule is the lifecycle boundary. What groups into one envelope, and what stays nested inside a component, is decided by whether the part has independent supplier, cancellation, tax, and fulfillment state.- Independent commitments become sibling component bookings. A customer-composed addition with its own supplier, its own cancellation rules, and its own fulfillment state is a sibling Component Booking under the same Trip Envelope. Cancelling it previews and executes against that component alone.
- Dependent parts stay nested. Product-internal bundles and dependent Extras stay inside their component booking. They share that component’s lifecycle and are cancelled, fulfilled, taxed, and supported with it.
- A Cruise Extension is decided by the same rule. Its catalog definition may be reused across cruises, but the selected extension is an Extra when cruise-owned and lifecycle-dependent, or a sibling component when independently supplied and confirmed.
The envelope lifecycle
The envelope hardens through an explicit status as pricing, reservation, and checkout complete. Each component carries its own parallel status, so one component can beunavailable or failed while siblings advance.
What it owns
Trips landed as a composition layer with a deliberately narrow, deterministic surface.- The durable schema (
./schema): trip envelopes, trip components, component events, trip snapshots, and trip reservation plans. - The deterministic trip service (
./service): create, get, list, add and update and reorder components, and update component refs. - Price aggregation (
priceTrip) that rolls component prices into one envelope total, andapplyQuoteToComponent. - The reserve workflow (
reserveTrip) that runs across components and produces a reservation plan. - Checkout handoff (
startCheckout,completeTripCheckout), dependency-injected so the runtime owns payment, bank transfer, and storefront URL policy. - Component-level cancellation (
previewCancellation,cancelComponents), so support can preview and cancel one component without touching siblings. - Cruise Extension helpers (
./cruise-extension) and a catalog component adapter (./catalog-component-adapter) that maps catalog entities into trip components. - MCP tools (
./mcp-tools): AI-safe trip planning, revision, pricing, and reserve operations.
Dynamic assembly: requirements and candidates
Everything above assumes the components are already pinned. Trips also supports dynamic assembly, where a component starts as an unresolved need and is filled from live, independently-sourced supply. This is the trips half of dynamic packaging.- A Trip Requirement (
tripRequirements) is an unresolved need: averticalpluscriteria. Its status walksopentosourcingtocandidates_readytoselected, orno_availability. - Trip Candidates (
tripCandidates) are persisted, rankedAvailabilityCandidaterows under a requirement, each with a TTL; status isranked,selected,expired, ordiscarded. sourceRequirementCandidatesruns the catalog availability fan-out (injected throughdeps.search, so trips never imports a catalog adapter) and persists the ranked candidates.selectCandidatepins one into a draft component, enforcing selected-uniqueness.reshopRequirementandreshopTripre-source when supply goes stale, andexpireStaleTripCandidatesis a TTL reaper for a deployment cron.- The candidate’s
candidateRefis not replay-safe:priceTripre-resolves the selection andreserveTripre-validates before any supplier dispatch, gated byassertEnvelopeRequirementsSatisfied.
The requirement-sourcing routes (
POST /:envelopeId/requirements, /requirements/:id/candidates, /requirements/:id/select, /requirements/:id/reshop, /:envelopeId/reshop) need the fan-out search dependency injected. Until a deployment wires it, the sourcing routes return 501. See dynamic packaging for what is wired today.Working with it
Standard modules are not registered by hand.@voyant-travel/trips is part of the standard product graph, so its routes, services, subscribers, and jobs are resolved into the application at build time. See Configuration.
The service functions take a Drizzle handle and return plain trip records.
Checkout and cancellation are dependency-injected. The trips package stays deterministic and side-effect-free at its core; the app supplies the payment provider, bank-transfer, storefront-URL, supplier, and staff-remediation policy when it wires the routes.
Links to other modules
- bookings owns each Component Booking’s commitment truth. A booking’s origin can point at the trip snapshot the envelope froze.
- proposals produces proposal versions whose Trip Envelope snapshot mirrors a composed itinerary; reserve crosses from accepted version into component reservation.
- catalog is the source the catalog component adapter maps into trip components, so owned and sourced inventory compose into one envelope.
- The cruises vertical pairs with the Cruise Extension helpers to decide whether an extension nests as an Extra or splits into a sibling component.
- The finance module collects against the envelope’s checkout while each component keeps its own tax treatment.
React package
@voyant-travel/trips-react provides admin and public API clients, validation-aware operations, TanStack Query keys and options, cache writers, provider wiring (VoyantTripsProvider under ./provider), and hooks (./hooks) for Trip Envelope composition. It supports trip creation, component add, pricing, reserve, checkout handoff, and support-facing component cancellation preview and cancel. Draft is a trip lifecycle status, not a separate object name. Fetcher and error utilities live under ./client, and stable query keys under ./query-keys.
Next steps
Bookings
The component bookings each envelope references, with their own lifecycles.
Proposals
The proposal whose Trip Envelope snapshot a composed itinerary mirrors.
Cruises
The vertical behind Cruise Extensions that nest as Extras or split as siblings.
Glossary
The shared vocabulary: Trip Envelope, Component Booking, Composed FIT Trip, Extra.