Skip to main content

EP-011 — Booking Engine RTM

EPIC: EP-011 Booking Engine Reconciled: 2026-05-25 Wave 1 of per-EPIC RTM scale-out (21 parallel EPICs) — see ./PLAYBOOK.md for the pattern derived from the BF-001 pilot. Source of truth for "% passing": hmp-docs/docs/platform-registry/2026-05-16-13-flow-demo-readiness.md until Task #12 re-measures.

Status legend​

StatusMeaning
BuiltCode exists + tests exist + tests pass
Built-UntestedCode exists, no automated test coverage (or test exists but is a stub)
PartialCode exists for some acceptance criteria, gaps for others
StubCode is a placeholder / TODO / returns mock data
Not StartedNo code

Summary​

MetricCount
Total stories (catalog)16
Catalog claims "Built"12
Catalog claims "Partial"3
Catalog claims "Not Started"1
Verified-Built10 (US-011-001, -002, -003, -005, -009, -010, -011, -012, -014, -016)
Built-Untested1 (US-011-013 — widget/iframe/standalone modes; no automated cross-mode tests)
Partial4 (US-011-004, -006, -007, -008)
Stub0
Not Started1 (US-011-015 — conversion funnel analytics)
Drift findings13 (D-001..D-013; 6 P0 / 7 P1 / 0 P2)
[SME-PROPOSED] refinements29

Major reviewer-stale alert: 8 of 9 review files in reviews/EP-011/ were authored before the 2026-04 to 2026-05 booking-engine maturity wave (runtime host resolution 2026-05-18, ApiKeyService rotation AP-T03-002 2026-04-29, payment-service proxy initiate-v2 2026-04-15, SE-Asia locales registered in i18n.ts, LanguageSwitcher shipped, promo-codes module shipped, gateway routes for /booking-engine + /booking-engine-config added). Many P0 review verdicts are now wrong. Drift D-001..D-006 below capture this.


US-011-001 — Room Search with Availability Calendar​

Catalog status: Built (P0, 5 points) Verified status: Built

Code locations:

  • hmp-booking-engine/src/components/SearchForm.tsx — Date/guest/promo entry (Vite + React)
  • hmp-booking-engine/src/components/booking/DateRangePicker.tsx — Calendar with past-date block
  • hmp-booking-engine/src/components/booking/GuestSelector.tsx — Adults/children/rooms
  • hmp-booking-engine/src/components/booking/RoomList.tsx + RoomCard.tsx — Results render
  • hmp-booking-engine/src/hooks/useAvailability.ts — TanStack Query, 2-min staleTime
  • hmp-reservation-service/src/booking-engine/booking-engine.controller.ts:70-91 — GET /booking-engine/availability + POST /booking-engine/search/availability (both @SetMetadata(REQUIRE_API_KEY, true) per W0.2.2 2026-05-06)
  • hmp-reservation-service/src/booking-engine/booking-engine.service.ts — checkAvailability(), searchAvailability()
  • hmp-property-service/src/booking-engine-support/booking-engine-support.controller.ts — Internal aggregated GET /properties/:propertyId/booking-engine/availability-snapshot (service-account JWT)
  • hmp-api-gateway/src/config/services.config.ts:1257 — /booking-engine prefix → reservation-service

Unit + integration tests:

  • hmp-booking-engine/src/components/__tests__/SearchForm.test.tsx — 6 specs (header, labels, selector, button disabled, promo)
  • hmp-booking-engine/src/components/__tests__/RoomCard.test.tsx — 22 specs (info, gallery, amenities, urgency badge, rate plan selection)
  • hmp-reservation-service/src/booking-engine/booking-engine.service.spec.ts — covers checkAvailability + searchAvailability
  • hmp-property-service/src/booking-engine-support/booking-engine-support.service.spec.ts — aggregation tests

Flow tests:

  • hmp-infrastructure/testing/flow-tests/src/flows/flow-3-booking-engine.spec.ts :: "search → results" — PARTIAL (per 13-flow doc 2026-05-16; pass count TBD)
  • hmp-infrastructure/testing/flow-tests/src/flows/booking-engine.flow.spec.ts :: full flow — TBD
  • hmp-infrastructure/testing/flow-tests/src/flows/booking-engine-selfservice.flow.spec.ts — self-service path

E2E (Playwright):

  • hmp-booking-engine/e2e/booking-flow.spec.ts — search + results
  • hmp-booking-engine/e2e/booking-full-flow.spec.ts — full booking flow
  • hmp-booking-engine/e2e/host-bootstrap.spec.ts — runtime host resolution

SME notes:

  • Review file (US-011-001-review.md) called this PASS_WITH_NOTES citing "no gateway route" — that gap is closed as of services.config.ts:1257. Verdict refresh needed.
  • Backend path/param naming mismatch flagged in GAP-6 of the review (/booking-engine/search/availability vs /availability) is resolved — both paths exist in booking-engine.controller.ts.
  • 30-night max-stay rule mentioned in GAP-3 of review file is still enforced only at backend search time, not at calendar disable. Real-world tenants regularly want to override per-property (long-stay corporate, leisure resort 60-night plans).
  • Calendar response uses staleTime: 2 * 60 * 1000 (2 min) — for high-demand peak nights (NYE, Diwali), 2 min is long enough that two guests can both see "1 room left" simultaneously and one will fail at checkout. Lock-or-quote pattern is undocumented.

Drift findings:

D-001 — [P0] [Stale-review + catalog-honest] reviews/EP-011/US-011-006-review.md and …/US-011-007-review.md say "FAIL — no /booking-engine gateway route" but the routes prefix: '/booking-engine' (line 1257) and prefix: '/booking-engine-config' (line 1249) exist in hmp-api-gateway/src/config/services.config.ts, with matching ProxyController @All decorators. The reviews are ~6 months stale. A new reviewer reading these alone reopens closed P0 work.

  • [SME-PROPOSED] Refresh all 9 EP-011 review files against current master. Drop FAIL verdicts that depend on the "no gateway route" claim. Add a Last verified against master header on each review file.

