SYNC_CONTRACT — bff-tenant-booking-service
Sibling: SERVICE_OVERVIEW · DATA_MODEL
Cross-cutting: 02 Enterprise Architecture §8 · ADR-0003 Electron Offline-First
Important.
bff-tenant-booking-serviceis never consumed by the Electron desktop app. The desktop is the authenticated staff backoffice surface (served bybff-backoffice-service); this BFF is the tenant-branded guest booking surface. No aggregate owned by this service is replicated to any client SQLite store.
1. Replication scope: NONE
| Aggregate owned | Replicated to desktop SQLite? | Conflict policy |
|---|---|---|
TenantBootstrap (cache) | no | n/a (cloud-only composition cache) |
BookingDraft | no | n/a (Memorystore-only; mobile/web SPAs hold their own client-side state) |
BookingHandoffArrival | no | n/a (single-use server-side ledger) |
MarketingAttribution | no | n/a (server-side session blob) |
LoyaltyContext | no | n/a (read-through from iam-service, not replicated) |
ConfirmationView (cache) | no | n/a (server-side view-model) |
BookingDraftSnapshot | no | n/a (analytics cold mirror, server-side only) |
The platform sync registry (@ghasi/sync-protocol/registry.ts) must not include any bff-tenant-booking.* aggregate. CI fails if a row is added.
2. Why this BFF is not in the sync graph
- Mobile guest apps don't sync. The tenant-booking mobile app holds short-lived in-memory + local-storage state for the active funnel only. There is no offline booking flow — payment redirects, hold/confirm calls, and quote issuance all require a live network. Loss of connectivity returns the user to a "reconnect to continue" screen.
- Booking draft is server-authoritative. The BFF holds the canonical
BookingDraftin Memorystore so any tab refresh, page navigation, or mobile-app cold-start can reconstruct the funnel fromGET /draft/{id}. The client never owns the draft. - Trust boundary mismatch with the desktop. The Electron desktop's local DB is encrypted at rest with a device-bound key derived through
iam-service. The tenant-booking surface is anonymous (Phase 1) or guest-authenticated (Phase 2+); neither has the device-binding root-of-trust. - Money rails require live verification. Confirming a booking depends on
payment-gateway-servicereturning a verified payment status. Replaying that decision offline would either over-charge guests or under-confirm reservations.
3. Cross-BFF handshake (handoff arrival)
The BFF participates in a one-shot inbound handshake with bff-consumer-service over the BookingHandoff token. This is not a sync — it is:
- Stateless verification (HMAC over a canonical signing string with shared symmetric key from Secret Manager).
- Single-use (replay-protected via
handoff_arrival_logand theconsumedboolean). - Cross-BFF only (never reaches the desktop or any client SQLite store).
bff-consumer mints ─► signed redirect URL ─► guest browser arrives
│
▼
POST /handoff/consume
(HMAC verify + consumed flag)
│
▼
bootstrap returns handoffPayload
The handshake is documented here for completeness because operators sometimes confuse it with sync; it is not.
4. Mobile-app local state (NOT sync, just local cache)
The React Native client holds a small local cache (AsyncStorage) for UX continuity only:
| Local key | Source | TTL | What happens on conflict |
|---|---|---|---|
tnt_id | server-set cookie equivalent | until app reset | n/a (server-authoritative) |
last_locale | local pref | until user changes | server wins on next bootstrap |
last_currency | local pref | until user changes | server wins on next bootstrap |
recent_search | local UX | 7 d | server-side recentlyViewed (Phase 2 if added) wins |
bookmarked_property | local fav | until user removes | merged on Phase 2 sign-in |
None of these are replicated to or from the BFF beyond the standard request/response cycle.
5. Future-state sync candidates (Phase 2+)
| Candidate | Trigger | Conflict policy that would apply | Status |
|---|---|---|---|
Authenticated guest profile (iam-service user) | Phase 2 guest sign-in | n/a (owned by iam-service, not this BFF) | n/a |
| Multi-device booking-draft resumption (e.g., start on mobile, continue on web) | Phase 2 | server-authoritative draft (no merge needed) | Deferred; no design owner |
| Loyalty wallet (points balance) | Phase 2 | n/a (owned by future loyalty-service) | n/a |
Even when Phase 2 lands, none of these will sync to the Electron desktop through this BFF; staff workflows live exclusively in bff-backoffice-service.
6. Compliance with the platform sync registry
The @ghasi/sync-protocol/registry.ts file enumerates every aggregate the platform replicates. The CI check sync-registry-allowlist.spec.ts asserts:
import { syncRegistry } from '@ghasi/sync-protocol/registry';
it('does not include bff-tenant-booking aggregates', () => {
for (const entry of syncRegistry) {
expect(entry.serviceName).not.toBe('bff-tenant-booking-service');
}
});
This test runs against every PR in the documentation repo; landing this bundle does not require any registry change.
7. Operational note for SRE
If the platform's per-aggregate conflict-policy matrix in 02 §8.2 is regenerated from per-service SYNC_CONTRACT.mds, this service contributes zero rows. The matrix-builder script must filter on replicatedAggregates.length > 0.