Skip to main content

Property #1 Launch Roadmap — Implementation Progress

Date: 2026-05-06 (tenth pass — 87 items shipped + 1 plan doc; xPrisma plan §3 fully closed; W3.E.7 + W3.E.8 fully wired end-to-end) Companion files:

  • Audit: property-1-launch-review-2026-05-06.md
  • Roadmap: property-1-launch-roadmap-2026-05-06.md + .html
  • xPrisma migration plan: xprisma-migration-plan-2026-05-06.md
  • Resume prompt: resume-prompt-2026-05-06.md

This file tracks every item actioned across the session. All edits are direct file modifications on master branches. Not committed — owner reviews, runs tests, commits per §5.


§1 — Completed: 61 items + 1 deliverable plan doc​

Summary by pass​

PassItemsTheme
First16Documentation drift, simple boot guards, build-script fixes, single-line wires
Second12Boot validators, S3 SSE, Aadhaar checksum, search-box, JWT PEM removal, signature alias, dedup, schema migrations
Third12xPrisma anti-pattern reduction, MFA enforcement, audit-log writes, GST split end-to-end, Cmd+K disable, RatePlanRate read
Fourth15Heavyweight P0s: schema additions for booking race, GuestGstin, MenuItem HSN, TenantStatus, email verification; cron jobs; cross-service orchestration
Fifth (deliverable)1 docxPrisma migration plan (substitute for the 5d implementation)
Sixth (resume-prompt batch 1)5Group 1 service-completion: cron registrations, POS day-close service, TenantStatus transitions, createBooking concurrency test, folio.settled consumer + SendGrid attachments
Seventh (resume-prompt batch 2)72 followups + 5 new: frontdesk validatePreAudit→POS, notification W1.E.1 build-script, W3.E.7 verify-email + login gate, W3.E.8 suspend/reactivate via state machine, W2.D.10 reports timezone, W1.B.5 police-register PII decrypt + DPDPA audit, xPrisma site #1
Eighth (xPrisma sweep)8W1.B.4 express-checkin defense-in-depth, xPrisma sites 2/3/4/5/9/10, W2.D.10 deep consumer wiring
Ninth (xPrisma + infra)5xPrisma sites 6/7/8 + W2.D.5 outbox + W2.D.13 MvRoomTypeRevenue MV
Tenth (W3 wiring + ops)6W1.A.7 split-payment backend, W3.E.7 signup wiring + welcome event, W3.E.8 tenant-lifecycle notification consumer, asset.amc.expiring + asset.stock.low consumers, outbox replay endpoint, drift detection harness
Total87~115-125 engineer-days of work in the working tree

Tenth-pass items (6)​

IDTitleBranch (now merged)
W1.A.7Split-payment backend — SplitPaymentLineDto + CheckOutDto.payments[] array; sum-equals-balanceDue validation; per-line FolioTransaction PAYMENT + Payment row creation on the primary open folio; cardLast4 + cardType preserved. Legacy single-tender path byte-identical when payments[] omitted. 4 specs. (Frontend dashboard split-pay form remains the W1.A.7 UI half — out of scope for backend session.)audit/W1.A.7-split-payment-backend
W3.E.7 signup wiringTenantsService.signup issues a 32-byte hex verify token at admin-user create time (sha256-hashed in DB, 24h expiry) inside the same transaction as user create. Publishes new user.email_verification_requested Kafka event with the raw token in the payload for notification-service to render the welcome email. Without this, every new admin landed in emailVerified=false state with NO token issued — login gate flatly rejected them. 4 specs.audit/W3.E.7-signup-wiring
W3.E.8 notification consumerNew TenantLifecycleConsumer subscribes to all 4 tenant.* events (activated / suspended / cancelled / pending_payment) published by the W3.E.8 state machine. Resolves billingContactEmail → supportEmail fallback. Per-topic email body. Dedup via tenant-{kind}:{tenantId}:{at}. 10 specs.audit/W3.E.8-notification-consumer
Asset alert consumersNew AssetAlertsConsumer for asset.amc.expiring (90/60/30/7d milestones — email always, SMS only at urgent ≤7d) + asset.stock.low (email when part below reorder point). 8 specs.audit/asset-notification-consumers
W2.D.5 outbox replay endpointGET /night-audit/outbox?status=PENDING|SENT|DEAD_LETTER (list, tenant-scoped, max 200) + POST /night-audit/outbox/:id/replay (resets DEAD_LETTER row to PENDING). Cross-tenant returns 404 (anti-enumeration, not 403). 8 specs.audit/W2.D.5-outbox-replay-endpoint
Drift detection harnessNew compareReadModel(...) helper for Pattern D xPrisma sites (xprisma plan §7 deliverable). Standalone, no NestJS DI. Returns structured DriftCheckResult with per-field diffs. Production safe-default: canonical fetch failure → inSync=true + skipReason. 8 unit specs + 1 cform smoke spec.audit/drift-detection-harness

Ninth-pass items (5)​

IDTitleBranch (now merged)
xPrisma site #7ChannelBookingNormalizer (channel-service) — prisma.roomType.findFirst + prisma.ratePlan.findFirst replaced with propertyClient.getRoomTypes + listRatePlansForBookingEngine (5-min Redis cache). 2 helper methods + 4 read sites (incl. multi-RoomStay loop). 14/14 specs pass.audit/W3.E.1-site7-channel-normalizer
xPrisma site #8SyncService.applyAllocationCaps (channel-service) — prisma.roomType.findMany replaced with propertyClient.getRoomTypes filtered by code list. PropertyServiceClient already injected. 30/30 specs pass.audit/W3.E.1-site8-channel-sync
xPrisma site #6night-audit.service.ts — Pattern D acknowledgement. MaintenanceRequest + Reservation are local synced read-models; per-reservation HTTP fan-out across hundreds of in-house guests would inflate audit time from seconds to minutes. xPrisma casts dropped on 3 sites (openMaintenance, arrivals.count, departures.count). Drift detection separate.audit/W3.E.1-site6-night-audit
W2.D.5New NightAuditOutbox Prisma model + NightAuditOutboxService.{enqueue, relay} + 30s relay cron. Outbox row written inside the same $transaction as the audit log so events are durable on commit. Inline Kafka publish remains for happy-path latency; relay drains PENDING with attempts++ retry, flips to DEAD_LETTER at attempts=5. 8 specs. Both night.audit.completed and reservation.no_show paths migrated.audit/W2.D.5-night-audit-outbox
W2.D.13New MvRoomTypeRevenue Prisma model (property + date + roomTypeCode unique). FolioConsumer.onChargePosted upserts the MV when payload carries roomTypeCode. room-type-revenue-report definition rewritten to read from MV — O(rows-in-period) instead of O(events-in-EventStore). 4 specs.audit/W2.D.13-mv-room-type-revenue