D-002 — [P1] [Story-incomplete + India-hospitality-critical] US-011-001 AC body does not mention inventory holds or quote freezing. A high-demand date (NYE/Diwali) lets two browsers both see availableCount=1 from a 2-min-stale React-Query cache and both proceed to checkout. The 2026-05-19 BookingTrip multi-leg cart shipped BookingTrip.expiresAt = now + 10min for chain hub trips, but single-property search has no equivalent.

  • [SME-PROPOSED] Add AC: "Search results SHALL display a 'Reserved for 10 minutes' badge for any room-type-night combination with availableCount ≤ 3. When the guest clicks 'Select', POST /availability/hold SHALL create a Redis-backed inventory hold tied to the session, and the room cards SHALL switch to 'Reserved for you' state. Holds expire 10 min after creation and release back to inventory."

US-011-002 — Rate Plan Display & Comparison​

Catalog status: Built (P0, 3 points) Verified status: Built

Code locations:

  • hmp-booking-engine/src/components/booking/RatePlanSelector.tsx — Rate plan cards
  • hmp-booking-engine/src/components/booking/RoomList.tsx — Compare view
  • hmp-booking-engine/src/components/booking/RoomCard.tsx — Rate plan child
  • hmp-reservation-service/src/booking-engine/booking-engine.controller.ts:103-110 — GET /booking-engine/rate-plans/:propertyId
  • hmp-property-service/src/booking-engine-support/booking-engine-support.controller.ts — GET /properties/:propertyId/booking-engine/rate-plans

Unit + integration tests:

  • hmp-booking-engine/src/components/__tests__/PriceBreakdown.test.tsx — 37 specs (BookingSummary, PriceDisplay, currency, GST, discounts)
  • hmp-booking-engine/src/components/__tests__/RoomCard.test.tsx — 22 specs include rate plan selection
  • hmp-reservation-service/src/booking-engine/booking-engine.service.spec.ts — getRatePlans() + searchAvailability rate-plan binding

Flow tests:

  • hmp-infrastructure/testing/flow-tests/src/flows/flow-3-booking-engine.spec.ts — search results include rate plans

E2E (Playwright):

  • hmp-booking-engine/e2e/booking-flow.spec.ts — rate plan selection step

SME notes:

  • BookingSummary.tsx flat-12%-GST hardcoding (US-011-001 review GAP-2; US-011-004 review GAP-6; US-011-008 review AC-11) is real today. GST slab is per-room-tariff: 0% (room ≤ ₹1,000/night), 12% (₹1,001-7,500), 18% (₹7,501+). Mis-quoting GST = guest sees wrong total = post-booking dispute + DPDPA risk on misleading-info ground.
  • "Member Rate" rate-plan is shown to non-members with a lock icon — but US-011-002 AC body specifies the lock; story is silent on whether the lock click links to login or to membership signup. Today: links to login (/loyalty/login).
  • "Best Value" badge logic lives in RoomCard.tsx as a client-side comparison — no server-side authoritative isRecommended flag. A revenue manager can't force-highlight a specific plan.

Drift findings:

D-003 — [P0] [India-hospitality-critical + tests-assert-behavior-story-doesnt-describe] The 22-spec PriceBreakdown.test.tsx asserts INR formatting per Intl.NumberFormat('en-IN') lakhs notation, but no spec asserts the per-tariff GST slab that the story body explicitly states (12% for ≤7,500 INR; 18% for 7,501+). BookingSummary.tsx instead hardcodes subtotal * 0.12. The Razorpay payment amount the guest pays is therefore wrong by 6 percentage points for any room ≥ ₹7,501/night.

  • [SME-PROPOSED] Add AC: "GST SHALL be computed per per-night rate using the slab { ≤₹1,000: 0%, ₹1,001-7,500: 12%, ≥₹7,501: 18% }. The slab determination SHALL use the post-discount per-night rate (not the pre-discount), so that a promo dropping a ₹8,000 rate to ₹7,000 re-slots to 12%. Coverage: 6 new specs in PriceBreakdown.test.tsx covering boundary cases (999/1000/1001/7499/7500/7501)."
  • [SME-PROPOSED] Add AC: "RatePlan SHALL expose a tenant-managed isRecommended boolean. BookingSummary + RatePlanSelector SHALL render the 'Best Value' badge from this flag, not from client-side comparison. Revenue managers can override the algorithmic recommendation per property + per season."

US-011-003 — Guest Details Form with Validation​

Catalog status: Built (P0, 3 points) Verified status: Built

Code locations:

  • hmp-booking-engine/src/components/GuestForm.tsx — Guest details form (name, email, phone, ID, GST)
  • hmp-booking-engine/src/components/__tests__/GuestForm.test.tsx — Form spec
  • hmp-reservation-service/src/booking-engine/booking-engine.service.ts — lookupGuestByEmail() returning-guest pre-fill
  • hmp-reservation-service/src/booking-engine/dto/booking-engine.dto.ts — CreateBookingDto field validation

Unit + integration tests:

  • hmp-booking-engine/src/components/__tests__/GuestForm.test.tsx

Flow tests:

  • hmp-infrastructure/testing/flow-tests/src/flows/flow-3-booking-engine.spec.ts — guest details step

E2E (Playwright):

  • hmp-booking-engine/e2e/booking-flow.spec.ts — guest entry

SME notes:

  • Aadhaar masking-for-display (XXXX-XXXX-9012) is a story-body promise — verified in form code, but no automated assertion that the API-submitted body drops the masked digits. If the masked string is what hits guest-service, the row stores XXXX-XXXX-9012 which fails C-Form lookup at check-in for international guests.
  • GSTIN regex enforcement (per US-002-001 D-003 from EP-002 RTM) is owned by property-service / guest-service DTO — booking-engine should reuse the same validator, not roll its own. Today the form's validation is client-side only.
  • Phone field auto-prepends +91 per AC-03 — but no India-vs-foreign branching. A guest in the US booking from a US IP cannot enter +1-555-… from the form (auto-prepend overwrites). Today: foreign guests have to switch their browser locale to en-US to bypass.

Drift findings:

D-004 — [P1] [Story-incomplete + DPDPA + India-hospitality-critical] Aadhaar masking is referenced in display but not in API submission. DPDPA art. 8 + Aadhaar Act §29(2) require the last 4 digits only be persisted unless a legitimate purpose-bound consent is captured. Today there is no audit that the booking flow does not transmit + persist full Aadhaar to guest-service.

  • [SME-PROPOSED] Add AC: "When idType=AADHAAR, the booking engine SHALL transmit only the last 4 digits + a SHA-256 hash of the full number (for de-duplication at check-in). Display masking and storage masking SHALL be independently asserted by 2 separate specs (1 frontend transmit spec + 1 backend persist spec). A consentGivenForFullAadhaar checkbox is required to transmit the full number."
  • [SME-PROPOSED] Add AC: "Phone country code SHALL be a separate field (default +91 when Accept-Language=hi-IN|en-IN, else inferred from browser Intl.DateTimeFormat().resolvedOptions().timeZone). Foreign guests booking Indian properties SHALL be able to enter their home country phone without the +91 auto-prepend breaking them."

