EVENT_SCHEMAS — bff-tenant-booking-service
Sibling: API_CONTRACTS · DATA_MODEL · DOMAIN_MODEL
Cross-cutting: 04 Event-Driven Architecture · Standards · NAMING
1. Scope and posture
This BFF emits only telemetry events under the melmastoon.bff.tenant.* subject prefix. It emits no domain events — the booking domain belongs to reservation-service, money belongs to payment-gateway-service, theme belongs to theme-config-service. The BFF subscribes to a small set of platform events solely for cache invalidation.
| Aspect | Posture |
|---|---|
| Backbone | GCP Pub/Sub |
| Outbox | Postgres outbox table (per-service, drained by outbox-relay worker) |
| Delivery semantics | At-least-once; consumers must dedupe by eventId |
| Subject pattern | melmastoon.bff.tenant.<aggregate>.<verb-past-tense>.v<n> |
| Retention class | operational for bootstrap.*, flow.*, locale.*, currency.*; audit for handoff.consumed.v1; regulated for booking.draft.converted.v1, payment_intent.created.v1, confirmation.viewed.v1 (regulated because they are evidence of consumer-facing booking events) |
| Schema registry | Schemas live in @ghasi/event-envelope/schemas/bff-tenant/ and are validated in CI |
| PII posture | Guest fields hashed unless explicitly opted-in for analytics; raw email/phone NEVER on telemetry |
2. Event envelope
{
"envelope": {
"eventId": "evt_01H8YN7QV4D8KZ4F8Y5CK4MV3D",
"subject": "melmastoon.bff.tenant.booking.draft.created.v1",
"version": 1,
"occurredAt": "2026-04-23T09:14:22.041Z",
"publishedAt": "2026-04-23T09:14:22.058Z",
"producer": "bff-tenant-booking-service",
"producerInstance": "bff-tenant-asia-south1-7f8d9c-x4z2",
"tenantId": "tnt_01H8Y...",
"userId": null,
"sessionId": "tnt_session_01H8Y...",
"requestId": "req_01H8YN7QF9RTBRZG4F8Y5CK4MV",
"traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
"causationId": "evt_01H8YN7QV4D8KZ4F8Y5CK4MV3C",
"correlationId": "req_01H8YN7QF9RTBRZG4F8Y5CK4MV",
"schemaUri": "https://schemas.melmastoon.ghasi.io/bff-tenant/booking-draft-created/v1.json",
"retentionClass": "regulated",
"samplingRate": 1.0,
"marketingAttribution": { "source": "google", "medium": "cpc", "campaign": "spring-2026" }
},
"payload": { /* event-specific */ }
}
tenantId is always non-null. marketingAttribution is added at the envelope level for funnel reconstruction across funnel-step events.
3. Events published
3.1 melmastoon.bff.tenant.bootstrap.served.v1
When: GET /bootstrap succeeded for a session (debounced at 1× per session per 5 min).
Sample rate: 100% (after debounce)
Retention: operational (90 days)
{
"tenantId": "tnt_01H8Y...",
"tenantSlug": "kabul-grand-hotel",
"sessionId": "tnt_session_01H8Y...",
"locale": "ps-AF",
"currency": "AFN",
"themeVersion": "thm_01H8Y_v3",
"tenantConfigVersion": "v12",
"fanoutLatencyMs": { "tenant": 14, "theme": 38, "policies": 22 },
"cache": "MISS",
"handoffArrivalId": "bha_01H8Y..."
}
3.2 melmastoon.bff.tenant.handoff.consumed.v1
When: Inbound BookingHandoffArrival consumed.
Sample rate: 100%
Retention: audit (1 year)
{
"tenantId": "tnt_01H8Y...",
"handoffArrivalId": "bha_01H8Y...",
"consumerSessionId": "gms_01H8Y...",
"propertyId": "ppt_01H8Y...",
"campaign": { "source": "google", "medium": "cpc", "campaign": "spring-2026" },
"mintedAt": "2026-04-23T09:14:00.000Z",
"consumedAt": "2026-04-23T09:14:22.000Z",
"elapsedMs": 22000,
"hmacSignatureFingerprint": "sha256:9c8b..."
}
3.3 melmastoon.bff.tenant.booking.draft.created.v1
When: First successful POST /hold for a session.
Sample rate: 100%
Retention: regulated (3 years)
{
"tenantId": "tnt_01H8Y...",
"draftId": "bdr_01H8Y...",
"sessionId": "tnt_session_01H8Y...",
"reservationId": "rsv_01H8Y...",
"holdExpiresAt": "2026-04-23T10:14:22.041Z",
"propertyId": "ppt_01H8Y...",
"roomTypeId": "rmt_01H8Y...",
"ratePlanId": "rate_01H8Y...",
"stayWindow": { "checkIn": "2026-05-20", "checkOut": "2026-05-22", "nights": 2 },
"occupancy": { "adults": 2, "children": 0, "rooms": 1 },
"totalDisplay": { "currency": "AFN", "amountMinor": 12500 },
"promoCode": "SPRING25",
"deviceClass": "browser-mobile",
"handoffArrivalId": "bha_01H8Y..."
}
3.4 melmastoon.bff.tenant.booking.draft.abandoned.v1
When: Draft TTL expired without conversion (sweep job emits).
Sample rate: 100%
Retention: regulated (3 years)
{
"tenantId": "tnt_01H8Y...",
"draftId": "bdr_01H8Y...",
"sessionId": "tnt_session_01H8Y...",
"lastFlowState": "collecting_details",
"createdAt": "2026-04-23T09:14:22.041Z",
"abandonedAt": "2026-04-23T09:46:11.000Z",
"lastActivityAt": "2026-04-23T09:18:00.000Z",
"propertyId": "ppt_01H8Y...",
"roomTypeId": "rmt_01H8Y...",
"totalDisplayPotential": { "currency": "AFN", "amountMinor": 12500 },
"errorTrail": [],
"guestEmailHash": "sha256:..."
}
3.5 melmastoon.bff.tenant.booking.draft.converted.v1
When: Draft transitions to confirmed.
Sample rate: 100%
Retention: regulated (7 years)
{
"tenantId": "tnt_01H8Y...",
"draftId": "bdr_01H8Y...",
"sessionId": "tnt_session_01H8Y...",
"reservationId": "rsv_01H8Y...",
"createdAt": "2026-04-23T09:14:22.041Z",
"convertedAt": "2026-04-23T09:21:33.000Z",
"elapsedMs": 431000,
"propertyId": "ppt_01H8Y...",
"roomTypeId": "rmt_01H8Y...",
"ratePlanId": "rate_01H8Y...",
"totalCapturedMinor": 12500,
"totalCapturedCurrency": "AFN",
"paymentMethod": "card",
"paymentProvider": "adyen",
"marketingAttribution": { "source": "google", "medium": "cpc", "campaign": "spring-2026" },
"handoffArrivalId": "bha_01H8Y...",
"deviceClass": "browser-mobile"
}
3.6 melmastoon.bff.tenant.payment_intent.created.v1
When: PaymentIntent created via gateway.
Sample rate: 100%
Retention: regulated (7 years)
{
"tenantId": "tnt_01H8Y...",
"draftId": "bdr_01H8Y...",
"intentId": "pyi_01H8Y...",
"method": "card",
"provider": "adyen",
"amountMinor": 12500,
"currency": "AFN",
"redirectFlow": true,
"createdAt": "2026-04-23T09:18:11.000Z"
}
3.7 melmastoon.bff.tenant.confirmation.viewed.v1
When: GET /confirmation/{reservationId} rendered.
Sample rate: 100% (debounced 1× per reservation per session per 24 h)
Retention: operational (90 days)
{
"tenantId": "tnt_01H8Y...",
"reservationId": "rsv_01H8Y...",
"sessionId": "tnt_session_01H8Y...",
"viewedAt": "2026-04-23T09:21:55.000Z",
"elapsedFromConfirmMs": 22000,
"deviceClass": "browser-mobile",
"locale": "ps-AF",
"cache": "HIT"
}
3.8 melmastoon.bff.tenant.flow.step_completed.v1
When: Flow step transitioned forward.
Sample rate: 25% (high-cardinality)
Retention: operational (30 days)
{
"tenantId": "tnt_01H8Y...",
"draftId": "bdr_01H8Y...",
"sessionId": "tnt_session_01H8Y...",
"fromState": "quoting",
"toState": "holding",
"stepLatencyMs": 412,
"completedAt": "2026-04-23T09:14:22.041Z"
}
3.9 melmastoon.bff.tenant.flow.error_encountered.v1
When: Any flow step returned an error visible to the user.
Sample rate: 100%
Retention: operational (90 days)
{
"tenantId": "tnt_01H8Y...",
"draftId": "bdr_01H8Y...",
"sessionId": "tnt_session_01H8Y...",
"atState": "paying",
"errorCode": "MELMASTOON.PAYMENT.DECLINED",
"upstream": "payment-gateway-service",
"occurredAt": "2026-04-23T09:18:33.000Z",
"userActionable": true,
"retryAfterSec": 0
}
3.10 melmastoon.bff.tenant.locale.changed.v1
When: Locale changed mid-session.
Sample rate: 100%
Retention: operational (90 days)
{
"tenantId": "tnt_01H8Y...",
"sessionId": "tnt_session_01H8Y...",
"from": "ps-AF",
"to": "fa-AF",
"isRtlChange": false,
"changedAt": "2026-04-23T09:14:22.041Z"
}
3.11 melmastoon.bff.tenant.currency.changed.v1
When: Display currency changed.
Sample rate: 100%
Retention: operational (90 days)
{
"tenantId": "tnt_01H8Y...",
"sessionId": "tnt_session_01H8Y...",
"from": "AFN",
"to": "USD",
"draftId": "bdr_01H8Y...",
"changedAt": "2026-04-23T09:14:22.041Z"
}
4. Events consumed
The BFF consumes platform events solely for cache invalidation:
| Subject | Effect |
|---|---|
melmastoon.theme.published.v1 | cache.invalidate('tenant-bootstrap:<tenantId>:*') |
melmastoon.tenant.config_updated.v1 | invalidate bootstrap + slug map for the tenant |
melmastoon.tenant.suspended.v1 | mark slug suspended in cache; subsequent bootstrap returns 503 |
melmastoon.pricing.rate_plan.published.v1 | invalidate cheapest: + availability: for affected (tenantId, propertyId) |
melmastoon.inventory.allocation.committed.v1 | invalidate availability: for affected (tenantId, propertyId, dateRange) |
melmastoon.iam.session.revoked.v1 (Phase 2+) | drop loyalty context for matching authenticated session |
Subscriptions named: bff-tenant-booking.<topic>. Inbox table dedupes by eventId.
5. PII rules
email,phone,nationalIdNumberare NEVER in raw form on telemetry. SHA-256 hash with per-environment pepper, prefixedsha256:.firstName,lastNameare NEVER in telemetry; analytics receives onlyguestNameInitialsHash.addressandcountry(when collected for billing) are NEVER in telemetry.marketingAttributionis acceptable in plain text (UTM is intentionally non-sensitive).
PII test (test/integration/telemetry-pii.spec.ts) asserts every event payload against a strict allow-list and fails CI if any disallowed field appears.
6. Sampling configuration
Sample rates are configuration, not code. They live in bff-tenant-flags Memorystore key, refreshable in 30 s. Defaults:
| Subject | Default sample rate | Override conditions |
|---|---|---|
bootstrap.served.v1 | 1.0 (after 5-min session debounce) | none |
handoff.consumed.v1 | 1.0 | always |
booking.draft.created.v1 | 1.0 | always |
booking.draft.abandoned.v1 | 1.0 | always |
booking.draft.converted.v1 | 1.0 | always |
payment_intent.created.v1 | 1.0 | always |
confirmation.viewed.v1 | 1.0 (debounced) | always |
flow.step_completed.v1 | 0.25 | raise to 1.0 during incident analysis |
flow.error_encountered.v1 | 1.0 | always |
locale.changed.v1 | 1.0 | always |
currency.changed.v1 | 1.0 | always |
7. Schema evolution rules
- Additive fields ship under same
v1. - Removed or renamed fields require a new
v2topic;v1retained for at least 90 days with dual-publish. - Type changes always require
v2. - Schema diff CI gate enforces.
8. Replay + back-fill
Telemetry events have no replay semantics for downstream consumers; analytics consumers idempotently aggregate by (eventId, draftId). If a telemetry event is lost (impossible with at-least-once + outbox + DLQ, but defensively), no domain effect occurs.
9. DLQ posture
All bff-tenant.* topics have a DLQ subscription bff-tenant-dlq.<topic> with 7-day retention. Failed delivery alerts at queue-depth > 50.