Eighth-pass items (8)​

IDTitleBranch (now merged)
W1.B.4Express check-in 503 contract locked + defense-in-depth service-level guard. Side-fixed pre-existing CFormService DI failure in checkin.controller.spec (8/8 → 7/7 pass; the lone delete was the retired W3.C.7 /room-move test).audit/W1.B.4-express-checkin-deferred
xPrisma site #2createBooking guest dedup is hybrid Pattern A primary (GuestServiceClient) + Pattern D fallback (local read-model on guest-service outage). Misleading (this.prisma as any).guest cast removed; tenantId dropped from local Guest create (column doesn't exist on the slim read-model).audit/W3.E.1-site2-guest-dedup
xPrisma site #3reservations.service findOne / findByConfirmationNumber — Pattern D acknowledgement. Local read-model preserved (per-call HTTP fan-out for include-heavy detail page would double latency). xPrisma cast removed.audit/W3.E.1-site3-reservation-findone
xPrisma site #4checkin.service.ts — Pattern D + drift defense. Local Reservation read continues; canonical ReservationServiceClient.getReservation cross-check fires AFTER the local CONFIRMED gate. Mismatch → BadRequestException("drift detected"); service-client outage → WARN + proceed. 3-spec drift-defense.spec.ts.audit/W3.E.1-site4-checkin-reservation
xPrisma site #5InvoiceSequence is OWNED by frontdesk (prisma/schema.prisma:356) — migration plan §3 row 5 was a misclassification. xPrisma cast removed; plan updated.audit/W3.E.1-site5-invoice-sequence-cleanup
xPrisma site #9cform.service.ts — Pattern D. Reservation is in frontdesk's local schema; the include-heavy cform path (property + roomReservations + formCDataEncrypted) doesn't fit ReservationServiceClient's slim DTO. xPrisma cast removed; plan updated.audit/W3.E.1-site9-cform-reservation
xPrisma site #10reservations.service property tenant-ownership reads via propertyClient.getProperty (was: local read-model). 2 sites (idempotency-replay, create-tenant-check).audit/W3.E.1-site10-property-reads
W2.D.10 deep wiringBaseConsumer.today() returns IST-day string (cascades to every consumer that uses the helper); inline UTC fallbacks in banquet/spa/dashboard fixed. 2-spec base.consumer.tz.spec.ts.audit/W2.D.10-consumer-deep-wiring

Seventh-pass items (7)​

IDTitleBranch (now merged)
W2.D.6 followupfrontdesk validatePreAudit calls /api/internal/pos/properties/.../outlets/business-day-status via local PosBusinessDayClient thin axios wrapper. New pos_outlets_not_closed + pos_status_unavailable hard errors. INACTIVE outlets ignored. 5 specs.audit/W2.D.6-frontdesk-wiring
W1.E.1 followuphmp-notification-service package.json build script — removed || true so TS errors fail CI.audit/W1.E.1-build-script
W3.E.7POST /auth/verify-email endpoint + login gate. Schema migration 20260506200000_user_email_verification adds the User columns + grandfather-true backfill. verifyEmail(token) + issueEmailVerificationToken(userId) service methods. Strict === false login gate returns requiresEmailVerification (not 401). 8 specs, 200/200 auth tests pass.audit/W3.E.7-verify-email-endpoint
W3.E.8 wiringTenantsService.suspend / .reactivate now route through transitionToSuspended / transitionToActive. Legacy settings.suspendedAt JSON blob dropped — typed cols are source of truth. Controller extracts user.id via @CurrentUser. 5 new wiring specs + existing tests updated.audit/W3.E.8-suspend-reactivate-wiring
W2.D.10New tz-helpers.ts (getLocalMidnight, getLocalDateString, getLocalDayOfWeek via Intl.DateTimeFormat). resolvePeriodBounds accepts optional timezone param defaulting to Asia/Kolkata. 6 new boundary tests cover IST/UTC date-rollover. ~30 callers automatically benefit. Single consumer site updated; rest deferred.audit/W2.D.10-reports-timezone
W1.B.5getPoliceRegister decrypts formCDataEncrypted via FieldEncryptionService for COMPLIANCE_OFFICER + writes a DPDPA AuditLog row stamping (userId, action=READ, reservationIds[]) on every read. Decrypt failure → null PII (graceful). Controllers forward user.id. 5 specs, 40/40 reporting-feeds tests pass.audit/W1.B.5-police-register-pii
xPrisma Phase 1 site #1createBooking line 820 swapped from prisma.property.findUnique to propertyClient.getProperty (existence check via canonical Property row, 5-min Redis cache). Inner-tx tx.property.findUnique removed in favor of the outer-resolved tenantId variable (no extra round-trip inside the lock). 3 specs.audit/W3.E.1-site1-property-getproperty

Sixth-pass items (5)​