US-011-004 — Razorpay Payment Integration​

Catalog status: Partial (P0, 8 points) Verified status: Partial — initiate-v2 proxy + payment-service shipped (2026-04-15); legacy direct-Razorpay path still in usePayment hook; webhook + refund + amount-revalidation gaps remain

Code locations:

  • hmp-booking-engine/src/components/booking/PaymentMethods.tsx — UPI/card/netbanking/wallet selection
  • hmp-booking-engine/src/components/PaymentForm.tsx — Booking → payment orchestrator
  • hmp-booking-engine/src/hooks/useRazorpayCheckout.ts — Razorpay SDK loader + dev-mock
  • hmp-booking-engine/src/lib/razorpay.ts — SDK adapter + verify call
  • hmp-reservation-service/src/booking-engine/booking-engine.controller.ts:225-237 — POST /booking-engine/payments/initiate-v2 (Phase 1 proxy to payment-service via PaymentServiceClient)
  • hmp-reservation-service/src/booking-engine/booking-engine.service.ts:1784 — initiatePaymentV2() resolves tenantId+propertyId from reservation, forwards to payment-service
  • hmp-payment-service/ — Owns /payments/initiate, /webhooks/razorpay-gateway (Phase 0 + 0.5 + 1, port 3014)
  • hmp-shared-packages/packages/payments/ — @hmp/payments@2.1.0 Razorpay adapter (interfaces only after BREAKING 1.x)

Unit + integration tests:

  • hmp-reservation-service/src/booking-engine/booking-engine.service.spec.ts — service-level
  • hmp-payment-service/.../payments.service.spec.ts — adapter + signature verification
  • hmp-booking-engine — no PaymentForm/PaymentMethods/useRazorpayCheckout specs (review GAP-2 confirmed)

Flow tests:

  • hmp-infrastructure/testing/flow-tests/src/flows/flow-3-booking-engine.spec.ts — payment step
  • hmp-infrastructure/testing/flow-tests/src/flows/payment-links.flow.spec.ts — payment-link flow (paired to US-011-007)
  • hmp-infrastructure/testing/flow-tests/src/flows/upi-qr.flow.spec.ts — UPI QR variant

E2E (Playwright):

  • hmp-booking-engine/e2e/booking-full-flow.spec.ts — checkout including payment-modal mock
  • hmp-booking-engine/e2e/payment-link-page.spec.ts, payment-link-otp.spec.ts — payment-link guest flow

SME notes:

  • Flag VITE_FEATURE_PAYMENT_SERVICE_ENABLED (default OFF per platform CLAUDE.md) controls whether the front-end calls initiate-v2 (proxy) vs the legacy direct path. Production has NOT flipped this on per the 2026-05-16 demo-readiness doc. The story is therefore "two paths, only one tested end-to-end against Razorpay sandbox".
  • Server-side amount-revalidation (review GAP-1) — fixed in initiate-v2 proxy (resolves total from reservation.totalAmount, NOT from client amount), but legacy direct path still trusts client total. Re-enabling tampering on the flag-off codepath.
  • Webhook (review GAP-3) — hmp-payment-service handles payment.captured and emits PAYMENT_CAPTURED event; hmp-frontdesk-service PaymentCapturedConsumer (20/20 specs) flips folio + reservation paymentStatus. End-to-end pipeline shipped 2026-05-19 (folio reconciliation 7-phase plan). Review's "webhook only returns { status: 'processed' }" is stale.
  • Refund flow (review GAP-other) — manual refund via dashboard exists; guest-self-service refund-on-cancel via cancelBooking() triggers but UI feedback is minimal.

Drift findings:

D-005 — [P0] [Catalog-honest + tampering-risk] Legacy direct-Razorpay path (flag OFF) trusts client-supplied amount which can be tampered. Even with VITE_FEATURE_PAYMENT_SERVICE_ENABLED=true shipped, production still runs OFF per the 2026-05-16 readiness snapshot. The fix shipped 2026-04-15 in code is unreachable in production.

  • [SME-PROPOSED] Add AC: "POST /booking-engine/payments/initiate (legacy path) SHALL be deprecated and return 410 GONE once initiate-v2 reaches GA. Until then, the legacy path SHALL re-derive amount from reservation.balanceDue server-side and reject any client-supplied amount mismatch with 400 code=PAYMENT_AMOUNT_TAMPERED."
  • [SME-PROPOSED] Add AC: "VITE_FEATURE_PAYMENT_SERVICE_ENABLED SHALL be promoted to default-ON for all environments by 2026-06-15, with the legacy path removed by 2026-07-15. The flip SHALL be gated by 7 days of initiate-v2 traffic = 100% in QA + staging with error_rate < 0.1%."

D-006 — [P1] [Test-vs-code + missing-coverage] Review GAP-2 documented "0 frontend tests for PaymentForm / PaymentMethods / useRazorpayCheckout" — still true at 2026-05-25. The single most error-prone step in the funnel has zero unit coverage. Payment-service has 20+ specs for the consumer side, but a UI regression that breaks Razorpay modal opening would not be caught until E2E.

  • [SME-PROPOSED] Add AC: "PaymentForm.test.tsx, PaymentMethods.test.tsx, useRazorpayCheckout.test.ts SHALL ship with ≥6 specs each covering: method selection, button disabled state, Razorpay open success, Razorpay open failure (network), verify call retry on 5xx, dev-mock-mode short-circuit. Coverage thresholds: ≥80% line + branch on payment-flow files."

US-011-005 — Booking Confirmation Page & Email​

Catalog status: Built (P0, 3 points) Verified status: Built

Code locations:

  • hmp-booking-engine/src/components/booking/ConfirmationPage.tsx — Confirmation render
  • hmp-booking-engine/src/components/booking/BookingConfirmation.tsx — Inner confirmation UI
  • hmp-reservation-service/src/booking-engine/booking-engine.controller.ts:296-305 — POST /booking-engine/notifications/booking-confirmation
  • hmp-reservation-service/src/booking-engine/booking-engine.service.ts — sendBookingConfirmation() publishes Kafka event
  • hmp-notification-service/ — Email render + delivery via SendGrid (now AWS SES per 2026-05-21 memory)
  • hmp-shared-packages/packages/template-engine — MJML block-tree renderer (powers confirmation email template)

