Property #1 Launch Readiness Review — 2026-05-06
Reviewer: Claude (Opus 4.7, 1M context). Run timestamp: 2026-05-06 IST.
Review duration: ~3.5 hours (Phase A inventory ≈ 30 min; Phase B 13 parallel flow sub-agents ≈ 90 min wall-clock; Phase C cross-cutting ≈ 30 min; Phase D synthesis ≈ 30 min).
Working directory: C:\CLProjects\HMP.
Methodology: every finding cited to file:line in the actual repository at the HEADs below. CLAUDE.md, MEMORY.md, prior platform-registry audits, and any "shipped"/"complete" claim in spec/plan files were treated as suspect and re-verified against code. PMS/OTA domain perspective applied throughout. No edits.
Repo HEADs at review time
| Repo | HEAD | Branch |
|---|---|---|
| hmp-api-gateway | d343416 | master |
| hmp-auth-service | 5512770 | master |
| hmp-property-service | eec9cfc | master |
| hmp-reservation-service | cf29aa5 | master |
| hmp-guest-service | 0c1f179 | master |
| hmp-frontdesk-service | 95a5886 | master |
| hmp-housekeeping-service | 794e6a8 | master |
| hmp-pos-service | e09921d | master |
| hmp-banquet-service | 01944b8 | master |
| hmp-channel-service | 6e35453 | master |
| hmp-notification-service | 17f122a | master |
| hmp-crm-service | 368d5f9 | master |
| hmp-analytics-service | 264564a | master |
| hmp-payment-service | 7a6d77a | master |
| hmp-spa-service | 74aef5d | master |
| hmp-asset-service | 509ed53 | master |
| hmp-web-dashboard | 22b0f71 | master |
| hmp-admin-portal | f5673a2 | master |
| hmp-booking-engine | 4713bcb | feature/be-goat-phase-8 (NOT master) |
| hmp-mobile-apps | 84fe56d | master |
| hmp-kds-kiosk | 02f1b07 | master |
| hmp-shared-packages | 32885ae | master |
| hmp-infrastructure | d1b1b13 | master |
| hmp-docs | 240d904 | master |
Note: hmp-booking-engine is on a feature branch. Production launch from master would not include the GoaT Phase 8 frontend work that the backend (already merged on master) was built to support.
§1 — Executive verdict
NO-GO for property #1 launch. Of 13 minimum-viable launch flows, 11 are RED (block launch outright) and 2 are AMBER (significant gaps). 0 flows are GREEN. Every flow that involves money movement, regulatory compliance, or operational state machines has at least one P0 defect that would surface within the first week of real hotel operations.
Verdict counts
| Verdict | Count | Flows |
|---|---|---|
| GREEN | 0 | — |
| AMBER | 2 | 11 (ResAvenue), 12 (Payments) |
| RED | 11 | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 13 |
| UNKNOWN | 0 | — |
Top 5 launch blockers (one line each)
- Booking-engine race condition: two guests can both book the last room. No row lock, no unique constraint, no advisory lock anywhere in
createBooking(hmp-reservation-service/src/booking-engine/booking-engine.service.ts:981-1068). Zero concurrency tests. - Razorpay verifyPayment auto-confirms reservations with NO HMAC check when key is unset.
booking-engine.service.ts:1741-1754, 1888-1938— a property whosesettings.razorpayKeyIdis missing flipsPENDING → CONFIRMEDon a guest-suppliedpaymentIdwith no signature verification, noPaymentAttemptrow, and a "mock fallback for dev/demo" branch one missing env-var away from production. - Tape Chart hero feature missing context menu and all DetailPanel actions.
hmp-web-dashboard/src/components/modules/front-desk/tape-chart-v2/tape-chart-v2-shell.tsx:365-372shows literal'coming in Phase 2'placeholder for every operation except "navigate to detail page". Right-clicking a bar in production does nothing. No virtualisation in v2 either (working code from v1 sits unused). - Front-desk ID photo capture is broken end-to-end. Dashboard sends
data:image/jpeg;base64,...URLs (hmp-web-dashboard/src/app/(dashboard)/front-desk/check-in/[id]/keynova.tsx:444-446, 966) into a DTO field decorated@IsUrl({require_protocol:true,protocols:['https','http']})(hmp-frontdesk-service/src/checkin/dto/checkin.dto.ts:152-154). Validator rejects → 400. ID photo capture is functionally non-existent in production right now. - Empty-nationality foreign guest bypasses FRRO entirely.
hmp-frontdesk-service/src/checkin/checkin.service.ts:189-194checksnationality.toLowerCase() === 'indian'/'india'/'in'and treats blank as Indian. The booking-engine path can omit nationality, so a foreign guest who self-registers without setting it skips Foreigners Act Form C requirement at check-in. Walk-in path has no FRRO enforcement at all.
Top 3 cross-cutting risks
buildscript silently swallows TypeScript errors. Every backend service'spackage.jsonrunstsc -p tsconfig.docker.json || true. A service with a real type error ships anyway. The|| trueshould not exist. Hidden defect surface area is unknown.- xPrisma anti-pattern still pervasive — 79 foreign-model Prisma reads in
channel-service+reservation-servicealone (prisma.roomType,prisma.ratePlan,prisma.property,prisma.guest). Local read-model-sync consumers exist but they're a parallel implementation of what@hmp/service-clientsis supposed to be the canonical answer to. Event-bus drift between owner and reader is silent. EINVOICE_MOCK=truein QA (hmp-infrastructure/docker/docker-compose.qa-aws.yml:42) — the most regulated part of the platform (NIC IRN e-invoicing) is not exercised against the real NIC IRP in any pre-prod environment. Production has no boot guard against accidentally inheritingEINVOICE_MOCK=true. Compare toEmailService.onModuleInitwhich DOES hard-fail without SendGrid creds. A misconfigured prod deploy would issue invoices that look real but carry mock IRNs.
§2 — Flow status table
| # | Flow | Verdict | P0 count | Top blocker |
|---|---|---|---|---|
| 1 | Tenant onboarding | RED | 8 | Subscription row never written to typed table; legacy code path writes JSON blob. Onboarding never imports the SubscriptionsModule. |
| 2 | Property onboarding (7 sub-flows) | RED | 8+ | Booking engine room cards use hardcoded mock data, not API; Google Maps autocomplete absent; tax slab boundaries hard-coded; bulkCreate has cross-tenant insertion bug. |
| 3 | Booking engine integration | RED | 2 | Last-room race + payment auto-confirm-without-HMAC. |
| 4 | Staff verifies reservation | RED | 5 | Folio tab is client-side fabrication; search box ignores typed text; Modify/Reg-card buttons dead; Cmd+K calls non-existent route; xPrisma guest reads. |
| 5 | Front-desk / Housekeeping updates | RED | 6 | Two /room-move controllers; no folio realignment on move; no availability check on extend; mobile housekeeper photo-proof flow broken. |
| 6 | Front-desk check-in (IN+INTL) | RED | 7 | ID photo data: URL rejected by validator; empty-nationality bypasses FRRO; express check-in is a stub; walk-in skips FRRO; police register PII feed unimplemented. |
| 7 | Room service to folio | RED | 3 | V2 GST split silently dropped at frontdesk receiver; MenuItem has no HSN/SAC; service-charge GST hard-coded 0.18. |
| 8 | Guest check-out & invoice | RED | 6 | IRN ack number overwrites sequential invoice number; two divergent invoice generators; no production guard against EINVOICE_MOCK; no split-payment; no invoice attachment on email. |
| 9 | Night audit | RED | 8 | No @@unique([propertyId, auditDate]) constraint; dedup window timezone math wrong; rate read by averaging not from rate calendar; no cancellation-policy-aware no-show; no POS day-close cascade; no event outbox. |
| 10 | Reports & dashboards | RED | 4 | KPI tile reportIds wrong (every Operations Hub home tile 404s); reports hard-coded UTC not property TZ; roomsSold increments per booking not per night. |
| 11 | ResAvenue channel manager | AMBER | 4 | Webhook idempotency race-vulnerable; itemsProcessed always 1; stay-only restrictions silently dropped; no DB-level idempotency on retries. |
| 12 | POS terminal & payments | AMBER | 4 | Razorpay terminal adapter still sends defunct X-Payment-Idempotency-Key; PineLabs batch-settlement is no-op (no HMP-side reconciliation); refund modal absent; subscription flag in CLAUDE.md doesn't exist in code. |
| 13 | Tape Chart Calendar | RED | 7 | No context menu wired; DetailPanel actions are placeholder toasts; no virtualisation in v2 production shell; no optimistic concurrency; no mobile/a11y; only 5 e2e smokes. |
Total P0 findings across the 13 flows: ≈ 73.
§3 — Per-flow detailed findings
The following 13 sections are the verbatim outputs of the 13 parallel flow sub-agents, lightly normalised for heading consistency. Each finding cites file:line and was produced by independent investigation.
Flow 1 — Tenant Onboarding
Verdict: RED. Two divergent signup paths with different validation rules; no email verification; no audit trail on tenant lifecycle; subscription row never written to the typed table; default property data is a documented stub; MFA never enrolled for ADMIN; no DPDPA data-residency support.
Flow definition. (1) Self-service signup — POST /api/tenants/signup → TenantsController.signup (hmp-auth-service/src/tenants/tenants.controller.ts:34-56). (2) Admin-driven create — POST /api/tenants (tenants.controller.ts:58-72) — same code path. (3) Onboarding wizard (ADMIN/SUPER_ADMIN) — POST /api/onboarding/start | /:tenantId/property | /:tenantId/rooms | /:tenantId/activate (onboarding.controller.ts:53-167) — calls property-service via PropertyServiceClient. (4) Subscription activation via legacy BillingModule.SubscriptionService.createSubscription, NOT the typed-row subscriptions/subscription.service.ts. (5) Login — POST /api/auth/login (no email-verification gate).
Data layer.
- [P0]
Tenantmodel has nolegalName,tradeName,pan, ordataResidencycolumns (hmp-auth-service/prisma/schema.prisma:19-58). Indian compliance demands legal-name vs trade-name separation; both are missing. PAN is required for B2B GST invoices. - [P0] No
TenantStatusenum. Lifecycle is a single booleanTenant.isActive(schema.prisma:25). Suspension storessuspendedAt/suspendReasoninsidesettingsJSON (tenants.service.ts:240) — unindexed, untyped. No PENDING (e.g., awaiting email verification) and no CANCELLED state. - [P0]
User.roledefaultsSTAFFbut signup hardcodesADMIN(tenants.service.ts:80,onboarding.service.ts:136). MFA columns exist (mfaEnabled,mfaSecret—schema.prisma:103-107) but no MFA-enrolment hook in onboarding. - [P1]
Tenant.billingContactName/Email/Phoneexist (schema.prisma:40-42) but are not exposed inCreateTenantDto/UpdateTenantDto(dto/create-tenant.dto.ts); cannot be set during signup or update at all.SubscriptionService.createSubscriptionreads these (subscriptions/subscription.service.ts:138-142) — silent fallback tosupportEmailorbilling+{code}@keynovahotels.com. - [P1]
TenantSubscription.statusenum hasTRIAL/ACTIVE/PAST_DUE/...(schema.prisma:406) but the legacy path used by onboarding writes status intotenant.settings.subscriptionJSON instead (billing/subscription.service.ts:215, 824-846).
Backend.
- Gateway two-file rule for
/tenants(configservices.config.ts:19; controllersproxy.controller.ts:32, 39): PASS. - Gateway two-file rule for
/onboarding(services.config.ts:54;proxy.controller.ts:107, 114): PASS. - [P2] Gateway
/authparent path missing decorator —proxy.controller.ts:23only has@All('auth/*'). BarePOST /api/authwould 404. Inconsistent with platform two-file rule. - [P0]
TenantsController.signupis@Public()(tenants.controller.ts:33) — no rate limit specific to signup beyond the gateway's default-write 60/min bucket. Account-creation endpoints normally need a stricter bucket. OnboardingControllerroutes are gated@Roles('SUPER_ADMIN','ADMIN')(onboarding.controller.ts:53,83,112,141,170) and theassertTenantScopehelper enforces tenant isolation for ADMIN: PASS.- [P0]
POST /tenants/:id/suspendand/reactivate(tenants.controller.ts:188, 206) only flipisActive; no audit row written, no Kafka event published, no session invalidation for already-issued JWTs.
Cross-service / events.
- [P0] Tenant create publishes
TENANT_CREATEDKafka event (tenants.service.ts:97-111).OnboardingService.startOnboardingdoes NOT publishTENANT_CREATED(onboarding.service.ts:110-145). Welcome-email consumer (hmp-notification-service/src/dispatcher/dispatcher.service.ts:826-878) only fires for the/tenants/signuppath. - [P0]
OnboardingService.activateSubscription(onboarding.service.ts:386-428) injects the legacyBillingModule.SubscriptionService. That class persists subscription data intotenant.settings.subscriptionJSON, NOT into theTenantSubscriptionPrisma table. The newersubscriptions/subscription.service.tsis wired into a parallel module that onboarding does not consume. - [P0]
SUBSCRIPTION_VIA_PAYMENT_SERVICE_ENABLEDflag is mentioned in rootCLAUDE.mdbut never read byOnboardingService.activateSubscription. Onboarding hardcodes the legacy SubscriptionService. - [P0]
OnboardingService.addPropertycallsPropertyServiceClient.seedPropertyDefaults, which is a documented stub returning zeros (hmp-shared-packages/packages/service-clients/src/lib/property-service.client.ts:530-550). New tenants get zero default room types / rate plans / outlets / tax config. - [P0] No email-verification flow exists.
Grep verifyEmail|EMAIL_VERIFICATION|emailVerifiedacrosshmp-auth-service/srcreturns zero matches.Usermodel has noemailVerifiedcolumn. - [P0] No audit-log write in
TenantsService.signup,suspend,reactivate, or anyOnboardingServicemethod. TheAuditLogmodel (schema.prisma:230-249) is not populated for tenant lifecycle events. Hash-chained tamper-evident audit (advertised in service CLAUDE.md) is absent on this flow.
Frontend.
- [P0] Admin-portal signup page client-side password rule is
>= 8 chars + 1 upper + 1 digit(hmp-admin-portal/src/app/(auth)/signup/page.tsx:68-78); backend requires>= 12 chars + 1 upper + 1 lower + 1 digit + 1 special(tenants/dto/create-tenant.dto.ts:71). Frontend will accept passwords the backend rejects, surfacing as a 400 only after submit. - [P0]
OnboardingDto.passwordrequiresMinLength(8)(onboarding/dto/onboarding.dto.ts:48) butOnboardingService.startOnboardingcallsvalidatePasswordStrength(onboarding.service.ts:103) which enforces 12. DTO and runtime check disagree. - [P1] Tenant detail page
hmp-admin-portal/src/app/(admin)/tenants/[id]/page.tsx:395displays GSTIN read-only.legalName,tradeName,billingContactName/Email/Phone,dataResidencyare not editable. - [P1] Dashboard has no "My Organization" / tenant-edit page (no
organization/ortenant/directory underhmp-web-dashboard/src/app/(dashboard)/settings/). Tenant ADMINs cannot self-edit GSTIN, contact info, or billing contact.
Test coverage.
hmp-auth-service/src/tenants/tenants.service.spec.ts— 27 tests; zerosignuphappy-path tests, zerosignupKafka-publish tests, zero negative-path tests for duplicate email or duplicate code on the signup path.onboarding.service.spec.ts— 22 tests; no tests for password rule mismatch, no tests forseedPropertyDefaultsstub returning zeros, no tests assertingTenantSubscriptionrow is written.- No e2e Playwright test exercises the full signup → onboarding wizard → subscription path.
Domain correctness.
- [P0]
Tenant.code @uniquebutslugify("My Hotel ™")strips Unicode.User.@@unique([tenantId, email])allows same email across tenants.OnboardingService.startOnboardingrejects same-email-cross-tenant via globalfindFirst(onboarding.service.ts:87-95);TenantsService.signupdoes NOT (tenants.service.ts:72-87). Two onboarding entry points, different uniqueness semantics. - [P0] DPDPA / data residency: zero schema or code support. No
dataResidencycolumn, no per-tenant DB partitioning, no RLS middleware on this flow. - [P0]
signupskips GSTIN pre-check (tenants.service.ts:62-70); update path does it (tenants.service.ts:194-200). Duplicate GSTIN at signup produces generic PrismaP2002instead of the friendly 409. - [P0] MFA never enrolled for ADMIN at create time. Contradicts platform CLAUDE.md "MFA TOTP enforced for ADMIN/SUPER_ADMIN."
- [P0] Tenant suspension blast radius: only flips
isActive=false, no session invalidation. A user logged in 14 minutes before suspend retains access for up to 15 min.
Flow 2 — Property Onboarding
Verdict: RED. Flow 2 fails the audit's mandatory checks in all 7 sub-flows.
Data layer.
- [P0] Property
latitude/longitudetyped asFloat?notDecimal—hmp-property-service/prisma/schema.prisma:115-116. IEEE-754 floats lose precision at the 7th decimal (~1cm). Needed:@db.Decimal(10,7). - [P0] PIN code field is
postalCode String?with no length/format validation —prisma/schema.prisma:96andsrc/properties/dto/property.dto.ts:101-103, 289-292. India 6-digit PIN check absent. - [P0]
RatePlanhas nocurrencycolumn —prisma/schema.prisma:359-397. Multi-property-different-currency tenants cannot run cross-currency rate plans. - [P0]
CancellationPolicy.rulesis untyped JSON —prisma/schema.prisma:474. Zod helper exists atsrc/cancellation-policies/dto/cancellation-rules-validator.tsbut it is application-level only. - [P0] Tax has no slab-rule column —
prisma/schema.prisma:521-544. Indian GST tiering (12% if room < ₹7,500/night vs 18% above) is hard-coded insrc/tax/tax-rule-engine.service.tsand worked around by creating twoTaxConfigurationrows with different SAC codes (996311 vs 996312) insrc/tax/tax.controller.ts:103-131. Slab boundary changes require code deploy. - [P0]
inclusive Booleannot honored by tax engine — gotcha 6 insrc/tax/CLAUDE.mdadmits "TaxRuleEngineService does NOT yet factor inclusive into the calculation". Tax-inclusive rate plans (common with OTAs) will double-tax. - [P1]
RoomType.amenitiesisString[](prisma/schema.prisma:245) — audit asked for typed{icon, label}. Property-levelamenitiesJson is structured (:129) but room-type level is plain array. Not booking-engine renderable as iconified chips. - [P1]
RoomType.imagestypedJson @default("[]")— duplicates thePropertyImagetable. No FK, no S3-key tracking. - [P1]
RatePlanRateis row-per-(plan×roomType×date) —prisma/schema.prisma:419-443. 365 days × 5 room types × 5 plans = 9,125 rows per property. No range/RLE compression. - [P2]
RateRestrictionandratePlan.settings.restrictions[]JSON both carry restriction state. Two writers, one truth — drift.
Backend.
- [P0] Gateway two-file rule violation:
/extra-chargesand/property-taxesregistered inservices.config.ts:1055,1063but onlyproperty-taxeshas@Alldecorators inproxy.controller.ts:2258,2260;/extra-chargesis completely missing fromproxy.controller.ts./seasonsis missing from BOTH files. Root CLAUDE.md warning: "Missing either → 404 before the config is even consulted." - [P0]
RoomsService.bulkCreatehas no tenant ownership check —hmp-property-service/src/rooms/rooms.service.ts:61-100. Only validatesProperty.idexists, never asserts the property'stenantId === callerTenantId. TheassertRoomTenanthelper is used byupdate/updateNotes/updateStatus/removebut not bycreateorbulkCreate. Cross-tenant room insertion bug. - [P0]
bulkCreatecapped at 100 —src/rooms/dto/bulk-create-rooms.dto.ts:23@Max(100). A 200-room hotel needs two requests. No idempotency key on the bulk endpoint. - [P0] No bulk-rooms UI in dashboard —
Grep "/rooms/bulk"inhmp-web-dashboard/srcreturns zero. Rooms settings page only has single-room create dialog. Audit requirement "must support 100-room property without 100 manual clicks" — fails outright. - [P1]
applyIndianGstDefaultsderives stateCode from GSTIN prefix —src/tax/tax.controller.ts:97— but skips validating GSTIN's state matches property'sstatefield. - [P1] No e-invoice GSTIN-of-counter-party handling in property-service —
hmp-frontdesk-service/src/einvoice/einvoice.module.ts:79-124shows the provider is mocked by default (EINVOICE_MOCK=true).
Frontend.
- [P0] No Google Maps Places autocomplete on property settings —
hmp-web-dashboard/src/app/(dashboard)/settings/property/page.tsx:1176-1228shows manual<Input type="number">for lat/lon.Grep "Places|Autocomplete|google.maps"returns nothing relevant. - [P0] No embedded map preview on property settings.
googleMapsUrlfield is not even rendered in the settings UI. - [P0] Booking engine room cards use hardcoded mock data —
hmp-booking-engine/src/pages/direction-b/RoomDetailPage.tsx:74-99—ROOM_TYPESis a static array with Unsplash URLs (:152-156) and hardcoded amenity strings. Cancellation/refund text is hardcoded literal "Free cancellation until 23 Apr" (:558). NOT API-driven. Not consuming property-serviceRoomTypeorCancellationPolicy.rulesat all.
Indian / domain.
- [P0] Indian GST slab boundaries (
<=7500=12%, >7500=18%) hard-coded in code, not config —src/tax/CLAUDE.mdgotcha 2. - [P0]
Property.gstinnot state-aware —prisma/schema.prisma:102is a single string. India multi-state chains hold one GSTIN per state. Per-state GSTIN is onTaxConfiguration.gstinNumber(:527), but folio printing pulls fromproperty.gstin— multi-state property prints wrong GSTIN.
Tests. 53 spec files in property-service — but no spec for bulkCreate tenant-ownership (the missing check), no spec asserting RatePlanRate row-write count, no spec for inclusive flag actually changing tax math.
Flow 3 — Booking Engine Integration
Verdict: RED. Two ship-blocking defects.
Flow definition. Guest opens the Vite booking widget, hits search → selects a room → submits guest+payment → reservation appears on the dashboard list. Implemented via API-key-gated @Public() controller on reservation-service (/api/booking-engine/*), with payment going either through legacy in-process Razorpay (default) or proxied to payment-service when build flag is on.
Data layer.
- [P0] No inventory lock at booking commit —
hmp-reservation-service/src/booking-engine/booking-engine.service.ts:981-1068—createBookingopens aprisma.$transactionthat re-reads no inventory and createsReservation+RoomReservationrows unconditionally. NoSELECT FOR UPDATE, no advisory lock, no@@unique([propertyId, roomTypeId, date])row.Grep forUpdate|advisory|Serializableacross the entire repo: zero matches. Two guests booking the last room at the same instant both succeed. - [P0]
RoomReservationcarries no per-night uniqueness —prisma/schema.prisma:187. Inventory is computed by countingRoomReservationrows post-hoc. Combined with the missing lock, no DB-level safety net. - [P1]
Reservation.holdReleaseDateis@db.DatebutholdReleaseTimeisString"HH:mm" —prisma/schema.prisma:74-75. Scheduler atsrc/reservations/hold-release.scheduler.ts:67-71reconstitutes the Date withsetHours(...)in server local time (no UTC marker), so the 30-min payment window can drift hours across timezone-heterogeneous deployments. - [P1]
Idempotency-Keyis supported onPOST /booking-engine/reservations(admin-style payload) but the actual frontend routePOST /booking-engine/bookings(createBookingFromEngine) callscreateBookingwith no idempotencyKey —booking-engine.service.ts:1508. Guest double-clicks duplicate the reservation.
Backend endpoints.
- [P0] Search/availability/calendar are
@SetMetadata(REQUIRE_API_KEY, false)on the@RequireApiKey()controller —booking-engine.controller.ts:71, 86, 94, 104, 263, 273, 287— these endpoints accept ZERO authentication. Search remains fully open and DoS-able by anyone who knows a propertyId. - [P1] Server-side amount cross-check uses
Math.abs(params.amount - serverAmount) > 1—booking-engine.service.ts:1682, 1797— guest can underpay by ₹1 silently. - Two-file rule satisfied:
services.config.ts:1213/booking-engine,:1221/booking-trips,:508/payments; matching decorators inproxy.controller.ts.
Cross-service & events.
- [P0] Two parallel payment paths exist; legacy is default.
booking-engine.service.ts:1660-1755(initiatePayment) readsrazorpayKeyId/razorpayKeySecretfromproperty.settingsJSON with a "mock fallback for dev/demo" at:1741-1754. If credentials are absent, the booking is "confirmed" against a fakeorder_…ID.verifyPaymentat:1888-1908only callscrypto.createHmacwhen credentials exist — when they don't, theifblock is skipped entirely and the reservation flips PENDING → CONFIRMED with NO signature check (:1925-1938). Critical PCI/business-logic hole. - [P1]
sendBookingConfirmation(booking-engine.service.ts:2372-2394) publishesEventTopics.NOTIFICATION_SENDwithtype: 'BOOKING_CONFIRMATION_EMAIL'— that string is NOT a registered event-code. The actual confirmation email is fired byRESERVATION_CREATEDconsumed by dispatcher. The notification call is functionally redundant.
Frontend.
- [P1] Default API key fallback
'hmp-default-public-booking-engine-key'—hmp-booking-engine/src/lib/api/client.ts:23. DEV-only warning; production cliff is atPOST /reservations. - [P1]
bookingApi.calculatePricingis@deprecated-throws (bookingApi.ts:75-81).searchAvailability'spricePerNightis BAR-only; contract rate adds arateQuotefield but the frontend'sCreateBookingRequestdoes NOT includerateAgreementId. A guest who sees a contract rate at search will be charged BAR at booking commit. Quote-to-book drift.
Tests.
- [P0] Zero concurrency tests.
Grep "concurrent|race|two guests|last room|simultaneous"across reservation-service: only one comment-level hit. No test asserts two parallelcreateBookingcalls for the last available room produces exactly one CONFIRMED reservation. - [P1]
HoldReleaseSchedulerhas no test —hold-release.scheduler.spec.tsdoes not exist.
Domain correctness.
- [P1] GST stacked onto
totalAmountper-room (booking-engine.service.ts:927-931) — but the OTA path usesRoomReservationDto.isTaxInclusive(per CLAUDE.md gotcha 7) which the booking-engine path does NOT set. If a hotel ever ingests both flows, double-GST possible. - [P1]
room.assignmentincreateBooking(:1071-1110) is "best-effort, does not fail the booking" — a property with no available physical room creates a CONFIRMED reservation withRoomReservation.roomId = null. Check-in flow then must scramble. - [P2]
paymentStatusisString?not enum (schema.prisma:99) — typo silently produces garbage.
Flow 4 — Staff Verifies Reservation
Verdict: RED (NO-GO). The detail page's folio is a 3-line client-side fabrication; guest panel reads from a possibly-stale local Prisma read-model instead of GuestServiceClient; list-page search box passes filter values into a DTO field the backend ignores; global Cmd+K calls a non-existent route; Modify/Reg card buttons are dead.
Backend endpoints.
- List/search —
POST /reservations/searchathmp-reservation-service/src/reservations/reservations.controller.ts:433-461. Implementationreservations.service.ts:2210-2302. - Detail —
GET /reservations/:idatreservations.controller.ts:262,findOneatreservations.service.ts:1421-1464. Eagerly includesproperty, guest, company, group, roomReservations, folios, payments, reservationHistory. - Modify —
PUT /reservations/:idatreservations.controller.ts:300. Invalidatesavailability:{propertyId}:*Redis atreservations.service.ts:1780. - History —
GET /reservations/:id/historyat:370. - Folio (cross-service) — NOT called from the detail page. Dashboard's
frontDeskApi.getFolios(reservationId)(hmp-web-dashboard/src/lib/api/front-desk.ts:225-234) hitsGET /folios/by-reservation/:id, but the detail page never invokes it.
Cross-service reads.
- [P0] Guest profile served from local Prisma
Guestread-model, NOTGuestServiceClient.getGuest—reservations.service.ts:1438include: { guest: true }. Schema declares fullGuestmodel athmp-reservation-service/prisma/schema.prisma:371-393. The detail page rendersres.guest.email,res.guest.phone,res.guest.totalStays,res.guest.vipLevel(keynova.tsx:1141-1145, 925, 1344) directly from this stale read-model. Charter violated. - [P0] Same issue on search and findAll:
reservations.service.ts:2286, 1339, 1399, 1665allinclude: { guest: true }. - [P0] Folio tab on detail page is fabricated client-side, not fetched.
keynova.tsx:947-992builds 3 syntheticFolioLinerows fromres.totalAmount,res.taxAmount,res.paidAmount. NO call tofrontDeskApi.getFolios(id). Real folio rows in frontdesk-service (POS charges, mini-bar, manual charges, multiple payments) are invisible.
Frontend.
- [P0] Search box is broken end-to-end.
keynova.tsx:217passesguestName: debouncedSearchintoReservationList. Filter flows throughreservationApi.list()atlib/api/reservations.ts:124which routesquery: filters.guestName || filters.confirmationNumber(clobbersguestNametoquery). Backendsearch()readssearchDto.guestNameandsearchDto.confirmationNumber; it never readssearchDto.query. Typing in the search box produces zero filtering. - [P0]
ModifyandReg cardbuttons are dead — noonClick(keynova.tsx:1388-1393). - [P1] Keys / KeySetupGrid (
keynova.tsx:218-279, 1473-1481) is mock-only. - [P1] Travel party (
keynova.tsx:996-1007) synthesisesAdult guest 2,Child 1strings — does NOT readPaxRecord(the actual per-pax model). - [P1] Hero "Activity · last 48h"
TimelineRail(keynova.tsx:1273-1292) hardcodes 2 events — does not readreservationHistory.
Domain correctness.
- [P0] Global search calls non-existent route.
apiClient.get('/search')(hmp-web-dashboard/src/lib/hooks/use-global-search.ts:85) — no/searchregistered in gatewayservices.config.ts, no service exposes@Get('search')matching the federated shape. Cmd+K returns 404 in production. - [P1]
reservation.totalAmountin "Rate & policy" card re-derives pre-tax via pro-rate hack(tax * rateAmount) / total— fragile across multi-room reservations with mixed GST slabs.
Tests. Unit: __tests__/reservations-page.test.tsx exists (single file). Detail page has NO unit tests. E2E: 5 specs exist (list, detail, amend, cancel, create) — they did not catch the broken search-box wiring or dead Modify button (both bugs are in production).
Flow 5 — Front-desk / Housekeeping Updates
Verdict: RED (NOT launch-ready). The HK gateway auth and tape-chart drag wiring are good. Everything else in this flow violates the audit charter.
Data layer.
- [P0] No
RoomMovemodel. Audit trail lives inReservationHistory.action='ROOM_MOVE'only. No dedicated history table withfromRoomId/toRoomId/movedAt/movedBy— every move requires JSON blob spelunking. - [P0] No retroactive folio rate adjustment on move.
roomMove()androomUpgrade()mutateRoomReservation.roomId(andrateAmountonly whendto.adjustRateis set) but never split the existing folio room-charge line.room-ops.service.ts:118-122updates the row in place; no folio CHARGE/ADJUSTMENT is posted. Industry rule violated. - [P0] No availability re-check on extend.
reservations.service.ts:1606-1647recomputestotalAmountfor the new night count but never queries inventory for the added nights — you can extend into a sold-out date. - [P0] No channel-manager push on extend.
reservations.service.ts:1772-1778publishesRESERVATION_MODIFIED. Channel-service has noReservationModifiedConsumerthat pushes inventory deltas back to OTAs. - [P1] No cancellation-policy timer reset on extend.
Backend endpoints.
- [P0] Two
/room-movePOST handlers.checkin.controller.ts:97(top-level, takesx-property-idheader) androom-ops.controller.ts:13(@Controller('front-desk'), no header). Dashboard callsapiClient.post('/room-move', dto)→ gateway@All('room-move')(proxy.controller.ts:517) → frontdesk → hits the inferior checkin.controller.ts handler. The room-ops one with the better validated code is reachable only via/front-desk/room-move. Two implementations, divergent behavior. - [P0] No
/extend-stayendpoint. Extend is implemented asPUT /reservations/:id { departureDate }. The seven required steps in the audit checklist (availability+rate+policy+OTA-push) are not in one place. - [P0] EXTENSION_NIGHT folio charge propagates over Kafka, not in-transaction.
reservations.service.ts:1744-1765publishesFOLIO_CHARGE_POSTEDoutside the DB transaction (comment at:1723-1726admits "no transactional outbox"). At-least-once delivery; if reservation-service writes succeed but Kafka publish fails permanently, balance under-bills silently. - [P0]
PosFolioChargeConsumeris default-OFF behindENABLE_POS_FOLIO_CHARGE_CONSUMER=true(pos-folio-charge.consumer.ts:70). If staging/prod miss the flag, no extension-night CHARGE row ever lands on the folio. - [P0] Room move uses xPrisma cross-service direct DB access.
room-ops.service.ts:15-29, :65-72readsreservation/roomReservationfrom a slim per-service Prisma schema that doesn't carry the model —STRICT-TODOleft explicit.
WebSocket auth. HousekeepingGateway (gateway/housekeeping.gateway.ts:97-174) verifies JWT in handleConnection, decodes alg from header (:114-123) instead of trusting client claim, requires RS256 in production with JWT_PUBLIC_KEY (:127-138), HS256 fallback only in dev. Tenant guard at :269-275, property guard at :279-285. Solid — the highest-quality piece of Flow 5.
Frontend.
- Tape-chart drag → room-move: wired (
tape-chart/drag/use-drag-reservation.ts:9, :71). - Reservation detail "Extend" button: navigates to
/front-desk/calendar?action=extend(reservations/[id]/keynova.tsx:936) — does NOT call any extend endpoint directly. - [P0] Mobile housekeeping task page (
hmp-mobile-apps/apps/housekeeping/app/task/[id].tsx): only renderstask.photos.lengthcount; has no<PhotoUpload taskId>widget. PhotoUpload is wired only to room/[id] with aroomIdprop. Mark Complete (:175-184) callsupdateTaskStatus(id, 'COMPLETED')directly — when property hasrequireInspectionPhoto=true, mobile housekeeper hits theHK_INSPECTION_PHOTO_REQUIREDsafeguard (tasks.service.ts:653-665) and is blocked with no UI path to attach a photo. Critical mobile flow break.
Tests. room-ops.service.spec.ts (11), upgrades.service.spec.ts (11), tasks.service.spec.ts (11), housekeeping.gateway.spec.ts (15). No coverage for retroactive rate split, OTA push, cancellation-policy reset, or end-to-end EXTENSION_NIGHT bus integration.
Domain correctness — PMS. Room move destructive without folio realignment. Extend can oversell. No OTA push on extend. EXTENSION_NIGHT consumer opt-in. Mobile photo path broken. Two /room-move controllers. Discrepancy detection misses sleeper case. Room-status board hard-codes doNotDisturb: false (rooms.service.ts:196) — DND flag is dead.
Flow 6 — Front-desk Check-in
Verdict: RED (NO-GO). Multiple P0 defects across both Indian and International tracks.
Data layer.
Folio(hmp-frontdesk-service/prisma/schema.prisma:20-62) opened on check-in viatx.folio.createatcheckin.service.ts:374-382. Single folio per reservation; no per-pax folios.GuestIdVerification(schema.prisma:860-877) — persisted fire-and-forget viasetImmediateafter the response is sent (checkin.service.ts:515-532, 593-609). IftenantIdcannot be resolved, the row is silently skipped with a WARN — a successful check-in can return 201 with NO ID-verification record persisted.KeyCardLog(schema.prisma:304-324) present, butKeyCardService.encodeForCheckIndoes NOT write a row; writes only happen in the hardware adapters. No persistent key-card audit trail.- Guest-service
GuestIdentity(hmp-guest-service/prisma/schema.prisma:203-219):idTypeenum supports PASSPORT/AADHAAR/etc.idNumber Stringplaintext column, application-encrypted viaFieldEncryptionServiceinguests.service.ts:273. Aadhaar masked on read to last-4 (guests.service.ts:90-93). 12-digit regex at:104— no Verhoeff checksum.
Backend endpoints.
POST /check-in/:reservationId(checkin.controller.ts:58-84).POST /check-in/batch(max 50).- [P0]
POST /express-check-inis a stub — only returns{assignedRoom, message: "Room assigned. Complete check-in at kiosk..."}(checkin.service.ts:691-752). Does NOT flip reservation status, open folio, persist ID, or enforce FRRO. RED for international guests at kiosks. - [P0]
POST /walk-in(:115) — has its own duplicate-FRRO-bypass:WalkInDto(dto/checkin.dto.ts:212-278) acceptsidDocumentType/idDocumentNumberbut has noformCfield and no FRRO enforcement code inwalkIn(). - C-Form:
POST /:reservationId/guest/:guestIdgenerate;GET /pending-submission24h overdue digest;GET /:id/pdf;POST /:id/mark-submitted. - Gateway routing two-file rule verified at
services.config.ts:261-351andproxy.controller.ts:449, 457, 486, 493/499, 505/511, 2027/2029, 2145/2151. PASS.
Compliance — Form C / FRRO / Aadhaar / DPDPA.
- [P0]
purposeOfVisithardcoded "Tourism" (cform.service.ts:319);arrivedFromhardcoded empty string (:318). Dashboard never collects either. The dashboard'sformC.arrivedFrom/purposeOfVisitis DROPPED —generateCForm()builds a freshcformobject from guest-service data.arrivedFromwill be empty in every generated PDF. - [P0] No visa type captured into the generated C-Form.
cform.service.ts:312writesvisaType: ''regardless of input. - [P1]
dateOfBirthandgenderpulled from guest-service — foreign guest created via booking engine without DOB/gender will have empty fields on FRRO form — FRRO will reject. - [P0] FRRO submission is
ManualUploadFrroClient(hmp-shared-packages/packages/integrations/src/lib/frro/manual-upload-frro-client.ts:29-54):submitFormCreturns hardcoded portal URL and synthesisedMANUAL-<ts>-<last4>reference. No real e-FRRO API integration. - [P0] Foreigner detection naive:
(guest.nationality || '').trim().toLowerCase() === 'indian'/'india'/'in'(checkin.service.ts:189-194). A foreign guest withnationality=''is treated as Indian — silently bypasses FRRO. The booking-engine flow can omit nationality entirely.
Aadhaar / DPDPA.
- Aadhaar storage:
GuestIdentity.idNumberapplication-encrypted, masked on read to last-4. PASS for storage. - [P1]
GuestIdVerification.idDocumentNumber(frontdesk-service) — falls back to plaintext with a WARN (checkin.service.ts:91-95) on misconfigured prod env (noENCRYPTION_KEY). Should hard-fail in prod. - [P1] DPDPA 90-day retention: only fires on
RESERVATION_CHECKED_OUTfor guest-service rows —GuestIdVerificationrows in frontdesk-service have no retention timer.
State guest-register.
- [P0]
GET /reporting-feeds/frontdesk/police-register(reporting-feeds.service.ts:409-464) is a stub —:458:passportOrIdNumber = null; // Would decrypt formCDataEncrypted in production. Not implemented. State guest-register cannot be filed.
Frontend.
- [P0] DTO validator rejection — dashboard sends
data:image/jpeg;base64,...URL intoidDocumentImageUrl(hmp-web-dashboard/src/app/(dashboard)/front-desk/check-in/[id]/keynova.tsx:444-446, 966).CheckInDto.idDocumentImageUrlis@IsUrl({require_protocol:true,protocols:['https','http']})(dto/checkin.dto.ts:152-154). Validator FAILS → 400. Same defect forsignatureImageUrl. No client-side upload to S3 before posting; no presigned-URL flow. Check-in is broken end-to-end for any prop with image capture. - [P0] Field name mismatch: dashboard sends
signature(front-desk.ts:37) — backend DTO expectssignatureImageUrl. Signature silently dropped by validation pipe. - [P1] Storage encryption-at-rest:
StorageService.getPresignedUploadUrl/uploadFile(hmp-shared-packages/packages/infra/src/lib/storage/storage.service.ts:60-109) issues presigned PUTs with NOServerSideEncryption: 'AES256'|'aws:kms'. Bucket-default SSE is the only line of defense.
Tests. checkin.service.spec.ts (31), checkin.service.p6.spec.ts (9), checkin.controller.spec.ts (8), cform.service.spec.ts (14). No e2e Playwright spec for check-in flow with mandatory-fields rejection; no test asserts the dashboard's data: URL flow round-trips.
Flow 7 — Room Service to Folio
Verdict: RED (NO-GO). Three blockers.
Data layer.
OutletTypeenum athmp-pos-service/prisma/schema.prisma:43-53includesROOM_SERVICE.OrderType.ROOM_SERVICEexists at:253-259. Outlet uniqueness@@unique([propertyId, code])(:38).- [P0] HSN/SAC codes are not stored on the menu item.
MenuItem(schema.prisma:111-139) hastaxRateand that's it — nohsnCode, nosacCode, no GST rate-card linkage. The repo CLAUDE.md claimshsnCodeexists; it does not. NIC e-invoice IRN requires HSN/SAC per line. CLAUDE.md drift — file is wrong. - [P1]
OrderItem.modifiersis a Json blob — modifier prices not joined relationally toItemModifierat order time.
Backend endpoints.
POST /orders/:id/room-chargeathmp-pos-service/src/orders/orders.controller.ts:326-345,@Roles SUPER_ADMIN/ADMIN/MANAGER/F_AND_B. Pulls tenantId from JWT; propertyId from header.- Validation chain
orders.service.ts::postRoomCharge:1141-1318is solid: tenant-scoped lookup, duplicate prevention viaorder.folioPosted, cross-service reservation lookup viaReservationServiceClientverifyingroomIdmatch ANDstatus==='CHECKED_IN'. Fail-closed on lookup error. - [P1] Gateway two-file-rule violation.
services.config.ts:405-447declares/menu,/tables,/kitchen, butproxy.controller.tsonly has@All('menu/*'),@All('tables/*'),@All('kitchen/*')(:667, 674, 695) — no bare@All('menu'),@All('tables'),@All('kitchen'). Compare tooutlets/orders/shifts/tokens/pre-orderswhich have both.
Folio-posting integration & idempotency.
-
POS publishes
FOLIO_CHARGE_POSTED(orders.service.ts:1306-1315) with v2 typed payload atevents/folio-charge-posted.event.ts:15-76. Includes pre-splitcgstAmount/sgstAmount/igstAmount/customerState/placeOfSupplyandtransactionCodemapped fromorderType. -
Receiver
PosFolioChargeConsumer(hmp-frontdesk-service/src/folios/consumers/pos-folio-charge.consumer.ts):- Feature flag default OFF (
:70) —ENABLE_POS_FOLIO_CHARGE_CONSUMERmust be'true'. - Dedup key
folio:${folioId}:pos-order:${externalOrderId}(:353-366). Test exists atpos-folio-charge.consumer.spec.ts:218. - Self-loop guard (
:236). - Property mismatch guard (
:343-348). - Single-OPEN-GUEST-folio guard (
:296-323).
- Feature flag default OFF (
-
[P0] V2 GST split is dropped by the receiver.
extractChargeEvent()at:108-155extracts onlytransactionCode/description/amount/taxAmount. ThecgstAmount/sgstAmount/igstAmount/customerState/placeOfSupplyfields published in v2 are never read (grep on the file: 0 hits). Consumer callsFoliosService.postChargewith no GST breakdown (:373-383).folios.service.ts::postCharge:507-516re-derives tax fromgetSACCode(transactionCode) → getGSTRate(...)— losing the customer-state-driven CGST/SGST vs IGST distinction that POS deliberately persisted. CLAUDE.md gotcha #9 documents the rule the code violates. AP-T07-004 is not actually shipped end-to-end. -
[P1] Dedup-key fallback
event.transactionCode || 'FnB'(:375) — legacy hardcoded string the v2 contract was supposed to retire.
Tax handling.
- POS service charge at
orders.service.ts:951-956correctly splits 50/50 for intra-state, 100% IGST for inter-state. However the rate is hardcoded0.18(:140, 807, 952) — does not read property tax config. GST-exempt / composition-scheme properties wrong. MenuItem.taxRatedefaults5(schema.prisma:121) per-item — no link to property tax-config table.- [P1] Tip handling: not implemented.
tip/TIPabsent frompos-service/src/orders(grep: 0). Order schema has no tip column.
KDS integration. KitchenGateway JWT-verified (hmp-pos-service/src/gateway/kitchen.gateway.ts:101-178). RS256 in production with boot validation. joinOutlet re-verifies outlet.propertyId === client.data.propertyId. Auth is solid. Per CLAUDE.md gotcha #1, OrdersService.generateKOT must fire both WebSocket kot:created AND Kafka pos.kot.created; the doc asserts code does this.
Frontend. Folio page hmp-web-dashboard/src/app/(dashboard)/front-desk/folio/[id]/page.tsx displays transactionCode. No drill-down to originating POS order — FolioTransaction.reference stores folio:<id>:pos-order:<orderId> but UI doesn't parse it.
Flow 8 — Guest Check-out & Invoice
Verdict: RED (NO-GO for production). Six P0 defects.
Data layer.
Folio(hmp-frontdesk-service/prisma/schema.prisma:20-62) carriesirn,signedQRCode,ackDate,ackNumberdenorm columns. HasclosedAtbut nosettledAtcolumn —settleFolioreturns a syntheticsettledAt: new Date().toISOString()(folios.service.ts:1450) that is never persisted.Payment.paymentModeenum lacksSPONSOREDin DB but schema enum at:145lists 11 values. NopartialAmountonCheckOutDto; split payments not supported via checkout endpoint.EInvoiceJob.ackDateisString?notDateTime— drift fromFolio.ackDate DateTime?denorm column.InvoiceSequencecomposite unique on(tenantId, propertyId, financialYear), atomiclastNumberincrement.
Backend endpoints.
POST /check-out/:reservationIdatcheckout.service.ts:51. Acquires 30s Redis lock per reservation. Late-checkout charge auto-posted (:294). Folios closed in batch (:316). E-invoice queued + polled 2.5s (:356-378) — non-blocking on failure.POST /check-out/express(:504) — zero-balance path.POST /late-checkout(:594) — auto-calculates fee with GST.POST /folios/:id/invoiceand/folios/:id/invoice/pdf.POST /einvoice/generate, /cancel, /credit-note, /compliance-report.
Settlement modes.
- [P0]
CheckOutDto(dto/checkout.dto.ts:30) accepts a singlepaymentModeenum andpaymentReference. No amount, no split-payment array, no card last4 / cardType. Inline settle loop (:215-267) posts the entire open-folio balance to the single chosen mode. Cash + card combo cannot settle in one call. - [P0]
paymentModemissingBTCexplicitly;CITY_LEDGERis closest but no validation that acompanyIdis attached when picking it. - [P0]
Payment.cardLast4/cardTypecolumns exist (schema.prisma:131-132) but inline checkout payment write (:236-247) does not populate them — terminal RRN dropped. - [P0] Branded ESC/POS receipt path is gated by
ENABLE_FOLIO_PAYMENT_SUCCEEDED_CONSUMER, default OFF (folio-payment-succeeded.consumer.ts:55-61). Inline-settled cash/UPI payments inside the checkout transaction never publishpayment.succeeded— receipts only print for terminal-card flows.
GST / IRN compliance.
getNextInvoiceNumber(folios.service.ts:1620) resolves prefix fromProperty.codeviaPropertyServiceClient, guarded by Redis lock + atomic Prismaincrement. Gap-free at row level but no nightly gap-detection cron despite comment at:1629.- [P0] Divergent invoice-number generator at
invoice-pdf.service.ts:601-618: hard-codesHMP/prefix, ignoresProperty.code. Two competing implementations, two output formats. - [P0] IRN clobbers invoice number.
einvoice-job.processor.ts:64writesinvoiceNumber: result.ackNumber(the 15-digit NIC ack number) after the sequential value was already issued. Once NIC adapter completes, the GST-Rule-46 sequential invoice number is overwritten. Reporting (GSTR-1) loses the per-FY sequence. - [P0] B2B vs B2C resolution at
einvoice.service.ts:99-101keys offfolio.company?.gstin. Walk-in guests with personal GSTIN can never become B2B — noFolio.guestGstinfield. AATO ₹5cr threshold not validated anywhere — every B2B folio is pushed for IRN regardless. - [P0] Inter-state vs intra-state derived from GSTIN first-2-digits (
:103-110) and falls back topropertyStateCodewhen guest address is unavailable — guest read-model carries noaddressesrelation. Every B2C folio defaults to intra-state CGST/SGST, even if the guest is from a different state — IGST should be the correct treatment. - [P0] No production guard against
EINVOICE_MOCK=true. Mock-IRN path generates SHA-256-based 64-char IRNs that are structurally valid but not registered with NIC. A misconfigured prod deploy that inheritsEINVOICE_MOCK=truewill issue invoices that look real. - [P1] NIC adapter has only 2 specs in
nic-einvoice.adapter.spec.ts— auth flow + happy-path. No retry-on-401, no idempotency-key, no rate-limit-window, no production cert pinning.
Receipt / PDF. generateFolioReceipt in hmp-shared-packages/packages/common/src/lib/printing/receipt-templates.ts. Spec has 53 cases (above the claimed 47) covering ESC/POS bytes, GST splits, HSN, IRN. Solid. "DRAFT — IRN PENDING" watermark correctly emitted by invoice-pdf.service.ts:570-580 when folio.irn is null.
Cross-service & events.
- [P0] No invoice attachment on thank-you email.
DispatcherService.handleEvent('reservation_checked_out')(hmp-notification-service/src/dispatcher/dispatcher.service.ts:130-132) routes to generic HTML, EMAIL only, no folio invoice attachment. There is nofolio.closedorfolio.settledevent published. - [P0] No
FOLIO_PAYMENT_RECEIVEDpublish from inline checkout payments (checkout.service.ts:236). Reservation-serviceFolioPaymentConsumer(added 2026-05-05) is bypassed →paymentStatusdrift. - [P1] Duplicate task creation. Housekeeping
EventsService.handleCheckoutEvent(hmp-housekeeping-service/src/events/events.service.ts:155) creates aCHECKOUT_CLEANINGtask onRESERVATION_CHECKED_OUT, AND the explicitHOUSEKEEPING_TASK_CREATEDevent from frontdesk has no consumer-side dedup. Two HK tasks per checkout. - No loyalty accrual event on checkout — spec asks for accrual; no code path here calls a loyalty service.
Tests. checkout.service.spec.ts (24), folios.service.spec.ts (25), einvoice.service.spec.ts (31), invoice-pdf.service.spec.ts (10), nic-einvoice.adapter.spec.ts (2), receipt-templates.spec.ts (53). invoice-numbering.spec.ts (4) tests a local helper, not the production path. No concurrency test for invoice numbering, no gap-detection test.
Flow 9 — Night Audit
Verdict: RED (NO-GO for property #1). Eight P0 issues.
Data layer. NightAuditLog (hmp-frontdesk-service/prisma/schema.prisma:346-367) — status RUNNING/COMPLETED/COMPLETED_WITH_ERRORS/FAILED/ROLLED_BACK, KPIs, errors Json, performedBy.
- [P0] NO unique constraint on
(propertyId, auditDate)in frontdesk's schema; only@@index. Duplicate-run defense is afindFirstrace window innight-audit.service.ts:227-241. Redis lock (audit:lock:{propertyId}30-min TTL,:130-150) closes most of that window in practice, but withRedis unavailable for audit lock — proceeding without lock(:147) the system openly downgrades to no-lock — a single Redis blip during cron can double-post a property's nightly charges. Analytics-service mirror DOES have it (hmp-analytics-service/prisma/schema.prisma:202) — read-side safer than write-side.
Trigger & idempotency.
@Cron(CronExpression.EVERY_HOUR)innight-audit-scheduler.service.ts:17, computeslocalHourper property timezone (Asia/Kolkata default), runs only whenlocalHour === settings.nightAuditHour ?? 2. Wraps inDistributedLockService.withLock.- Manual
POST /night-audit/:propertyId/start(night-audit.controller.ts:132-153),?dryRun=trueshort-circuits. - Chain audit
POST /night-audit/chainruns sequentially across tenant properties. - Idempotency: per-folio room-charge dedup checks
transactionCode='ROOM' AND postingDate∈[today,tomorrow] AND isVoid=false(:456-474). - [P0] Dedup window timezone math wrong.
:454:todayStart.setUTCHours(0,0,0,0)on atodayvalue computed from property local-midnight (:225). For a property in IST,todayis2026-05-05T18:30:00Z;todayStartbecomes2026-05-05T00:00:00Z, sliding the dedup window 5.5h backwards. Charges posted at 23:00 IST on 5 May (17:30 UTC, falling into UTC 5 May) accidentally land in scope.
Posting loop.
:392-410cursor-paginatesstatus=CHECKED_INreservations (200/batch).- [P0] Rate computed by averaging the booking's total over the stay, not by reading today's
RatePlanRate. Any rate change mid-stay invisible — guest billed the original average. Real PMS posts the night's contracted rate from the rate calendar. - No CGST/SGST/IGST split happens here —
taxAmountis a single number;folio.update :542-552only incrementstotalTax. - Overstay tagging (
:484-537): whenreservation.departureDate < todayMidnightcode tagsOVERSTAY_NIGHT— correct. createMany+ parallel folio updates is sound for ≤1000 rooms but holds a single 120s interactive transaction (:665) that can blow past the timeout on 500+ rooms.- [P0] Multi-currency aggregation fudged:
:418-427logs WARN if any reservation has non-INR currency, proceeds with 1:1 sums anyway. Total revenue, ADR, RevPAR, GSTR-3B all wrong on USD/AED bookings.
No-show. :559-655 posts first-night cancellation fee. Updates reservation status=NO_SHOW.
- [P0] NO consultation of
CancellationPolicy. Some plans charge 100% of stay, some 1 night, some non-refundable already deposited. Posting "first night" universally violates contract terms on prepaid rate plans. No check whether deposit already collected — risk of double-charging.
Reports.
night-audit-pdf.service.tsuses pdfkit;GET /night-audit/record/:id/pdfreturns PDF,@Roles SUPER_ADMIN/ADMIN/MANAGER/ACCOUNTS.- [P0] PDF generated on-demand, NOT stored to S3 or any durable store. No retention policy. No archival. No CSV export endpoint. RBI/CERT-In require 5-year audit trail; this PDF is render-and-discard.
- Property name on PDF is
propertyName?parameter the controller never passes (:86) — every PDF saysNIGHT AUDIT REPORTwith no hotel identification. - Email template at
hmp-notification-service/src/templates/night-audit-report.tsdispatched onnight.audit.completedKafka event — emails the GM/Revenue Manager. The only durable distribution.
Cross-service.
- 6 bus events.
night.audit.completed(:295-304) consumed byNightAuditConsumer(analytics) which upsertsMvDailyOccupancy/MvDailyKpi/NightAuditLog. Also notification dispatcher. - [P0]
reservation.no_showper-reservation (:695-702) — fire-and-forget with.catch()log only, NOT in-transaction. DB write committed before events published. On Redis Streams broker outage right after commit, no-shows go silent — analytics MV stale, channel-service never sends OTA cancellation. - POS reconciliation:
:758-806callsPosServiceClient.getPosRevenueTotal, raisesNA_POS_RECONCILIATION_MISMATCHsafeguard ifvariance > ₹1. - [P0] NO POS day-close cascade. Audit treats POS as passive read-source.
hmp-pos-servicehasshifts/(cash) but nocloseOutletForDayendpoint. Open POS order at 02:00 IST (1) not in revenue total, (2) post-audit charges flow to same folio under new business date, (3) reconcile silently undercounting.
Frontend. hmp-web-dashboard/src/app/(dashboard)/night-audit/keynova.tsx:1-120. Pre-checks call getNightAuditPreCheckReport. Has rollback button.
- [P1]
validatePreAuditsilently catches all cross-service errors and reports0mismatches::1057-1062swallow PropertyServiceClient outage. Dashboard says "All clear, proceed" while data is unreachable.canProceed(:1275) derived purely frompendingDepartures === 0— open folios, dirty rooms, FO/HK mismatches do NOT block.
Tests. night-audit.service.spec.ts (55), chain-audit-orchestrator.spec.ts (17), night-audit.controller.spec.ts (16). Dual-run idempotency test exists but no concurrent-goroutine simulation. No tests for: timezone-shifted dedup window, multi-currency drift, cancellation-policy-aware no-show, POS-open-shift cascade, failed validatePreAudit masking.
Flow 10 — Reports & Dashboards
Verdict: RED (NOT launch-ready for property #1).
Required reports inventory.
| KPI / Report | MV | Definition file | Status |
|---|---|---|---|
| Occupancy % | MvDailyOccupancy (hmp-analytics-service/prisma/schema.prisma:36) | occupancy.definition.ts (occupancy-report) | OK |
| ADR / RevPAR | MvDailyKpi (schema.prisma:161) | revenue-daily.definition.ts:18; manager-flash.definition.ts:27 | OK |
| Arrivals today | MvDailyArrivals (:308) | arrival.definition.ts (arrival-report) | OK |
| Departures today | MvDailyDepartures (:328) | departure.definition.ts (departure-report) | OK |
| In-house | live-proxy (not MV) | in-house.definition.ts (in-house-report) | Live-proxy |
| Daily revenue (room) | MvDailyKpi.totalRoomRevenue | revenue-daily.definition.ts | OK — only roomRev |
| Top room types | NONE — falls back to EventStore.findMany | room-type-revenue.definition.ts:37 | BROKEN — bypasses MV pattern |
| Channel mix | MvChannelPerformance | source-wise-revenue.definition.ts:50 | OK |
| Source mix | Same | same | OK |
| YoY vs prior | MvDailyKpi LY window via LyEnricher | comparator/ly-enricher.ts:83 | PARTIAL |
MV consumer wiring. EventDispatcherService (src/consumers/event-dispatcher.service.ts:39-75). 16 consumers wired. SKIP_EVENT_SUBSCRIPTIONS=true skip flag. ReservationConsumer subscribes 10 topics, upserts 9 MVs. NightAuditConsumer (night-audit.consumer.ts:82-226) authoritative SET.
API endpoints. GET /api/reports/v2/catalog, POST /api/reports/v2/:id/run?format=..., GET /api/reports/v2/:id/runs. Dashboard endpoints at dashboard.controller.ts:98-238. Gateway routing wired.
- [P2]
night-audit-reportsregistered atproxy.controller.ts:1429but I don't see a matchingservices.config.tsentry — possible 404 trap (two-file rule).
Frontend. Legacy /reports redirect IS in place: next.config.ts:64-66 emits 308 to /reports/v2 (with regex (?!v2$) to prevent redirect-loop). v2 catalog at (dashboard)/reports/v2/page.tsx. Run page at (dashboard)/reports/v2/[reportId]/page.tsx. Anomalies page calls real endpoint — confirmed not stubbed. Operations Hub home gates on isOperationsHubEnabled() (defaults ON post-2026-05-02). ActionsQueueSection calls /actions endpoint that does not exist yet — endpoint returns 404, hook silently empty-states.
KPI-tile broken-IDs (showstopper).
- [P0]
tiles/kpi/revpar.tile.tsx:11,19,adr.tile.tsx:11,19,occupancy.tile.tsx:11,19referencereportId="manager-flash". Registered ID ismanager-flash-report. - [P0]
arrivals-today.tile.tsx:11,18usesarrivalvs registeredarrival-report. - [P0]
departures-today.tile.tsxusesdeparturevsdeparture-report. - [P0]
inhouse-count.tile.tsxusesin-housevsin-house-report. - [P0]
no-shows-today.tile.tsxusesno-show-reservationvsno-show-reservation-report. - Every Operations Hub home tile will hit the registry, miss, and surface an error/empty card. KPI-card test file (
kpi-card.test.tsx:22,31,…) propagates the same wrong ID — bug never caught.
Numerical-correctness deep-dive — Daily Occupancy / ADR / RevPAR.
Three concrete bugs in the math:
- [P0]
roomsSoldincrements by 1 perreservation.created(reservation.consumer.ts:175,200). Counts bookings, not room-nights. A 3-night reservation contributesroomsSold=1andtotalRoomRevenue=full 3-night total, soadr = 3-night total / 1 = 3× the real ADR. Night audit later overwrites this (:179: roomsSold = occupiedRooms), but until audit runs the dashboard shows wildly inflated ADR. - [P1]
totalRoomskeyed bypropertyId_dateonMvDailyOccupancybut seeded from0on firstreservation.createdof the day if no occupancy row exists yet (reservation.consumer.ts:170-173). Result:revpar = 0until firstreservation.checked_in(which doesn't writetotalRoomseither). RevPAR shows 0 every fresh day until ~02:00 IST. - [P0] Date is
new Date(arrivalDate)(reservation.consumer.ts:103). IfarrivalDateis date-only"2026-05-06",new Date('2026-05-06')= UTC midnight. Same innight-audit.consumer.ts:121. Dashboard'sgetTodayAdrRevparRow(dashboard.service.ts:255) buildstodayin server local time, NOT property TZ. Run on UTC pod and "today" can disagree by 5h30m from "today IST" between 18:30-23:59 IST.Asia/Kolkatanot honored on any MV write or any dashboard read.
Date filters / timezone. resolvePeriodBounds (reporting/reports/shared/period-helpers.ts:9-40) hardcodes UTC for today / yesterday / mtd / ytd / wtd. After 18:30 UTC each day the IST date has rolled over; reports run with period: { type: 'today' } between 18:30 IST and midnight IST will return rows for the previous IST day.
Saved views & exports. ReportSavedView Prisma model exists. Endpoints registered. Default schedule timezone IS Asia/Kolkata (saved-views.controller.ts:131) — good. Export pipeline 5 formatters; sync ≤500 rows; queued via BullMQ above. PDF requires Chromium — SKIP_PUPPETEER must be set in CI/test or render() throws.
Flow 11 — ResAvenue Channel Manager
Verdict: AMBER. Cert-handshake plausibly complete (real partner traffic verified); 4 P0 issues remain in the production code path.
Data layer. IntermediaryConnection (hmp-channel-service/prisma/schema.prisma:496-532) — encrypted creds, webhookCallbackUrl, webhookSecret, outboundIp. IntermediaryProviderConfig (schema.prisma:626-) — Tier-1 platform-tier provider catalog.
- [P0]
WebhookEvent(schema.prisma:317-355) has NO@@uniqueconstraint on(provider, parsedBookingId, status)or(provider, parsedBookingId, eventType). Idempotency relies onfindFirst+ manualstatus='ACCEPTED'check (resavenue-webhook.controller.ts:480-488, :758-765) — race-vulnerable on concurrent retries. ChannelSyncLoggainedrequestXml/responseXmlcolumns 2026-05-05;SyncServicepersists actual wire bytes.
Push primitives.
- Rates:
POST /channel-sync/push-rates. RedissetNxdistributed lock (60s), 3-attempt exponential backoff (sync.service.ts:130-156).ResAvenueXmlBuilder.buildRateXml(resavenue-xml.builder.ts:238-309) emitsOTA_HotelRateAmountNotifRQwith<BaseByGuestAmt NumberOfGuests=1/2>,<MinStay>/<MaxStay>/<StopSell>inside<Rate>, optional<Promotion>, optional<OTAName>(omitted by default per spec v1.7). - Inventory:
POST /channel-sync/push-availability.buildInventoryXml(:171-217) emitsOTA_HotelInvCountNotifRQwithStatusApplicationControl Start/End/InvTypeCode+InvCounts(with optional weekday attrsMon...Sun),<InvCount Count>,<StopSell>,<CloseOnArrival>,<CloseOnDeparture>,<CutOff>. AP-T11-013 drip-feed allocation cap applied before adapter call. - Stop-sale: rate path (
<StopSell>inside<Rate>, since 2026-05-05 spec v1.7 page 11) and inventory path. KNV_R_07 cert test exercises rate path. - Restrictions:
POST /channel-sync/push-restrictions.ResAvenueAdapter.handleRestrictionPush(resavenue.adapter.ts:59-118) splits restrictions: closure flags ride inventory XML; stay-only rows silently dropped withconsole.warnonly — caller must re-route viapushRates. Counted as droppedStayOnly but never surfaced inSyncResult.errors[](:84-89). - Range collapse: verified working.
collapseRanges(:91-128) groups strictly-consecutive same-value rows. Cert evidence KNV_I_02 collapsed 30 days into one block.
Inbound webhook & parser.
POST /webhooks/resavenue/booking(resavenue-webhook.controller.ts:227-262) — root-element dispatcher.- Reservation flow (
processReservation:323-601): IP allowlist, credential check, parse viaResAvenueXmlParser, multi-RoomStay aggregation (:507-512), idempotency viafindFirst+ status check, normalize viaChannelBookingNormalizer, publishCHANNEL_BOOKING_RECEIVEDKafka topic. - HMAC-SHA256 with
timingSafeEqual(:103-112).
Failure handling & cert pinning.
- 3-attempt exponential backoff (100/200/400ms).
- 30s fetch timeout via
AbortController(resavenue.adapter.ts:121-145). - [P1] Cert pinning agent factory exists (
pinning-https-agent.factory.ts) but is NOT wired intoResAvenueAdapter.sendRequestwhich uses plainfetch()(resavenue.adapter.ts:125-130). CLAUDE.md acknowledges this gap. Live ResAvenue endpoint is HTTP today (http://203.109.97.241:8080/...) so pinning is moot until they migrate to HTTPS.
Tests & cert evidence. 6 ResAvenue-specific spec files totaling 2,691 lines. Cert format spec has 13 byte-level shape assertions. Cert evidence file at hmp-docs/Integrations/ResAvenue/cert-evidence-2026-05-05.md documents 18 PASS rows (KNV_I_01..09 + KNV_R_01..09) against HotelCode 261 with real <Success/> responses captured in XML. Memory's claim is accurate.
Frontend. Sync logs / webhook events: /distribution/audit?type=outbound|inbound. Mapping CRUD: /settings/channels/mappings, /managers, /routes. Admin portal: channel-providers, connections-overview, changes (4-eyes queue).
P0 issues blocking launch.
- Webhook idempotency race-vulnerable. Add
@@unique([provider, parsedBookingId, eventType])partial index OR Redis SETNX with the booking ID. Two concurrent ResAvenue retries could both pass thefindFirstand double-publish. itemsProcessedhardcoded to 1.ResAvenueXmlParser.parseSyncResponsereturnsitemsProcessed: 1regardless of how many rows were in the request (resavenue-xml.parser.ts:27,31). 360-rate push reads asitemsProcessed=1. Sync metrics + dashboards are wrong.- Stay-only restrictions silently dropped.
handleRestrictionPushwarns to console only (:84-89);SyncResult.errors[]stays empty — API responds 200 OK while nothing happens. - No DB-level idempotency on outbound retries. Three attempts in 100/200/400ms — if ResAvenue returns 200 with
<Errors>, retry resends the same XML. No idempotency key on outbound path.
Flow 12 — POS Terminal & Payment Gateway
Verdict: AMBER. Gateway path is launch-ready; card-present has reconciliation gap; subscription docs lie.
Data layer. hmp-payment-service/prisma/schema.prisma is real and substantive. ProviderCatalog (:148), MerchantAccount (:164, AES-256-GCM envelope encrypted, dekWrapped, kmsKeyVersion, no plaintext credentials), Payment (:198, idempotency key unique per tenant :226, status machine via PaymentStatus :40), PaymentAttempt (:232, carries only cardLast4 :242 + cardBrand :243 — no PAN), Refund :264, PaymentLink :284 with tipAmount :320, PaymentLinkBatch :337, TerminalDevice :354 (with OutletKind :366 covering FRONT_DESK/POS_OUTLET/BANQUET_VENUE/SPA_OUTLET), UpiQrCode :389, WebhookEvent :418 (@@unique([providerType, providerEventId]) :438 — DB-level idempotency), Subscription + SubscriptionCharge :463/498. Transactional outbox PaymentOutbox :545 with FOR UPDATE SKIP LOCKED claim semantics — production-grade.
POS terminal (card-present). PineLabs adapter is real (adapters/pinelabs/pinelabs-terminal.adapter.ts:60-279) — TCP socket against Plutus ECR, length-prefixed JSON frames. Mock/sandbox/live mode handling. ECONNREFUSED/EHOSTUNREACH → ProviderDeviceOfflineError. Tip-amount path real (:137, plutus-ecr.codec.spec.ts:37-46). Razorpay POS terminal adapter at razorpay-terminal.adapter.ts — poll-until-captured. REST surface TerminalChargeController at terminals/terminal-charge.controller.ts:43-74, returns 202, async dispatch via TerminalChargeWorker. Roles ADMIN | SUPER_ADMIN | FRONTDESK_AGENT | POS_CASHIER. Two-file rule satisfied for /terminal-devices (services.config.ts:525-530, proxy.controller.ts:999/1006).
- [P0] No HMP-side terminal batch-settlement reconciliation. PineLabs
batchSettlementatpinelabs-terminal.adapter.ts:246-252is documented no-op: "Batch settlement is handled on-device by Pine Labs." Dashboard POS hardware page calls/pos/hardware/batch-settlementsagainst pos-service which has no batch-settlement source-of-truth. End-of-day reconciliation between terminal acquirer batch and HMP Payment ledger is not implemented for PineLabs. RED for any property using card-present.
Razorpay online flow. Real adapter (razorpay-gateway.adapter.ts:34-279). Keys read from MerchantContext credentials (encrypted at rest, decrypted via CredentialCipherService); zero hard-coded keys in repo. Order creation passes payment_capture: 1 (:85), X-Razorpay-Idempotency-Key header (:93). verifyAndParseWebhook :152-177 throws ProviderSignatureError on mismatch. Status mapper covers authorized → AUTHORIZED, captured → CAPTURED, failed → FAILED, refunded → REFUNDED.
- [P1] No SETTLED or CHARGEBACK state in
PaymentStatusenum — gap for dispute / settlement reporting.
Webhook & idempotency. WebhookController at webhooks/webhook.controller.ts:31-71 is @Public(), accepts :providerCode/:merchantAccountId, raw-body via body-parser.raw registered before JSON parser. HMAC verified by verifyRazorpayWebhookSignature (razorpay-signature.ts:11-19) using createHmac('sha256').digest('hex') and timingSafeEqual — proper constant-time. Layered idempotency: tentative dedup, authoritative dedup, P2002 race recovery, DB-level unique constraint, failed-signature audit row. Reconcile cron every 10 min, capped 5 attempts. Production-grade.
Refunds. PaymentOrchestratorService.refund :330-540. Validates payment SUCCEEDED/PARTIALLY_REFUNDED. Computes priorTotal, enforces requested ≤ remaining. Idempotency via metadata path-match (:384-400).
- [P1] Refund idempotency lives in
metadata.idempotencyKeyJSON path — should be a first-class indexed column. - Routes to terminal vs gateway based on
attempt.terminalDeviceId :415. Provider failure marks refund FAILED. Webhook flips final status (webhook-dispatcher.service.ts:528-602). Audit row +payment.refundedevent published.
Subscriptions.
- [P0]
SUBSCRIPTION_VIA_PAYMENT_SERVICE_ENABLEDflag does NOT exist in code.hmp-auth-service/src/subscriptions/subscription.service.tsinjectsPaymentServiceClientunconditionally (:8, :69). Legacy adapter + flag have been REMOVED (subscription.module.ts:12: "RazorpaySubscriptionAdapter and its feature flag have been removed"). However,hmp-auth-service/src/billing/subscription.service.ts:189,370,890,926andbilling/billing.service.ts:207-481still hold privatecreateRazorpaySubscription/cancelRazorpaySubscriptionmethods — DEAD code path. Root CLAUDE.md is stale on this flag.
PCI. PCI-redaction infrastructure real at hmp-shared-packages/packages/common/src/lib/sanitizer/pci-sanitizer.ts:2-3 (CARD_FIELDS, CVV_FIELDS) with Luhn validation, masks PAN to ****1234, redacts CVV to ***. payment-orchestrator.service.ts:640 actively scrubs metadata via regex. Schema persists only cardLast4+cardBrand (:242-243); no PAN/CVV/track data anywhere. Booking-engine card form at hmp-booking-engine/src/pages/direction-a/CheckoutPage.tsx:279,281 uses useState('4532 •••• •••• 8842') — hardcoded display placeholders, never user input. Real flow uses Razorpay hosted checkout via useRazorpayCheckout (PaymentForm.tsx:5,48). SAQ-A scope preserved. Key-cards page uses cardNumber: string for room key card identifier — confirmed safe.
Frontend. Dashboard: front-desk/folio/[id]/page.tsx:1271 mounts TerminalChargeModal. POS hardware page calls /pos/hardware/batch-settlements — endpoint not actually backed by payment-service. Booking-engine: real Razorpay widget wired.
- [P0] Refund modal absent from dashboard. Backend endpoint exists; no UI. Frontdesk-manager users can't trigger partial refunds without API call.
Tests. Real coverage: razorpay-signature.spec.ts (4), webhook-dispatcher.service.spec.ts (11), payment-orchestrator.service.spec.ts (28), razorpay-gateway.adapter.spec.ts (idempotency-header tests), razorpay-terminal.adapter.spec.ts, pinelabs-terminal.adapter.spec.ts, plutus-ecr.codec.spec.ts, terminal-charge.service.spec.ts, terminal-charge.worker.spec.ts, pci-log-verification.spec.ts:33,124.
P0 hard blockers.
- Bug —
razorpay-terminal.adapter.ts:184,293still sendX-Payment-Idempotency-Key(silently ignored by Razorpay, defeats POS-terminal retry idempotency). The fix from AP-T12-003 was applied to gateway adapter but missed for terminal. Two-line fix. - No HMP-side terminal batch-settlement reconciliation.
- Refund modal absent from dashboard.
SUBSCRIPTION_VIA_PAYMENT_SERVICE_ENABLEDdocumented but does not exist.
Flow 13 — Tape Chart Calendar
Verdict: RED. This is the operational heart of any PMS, designated by the user as the hero feature, and the production shell is a half-implemented makeover that has dropped four of the eight checklist operations the older code already supported.
Page & component existence. Tape chart at /front-desk/calendar. Page file is a re-export: (dashboard)/front-desk/calendar/page.tsx:1 → ./keynova. keynova.tsx:8,334 mounts <TapeChartV2Shell /> from @/components/modules/front-desk/tape-chart-v2. Two tape-chart codebases in repo: older tape-chart/ (with virtualization, context menus, bulk-select toolbar, BAR_ACTIONS, CELL_ACTIONS) and newer tape-chart-v2/ (what production loads). The v2 shell is missing most of v1's features. Central finding.
Grid rendering & virtualization. tape-chart-v2/chart-grid.tsx:143-265 renders the entire grid as one CSS-grid: gridTemplateColumns: 'var(--room-col-w) repeat(var(--total-days, 14), var(--col-w))' (:155) with all rooms inline. No virtualization. The codebase ships a working @tanstack/react-virtual layer (tape-chart/virtual/use-virtual-chart.ts:5,80, virtual-row-renderer.tsx), but tape-chart-v2-shell.tsx does not use it. Grep for Virtual|tanstack/react-virtual|react-window under tape-chart-v2/: zero references.
Backend bulk endpoint. GET /front-desk/calendar?from=&to=&roomTypeId=&floor=&source=&vipOnly= — single bulk fetch.
- Controller:
hmp-frontdesk-service/src/calendar/calendar.controller.ts:18-49. - Service:
calendar.service.ts:94-438. Tenant-asserted (:105: propertyClient.assertPropertyTenant), max 90-day window enforced (:117). Returns{rooms, roomTypes, reservationBlocks, blockedRooms, unassignedReservations, availability, roomBlocks}. - Not paginated, not streamed. For 100 rooms × 30 days × ~40 active reservations, payload is acceptable. For 500 rooms × 90 days the unbounded
roomReservation.findMany(:167) will hurt — no per-page cap. - Frontend:
use-tape-chart-data.ts:46-82withstaleTime: 30s, no polling (WebSocket-driven viauseRealtimeTapeChart).
Drag-move + extend wiring. Drag implemented via @dnd-kit. The handlers are real and wired:
tape-chart/drag/use-drag-reservation.ts:39-123.kind === 'resize-right'→ extend;kind === 'move'→ reassign.kind === 'resize-left'explicitly out of scope ("Phase 2",:81-83) — shorten-by-dragging-left-edge does not work.- Extend mutation:
use-tape-chart-actions.ts:286-291→PUT /reservations/:id { departureDate }. - Move (CHECKED_IN):
:347-354→POST /room-move. - Move (pre-arrival):
:356-359→POST /reservations/assign-room. Comment at:622-625admits the assign endpoint takes ~30s in dev. - Optimistic cache patch + revert + undo-toast pipeline solid (
runCommand :194-250).
Context menu actions.
- [P0] v2 does not wire context menus.
BAR_ACTIONS(check-in, check-out, move-room, extend, shorten, add-charge, view-folio, print-grc, mark-no-show, cancel) andCELL_ACTIONSdefined intape-chart/context-menu/context-menu-items.ts:71-95.BarContextMenuwired in v1tape-chart/room-row.tsx:240-259.tape-chart-v2/bar.tsx:83,180acceptsonContextMenu;chart-grid.tsx:27,254acceptsonBarContextMenu. Buttape-chart-v2-shell.tsx(:339-357) never passes either — grep returns zero. Right-clicking a bar in production does nothing. No CellContextMenu either. - [P0] DetailPanel actions are placeholders.
tape-chart-v2-shell.tsx:365-372— every action exceptopen-reservationshowstoast.info('${action} for ${res.confirmationNumber} — coming in Phase 2'). So even from the side panel, check-in / check-out / cancel / room-move are non-functional.
Conflict & concurrency. Drag-time overlap check (use-drag-reservation.ts:55-67, :97-105) — client-side scan, blocks drop with toast. Server-side: use-tape-chart-actions.ts:239-247 — on 409/404, optimistic patch reverted and calendar query invalidated.
- [P1] No
version/ETag/If-Match column onReservation.Grepforversion|@@version|ETag|If-Matchin reservation schema: none. "409" handling is purely a generic-toast fallback; if two staff drag the same bar simultaneously, the second write silently wins on the database side.
Performance. 100 rooms × 30 days = 3,000 cells: realistic with virtualization. Without it, tape-chart-v2/chart-grid.tsx mounts:
- 100 sticky room-name divs + ~5 group-row divs.
- 100 × 30 = 3,000
<RoomRow>cell divs. - N reservation bars (
<Bar>) flat-mapped at:238-258. - With active drag, CSS-grid recompute on resize, and a non-memoized
allReservationsflatten at:136-141, real-world 100×30 will land in 400ms–1s, getting worse linearly past that. AMBER-to-RED.
Mobile & a11y.
- [P1] Mobile: zero handling. No
useMediaQuery, nomatchMedia, no breakpoint guard, no "desktop only" message undertape-chart-v2/. Pinch-zoom drag conflicts with@dnd-kit. - [P1] A11y: Bar has
role="button"(bar.tsx:175). Noaria-labelon day-headers, noaria-rowcount/aria-colcount, no keyboard cell focus model, no live-region announcements.useKeyboardShortcutsexists for pan/zoom only —onOpenDetail/onOpenWalkIncallbacks (:190-191) are() => {}stubs.
Tests.
- [P0] Unit tests for v2: zero. Search
tape-chart-v2/for.test.|.spec.: nothing. - [P0] E2E: only 2 specs, 5 tests total —
e2e/tape-chart-phase2.spec.ts(renders without errors,keyboard Home doesn't crash) andtape-chart-phase3.spec.ts(3 similar smokes). No drag-move e2e, no extend e2e, no walk-in e2e, no context-menu e2e, no conflict e2e.
Remediation ranked by ROI.
- Wire
BarContextMenuandCellContextMenuintotape-chart-v2-shell.tsx— components already exist. One day's work to restore the missing 50% of operations. - Replace v2's
chart-grid.tsx:143-268body with v1 virtualized renderer. One day's work using existing code. - Replace
'coming in Phase 2'placeholder intape-chart-v2-shell.tsx:365-372with real handler dispatch. Half a day. - Add
version: Int @default(0)column toReservation, increment on every update, send asIf-Matchheader from drag handlers. ~1 sprint. - Restore
BulkActionToolbarandSelectionwiring in v2; expand to OOO range-block. - Add Playwright e2e for drag-move, drag-extend, walk-in click, context-menu cancel, conflict reject. Hero feature deserves a 20-spec suite minimum.
The fastest path to GREEN is not new code — it is plumbing the existing v1 logic into the v2 shell. Most of the missing features are already written and tested.
§4 — Cross-cutting findings
4.1 Build hygiene — silent compilation failures
- [P0] Every backend service's
buildscript istsc -p tsconfig.docker.json || true. Confirmed forhmp-property-service,hmp-reservation-service,hmp-frontdesk-service(and the pattern is consistent across the platform). The|| trueswallows TypeScript compilation errors. A service with a real type error ships the previousdist/(or empty) and the Docker build still succeeds. The blast radius is unknown.
4.2 xPrisma anti-pattern — persistent and growing
- [P0] 79 foreign-model Prisma access sites in
channel-service+reservation-servicealone (excluding spec files). Examples:hmp-channel-service/src/intermediary/channel-booking-normalizer.service.ts—prisma.roomType.findFirst,prisma.ratePlan.findFirst.hmp-channel-service/src/sync/sync.service.ts—prisma.roomType.findMany.hmp-reservation-service/src/booking-engine/booking-engine.service.ts—prisma.property.findUnique,prisma.guest.findFirst(multiple call sites).hmp-reservation-service/src/internal/tenant-export.service.ts—prisma.property.findMany.
- These services ship local read-models (e.g., reservation-service's
Property/Guestmodels, channel-service'sRoomType/RatePlan) maintained byread-model-sync.service.tsconsumers. The platform CLAUDE.md asserts the canonical pattern is@hmp/service-clients; the read-model-sync consumers are a parallel implementation. Drift between the canonical owner (property-service / guest-service) and the consumer's read-model is silent and only catchable by integration test. - Confirmed by Flow 4 sub-agent: dashboard reservation detail page reads
reservation.guest.email|phone|totalStays|vipLeveldirectly from this read-model; if the sync consumer lags, staff see stale guest contact info.
4.3 Two-file rule violations
- [P0]
/extra-chargesregistered inhmp-api-gateway/src/config/services.config.ts:1055but missing entirely fromproxy.controller.ts. - [P0]
/seasonsmissing from BOTH files. - [P1]
/menu,/tables,/kitchenhave config entries but only*/*controller decorators (no bare@All('menu')/etc.). Sub-routes work; bare root paths 404. - [P2]
/authhas@All('auth/*')but no bare@All('auth'). Inconsistent. - [P2]
night-audit-reportshas decorator atproxy.controller.ts:1429but noservices.config.tsentry.
4.4 Idempotency gaps on critical paths
| Path | Status | File |
|---|---|---|
| Booking-engine confirm | MISSING at inventory boundary | booking-engine.service.ts:981-1068 |
| Razorpay capture (gateway) | OK (X-Razorpay-Idempotency-Key) | razorpay-gateway.adapter.ts:93 |
| Razorpay capture (terminal) | BUG — sends defunct key | razorpay-terminal.adapter.ts:184, 293 |
| Webhook receive (payment-service) | OK (@@unique([providerType, providerEventId])) | prisma/schema.prisma:438 |
| OTA inbound (channel) | race-vulnerable findFirst | resavenue-webhook.controller.ts:480-488 |
| Check-in | OK (Reservation.status guard) | checkin.service.ts:183-234 |
| Check-out | OK (Redis lock 30s) | checkout.service.ts:54 |
| Night audit | NO DB unique on (propertyId, auditDate) | prisma/schema.prisma:346-367 |
| POS folio post | OK (dedup-key in FolioTransaction.reference) | pos-folio-charge.consumer.ts:353-366 |
| Tenant signup | OK (Tenant.code @unique) | prisma/schema.prisma:22 |
4.5 Event-subscription wiring (compose flags)
SKIP_EVENT_SUBSCRIPTIONS discipline:
dev-all.yml: defaultstrue, explicitly=falseon consumer services (property, hk, frontdesk, channel, pos, analytics, notification). Correct.prod-aws.yml:64: one service hasSKIP_EVENT_SUBSCRIPTIONS: "true"— verify which (likely api-gateway, which is correct; if it's a consumer, P0).qa-aws.yml:113: same=trueon at least one service.dev-all.yml:32: JWT_PUBLIC_KEY PEM hard-coded in master. Even if dev-only, an actual RSA public key sits in repo history forever.
4.6 Compliance posture (Indian regulatory)
| Regulation | Status | Evidence |
|---|---|---|
| GST CGST/SGST/IGST split | PARTIAL — schema persists at POS event source, but receiver drops it (Flow 7 P0); B2C falls through to intra-state (Flow 8 P0); slab boundary hard-coded (Flow 2 P0). | |
| GST e-invoice IRN (NIC IRP) | MOCK ONLY in QA; no production guard against EINVOICE_MOCK=true (Flow 8 P0); ack number overwrites sequential invoice number (Flow 8 P0); 2 specs total on real adapter. | |
| Form C / FRRO | NON-FUNCTIONAL — empty-nationality bypasses (Flow 6 P0); walk-in skips entirely (Flow 6 P0); arrivedFrom/visaType dropped during PDF generation (Flow 6 P0); FRRO submission is ManualUploadFrroClient (no real e-FRRO API). | |
| Aadhaar / UIDAI | OK for guest-service storage (encrypted, last-4 mask). AMBER for frontdesk-service GuestIdVerification — falls back to plaintext on missing ENCRYPTION_KEY (checkin.service.ts:91-95). | |
| State guest-register (police register) | NOT IMPLEMENTED — reporting-feeds.service.ts:458 is a stub returning null PII (Flow 6 P0). | |
| DPDPA / data residency | NOT IMPLEMENTED — no dataResidency column on Tenant; no RLS middleware (Flow 1 P0). | |
| CERT-In incident reporting | wiring exists in auth-service /incidents but no integration with tenant-lifecycle / payment / data-breach surfaces (Flow 1 P1). | |
| TRAI DND for SMS | not verified; out of scope of 13 flows. | |
| PCI DSS | OK — booking-engine uses Razorpay hosted checkout (SAQ-A), payment-service stores only cardLast4+cardBrand, no PAN/CVV anywhere in repo (Flow 12). |
4.7 Schema drift
tenantId String @db.Uuidconsistent across all 16 sampled services. PASS.- Reservation-service Prisma carries 21 models including foreign-owned
Property,Guest,Company,RateRestriction,CancellationPolicy,Folio,Payment. Each is a read-model maintained by sync consumers. Same pattern in channel-service (foreignRoomType,RatePlan,Reservation), guest-service (foreignProperty), frontdesk-service (foreignReservation). paymentStatusisString?not enum onReservation(hmp-reservation-service/prisma/schema.prisma:99) — typo silently produces garbage (Flow 3).EInvoiceJob.ackDateisString?butFolio.ackDateisDateTime?— drift inside frontdesk-service.
4.8 @Public() exposure
- 102
@Public()decorators across services. Sample inspection shows most are legitimate (auth flows, JWKS, OIDC/SAML, billing webhook receiver, branding, partner session, JWT). Notable concerns:hmp-asset-service/src/asset-registry/asset-registry-health.controller.ts:7—@Public()on a health check is fine.hmp-auth-service/src/billing/billing-webhook.controller.ts:16— Razorpay billing webhook, must verify signature.hmp-auth-service/src/branding/branding.controller.ts:31— public branding.hmp-auth-service/src/partner/organizations/partner-organizations.controller.ts:28— VERIFY — public partner-organization CRUD raises eyebrows.- Reservation-service booking-engine endpoints: search/availability/calendar are
REQUIRE_API_KEY=false(Flow 3 P0) — fully open.
4.9 Hard-coded secrets / repo hygiene
JWT_PUBLIC_KEYhard-coded as PEM indocker-compose.dev-all.yml:32. Permanent in git history.INTERNAL_SERVICE_TOKENis HS256 in dev (per CLAUDE.md note), tracked as Phase 2 follow-up to RS256 before production.- No hard-coded Razorpay key/secret found in code (PASS).
- "Default API key fallback
'hmp-default-public-booking-engine-key'" hard-coded inhmp-booking-engine/src/lib/api/client.ts:23— DEV warning string, but permanent in master (Flow 3).
4.10 Boot-time guards (or absence thereof)
| Guard | Status |
|---|---|
Hardware adapter requires real provider OR HARDWARE_MOCK=true | OK (hardware.module.ts:127-155) |
EmailService requires SendGrid creds (AP-CC-5) | OK |
EventsGateway RS256 in production (JWT_PUBLIC_KEY) | OK (gateway CLAUDE.md) |
EINVOICE_MOCK !== 'true' in production | MISSING (Flow 8 P0) |
SUBSCRIPTION_VIA_PAYMENT_SERVICE_ENABLED validated | N/A — flag does not exist in code (Flow 12) |
ENCRYPTION_KEY required in frontdesk-service | MISSING — falls back to plaintext (Flow 6 P1) |
§5 — Top 20 P0 launch blockers, prioritised
Effort estimates are gross — assume one experienced full-stack engineer-day per estimate.
| # | Blocker | Files | Owner | Effort | Suggested fix |
|---|---|---|---|---|---|
| 1 | Booking last-room race — no inventory lock, two guests can both book | hmp-reservation-service/src/booking-engine/booking-engine.service.ts:981-1068, prisma/schema.prisma:187 | reservation | 3d | SELECT FOR UPDATE per (propertyId, roomTypeId, date) inside createBooking transaction; add @@unique([propertyId, roomTypeId, date]) on a per-night inventory row; concurrency regression test |
| 2 | Razorpay verifyPayment auto-confirms with no HMAC when key absent | hmp-reservation-service/src/booking-engine/booking-engine.service.ts:1888-1938, 1741-1754 | reservation | 1d | Boot-validate RAZORPAY_KEY_ID/SECRET for any property with booking-engine; remove mock-fallback branch; force HMAC fail-closed |
| 3 | Tape-chart context menu + DetailPanel actions are stub toasts | hmp-web-dashboard/src/components/modules/front-desk/tape-chart-v2/tape-chart-v2-shell.tsx:339-372 | dashboard | 2d | Wire existing BarContextMenu + CellContextMenu from v1 tree; replace 'coming in Phase 2' toasts with real handler dispatch |
| 4 | ID-photo capture broken end-to-end — data: URL rejected by @IsUrl | hmp-frontdesk-service/src/checkin/dto/checkin.dto.ts:152-159, hmp-web-dashboard/.../check-in/[id]/keynova.tsx:444-446, 966 | frontdesk + dashboard | 2d | Implement client-side S3 presigned upload before submit; send the public URL; or change DTO to @IsString() and re-upload server-side |
| 5 | Empty-nationality foreign guest bypasses FRRO | hmp-frontdesk-service/src/checkin/checkin.service.ts:189-194 | frontdesk | 0.5d | Reject check-in with nationality === '' OR null regardless of path |
| 6 | Walk-in path has no FRRO enforcement | hmp-frontdesk-service/src/checkin/checkin.service.ts walkIn(); dto/checkin.dto.ts:212-278 | frontdesk | 1d | Add identical FRRO block to walkIn(); WalkInDto accepts formC |
| 7 | Express check-in is a stub | hmp-frontdesk-service/src/checkin/checkin.service.ts:691-752 | frontdesk | 1d | Either implement full check-in or disable route until done; explicitly forbid for non-Indian nationality |
| 8 | Police-register PII feed unimplemented | hmp-frontdesk-service/src/reporting-feeds/reporting-feeds.service.ts:458 | frontdesk | 2d | Decrypt formCDataEncrypted; populate passport/Aadhaar columns; respect DPDPA logging |
| 9 | POS v2 GST split silently dropped at receiver | hmp-frontdesk-service/src/folios/consumers/pos-folio-charge.consumer.ts:108-155, 373-383; hmp-frontdesk-service/src/folios/folios.service.ts:507-516 | frontdesk | 1d | Read cgstAmount/sgstAmount/igstAmount/customerState from v2 payload; bypass getGSTRate re-derivation |
| 10 | MenuItem missing HSN/SAC | hmp-pos-service/prisma/schema.prisma:111-139 | pos | 1d (schema) + 2d (UI/seed/migration) | Add hsnCode String (and gstRate Decimal if per-item); migrate; surface in admin; propagate into v2 event |
| 11 | IRN ack number overwrites sequential invoice number | hmp-frontdesk-service/src/einvoice/einvoice-job.processor.ts:64 | frontdesk | 0.5d | Drop invoiceNumber: result.ackNumber write; keep sequential value, store ack in ackNumber only |
| 12 | Two divergent invoice-number generators | hmp-frontdesk-service/src/folios/folios.service.ts:1620; invoice-pdf.service.ts:601-618 | frontdesk | 1d | Delete the PDF-side generator; have it pull from FoliosService.getNextInvoiceNumber (or refactor into shared @hmp/common) |
| 13 | No production guard against EINVOICE_MOCK=true | hmp-frontdesk-service/src/einvoice/einvoice.module.ts | frontdesk | 0.25d | Boot validator: throw when NODE_ENV='production' && EINVOICE_MOCK !== 'false' |
| 14 | No split-payment support at checkout | hmp-frontdesk-service/src/checkout/dto/checkout.dto.ts:30; checkout.service.ts:215-267 | frontdesk + dashboard | 3d | Extend DTO to payments: { mode, amount, reference, cardLast4? }[]; loop inline settle; populate cardLast4/cardType; dashboard split-payment UI |
| 15 | No invoice attachment on thank-you email | hmp-notification-service/src/dispatcher/dispatcher.service.ts:130-132 | notification + frontdesk | 1.5d | Publish typed folio.settled event from checkout; dispatcher fetches PDF and attaches via SendGrid |
| 16 | No FOLIO_PAYMENT_RECEIVED publish from inline checkout | hmp-frontdesk-service/src/checkout/checkout.service.ts:236 | frontdesk | 0.5d | Publish per-payment event; reservation-service FolioPaymentConsumer already wired |
| 17 | Night audit no @@unique([propertyId, auditDate]) | hmp-frontdesk-service/prisma/schema.prisma:346-367 | frontdesk | 0.25d (schema) + 0.5d migration | Mirror analytics-service constraint; remove "proceeding without lock" code path |
| 18 | Night audit dedup window timezone math wrong | hmp-frontdesk-service/src/night-audit/night-audit.service.ts:453-455, 225 | frontdesk | 0.5d | Use property-local midnight to property-local-tomorrow window |
| 19 | Night audit reads averaged rate, not today's calendar rate | hmp-frontdesk-service/src/night-audit/night-audit.service.ts:392-410 | frontdesk | 1d | Read from RatePlanRate for the night via PropertyServiceClient |
| 20 | KPI tile reportIds wrong — every Operations Hub tile 404s | hmp-web-dashboard/src/components/modules/operations-hub/tiles/kpi/{revpar,adr,occupancy,arrivals-today,departures-today,inhouse-count,no-shows-today}.tile.tsx | dashboard | 0.5d | Fix to manager-flash-report, arrival-report, departure-report, in-house-report, no-show-reservation-report; add Vitest contract test |
Sub-total of top-20 P0 effort: ≈ 24 engineer-days.
§6 — Effort summary
The top-20 P0 list above is not the complete P0 set — there are ~73 P0 findings across the 13 flows. Rough aggregate:
| Bucket | Count | Aggregate effort |
|---|---|---|
| Top-20 P0 (above) | 20 | ~24 engineer-days |
| Remaining P0 (53 across 13 flows + cross-cutting) | ~53 | ~70-90 engineer-days |
| All P0 combined | ~73 | ~95-115 engineer-days |
| P1 findings (estimate) | ~80 | ~50-70 engineer-days |
| All P0 + P1 | ~153 | ~145-185 engineer-days |
For a team of 5 engineers running parallel, focused on launch readiness:
- All P0 done in 4-6 weeks (~95-115 engineer-days / 5 engineers).
- All P0 + P1 done in 6-8 weeks (~145-185 engineer-days / 5 engineers).
This is a launch-readiness arc, not a sprint. The gold-standard charter (per hmp-docs/docs/platform-registry/property-1-gold-standard-fix-plan-2026-04-28.md) further requires that every fix include CLAUDE.md updates, platform-registry updates, tests-before-fix, idempotency at every boundary, and "one canonical impl not two". Assume +30-50% effort if those requirements are followed, which they should be.
Honest verdict: property #1 cannot launch on this codebase in the next 4 weeks without explicit acceptance of multiple regulatory and business-correctness gaps. A 6-week focused arc is the minimum viable runway, and only if the work is gated by the gold-standard charter.
§7 — Appendix: verification log
Phase A inventory commands:
git -C <repo> rev-parse --short HEADfor all 24 repos.find . -maxdepth 4 -name "schema.prisma" -path "*/prisma/*"(17 schemas).wc -l hmp-api-gateway/src/config/services.config.ts hmp-api-gateway/src/proxy/proxy.controller.ts→ 1468 + 2551 lines.grep -n "model |enum " <each schema>.grep "SKIP_EVENT_SUBSCRIPTIONS|HARDWARE_MOCK|EINVOICE_MOCK|JWT_PUBLIC_KEY|INTERNAL_SERVICE_TOKEN" docker-compose.*.yml.
Phase B per-flow files opened (not exhaustive — see each flow's section for full citations):
- Flow 1 (Tenant):
hmp-auth-service/src/tenants/,src/onboarding/,src/billing/,src/subscriptions/,prisma/schema.prisma,hmp-shared-packages/.../service-clients/property-service.client.ts:530-550,hmp-admin-portal/src/app/(auth)/signup/page.tsx,hmp-admin-portal/src/app/(admin)/tenants/[id]/page.tsx. - Flow 2 (Property):
hmp-property-service/prisma/schema.prisma,src/properties/,src/rooms/,src/rate-plans/,src/cancellation-policies/,src/tax/,hmp-web-dashboard/src/app/(dashboard)/settings/property/page.tsx,hmp-booking-engine/src/pages/direction-b/RoomDetailPage.tsx. - Flow 3 (Booking engine):
hmp-reservation-service/src/booking-engine/,prisma/schema.prisma,hmp-payment-service/src/,hmp-booking-engine/src/lib/api/client.ts, dashboard reservations API. - Flow 4 (Verify reservation):
hmp-reservation-service/src/reservations/, dashboard(dashboard)/reservations/keynova.tsx,lib/api/reservations.ts,lib/hooks/use-global-search.ts. - Flow 5 (HK / room move / extend):
hmp-frontdesk-service/src/room-ops/,src/upgrades/,src/checkin/checkin.controller.ts,hmp-housekeeping-service/src/gateway/housekeeping.gateway.ts,hmp-mobile-apps/apps/housekeeping/, dashboard tape-chart drag actions. - Flow 6 (Check-in):
hmp-frontdesk-service/src/checkin/,src/cform/,src/sponsors/,src/reporting-feeds/,hmp-guest-service/src/guests/,hmp-shared-packages/packages/integrations/.../frro/manual-upload-frro-client.ts, dashboard check-in page. - Flow 7 (Room service):
hmp-pos-service/src/orders/,src/menus/,src/gateway/kitchen.gateway.ts,hmp-frontdesk-service/src/folios/consumers/pos-folio-charge.consumer.ts,src/folios/folios.service.ts, dashboard folio page. - Flow 8 (Check-out):
hmp-frontdesk-service/src/checkout/,src/folios/,src/einvoice/,src/late-checkout/,hmp-shared-packages/.../printing/receipt-templates.ts, dispatcher.service consumer. - Flow 9 (Night audit):
hmp-frontdesk-service/src/night-audit/,src/night-audit-pdf.service.ts,night-audit-scheduler.service.ts,hmp-analytics-service/src/consumers/night-audit.consumer.ts, dashboard night-audit page. - Flow 10 (Reports):
hmp-analytics-service/src/consumers/,src/reporting/reports/shared/period-helpers.ts,src/reporting/exports/, dashboard(dashboard)/reports/v2/, dashboardcomponents/modules/operations-hub/tiles/kpi/*.tile.tsx. - Flow 11 (ResAvenue):
hmp-channel-service/src/intermediary/,src/sync/,src/intermediary/providers/resavenue/,prisma/schema.prisma,hmp-docs/Integrations/ResAvenue/cert-evidence-2026-05-05.md, admin portal channels pages. - Flow 12 (Payments):
hmp-payment-service/src/,prisma/schema.prisma,src/adapters/pinelabs/,src/adapters/razorpay/,src/webhooks/,src/orchestrator/,hmp-shared-packages/.../sanitizer/pci-sanitizer.ts, dashboard folio TerminalChargeModal, booking-engine PaymentForm. - Flow 13 (Tape Chart):
hmp-web-dashboard/src/components/modules/front-desk/tape-chart-v2/,tape-chart/(legacy),hmp-frontdesk-service/src/calendar/,hmp-reservation-service/prisma/schema.prisma.
Phase C cross-cutting commands:
grep -rE "prisma\.(folio|payment|property|guest|roomType|ratePlan)\." hmp-channel-service/src hmp-reservation-service/src→ 79 matches.grep -rn "@Public()" hmp-*-service/src --include="*.controller.ts"→ 102 matches.grep -E "tenantId\s+String\s+@db\.Uuid" <all schemas>→ consistent (PASS).grep "build" hmp-{property,reservation,frontdesk}-service/package.json→tsc -p tsconfig.docker.json || true(P0).grep -rn "import type" hmp-*-service/src --include="*.ts" | grep "Service|Module|Controller"→ no DI metadata erasure (PASS).
End of report. Audit conducted under explicit instruction to distrust all prior documentation and verify against actual code, schema, and UI. Verdict is fact-based and unflattering by design. Owner action: triage the top-20 P0 list against headcount × calendar runway and decide whether to proceed, descope, or delay launch.