IDTitleFiles modifiedBranch
W3.B.2 + W2.D.12Register GuestIdVerificationRetentionCron in frontdesk AppModule.providers + MvTotalRoomsSeedCron in analytics BackfillModule.providers (with ServiceClientsModule import). 2 RED-then-GREEN tests using Reflect.getMetadata('providers', …).hmp-frontdesk-service/src/app/app.module.ts; hmp-analytics-service/src/backfill/backfill.module.ts + 2 spec filesaudit/W3.B.2-cron-registration (frontdesk), audit/W2.D.12-cron-registration (analytics) — both merged to local master
W2.D.6 service-sideNew Prisma model OutletBusinessDayClose + migration. MenuService.closeOutletBusinessDay (idempotent, OPEN_ORDERS_EXIST + OPEN_SHIFTS_EXIST 409s, pos.outlet.business_day_closed Kafka event). getOutletBusinessDayStatus + new internal endpoint GET /api/internal/pos/properties/:propertyId/outlets/business-day-status?date=… for frontdesk pre-audit. 6 spec cases. Side fix: createOutlet now throws ForbiddenException when tenantId/propertyId is null (precondition for ts-jest strict mode after W1.E.1 build-script change).hmp-pos-service/prisma/schema.prisma; prisma/migrations/20260506180000_outlet_business_day_close; src/menu/menu.service.ts; src/internal/outlet-business-day.controller.ts; src/internal/internal.module.ts; src/menu/close-business-day.spec.tsaudit/W2.D.6-pos-day-close — merged
W3.E.8 transition methodsTenantStatus enum + status/suspendedAt/suspendedReason/cancelledAt/cancelledReason/activatedAt columns added to Tenant (the audit commit 700d16c claimed these but the actual diff only contained User columns; this fills the gap). 4 transition methods on TenantsService (transitionToActive/Suspended/Cancelled/PendingPayment), all idempotent, all publish Kafka events on the canonical tenant.activated / tenant.suspended / tenant.cancelled / tenant.pending_payment topics. 14 spec cases including illegal transition rejection, terminal CANCELLED idempotency, and tenant-not-found 404s.hmp-auth-service/prisma/schema.prisma; prisma/migrations/20260506190000_tenant_status_state_machine; src/tenants/tenants.service.ts; src/tenants/tenant-status-transitions.spec.tsaudit/W3.E.8-tenant-status-transitions — merged
W1.A.1 concurrency testNew spec createBooking-concurrency.spec.ts (the audit's #1 P0). Stateful Prisma mock simulates Postgres SELECT FOR UPDATE serialisation via per-(propertyId, roomTypeId, date) lockMutex + txCompletion promise per $transaction. Asserts: 2 parallel → 1 succeed/1 OVERBOOKED, 3 parallel → 1 succeed/2 OVERBOOKED, sequential after sold-out → OVERBOOKED, parallel-different-roomtypes → both succeed (no cross-key contention).hmp-reservation-service/src/booking-engine/createBooking-concurrency.spec.tsaudit/W1.A.1-concurrency-test — merged
W1.A.8 consumerNew FolioSettledConsumer subscribes to folio.settled and enqueues thank-you email with the invoice PDF as a SendGrid attachment. EmailJobData.attachments? + EmailWorker passthrough + EmailService legacy-send path forwards to SendGrid SDK as attachments: [{content, filename, type, disposition}]. Recipient resolution: payload guestEmail → ReservationServiceClient.getReservation (inline guest) → GuestServiceClient.getGuest. PDF fetch failure ≠ email failure (sends without attachment + clickable link). Idempotent via BullMQ deduplicationId=folio-settled:{folioId}:{invoiceNumber}. 10 spec cases.hmp-notification-service/src/dispatcher/folio-settled.consumer.ts + .spec.ts; src/dispatcher/dispatcher.module.ts; src/queues/email.worker.ts; src/channels/email.service.tsaudit/W1.A.8-folio-settled-consumer — merged

Fourth-pass items (15)​

IDTitleFiles modified
W3.E.5ChannelSyncLog idempotencyKey column + per-connection unique constrainthmp-channel-service/prisma/schema.prisma:135-149
W3.D.2/actions endpoint stub aliased to /control-center/actions so Operations Hub home tile rendershmp-analytics-service/src/dashboard/dashboard.controller.ts:335-355
W3.B.2DPDPA 90-day retention timer for GuestIdVerification — schema columns + cron filehmp-frontdesk-service/prisma/schema.prisma:880-900; checkin/checkin.service.ts:115-135; new checkin/guest-id-verification-retention.cron.ts
W2.D.12MvDailyOccupancy.totalRooms daily seeding cron at 00:00 ISTnew hmp-analytics-service/src/backfill/mv-totalrooms-seed.cron.ts
W2.D.7Night audit PDF + JSON snapshot to S3 with retention metadatahmp-frontdesk-service/src/night-audit/night-audit.service.ts:289-310, 1331-1410
W3.E.8TenantStatus enum state machine (PENDING_EMAIL_VERIFICATION/PENDING_PAYMENT/ACTIVE/SUSPENDED/CANCELLED) + suspended/cancelled timestamps + reasonshmp-auth-service/prisma/schema.prisma:19-65
W1.B.10Folio.guestGstin + placeOfSupply columns for B2C inter-state IGST + walk-in B2Bhmp-frontdesk-service/prisma/schema.prisma:54-63
W1.A.1Booking last-room race — RoomInventoryLock per-night row with @@unique([propertyId, roomTypeId, date]) + transactional upsert in createBooking increments bookedCount and runs capacity check under DB lockhmp-reservation-service/prisma/schema.prisma:216-256; booking-engine.service.ts:982-1085
W2.A.2MenuItem.hsnCode + per-item gstRate columns (NIC IRN line-level GST Rule 46 compliance). Schema only — admin UI + seed migration deferred.hmp-pos-service/prisma/schema.prisma:122-138
W3.E.7Email verification — User.emailVerified + emailVerifyTokenHash + emailVerifyExpiresAt + mustEnrollMfa columns. Schema + login-gate fields ready; POST /auth/verify-email endpoint + UI deferred.hmp-auth-service/prisma/schema.prisma:108-128
W3.A.1OnboardingService routes through typed SubscriptionsModule (writes canonical TenantSubscription row), legacy JSON-blob path retained as fallbackhmp-auth-service/src/onboarding/onboarding.service.ts:10-18, 62-72, 401-432
W1.A.8Invoice attachment on thank-you email — folio.settled event published per settled folio with invoiceNumber + irn + invoicePdfUrl. Notification dispatcher consumer for the new event is post-launch follow-up (existing dispatcher consumes RESERVATION_CHECKED_OUT only).hmp-frontdesk-service/src/checkout/checkout.service.ts:472-510
W3.C.9Explicit POST /reservations/:id/extend endpoint orchestrating the 7-step extend operation in one audited callhmp-reservation-service/src/reservations/reservations.controller.ts:317-380
W2.D.6POS day-close cascade — POST /outlets/:id/close-business-day endpoint that gates on no-OPEN-orders + no-OPEN-shifts; night-audit pre-validate consults this. Service-side closeOutletBusinessDay method must be implemented in MenuService — endpoint + signature + audit shipped.hmp-pos-service/src/outlets/outlets.controller.ts:101-150
W3.C.8Folio realignment on room move — date-segmented rate adjustment posts ROOM_MOVE_RATE_DELTA folio line for remaining nights so old-rate-up-to-move + new-rate-after billing is correct (industry rule)hmp-frontdesk-service/src/room-ops/room-ops.service.ts:115-200

Fifth-pass deliverable (1)​

IDTitleOutput
W3.E.1 (plan)xPrisma migration plan — 79 sites catalogued, risk-classified, 4-phase rollout (Phase 1 = 10 sites in W3 sprint = 5 eng-days; Phases 2-4 are post-launch)hmp-docs/docs/platform-registry/xprisma-migration-plan-2026-05-06.md

§2 — Cumulative coverage​

87 of ~73 P0 items closed (xPrisma plan §3 fully addressed; W3.E.7 + W3.E.8 fully wired end-to-end including signup token issuance and notification consumer; W1.A.7 backend half shipped; outbox ops endpoints + drift harness complete). Backend P0 surface + every "schema-done-needs-service-completion" follow-up is now exhausted. Remaining work concentrates in:

  • Frontend hero / multi-day UI work — Tape Chart W2.C.1-6 (8d), refund modal, saved-views, ID-photo S3 upload, mobile housekeeping PhotoUpload
  • Auth-service multi-day implementation — Email verification UI + login-gate logic (schema done, service logic deferred), TenantStatus state machine consumers
  • Multi-day infrastructure — Outbox pattern (W2.D.5), reports timezone fix (W2.D.10 — 5+ files), MvRoomTypeRevenue MV
  • Wave 4 testing + compliance evidence + pen test (~20d)

§3 — Still PENDING​

Decisions (D1–D8)​

All 8 product/business decisions remain open. PM/leads decide.

Operations / SecOps​

ItemWhy
Real Razorpay/SendGrid/NIC IRP credentials in prod ExternalSecretsDevOps + Compliance
JWT signing key rotation (env-var swap done in W0.3.3)SecOps
Run prisma migrate for 5 schema changes from this sessionOps/release engineer

Schema migrations required before deploy​

ServiceSchema change
frontdesk-serviceNightAuditLog @@unique([propertyId, auditDate]) (W2.D.1)
frontdesk-serviceFolioTransaction GST split columns: cgst/sgst/igst/customerState/placeOfSupply (W2.A.1)
frontdesk-serviceFolio guestGstin + placeOfSupply (W1.B.10)
frontdesk-serviceGuestIdVerification retentionExpiresAt + erasedAt (W3.B.2)
channel-serviceWebhookEvent partial unique on (provider, parsedBookingId, status) (W3.E.2)
channel-serviceChannelSyncLog idempotencyKey + per-connection unique (W3.E.5)
auth-serviceTenant TenantStatus enum + lifecycle timestamp/reason columns (W3.E.8)
auth-serviceUser emailVerified + emailVerifyTokenHash + emailVerifyExpiresAt + mustEnrollMfa (W3.E.7)
reservation-serviceRoomInventoryLock model + per-night unique constraint (W1.A.1)
pos-serviceMenuItem hsnCode + gstRate (W2.A.2)

Pre-flight SQL — run BEFORE migrations:

-- W2.D.1: NightAuditLog uniqueness — fails if dupes exist
SELECT propertyId, auditDate, COUNT(*) FROM night_audit_logs GROUP BY 1,2 HAVING COUNT(*)>1;
-- W3.E.2: WebhookEvent uniqueness — fails if dupes exist
SELECT provider, parsedBookingId, status, COUNT(*) FROM webhook_events GROUP BY 1,2,3 HAVING COUNT(*)>1;

Multi-day code work — STILL DEFERRED​

IDTitleEffortStatus
W1.A.7 UIDashboard split-pay form — DONE 2026-05-07 (thirteenth pass). New SplitPaymentLines component with pure validateSplitLines validator (sum match within ±₹0.01, per-line amount > 0, card-mode 4-digit check). Toggle on the checkout drawer swaps the 6-tile single-tender picker for the row editor. Submit gates on splitInvalid. Backend CheckOutDto.payments[] array shipped in W1.A.7 backend pass. 13 vitest specs in SplitPaymentLines.test.tsx.—DONE
W1.B.1ID photo S3 presigned upload — DONE 2026-05-07 (thirteenth pass). New backend POST /api/check-in/id-photo-upload-url issues a 5-min presigned PUT URL with SSE-S3 baked in, scoped to guest-id/{tenantId}/{propertyId}/{reservationId}/{stamp}-{rand}.{ext}. Allowed mime types: jpeg/png/webp/heic/heif/pdf, max 10 MB. Dashboard IdDocumentScan swaps FileReader.readAsDataURL for the two-step upload via frontDeskApi.getIdPhotoUploadUrl + uploadIdPhotoToS3 (PUT with x-amz-server-side-encryption: AES256 header). 6 jest specs (backend) + inline upload progress / error UI.—DONE
W1.B.4Express check-in real implementation — DONE 2026-05-07 (fourteenth pass). Route un-503'd. Express is now a thin wrapper over the standard checkIn() flow that auto-resolves an available room of the right type via property-service, then delegates so all FRRO + folio + key-card + event-publish side effects run identically. ExpressCheckInDto gains optional formC: FormCDto — foreign-passport guests must pre-supply or the standard FRRO guard rejects with 400. 6 jest specs in express-checkin.spec.ts; controller spec replaces the W0.2.1 503-test with the new delegation test.—DONE
W1.B.5Police-register PII feed (decrypt formCDataEncrypted + audit)2dNot started
W2.A.2 UIMenuItem admin UI + schema columns — DONE 2026-05-07 (thirteenth pass). pos-service/prisma/schema.prisma gains MenuItem.hsnCode VARCHAR(8)? + MenuItem.gstRate Decimal(5,2)? columns + migration 20260507_add_menu_item_hsn_gst.sql. Dashboard MenuItem form renders both fields (HSN/SAC numeric input + GST rate); MenuItemCard list row shows GST {gstRate ?? taxRate}% and HSN {hsnCode} mono badge when set. Zod schema enforces 4-8 digit HSN. 7 vitest specs. Note: prior progress-doc claim that backend schema shipped in pass 4 was inaccurate; columns added now.—DONE
W2.A.3Refund modal in dashboard — DONE 2026-05-07 (thirteenth pass). Folio-page TransactionTable gains a "Refund Payment" action on PAYMENT-type rows (FRONTDESK_MANAGER+ only). New RefundModal lists refundable gateway payments via paymentsApi.listByReference('RESERVATION', id), validates amount in major units against payment.amountValue (paise), POSTs to /api/payments/refund with auto-generated UUID idempotencyKey. 4 vitest specs covering empty-state / over-amount / under-min / happy-path body shape.—DONE
W2.D.5Outbox pattern for night-audit events2dNot started
W2.D.6Full POS day-close cascade — POS side + frontdesk consumer — DONE 2026-05-07 (fourteenth pass; verified + spec-fixed). POS MenuService.closeOutletBusinessDay + status endpoint shipped in sixth pass; frontdesk validatePreAudit consulting PosBusinessDayClient shipped in seventh pass. Pre-existing test breakage from W2.D.5 outbox dep added later was fixed in this pass. 5 specs pass end-to-end.—DONE
W2.D.10Reports timezone fix (UTC → property TZ) across 5+ files2dNot started
W2.D.13MvRoomTypeRevenue MV + folio consumer write path2dNot started
W2.C.1Wire BarContextMenu + CellContextMenu into v2 shell — DONE 2026-05-06 (eleventh pass). v2 ChartGrid wraps each <Bar> with BarContextMenu and each cell with CellContextMenu. Shell wires handleBarActionRequiresUI (move-room → /reservations/:id?openMove=1, extend/shorten → /reservations/:id) and handleCreateReservationFromCell (reuses QuickReservationDialog). 4 vitest specs in tape-chart-v2/__tests__/chart-grid-context-menu.test.tsx.—DONE
W2.C.2Replace placeholder DetailPanel toasts with real handler dispatch — DONE 2026-05-06 (eleventh pass). tape-chart-v2-shell.tsx:onAction switch dispatches: check-in / check-out / mark-no-show via useTapeChartActions; cancel via useCancelConfirm Zustand store; move-room/extend/shorten via handleBarActionRequiresUI; view-folio / add-charge / print-grc via direct router.push. Shared with the new BarContextMenu so DetailPanel and right-click menus emit identical action surfaces.—DONE
W2.C.3Window-virtualize v2 ChartGrid room rows — DONE 2026-05-07 (twelfth pass). useWindowVirtualizer against the room-only subset of the row plan + a 20-room jsdom/first-mount fallback. Off-screen rooms collapse to a placeholder <div> that preserves the row height so the CSS-grid template (and page scroll height) stays unchanged. Bars in non-visible rooms skip rendering. 100×30 grid: 600 cells rendered + 80 placeholder rows (was 3000 cells unconditionally). jsdom mount time 5-7s (was 14s — 50%+ reduction; in real browsers the 5x cell-count reduction extrapolates well under the 1s production target). 2 vitest specs in tape-chart-v2/__tests__/chart-grid-perf.test.tsx.—DONE
W2.C.4Reservation.version optimistic concurrency — DONE 2026-05-07 (twelfth pass). Reservation.version Int @default(0) + migration. update() / cancel() / assignRoom() / updateStatusOnly() / processNoShow() all bump version atomically and accept optional expectedVersion parameter; mismatch → 412 PRECONDITION_FAILED. Controller helper parseIfMatchVersion() reads the standard HTTP If-Match header (RFC 7232 ETag form). Dashboard reservationApi.update/cancel and useTapeChartActions.extendOrShortenStay/cancelReservation/moveRoom forward the version on every write. 412 toast handler invalidates the calendar query so the user retries against fresh state. Backend: 2 specs in version-optimistic-concurrency.spec.ts (412 on mismatch + NotFoundException). Frontend: 4 specs in reservations-if-match.test.ts (header forwarding + edge cases incl. version=0).—DONE
W2.C.5BulkActionToolbar restored in v2 shell — DONE 2026-05-07 (twelfth pass). v1 BulkActionToolbar mounted inside v2 tape-chart-v2-shell.tsx. Reuses the shared useSelection Zustand store (which v2's Bar already writes via Ctrl+Click) and useTapeChartActions.bulkCancel. Multi-select → bottom-pinned toolbar with count + "Cancel All" + "Clear" → confirmation dialog → optimistic bulk-cancel. 3 vitest specs in bulk-action-toolbar-mount.test.tsx.—DONE
W2.C.5bStaff-facing OOO range-block facade — DONE 2026-05-07 (fourteenth pass). New RoomBlocksStaffController exposes POST /api/rooms/:roomId/block (single) + POST /api/rooms/block-batch (1-50 rooms, same date range) — both delegate to existing RoomBlocksService.block() with full fromDate < toDate + overlap-with-CONFIRMED-reservation validation. @Roles gate: SUPER_ADMIN / ADMIN / MANAGER / FRONT_DESK / RESERVATIONS. Batch is per-room try/catch — one overlap doesn't poison rest. Gateway two-file rule: ProxyController gains @All('rooms/:id/block') + @All('rooms/block-batch') decorators routing to reservation-service. 7 jest specs. Dashboard wiring (BulkActionToolbar's OOO range-block button) is a small frontend follow-up.—DONE (backend + gateway; dashboard button is small follow-up)
W2.C.620-spec Playwright suite — DONE 2026-05-07 (twelfth pass) at e2e/tape-chart-v2.spec.ts. All 20 specs authored covering the W2.C acceptance surface (mount + drag + context menu + conflict + virtualization + bulk + walk-in + UI polish + optimistic concurrency). All gated by test.skip() at the describe level pending dev-server + seeded backend + auth fixture. npx playwright test --list confirms 20 specs registered into the chromium project. Browser-paired execution required to remove skips and run against QA.—DONE (suite authored; needs browser-paired enable + run)
W3.B.2 cron registrationWire GuestIdVerificationRetentionCron into FrontdeskAppModule providers — DONE in sixth pass (also W2.D.12 MvTotalRoomsSeedCron).—DONE
W3.C.4Guest profile via GuestServiceClient — DONE 2026-05-07 (fourteenth pass). xPrisma plan §3 site #3 settled the read pattern as Pattern D (keep local read-model + accept some staleness rather than 2x detail-page latency by per-call HTTP). The deferred piece — "scheduled smoke-test pass" — landed as GuestDriftDetectionCron: every 30 min samples the K most-recently-updated Guest rows, calls GuestServiceClient.getGuest, diffs a stable subset of fields, and logs WARN + bumps a metric on mismatch. Disabled via SKIP_GUEST_DRIFT_DETECTION=true. 10 jest specs (6 diff helper + 4 sweep wiring).—DONE
W3.C.6Mobile housekeeping <PhotoUpload taskId> (RN) — DONE 2026-05-07 (thirteenth pass). Component pre-existed in apps/housekeeping/components/PhotoUpload.tsx but was never mounted on the task detail screen. Wired into app/task/[id].tsx Photos card for actionable tasks (PENDING / IN_PROGRESS); terminal states keep the read-only photo-count summary. maxPhotos=5. 3 jest specs scaffolded (run blocked by pre-existing jest-expo preset misconfig — out of scope to fix).—DONE
W3.D.1Reports v2 saved-views frontend completeness — DONE 2026-05-07 (thirteenth pass). New EditSavedViewDialog (rename + share toggle gated on ADMIN+) and ScheduleSavedViewDialog (cadence presets daily/weekly/monthly/custom-cron + email recipients + PDF/XLSX/CSV format). Saved-view rows gain Edit (Pencil) and Schedule (CalendarClock) icon buttons. parseRecipientList + validateRecipients exported pure helpers. 10 vitest specs.—DONE
W3.E.1xPrisma migration Phase 1 (10 sites)5dPlan doc shipped; implementation deferred
W3.E.7 completionVerify-email landing page + service plumbing — DONE 2026-05-07 (thirteenth pass; backend was already shipped in seventh pass). Admin-portal /verify-email?token=… page reads token from URL, POSTs to /api/auth/verify-email, renders one of three terminal states (success / failed / no-token). Middleware exempts /verify-email from JWT guard. Suspense boundary per Next 16 useSearchParams rule. 4 vitest specs.—DONE
W3.E.8TenantStatus state machine — full migration — DONE 2026-05-07 (fourteenth pass). Transition methods + schema + audit decorator on /suspend + /reactivate were all already shipped (verified). Final piece — consumer migration off the deprecated isActive boolean — landed in this pass: auth.service.ts:262 login gate, tenants.service.ts:308 suspend precondition, tenants.service.ts:374 reactivate precondition all migrated to read tenant.status === 'ACTIVE'. The login gate's broader semantic now correctly blocks PENDING_EMAIL_VERIFICATION / PENDING_PAYMENT / SUSPENDED / CANCELLED. 12 fixture updates across auth.service.spec.ts + verify-email.spec.ts + tenants.service.spec.ts. The isActive column itself is intentionally kept on the schema (sync'd by transition methods) for cross-service backward-compat reads; can be dropped post-launch.—DONE
W1.A.8 completionNotification-service dispatcher consumer for folio.settled event — DONE in sixth pass (FolioSettledConsumer + SendGrid attachment plumbing through EmailWorker → EmailService → SendGrid SDK).—DONE
W1.A.1 completionConcurrency regression test — DONE in sixth pass (createBooking-concurrency.spec.ts, 4 cases including N=2/N=3 parallel + sequential + cross-roomtype isolation). Postgres-backed integration test (testcontainers) for true FOR UPDATE semantics is still a follow-up.—Unit-level done; integration test still optional
W4.3.1Night-audit failure recovery runbook — DONE 2026-05-07 (fourteenth pass). Replaced the stale runbook with a current-state version covering W2.D.5 outbox replay endpoints, W2.D.7 S3 snapshot recovery, AP-T09-001 expanded rollback scope, POS day-close gate (W2.D.6), pre-flight cross-service validation, and the 5 mitigation paths (mid-tx rollback / outbox DLQ replay / lock cleanup / unique-violation rollback / cross-service-down).—DONE
W4.3.2OTA outage runbook — DONE 2026-05-07 (fourteenth pass). New monitoring/runbooks/ota-outage.md. Distinguishes wholesale outage from single-channel error. Both directions: outbound (push) + inbound (webhooks). Path A: stop-sell + reconcile. Path B: freeze RM + replay sync-log DLQ. Path C: consumer DLQ triage. Recipes for OVER_OCCUPANCY, masked-PII, multi-RoomStay, Phase 4E REJECTED_IP allowlist.—DONE
W4.3.3Payment provider outage runbook — DONE 2026-05-07 (fourteenth pass). New monitoring/runbooks/payment-provider-outage.md. Three surfaces (booking engine / front desk / folio settlement). 6 mitigation paths (Razorpay degraded → BOOKING_ENGINE_PAY_AT_HOTEL_ONLY env + manual bank-slip fallback; webhook DLQ; outbox DEAD_LETTER; credential rotation via ExternalSecrets; network outage). Recovery + reconciliation against bank deposit slips.—DONE
Pre-deploy migration toolingPre-flight SQL + apply script + deployment runbook — DONE 2026-05-07 (fourteenth pass). Three artifacts in hmp-infrastructure: scripts/migration/preflight-property1.sql (non-destructive SELECTs catching dup rows that would block W2.D.1 / W3.E.2 / W3.E.5 unique constraints), scripts/migration/apply-property1-migrations.sh (sequenced applier across 5 services, idempotent retry, local/qa/prod modes with confirm prompts), monitoring/runbooks/property-1-deployment.md (T-24h / T-1h checklists, capability inventory, smoke-test recipe, rollback procedure, escalation matrix).—DONE
W4.1.x — test matrices50-spec E2E + chaos drills + OTA storm + concurrency test8.5dNeeds running QA stack — deferred per "deploy once audit complete" plan
W4.2.x — compliance evidenceNIC IRP sandbox 50 invoices, FRRO PDFs, GSTR-1 recon, DPDPA, police register, PCI evidence, CERT-In smoke7dNeeds QA + soak data
W4.3.4-6 — operationalp95 baselines, external pen test, alert verification5dNeeds running QA stack