Unit + integration tests:

  • hmp-reservation-service/src/booking-engine/booking-engine.service.spec.ts — sendBookingConfirmation Kafka publish test

Flow tests:

  • hmp-infrastructure/testing/flow-tests/src/flows/flow-3-booking-engine.spec.ts — confirmation step

E2E (Playwright):

  • hmp-booking-engine/e2e/booking-full-flow.spec.ts — verifies confirmation page renders post-payment

SME notes:

  • PDF voucher mentioned in story body — generator lives in hmp-shared-packages/packages/common/src/printing/ (per platform CLAUDE.md, generateFolioReceipt/generatePosReceipt are ESC/POS for thermal printers). A guest-facing PDF voucher path is NOT enumerated in source map — likely served from hmp-frontdesk-service invoice-pdf module. Verify cross-link.
  • QR code for mobile check-in (story body) — token-mint endpoint exists at POST /booking-engine/auth/notification-token (notification-token.service.ts) issuing 1-hour JWT for Socket.IO. But QR-on-voucher rendering not enumerated in code paths.
  • Confirmation email Indian-locale formatting (CGST/SGST/IGST split, IST timestamps, ₹ with lakhs) is the template-engine's responsibility. The story does not specify which CGST/SGST/IGST split logic to use — same gap as EP-002 D-008.

Drift findings: (none net new — feeds D-003 GST gap; deferred to fact-check)

  • [SME-PROPOSED] Add AC: "Confirmation email + PDF voucher SHALL break GST into CGST+SGST (intra-state) vs IGST (inter-state) based on (property GSTIN state-code, guest billing-address state). The split SHALL be unit-tested via confirmation-email.template.spec.ts for both paths."

US-011-006 — Connect to Live Backend API (Replace Mock)​

Catalog status: Not Started (P0, 8 points) Verified status: Partial — gateway routes shipped, bookingApi.ts toggles via VITE_API_URL, runtime host resolution (2026-05-18) ships the real flow; mock layer still present for dev

Code locations:

  • hmp-api-gateway/src/config/services.config.ts:1249, 1257 — /booking-engine-config + /booking-engine prefixes (line 1257 + line 1249)
  • hmp-api-gateway/src/proxy/proxy.controller.ts — @All('booking-engine') + @All('booking-engine/*') + @All('booking-engine-config') + @All('booking-engine-config/*') (verify per gateway 2-file rule)
  • hmp-booking-engine/src/lib/api/client.ts — Real axios client + setApiKey(), setPropertyId()
  • hmp-booking-engine/src/lib/api/index.ts — Mock/live toggle by VITE_API_URL presence
  • hmp-booking-engine/src/hooks/useHostBootstrap.ts — Runtime host resolution (calls /booking-engine-config/resolve-host)
  • hmp-booking-engine/src/App.tsx:AppWithTheme — Mode dispatcher (PROPERTY/BRAND/TENANT/CHAIN_HUB)
  • hmp-property-service/src/host-mappings/host-mappings.controller.ts — GET /booking-engine-config/resolve-host (public), CRUD endpoints (tenant admin)

Unit + integration tests:

  • hmp-booking-engine/src/App.PropertyBootstrap.test.tsx — bootstrap path
  • hmp-booking-engine/src/hooks/useHostBootstrap.test.ts — bootstrap hook
  • hmp-property-service/src/host-mappings/host-mappings.controller.spec.ts + host-mappings.service.spec.ts — controller + service
  • hmp-booking-engine/src/lib/api/__tests__/chainSearchApi.test.ts, bookingTripsApi.test.ts, selfCheckinApi.test.ts — API client coverage

Flow tests:

  • hmp-infrastructure/testing/flow-tests/src/flows/flow-3-booking-engine.spec.ts — full flow against live backend
  • hmp-infrastructure/testing/flow-tests/src/flows/booking-engine.flow.spec.ts — booking engine flow
  • hmp-infrastructure/testing/flow-tests/src/flows/booking-engine-selfservice.flow.spec.ts — self-service path

E2E (Playwright):

  • hmp-booking-engine/e2e/host-bootstrap.spec.ts — runtime host resolution
  • hmp-booking-engine/e2e/booking-full-flow.spec.ts — end-to-end against live API

SME notes:

  • Catalog Not Started is wrong by ~12 months. Runtime host resolution (2026-05-18 plan) shipped the real production architecture: one image serves any property/brand/tenant via /booking-engine-config/resolve-host. Mock layer (src/lib/api/mockData.ts, mockSpaApi.ts, mockLoyaltyApi.ts) is dev-only.
  • A5 (2026-05-22) added runtime x-api-key delivery via resolve-host returning a per-(tenant,property) publishableApiKey. Pre-A5 the SPA shipped a static hmp-default-public-booking-engine-key — now DEV-ONLY.
  • Widget mode (widget-entry.tsx) bypasses resolve-host — receives propertyId + apiKey via HMPBooking.init({ apiKey }). Tests cover this path independently.

Drift findings:

D-007 — [P0] [Catalog-honest] Catalog says Not Started (0 of 8 points). Reality: 8 of 8 — gateway routes + bootstrap + property-service host-mappings + per-property publishable key all shipped. Status promotion long overdue.

  • [SME-PROPOSED] Promote catalog status Not Started → Built. Title can be updated to "Runtime Host Resolution + Live Backend Integration" to reflect the matured architecture.
  • [SME-PROPOSED] Add AC: "Booking engine SHALL boot against any host without a code change — onboarding a new tenant property is a single DB row insert (host_mappings). E2E asserts that adding book.demo-hotel.com mapping → property bootstraps within 30 seconds without redeploy."

US-011-007 — Manage Booking (Lookup, Amend, Cancel)​

Catalog status: Partial (P1, 5 points) Verified status: Partial — lookup + cancel public endpoints shipped (booking-engine.controller.ts:131-161); amend shipped 2026-05-06 with PUT /booking-engine/bookings/:id; payment-link self-service shipped 2026-04-15; amendment deadline + payment-difference flow gaps remain

