Skip to main content

SYNC_CONTRACT — bff-consumer-service

Sibling: SERVICE_OVERVIEW · DATA_MODEL

Cross-cutting: 02 Enterprise Architecture §8 · ADR-0003 Electron Offline-First

Important. bff-consumer-service is never consumed by the Electron desktop app. The desktop is the authenticated staff backoffice; this BFF is the anonymous consumer-meta surface. No aggregate owned by this service is replicated to any client SQLite store. This document records that decision explicitly so the platform sync engine and audit tooling can confirm the BFF is out of scope.

1. Replication scope: NONE

Aggregate ownedReplicated to desktop SQLite?Conflict policy
GuestSessionnon/a (cookie-bound, server-side only)
SearchSessionnon/a (TTL 1 h, regenerated on demand)
Wishlist (anonymous)non/a (Phase 2 may sync to authenticated user, but not to desktop)
BookingHandoffnon/a (single-use, server-side only)
MetaPageViewnon/a (telemetry only)
ConversionFunnelEventnon/a (telemetry only)
BotScorenon/a (security audit, server-side only)

The platform sync registry (@ghasi/sync-protocol/registry.ts) must not include any bff-consumer.* aggregate. CI fails if a row is added.

2. Why this BFF is not in the sync graph

  1. No staff workflow on the consumer surface. The Electron desktop is for hotel staff. Anonymous guests never use the desktop. The shape this BFF returns has no relevance to a check-in board, housekeeping queue, or folio screen.
  2. No tenant scope. The desktop sync protocol is keyed on (tenantId, userId, deviceId, scope); this BFF emits cross-tenant view-models that don't fit a per-tenant cursor.
  3. No durable shared state. Caches and sessions are short-lived ergonomics. The only durable rows we own (handoff log, wishlist, telemetry, bot score) belong on the cloud only and have explicit retention policies.
  4. Trust boundary mismatch. The desktop's local DB is encrypted at rest with a device-bound key derived through iam-service. There is no anonymous-equivalent root-of-trust for the consumer surface.

3. Cross-BFF sync interaction (handoff handshake)

Although no aggregate is replicated, this BFF does participate in a one-shot handshake with bff-tenant-booking-service over the BookingHandoff token. That handshake is not a sync — it is:

  • Stateless verification (HMAC over a canonical signing string).
  • Single-use (replay-protected via handoff_replay_log and the consumed flag).
  • Cross-BFF only (never reaches the desktop).
bff-consumer mints ─► signed redirect URL ─► bff-tenant-booking verifies + consumes
│ │
│ │
▼ ▼
handoff_replay_log POST /internal/handoff/{id}/consume
(durable durable replay-guard) (marks consumed=true)

This is documented here for completeness because operators sometimes confuse it with sync; it is not.

4. Future-state sync candidates (Phase 2+)

CandidateTriggerConflict policy that would applyStatus
Authenticated Wishlist (per iam-service user)Phase 2 consumer accountslww+diff by addedAtDeferred; design lives in services/bff-consumer-service/_future/wishlist-auth-sync.md
Per-user search historyPhase 2lww+diffDeferred

Even when Phase 2 lands, none of these will sync to the Electron desktop; they will sync between consumer mobile and consumer web via the iam-service user account.

5. 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-consumer aggregates', () => {
for (const entry of syncRegistry) {
expect(entry.serviceName).not.toBe('bff-consumer-service');
}
});

This test runs against every PR in the documentation repo; landing this bundle does not require any registry change.

6. 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.