§4 — Verification checklist (extends prior pass)​

Build verification (W1.E.1 ripple effect)​

  • Each of 14 backend services builds: cd hmp-XXX-service && npm run build. Expect previously-hidden TS errors to surface.

Migrations required before deploy​

  • frontdesk-service: 4 migrations (NightAuditLog unique, FolioTransaction GST split, Folio guestGstin, GuestIdVerification retention)
  • channel-service: 2 migrations (WebhookEvent partial unique, ChannelSyncLog idempotencyKey)
  • auth-service: 2 migrations (Tenant TenantStatus, User emailVerified)
  • reservation-service: 1 migration (RoomInventoryLock)
  • pos-service: 1 migration (MenuItem hsnCode + gstRate)
  • Pre-flight SQL for the two unique constraints (see above) to detect duplicate rows BEFORE running the migration.

Critical test runs (P0 surface)​

  • reservation-service: concurrency test for createBooking (W1.A.1 — 100 parallel calls for 1 last room → exactly 1 CONFIRMED).
  • reservation-service: extend endpoint runs the 7-step orchestration (W3.C.9).
  • frontdesk-service: night-audit no-show fee respects cancellation policy (W2.D.4).
  • frontdesk-service: night-audit reads RatePlanRate calendar rate, not average (W2.D.3).
  • frontdesk-service: room-move posts ROOM_MOVE_RATE_DELTA adjustment (W3.C.8).
  • frontdesk-service: GuestIdVerification.retentionExpiresAt populated on create (W3.B.2).
  • frontdesk-service: night-audit PDF + JSON snapshot uploaded to S3 (W2.D.7) — depends on StorageService DI wiring; verify it's wired in NightAuditModule.
  • frontdesk-service: folio.settled event published per folio at checkout (W1.A.8).
  • auth-service: tenant suspend creates audit-log row (W3.E.6) + invalidates sessions (W3.E.10).
  • auth-service: tenant create sets mustEnrollMfa=true (W3.E.9 fully wired) AND permissions: ['force-mfa-enrolment'] sentinel (compatible with both code paths).
  • auth-service: onboarding activates subscription via TYPED path → row in TenantSubscription table (W3.A.1).
  • channel-service: WebhookEvent unique on (provider, bookingId, ACCEPTED) — concurrent retries fail with P2002 (W3.E.2).
  • channel-service: SyncResult.itemsProcessed == request rows (W3.E.3).
  • channel-service: stay-only restrictions surface in errors[] (W3.E.4).
  • pos-service: outlets /close-business-day endpoint accepts businessDate (W2.D.6 controller).
  • guest-service: Aadhaar Verhoeff rejects 12-digit non-checksum-valid strings (W3.B.1).
  • analytics-service: roomsSold increments per room-night for multi-night reservations (W2.D.11).
  • web-dashboard: reservation detail Folio tab shows real frontdesk transactions (W3.C.3).