Code locations:

  • hmp-booking-engine/src/pages/manage/BookingLookup.tsx — Lookup form
  • hmp-booking-engine/src/pages/manage/BookingDetails.tsx — Render booking
  • hmp-booking-engine/src/pages/manage/CancellationFlow.tsx — Cancel UI
  • hmp-booking-engine/src/pages/manage/AmendmentFlow.tsx — Amend UI
  • hmp-booking-engine/src/hooks/useManageBooking.ts — TanStack hook
  • hmp-reservation-service/src/booking-engine/booking-engine.controller.ts:131-203 — GET /booking-engine/reservations/lookup, POST /booking-engine/reservations/:id/cancel, PUT /booking-engine/bookings/:id amend (AmendBookingDto, W1.A.3 2026-05-06 honors Idempotency-Key)
  • hmp-reservation-service/src/booking-engine/booking-engine.service.ts — lookupReservation(), cancelBooking(), amendBooking()
  • hmp-reservation-service/src/booking-engine/booking-engine.controller.ts:314-388 — Payment-link self-service + OTP endpoints

Unit + integration tests:

  • hmp-reservation-service/src/booking-engine/booking-engine.service.spec.ts — lookup + cancel + amend
  • hmp-reservation-service/src/booking-engine/__tests__/api-key-guard.spec.ts — API key on these routes
  • hmp-booking-engine/src — no AmendmentFlow / CancellationFlow / BookingLookup specs

Flow tests:

  • hmp-infrastructure/testing/flow-tests/src/flows/booking-engine-selfservice.flow.spec.ts — self-service amend/cancel
  • hmp-infrastructure/testing/flow-tests/src/flows/payment-links.flow.spec.ts — payment-link path

E2E (Playwright):

  • hmp-booking-engine/e2e/manage-booking.spec.ts — manage-booking E2E
  • hmp-booking-engine/e2e/payment-link-page.spec.ts, payment-link-otp.spec.ts — payment-link guest flow

SME notes:

  • Review file claimed FAIL on every backend AC citing "no public endpoint". All three are now shipped under /booking-engine. Review is stale.
  • Amendment-deadline enforcement (review GAP-4 "24-hour cutoff") — service amendBooking() validates state (RESERVED/CONFIRMED only) and property scope, but does NOT enforce a "no amend within 24h of check-in" cutoff. Hospitality SOP universally caps amend at check-in minus 24h or 48h to protect inventory.
  • Amend payment-difference: when amendment extends nights or upgrades room type, the price goes UP — story body says "I am redirected to Razorpay to pay the difference" but amendBooking() body does NOT include the differential payment redirect. Today: a guest amending +2 nights gets the longer reservation without paying the delta until check-in.
  • Cancellation refund: cancelBooking() returns penalty + refund amount, but does NOT initiate the Razorpay refund. Refund is manual-via-dashboard. Mismatch with US-011-007 AC-1.

Drift findings:

D-008 — [P0] [Story-vs-code + DPDPA + revenue-leak] Amendment with cost-increase does not collect the differential payment. A guest who amends a 3-night stay to 5 nights gets +2 nights "free" until check-in, when the front desk has to chase. India hospitality SOP says quoted-amount = collected-amount at amendment time.

  • [SME-PROPOSED] Add AC: "When amendBooking() produces a priceDifference > 0, the response SHALL include { requiresPayment: true, paymentInitiation: { reservationId, amount, currency, paymentLinkSlug? } }. The frontend SHALL block amendment success until the differential payment is captured (use the existing POST /booking-engine/payments/initiate-v2 flow). When priceDifference < 0, the response SHALL include { requiresRefund: true, refundAmount } and trigger the same refund path as cancellation."
  • [SME-PROPOSED] Add AC: "Amendment SHALL be blocked when now ≥ arrivalDate - 24h (configurable per cancellationPolicy.amendDeadlineHours, default 24). Attempt returns 409 code=AMEND_DEADLINE_PASSED."

D-009 — [P0] [Story-vs-code + DPDPA] cancelBooking() returns refund amount but does NOT initiate the Razorpay refund. The 2026-05-19 folio-payment reconciliation pipeline (PaymentCapturedConsumer) handles the capture side; the symmetric refund side is dashboard-manual only. DPDPA art. 8(3) "reasonable period" interpretation = 7 business days; without auto-trigger, the SLA depends on staff diligence.

  • [SME-PROPOSED] Add AC: "cancelBooking() SHALL publish RESERVATION_CANCELLED AND PAYMENT_REFUND_REQUESTED events. hmp-payment-service SHALL consume PAYMENT_REFUND_REQUESTED, call Razorpay Refunds API, emit PAYMENT_REFUNDED, and hmp-frontdesk-service PaymentRefundedConsumer (NEW, 7+ specs) SHALL post a folio REFUND transaction. End-to-end SLA: refund initiated within 60 seconds of guest-cancel, settled per Razorpay's 5-7 business day standard."

US-011-008 — Promo Code Validation (Server-Side)​

Catalog status: Partial (P1, 3 points) Verified status: Partial — promo-code module exists with controller + service + spec; POST /booking-engine/promo-codes/validate shipped; per-room-type restriction, max-discount-cap, and GST-slab re-evaluation after discount remain gaps

Code locations:

  • hmp-reservation-service/src/promo-codes/promo-code.controller.ts — CRUD endpoints
  • hmp-reservation-service/src/promo-codes/promo-code.service.ts + spec
  • hmp-reservation-service/src/booking-engine/booking-engine.controller.ts:163-168 — POST /booking-engine/promo-codes/validate (public, API-key gated)
  • hmp-reservation-service/src/booking-engine/booking-engine.service.ts:2033 — validatePromoCode()
  • hmp-booking-engine/src/components/booking/PromoCodeInput.tsx — Apply/remove UI
  • hmp-booking-engine/src/hooks/usePromoCode.ts — TanStack hook
  • hmp-reservation-service/prisma/schema.prisma — PromoCode model (catalog-noted in reservation-service CLAUDE.md table)

Unit + integration tests:

  • hmp-reservation-service/src/promo-codes/promo-code.service.spec.ts
  • hmp-reservation-service/src/booking-engine/booking-engine.service.spec.ts:1283+ — validatePromoCode specs

Flow tests:

  • hmp-infrastructure/testing/flow-tests/src/flows/booking-engine.flow.spec.ts — promo-code step

E2E (Playwright):

  • hmp-booking-engine/e2e/booking-full-flow.spec.ts — promo entry within booking flow

