@guestcraft/server
The composition root wires API/auth/core to database and remote adapters. http
starts explicitly with validated config, injected clock/randomness and owned pools;
importing it performs no I/O. migrate alone uses owner credentials. Environment
reads belong in config/environment.ts.
The HTTP runtime composes persisted property, inventory, guest and reservation use cases. See reservations. Calendar/front-desk are local read models, populated by direct bookings and the Channex import projection. Outbound ARI is composed with explicit takeover and automatic bounded recovery.
pnpm --filter @guestcraft/server test:integration verifies the composed HTTP flow
on PostgreSQL/PgBouncer, including rollback, roles, persistence and schema revisions.
pnpm quality runs the complete suite and coverage gates.
The composed tests also exercise previews without writes, summary/history reads, free-unit choices, housekeeping, archive recovery, same-day undo and current ETags on stale guest/reservation edits.
The HTTP runtime wires identity-aware team operations into the public API, while keeping public signup closed and blocking legacy membership/invitation bypasses. See ADR 0030.
The runtime also composes local rate setup, effective calendar reads, quotes and atomic ARI
outbox writes (ADR 0027). The transaction-bound outbox also wakes the ARI publisher; production ownership is checked before writes.
The worker export composes Channex feed workers for resident Docker and budgeted cron execution. pnpm --filter @guestcraft/server worker starts the resident entry point. Production ownership gates and preview refusal protect acknowledgements and webhook registration. See booking import.
Property performance reports
Section titled “Property performance reports”The HTTP composition wires performance, history, pickup, cash and folio revenue queries to transaction-bound reporting aggregations. Reports are read-only and role-guarded. Performance/pickup use frozen booked prices; financial reports use ledger service dates (ADR 0031).
Folios
Section titled “Folios”Reservation folios and manual settlement are documented in the folio workflow and ADR 0029. The ledger, frozen room charges, property extras, manual payments/refunds, a check-out that settles only at the last room and never holds a credit (an owed balance needs settlement or an audited administrator approval), refunds due, net and full-ledger views and a non-fiscal statement are implemented. Taxes need a country module on the TaxCalculator port (with a re-rate operation); fiscal documents and online providers remain planned.
Guarded ARI publication has rate-plan mapping, numbered property leases, booking barriers, shared pacing and health/resync with explicit takeover. Automatic catch-up, bounded unknown repair, provider request lookup and nightly drift repair (the channex.ari_drift dispatch wakes due publishers) are implemented; channex.alerts reconciles alerts and channex.history_import runs historical booking imports (ADR 0048). See the ARI workflow.
On Vercel production, the HTTP bridge supplies Next.js after() to createHandler.
Committed ARI/feed requests wake a budgeted worker after the response; rollback, reads,
development and previews do not. Docker keeps its resident worker, and the authenticated
daily cron remains a safety net. See ADR 0034 for coalescing, lifetime and recovery limits.
Booking policies and no-shows
Section titled “Booking policies and no-shows”Property policies, immutable sold conditions, simulated cancellation fees, explicit no-show/recovery, guarantee attestations and prepayment tracking are composed with the folio ledger. See the policy workflow and ADR 0032 for permissions, deadlines, migration semantics, SDK operations and planned OTA reporting/provider capabilities.
HTTP, cron/request-pump and worker composition share an injectable business clock.
createBookingWorker accepts it as its fourth argument; the production default is
Temporal system time. PostgreSQL owns delivery/lease/pacing deadlines independently.
Channex Messages shares the production request after() pump with booking import and ARI. Committed jobs wake a bounded worker; only verified webhooks with a message event wake the messages drain. The safety job enqueues booking drains first and then only schedules messages drains, so a messages failure never stops booking recovery; Docker retains its resident worker. Message composition notifies only after commit, formats composer dates for the template language, and delays the drain job for a reply waiting on a backoff. credentials:reencrypt also rewrites message records.
HttpRuntimeOptions.requestWorker injects only the after-response worker factory. The
member-publication regression keeps real PostgreSQL/pg-boss/Channex handlers while disabling
idle waiting and advancing the injected gathering clock. Normal composition uses the
production worker and unchanged pump budgets.
The deployment command verifies existing role logins before schema writes, under the same
lock as grants and queue registration. Both Docker and Vercel build logs print safe
MigrationError/BootstrapError diagnostics; unclassified errors use
migration.unexpected_failure. See the database diagnostic codes
and rotation/recovery procedure.
Migration connections need one PostgreSQL session: the direct URL (on Neon, the unpooled one)
or a session pooler (on Supabase, port 5432). The command refuses a transaction pooler with
migration.session_required, using a probe connection with the same URL, and refuses the known
transaction-pooler addresses in its configuration. New installs provision an independent
GUESTCRAFT_DATABASE_ROLE_SECRET; legacy installations retain auth-derived passwords until pinned.
The built-in MCP server
Section titled “The built-in MCP server”http/mcp.ts serves @guestcraft/mcp at /api/mcp (ADR 0047), stateless, on Vercel and Docker
alike. Each request is authenticated first by the instance’s credential gateway, at the
configured resource URL and without its cookie; client sessions and device tokens are refused,
and a refused request is challenged toward
<base>/.well-known/oauth-protected-resource/api/mcp. Tools call the composed app in process:
each request they make is registered with the caller’s resolved principal, which the API’s
principal resolver uses before authenticating, so no bearer is forwarded and no instance key is
used. Confirmations use a key derived from AUTH_SECRET and the guestcraft_meta.mcp_confirmations
ledger, shared by every instance. See the MCP guide.
Security: cookie request origin
Section titled “Security: cookie request origin”The HTTP composition always installs the shared cookie-origin policy before all API
routes, including team and identity writes. POST, PUT, PATCH and DELETE require an
exact configured base/trusted Origin; without Origin, only Fetch Metadata same-origin
or none is accepted. Denial is a localized RFC 9457 request.untrusted_origin (403), with no
handler side effects. Request Host/Forwarded headers never establish trust.
Independently authenticated Channex webhook/cron and private-code setup retain their
own checks. ADR 0020 documents the
boundaries and browser/database regression. The origin policy is also shared with
the standalone team gateway; Better Auth keeps its additional CSRF checks.
Shared account mode scopes booking drain/recovery/acknowledgement and signals to workspace mappings. An authenticated unrelated global webhook records a signal without waking a shared drain. See Shared accounts in ADRs 0028/0034.
Guest identity operations share the existing transaction-bound reservation composition. The guest workflow covers duplicate review, manual merge, independent consent histories and ownership correction. The composed PostgreSQL HTTP regression checks that reservation write responses and reloads agree on the surviving identity, repointed reservations receive a new ETag and reject stale edits, and absorbed identities refuse consent/dismissal/undo writes with the final survivor pointer.
Property business dates, automatic and manual night audits are described in the workflow contract and ADR 0043. The night audit keeps the existing check-in charge timing; local-time scheduling, configured automatic no-shows and fee-free expired options share the same durable day-close path.
Channex composition shares ownsChannexWrites in http/channex.ts across booking import,
ARI, Messages and worker safety jobs. It checks production/write enablement before reading
the current installation marker on every call. channexVault in http/integration-secrets.ts wires the instance and
current/legacy keyrings for connections, bookings and ARI; Messages retains its larger
record bound through channexMessageVault.
Guest data rights
Section titled “Guest data rights”http/privacy.ts composes guest data rights (ADR 0052) for the API and the worker: the country
packs that http/country-packs.ts checks once for both runtimes, the Messages content and
maintenance photo modules, the booking-import vault that opens archived revisions (bound to their
workspace and revision identifier; one it cannot open is exported as null), and
privacy/export-archive.ts, which writes an export’s ZIP of guest-data.json and a CRLF
guest-data.csv with node:zlib. Jobs are queued in the caller’s transaction, and a committed
export request wakes the request pump. The worker schedules privacy.dispatch every hour, and a
hosted drain queues it at its start; it claims each due workspace’s retention pass alone, and
privacy.retention and privacy.export run only while ownsBackgroundWrites holds. Messages
takes its default content period from the workspace’s retention settings.
API keys and webhooks
Section titled “API keys and webhooks”The HTTP runtime composes API key administration (digests from auth) and webhook endpoint
administration (http/webhooks.ts); the principal resolver and the account’s workspaces resolve
credentials through auth.authenticate, which spends the budgets of keys, and the resolver audits
a key sent to another workspace or refused by an operation. http/integration-secrets.ts
seals Channex values and webhook signing secrets under separate labels. The worker schedules
webhooks.dispatch every minute and runs webhooks.deliver per workspace, both only while
ownsBackgroundWrites holds (not a preview, and the database’s instance record is this instance’s),
through http/webhook-sender.ts: node:https with agents of its own (no proxy from the
environment), Standard Webhooks signatures, no redirects, a 10-second deadline that destroys the
request, and http/destinations.ts, which refuses non-public addresses at registration and every
resolved address when the socket connects. credentials:reencrypt also rewrites endpoint signing
secrets. GUESTCRAFT_WEBHOOKS_ALLOW_PRIVATE=true (development
only) allows private destinations and http:. See
API keys and webhooks.
OAuth applications
Section titled “OAuth applications”http/oauth.ts composes OAuth applications: the consent page’s runtime methods
(oauthConsent, completeOAuthConsent), which verify the signed authorization request and the
browser session, resolve the member’s principal in the chosen workspace, store the grant through
core/integrations and complete Better Auth’s consent with it; and the discovery documents at the
public origin, /.well-known/oauth-authorization-server/api/auth and
/.well-known/oauth-protected-resource/api/v1|mcp, readable from any origin with a five-minute
cache. GUESTCRAFT_OAUTH_OPEN_REGISTRATION (default true) lets applications register without
signing in. The worker runs oauth.cleanup, a bounded pass of the applications’ lifetime rules,
every hour and at each hosted drain. See
OAuth applications.
Audit automation and cash drawers
Section titled “Audit automation and cash drawers”Night audit settings, configured no-shows and expired tentative options share the durable property-date audit workflow. Cash drawer shifts provide currency-aware opening float, server-linked cash activity and versioned counted closure. See the night audit contract and cash shift contract for public operations, permissions, recovery, persistence and the Vercel daily-trigger cadence limit.
The HTTP runtime wires the browser-only tenancy gateway and the cross-organization workspace directory into API 6.8.0 and the shell. Operational workspace selection resolves live membership for the requested workspace; archiving excludes it from operational principals while keeping the organization/workspace administrative routes available for recovery.
Instance-administrator recovery
Section titled “Instance-administrator recovery”pnpm --filter @guestcraft/server instance-admin list|grant usr_ID|revoke usr_ID is a
protected maintenance command using owner credentials and the private setup code on stdin.
It checks the installation identity and refuses to revoke the final administrator. See the
recovery procedure. The role gives no
organization data access.
Stored files and object storage
Section titled “Stored files and object storage”With GUESTCRAFT_FILE_STORAGE set to s3 or vercel_blob, the web and the worker give db’s
file store an adapter of @guestcraft/storage; the worker schedules files.collect_orphans
every ten minutes (and a hosted drain enqueues it), which lists one page of the instance’s prefix of the store at most
once an hour and deletes objects without a row after 24 hours. pnpm --filter @guestcraft/server files:move -- --to object|database moves the stored bytes with owner credentials, in resumable
batches, and prints how many files it moved. See object storage.