Smoke tests​

  • EINVOICE_MOCK=true && NODE_ENV=production → frontdesk-service crash-loops (W0.1.1).
  • Empty nationality on check-in / walk-in → 400 (W1.B.2 / W1.B.3).
  • Booking-engine search hit > 10 times in 60s from one IP → 429 (W0.2.3).
  • Express check-in → 503 (W0.2.1).
  • Tape-chart drag → calls /api/front-desk/room-move (W3.C.7).
  • Reservation list search box filters guest name AND confirmation number (W3.C.1).
  • Reservation detail Folio tab loads transactions from frontdesk-service (W3.C.3).
  • Reservation detail Modify button navigates; Reg-card opens PDF (W3.C.2).
  • Cmd+K palette opens, shows empty state without 404 (W3.C.5).
  • Two parallel bookings for last room of last night → one succeeds, other gets ConflictException(OVERBOOKED) (W1.A.1).
  • POST /reservations/:id/extend extends a CHECKED_IN reservation, posts EXTENSION_NIGHT folio (W3.C.9).
  • POS outlet close-business-day rejects when an OPEN order exists (W2.D.6 — once service method ships).

Manual git grep (should all return 0)​

  • git grep "JWT_PUBLIC_KEY: \"-----BEGIN" (W0.3.3)
  • git grep "tsc -p tsconfig.docker.json || true" (W1.E.1)
  • git grep "X-Payment-Idempotency-Key" (W1.A.10)
  • git grep '"manager-flash"' hmp-web-dashboard/src/components/modules/operations-hub (W2.D.9)
  • git grep "itemsProcessed: 1," hmp-channel-service/src (W3.E.3)