SME notes:

  • Review file US-011-008-review.md says NOT_IMPLEMENTED ("No PromoCode Prisma model, no validation endpoint…") — all three are shipped per reservation-service/CLAUDE.md Key Prisma Models table. Review is stale (D-001).
  • Per-room-type restriction (review GAP) — validatePromoCode() accepts room/rate context but no spec asserts that codes restricted to roomTypeId=X reject when applied to a roomTypeId=Y booking.
  • Brute-force protection on the validate endpoint — public + API-key-gated only (no rate limit per key). A scraper with one valid x-api-key can iterate promo-code space.
  • Re-validation at booking confirmation (review AC-12) — createBooking() accepts promoCode in DTO but does NOT call validatePromoCode() again. A code valid at apply-time but invalidated at booking-time (max-uses tripped, expired) won't be caught.

Drift findings:

D-010 — [P1] [Story-incomplete + abandoned-cart-recovery] Discount logic does not handle the GST slab re-evaluation that a discount can trigger. Example: ₹8,000/night room with WELCOME10 (10% off) drops to ₹7,200 — slab moves from 18% to 12%. Today: 18% is applied to the discounted amount. Result: guest is over-charged by 6%, OR the dashboard shows a misleading "discount" that the post-tax delta partially cancels.

  • [SME-PROPOSED] Add AC: "When promo discount drops the per-night rate across a GST-slab threshold (₹1,000 or ₹7,500), GST SHALL be re-computed at the new slab. Test fixtures: ₹8,000 → 10% off → ₹7,200 (18% → 12%); ₹1,100 → 20% off → ₹880 (12% → 0%). Both paths covered by validatePromoCode.spec.ts."

D-011 — [P1] [Story-incomplete + abandoned-cart-recovery] usePromoCode is a synchronous apply on click. No abandoned-cart-recovery flow exists: a guest who applies a code and bounces from the payment step never receives a "you left X behind, code Y still valid for 24h" email/SMS. This is the single highest-ROI lever on the booking-engine conversion funnel (industry: 8-15% lift on emailed abandons).

  • [SME-PROPOSED] Add AC (new story US-011-NEW-ABANDONED-CART): "When a guest reaches /checkout and leaves without completing payment, hmp-notification-service SHALL send (a) abandoned-cart email at +1h, +24h, +72h with the held cart contents + applied promo, AND (b) SMS at +24h (TRAI DND consent-gated). Email links restore the cart via bookingTripsApi.restoreTrip(tripId)."

US-011-009 — 14-Language Support (India + SE Asia)​

Catalog status: Built (P0, 5 points) Verified status: Built

Code locations:

  • hmp-booking-engine/src/locales/{en,hi,ta,te,kn,mr,bn,gu,ml,pa,th,vi,id,ms}.json — 14 locale files present
  • hmp-booking-engine/src/lib/i18n.ts — registers all 14 locales (verified — review file's claim of "only 10 registered" is stale)
  • hmp-booking-engine/src/components/common/LanguageSwitcher.tsx — Switcher component
  • hmp-booking-engine/src/components/common/Header.tsx — Mounts switcher (2 ref sites)

Unit + integration tests:

  • hmp-booking-engine/src/components/common/__tests__/ — common-components tests (EmptyState, ErrorState, LoadingState)
  • No dedicated LanguageSwitcher.test.tsx, no translation-completeness spec (review GAP-9 still valid)

Flow tests:

  • hmp-infrastructure/testing/flow-tests/src/flows/booking-engine.flow.spec.ts — locale param honored (TBD count)

E2E (Playwright):

  • hmp-booking-engine/e2e/multi-language.spec.ts — covers language switching

SME notes:

  • Review US-011-009-review.md is wholly stale. SE-Asia locales are registered in i18n.ts (verified via grep — th/vi/id/ms all imported). LanguageSwitcher component exists in components/common/. Review's FAIL verdict on every AC needs reset.
  • Reservation guestLanguage column gap (review GAP-4) — still real. The confirmation email goes via notification-service's template engine using whatever locale the request session held; persistence on Reservation for replay would require additive schema work.
  • PDF voucher Thai font embedding (review GAP-8) — Noto Sans Thai (or fallback) is not enumerated in hmp-shared-packages/packages/common/src/printing/ receipt templates. Thai voucher rendering today = boxes for Thai script unless the OS renderer has the font. Real Thai/Vietnamese guests will see broken PDFs.

Drift findings:

D-012 — [P1] [Story-incomplete + India-hospitality-critical + i18n] Reservation.guestLanguage is not persisted, so the post-checkout notification chain (check-in reminder, post-stay survey, marketing follow-up) cannot replay in the guest's language. The platform regresses to English-default for everything fired after the initial confirmation email.

  • [SME-PROPOSED] Add AC: "Booking flow SHALL persist Reservation.guestLanguage (BCP-47 tag, e.g. th, vi-VN, hi-IN). All downstream notification events (RESERVATION_CONFIRMED, CHECKIN_REMINDER, POST_STAY_SURVEY) SHALL include guestLanguage in payload. notification-service template renderer SHALL prefer guestLanguage over property's defaultLocale."
  • [SME-PROPOSED] Add AC: "PDF voucher renderer SHALL embed Noto Sans Thai + Noto Sans Vietnamese fallback fonts. Coverage: 4 byte-level specs verifying PDF font-tables contain the script's glyphs (one per th/vi/id/ms). Files in hmp-shared-packages/packages/common/src/printing/__tests__/pdf-fonts.spec.ts."

US-011-010 — Urgency Indicators & Social Proof​

Catalog status: Built (P2, 3 points) Verified status: Built

Code locations:

  • hmp-booking-engine/src/components/booking/UrgencyIndicator.tsx — "Only X rooms left" banner
  • hmp-booking-engine/src/components/booking/RoomCard.tsx:113 — availableCount <= 3 triggers urgency badge
  • hmp-reservation-service/src/booking-engine/booking-engine.controller.ts:285-291 — GET /booking-engine/social-proof?propertyId=…
  • hmp-reservation-service/src/booking-engine/booking-engine.service.ts — getSocialProof() (real data: recent reservation counts + occupancy)

Unit + integration tests:

  • hmp-booking-engine/src/components/__tests__/RoomCard.test.tsx — 22 specs include urgency-badge boundary cases (1/2/3 rooms vs 4)
  • hmp-reservation-service/src/booking-engine/booking-engine.service.spec.ts — getSocialProof test

Flow tests: none specific

E2E (Playwright): covered via results-page specs

SME notes:

  • Code switched from "random numbers" (review GAP) to real data via getSocialProof() per booking-engine.service.ts. Story body claim "5 people viewing this room" — the concurrent viewers part is approximated from recent reservation counts (not real WebSocket session tracking). Industry term: "false urgency" — risks TRAI/consumer-protection complaints if challenged in India.
  • US-011-014 (story-body in EPIC, mapped to US-011-010 in catalog) had AC "no fabricated urgency" — code honors availableCount <= 5 threshold per backend, and <= 3 per frontend RoomCard.tsx. Inconsistency: a 4-rooms-left case shows backend social-proof but not frontend urgency badge.

Drift findings: (none net new — consider folding US-011-014 AC-language into US-011-010)

  • [SME-PROPOSED] Add AC: "Concurrent-viewer count SHALL be either (a) real (WebSocket session-tracking against getSocialProof Redis-backed presence) OR (b) suppressed entirely. Approximated counts presented as real are a Consumer Protection Act 2019 §2(28) misleading-advertisement risk."

US-011-011 — Loyalty Dashboard (Points, History, Rewards)​

Catalog status: Built (P2, 5 points) Verified status: Built

Code locations:

  • hmp-booking-engine/src/pages/loyalty/ + src/components/loyalty/ — TierBadge, PointsDisplay, RewardCard, LoyaltyDashboard
  • hmp-booking-engine/src/hooks/useLoyalty.ts — TanStack Query hook
  • hmp-booking-engine/src/lib/api/loyaltyApi.ts — Real loyalty API client
  • hmp-booking-engine/src/lib/api/mockLoyaltyApi.ts — Mock fallback (dev)
  • hmp-crm-service/src/loyalty/ — Backend loyalty domain (per platform CLAUDE.md, CRM owns guest loyalty)

Unit + integration tests:

  • hmp-booking-engine/src/components/loyalty/*.test.tsx (if present per Glob — minimal coverage)

Flow tests: none specific to loyalty dashboard within booking engine

E2E (Playwright): minimal — loyalty section in booking-full-flow.spec.ts

SME notes:

  • Reward catalog browsing + redemption-in-booking are TWO separate flows. The dashboard (this story) is shipped; redemption-during-payment was originally scoped under EPIC body §US-011-09 but DOES NOT exist as a catalog row. Catalog stops at US-011-011 (dashboard); the "Points Redemption During Booking" story from EPIC body is silently dropped.
  • Tier progression visualization ("2,500 points to Platinum" with 83% bar) is a guest-facing pacing nudge that drives membership renewal. The story doesn't enumerate the formula — risk: front-end and crm-service disagree on bar-percentage calculation.

Drift findings:

D-013 — [P1] [Story-incomplete + missing-story + Indian-hospitality-business-critical] The "Points Redemption During Booking" flow (EPIC body §US-011-09) is documented in the EPIC narrative but has no catalog row. This is the most direct loyalty-monetization lever (guest applies points → smaller Razorpay charge → higher engagement). Without a catalog row, it cannot be sprint-planned.

  • [SME-PROPOSED] Author new catalog row US-011-017 "Loyalty Points Redemption at Booking Payment" with ACs: (1) Payment page surfaces points balance + max-redeemable cap (e.g. 50% of total); (2) Slider adjusts redeemable amount; (3) Razorpay charge SHALL equal total − redemptionInINR; (4) Points are debited ONLY on payment success (not on apply); (5) Failed payment leaves points balance untouched.

US-011-012 — Spa Treatment Booking​

Catalog status: Built (P2, 3 points) Verified status: Built

Code locations:

  • hmp-booking-engine/src/components/spa/SpaPackageCard.tsx, SpaAvailabilityCalendar.tsx, SpaConfirmation.tsx
  • hmp-booking-engine/src/hooks/useSpaAvailability.ts
  • hmp-booking-engine/src/lib/api/spaApi.ts + mockSpaApi.ts (dev fallback)
  • hmp-booking-engine/src/stores/spaStore.ts — Zustand store
  • hmp-spa-service/ — Backend (port 3015 per platform CLAUDE.md)

Unit + integration tests: minimal frontend coverage

Flow tests:

  • hmp-infrastructure/testing/flow-tests/src/flows/spa-booking.flow.spec.ts

E2E (Playwright): covered via spa-section in booking flow

SME notes:

  • Indian-market reality: spa booking is a low-conversion add-on on the booking engine. Industry data: <2% of room bookings include a spa add-on at time of booking; the higher-volume path is in-stay add-on via the dashboard / WhatsApp concierge.
  • Mock fallback (mockSpaApi.ts) ships with the production bundle — review what happens when hmp-spa-service is down (today: falls back to mock per src/lib/api/spaApi.ts toggle). This could show fake therapists. Confirm + lock to real-only in prod.

Drift findings: (none net new)

  • [SME-PROPOSED] Add AC: "Spa booking SHALL NOT fall back to mock data when hmp-spa-service is unreachable. Failure SHALL render an ErrorState with 'Spa booking temporarily unavailable, please call the property'. Mock import SHALL be tree-shaken in production builds (Vite import.meta.env.PROD guard)."

US-011-013 — Widget / Iframe / Standalone Modes​

Catalog status: Built (P1, 3 points) Verified status: Built-Untested — three modes ship; widget mode bypasses host-resolution and has limited cross-mode coverage

Code locations:

  • hmp-booking-engine/src/web-component.tsx — Web component build target
  • hmp-booking-engine/src/widget-entry.tsx — Widget entry (uses HMPBooking.init({ apiKey }))
  • hmp-booking-engine/vite.widget.config.ts — Widget-specific Vite config
  • hmp-booking-engine/test-embed.html — Manual embed test page
  • hmp-booking-engine/embed/ — Embed scripts and assets
  • hmp-booking-engine/INTEGRATION.md — Integration guide
  • Runtime host resolution (useHostBootstrap.ts) is the standalone mode; widget mode bypasses it

Unit + integration tests:

  • hmp-booking-engine/src/App.PropertyBootstrap.test.tsx — standalone mode
  • No widget-entry.test.tsx or cross-mode parity test

Flow tests: none

E2E (Playwright): host-bootstrap.spec.ts covers standalone; widget + iframe not enumerated

SME notes:

  • Three deployment modes is a textbook example of "code paths > tests". A change to client.ts:setApiKey() could silently break widget mode (no resolve-host to refresh it).
  • Iframe mode postMessage to parent page — story body claims this but no test asserts the message shape. A hotel's marketing page subscribing to 'hmp:booking-completed' events would silently fail on a contract change.

Drift findings:

  • [SME-PROPOSED] Add AC: "widget-entry.test.tsx, iframe-postMessage.test.tsx, and web-component-mount.test.tsx SHALL each ship with ≥3 specs covering: mount with HMPBooking.init, mount with bare attributes, postMessage contract { type: 'hmp:booking-completed', bookingId, confirmationNumber } validation."

Catalog status: Built (P1, 2 points) Verified status: Built

Code locations:

  • hmp-booking-engine/src/components/CookieConsent.tsx — Cookie consent banner
  • hmp-booking-engine/src/App.tsx — Mounts the banner

Unit + integration tests: minimal — no CookieConsent.test.tsx enumerated

Flow tests: none specific

E2E (Playwright): none enumerated for consent

SME notes:

  • Granular categories (Essential / Analytics / Marketing) — EPIC body US-011-15 specifies; verify CookieConsent.tsx renders all three, not just "Accept all / Reject all" (current implementation likely is the latter per EPIC Partial status — but catalog says Built — needs fact-check).
  • DPDPA art. 7 (consent must be free, specific, informed) + GDPR art. 7 BOTH require per-category consent. A single "accept all" violates both.
  • Audit trail (server-side persistence of consent record) — review GAP-3 said this is missing. Today: consent stored in localStorage only, so no audit log for "did this guest consent to marketing on Jul 4?".

Drift findings: (consolidate into DPDPA gap surfaced under D-004)

  • [SME-PROPOSED] Add AC: "Cookie consent banner SHALL render three distinct toggles: Essential (always on, greyed), Analytics (default off), Marketing (default off). Consent SHALL be persisted server-side via POST /booking-engine/consent with { guestSessionId, propertyId, categories: {essential, analytics, marketing}, ipCountry, userAgent, timestamp }. Retention: 3 years per GDPR art. 5(1)(e). Coverage: 1 backend spec + 3 frontend specs (toggle state, persistence, audit)."

US-011-015 — Booking Engine Analytics (Conversion Funnel)​

Catalog status: Not Started (P2, 5 points) Verified status: Not Started

Code locations:

  • hmp-booking-engine/src/lib/ab-testing/ — A/B testing scaffolding exists (ab-testing.service.ts, ab-testing.provider.tsx, use-experiment.ts) but is NOT a conversion-funnel analytics implementation
  • No booking-engine.funnel.* events enumerated in hmp-shared-packages/packages/messaging topics

Unit + integration tests: none

Flow tests: none

E2E (Playwright): none

SME notes:

  • "Not Started" is honest. The A/B-testing scaffolding is adjacent (experiment groups, variant assignment) but does not emit funnel events to analytics-service.
  • Industry baseline: 4-step funnel (search → results → guest-details → payment). Drop-off is concentrated at payment (60-70% of cart abandons happen in the Razorpay modal or at guest-details). Without funnel measurement, the abandoned-cart-recovery flow (D-011) has no trigger source.
  • Forex / currency conversion is a quarter-related question: when can a non-INR guest see prices in USD/EUR? Today the booking engine is INR-only per story. Conversion-funnel data would tell us how many sessions drop because the guest sees ₹ and bounces.

Drift findings: (none — story-vs-reality matches)

  • [SME-PROPOSED] Add AC: "Funnel events booking-engine.funnel.search, …results-viewed, …room-selected, …guest-details-entered, …payment-initiated, …payment-completed, …booking-abandoned SHALL publish to Kafka topic booking_engine_funnel. Payload: { sessionId, propertyId, tenantId, step, timestamp, locale, currency, deviceType }. hmp-analytics-service SHALL materialize mv_booking_funnel_daily (per-property step counts) and mv_abandoned_cart_recovery_targets (sessions stuck at payment step for >30 min)."
  • [SME-PROPOSED] Add AC (new candidate US-011-NEW-FOREX): "Booking engine SHALL display prices in the guest's locale currency (USD/EUR/GBP/SGD) using mid-market FX rates refreshed 4x daily from RBI reference rates. Razorpay still charges in INR; the conversion is display-only. Coverage: 5 specs covering INR display, USD display, FX cache TTL, fallback when FX feed unavailable, rounding rules."

US-011-016 — Mobile-Responsive Optimization​

Catalog status: Built (P0, 3 points) Verified status: Built

Code locations:

  • hmp-booking-engine/src/ — Mobile-first design per CLAUDE.md direction-a/b theme presets
  • hmp-booking-engine/src/components/booking/ — Cards/grids use mobile-first responsive utilities
  • hmp-booking-engine/src/components/common/ — Sheet primitive (Radix Dialog wrapped as bottom drawer) per platform CLAUDE.md Shared UI Primitives
  • hmp-booking-engine/src/hooks/useMediaQuery.ts — Breakpoint hook

Unit + integration tests: covered indirectly via component tests

Flow tests:

  • hmp-infrastructure/testing/flow-tests/src/flows/booking-mobile.spec.ts (per glob — covers mobile path)

E2E (Playwright):

  • hmp-booking-engine/e2e/booking-mobile.spec.ts
  • hmp-booking-engine/e2e/mobile-responsive.spec.ts

SME notes:

  • "Mobile-first" is a design principle — but without a measurement against the Lighthouse > 80 acceptance criterion from EPIC §8 AC-6, "Built" is aspirational. No CI gate on Lighthouse score enumerated.
  • Indian-market reality: 70%+ of booking-engine traffic is mobile (per industry data — KPI §9 target "Mobile booking share > 55%"). A 320px width minimum is the right target, but most tooling tests 375px+ default.
  • KPI §9 also asks "Page load time (LCP) < 2.5s" — no RUM (Real User Monitoring) integration enumerated in src/. Without RUM, we don't know if real guests hit the 2.5s target.

Drift findings: (none net new — performance target gap is a CI concern)

  • [SME-PROPOSED] Add AC: "Lighthouse CI SHALL run on every booking-engine PR with thresholds Performance ≥ 80, Accessibility ≥ 90, Best Practices ≥ 90 on the standalone-mode bundle. Configured via hmp-booking-engine/.lighthouserc.json and gated in the Azure Pipeline."
  • [SME-PROPOSED] Add AC: "RUM (Real User Monitoring) SHALL emit web-vitals (LCP, FID, CLS, INP) to hmp-analytics-service via the same booking_engine_funnel Kafka topic with step='rum'. Threshold alerts: LCP > 4s for p75 of any property over 7 days emits Slack #booking-engine-perf alert."