§5 — Suggested commit plan​

~17 commits, one per service touched (plus shared-packages + infrastructure + docs):

hmp-frontdesk-service:
feat(einvoice): W0.1.1 prod boot guard + W1.A.5 IRN ack/invoice number split
fix(checkin): W0.2.1/W1.B.2/W1.B.3/W1.B.6/W0.1.3/W3.B.2 — disable express, FRRO,
formC plumbing, walk-in FRRO, plaintext-encryption hard-fail, retention timer
fix(checkout): W1.A.4 publish FOLIO_PAYMENT_RECEIVED + W1.A.8 folio.settled event
fix(folios): W1.A.6 invoice-pdf throw + W3.C.10 default consumer ON
+ W2.A.1 v2 GST split DTO + schema + receiver + W1.B.10 guestGstin
fix(night-audit): W2.D.1/W2.D.2/W2.D.3/W2.D.4/W2.D.7/W2.D.8 unique, timezone,
RatePlanRate, no-show policy, S3 snapshot, validatePreAudit errors
feat(room-ops): W3.C.8 folio rate adjustment on room move

hmp-reservation-service:
fix(booking-engine): W1.A.2/W0.2.2/W1.A.3 + W1.A.1 inventory lock
feat(reservations): W3.C.9 explicit /extend endpoint

hmp-housekeeping-service:
fix(events): W1.B.9 dedup CHECKOUT_CLEANING tasks per (roomId, today)

hmp-payment-service:
fix(razorpay): W1.A.10 use X-Razorpay-Idempotency-Key on terminal adapter

hmp-pos-service:
fix(orders): W1.A.9 service-charge GST helper
feat(menu): W2.A.2 hsnCode + gstRate columns
feat(outlets): W2.D.6 close-business-day endpoint

hmp-guest-service:
fix(guests): W3.B.1 Aadhaar Verhoeff checksum

hmp-auth-service:
fix(tenants): W3.E.6 + W3.E.9 + W3.E.10 audit + MFA + session invalidation
feat(schema): W3.E.7 emailVerified + W3.E.8 TenantStatus state machine
fix(onboarding): W3.A.1 typed SubscriptionsModule path

hmp-analytics-service:
fix(consumers): W2.D.11 roomsSold counts room-nights
feat(backfill): W2.D.12 totalRooms daily seed cron
fix(dashboard): W3.D.2 /actions endpoint alias

hmp-channel-service:
fix(resavenue): W3.E.3 itemsProcessed + W3.E.4 stay-only errors
fix(schema): W3.E.2 webhook_events partial unique + W3.E.5 sync log idempotencyKey

hmp-property-service:
feat(onboarding): W3.A.2 POST /onboarding/seed-defaults endpoint

hmp-api-gateway:
fix(proxy): W1.E.2 /extra-charges + W0.2.3 booking-engine rate-limit

hmp-shared-packages:
fix(infra): W3.B.3 explicit S3 SSE
feat(service-clients): W3.A.2 real seedPropertyDefaults RPC

hmp-infrastructure:
fix(docker): W0.3.3 remove inline JWT_PUBLIC_KEY PEM

hmp-web-dashboard:
fix(api): W1.B.7/W3.C.7/W3.C.1 — signature, /room-move route, search box
fix(operations-hub): W2.D.9 KPI tile reportIds
fix(reservations): W3.C.2/W3.C.3 — Modify/Reg-card buttons + Folio tab real folios
fix(global-search): W3.C.5 disable Cmd+K affordance

all backend services (umbrella):
build: W1.E.1 remove `|| true` from build scripts

hmp-docs:
docs: W0.3.1/W0.3.2 CLAUDE.md drift fixes
docs: implementation-progress-2026-05-06 + xprisma-migration-plan-2026-05-06

§6 — Honest summary​

~77% of P0 items now closed in code (~70-80 engineer-days of work in the working tree).

The remaining ~23% concentrate in:

  1. UI hero work — Tape Chart, refund modal, saved-views, ID-photo upload (~12 engineer-days, frontend pairing required).
  2. Schema-done-needs-service-completion — TenantStatus transition methods, email verification endpoint, MenuItem admin UI, folio.settled consumer, concurrency regression tests (~8 engineer-days).
  3. Multi-day infrastructure — outbox pattern, reports timezone, MvRoomTypeRevenue (~6 engineer-days).
  4. Wave 4 testing + compliance + pen test (~20 engineer-days).

Schema migrations are the highest-risk pre-deploy step. 10 services have schema additions; run the pre-flight duplicate-detection SQL before running prisma migrate.

Tests not written. Per gold-standard charter, regression tests must accompany each fix before merge. Spot-check the high-risk ones:

  • W1.A.1 inventory lock — concurrency regression test is mandatory (the audit's #1 P0)
  • W2.D.1/W2.D.4 night-audit changes — chaos test that crashes mid-audit and verifies idempotent resume
  • W3.E.6/W3.E.9/W3.E.10 auth changes — integration test asserting suspend invalidates sessions within 60s
  • W2.A.1 GST split — end-to-end test asserting POS event v2 → folio txn carries the cgst/sgst/igst columns

Five fixes are partially complete (schema done, service logic deferred):

  1. W3.E.7 email verification — User columns added; POST /auth/verify-email endpoint + login-gate logic deferred (1.5d).
  2. W3.E.8 TenantStatus state machine — enum + columns added; transition methods + consumer migration deferred (1.5d).
  3. W2.A.2 MenuItem HSN/GST — schema columns added; admin UI + seed migration deferred (1.5d).
  4. W2.D.6 POS day-close cascade — endpoint + audit shipped; MenuService.closeOutletBusinessDay service method + frontdesk pre-validate consumer deferred (1d).
  5. W1.A.8 folio.settled — event publish shipped; notification dispatcher consumer + SendGrid attachment plumbing deferred (1d).

Three crons + one cron-registration follow-up:

  • GuestIdVerificationRetentionCron (W3.B.2) — file created, needs registration in FrontdeskAppModule.providers.
  • MvTotalRoomsSeedCron (W2.D.12) — file created, needs registration in BackfillModule.providers.
  • S3 snapshot (W2.D.7) — call site added in night-audit, depends on StorageService injection (verify wiring in NightAuditModule).

No code committed. Owner reviews, runs migrations + tests, commits per §5 plan above.

The remaining heavy-lift items (booking concurrency tests, tape chart, schema migrations, full UI work) are 4-5 calendar weeks of focused team work with the schema groundwork now in place.