Skip to main content

Property #1 Gold-Standard Fix Plan — 2026-04-28

Companion to: property-1-readiness-audit-2026-04-28.md (verdict: NO-GO; 47 P0 + 41 P1 + 39 P2). Goal of this plan: flip the verdict to GO by closing every P0 and P1 with a gold-standard fix — root-cause, not band-aid; one canonical implementation, not a parallel one; type-safe schema, not JSON blob; observable, not silent; tested, not hoped. Audience: every engineer or agent assigned an AP-* item. Read this in full before opening a PR.


Part 1 — Gold-Standard Charter (mandatory, applies to every fix)​

These twelve principles are the deal. A PR that violates any of them is rejected, regardless of severity.

1. Fix the root cause, delete the workaround​

A "real" implementation alongside an old stub is not a fix — it is a parallel implementation, which is worse than either. When you ship the canonical path, delete the legacy code in the same PR. If deletion is not safe yet, file a follow-up AP-CLEANUP-* item and reference it.

Examples already in the codebase (each is a debt to clear, not a model to copy):

  • Two C-Form controllers (frontdesk/cform.controller.ts + compliance/cform.controller.ts)
  • Two hardware stacks (@hmp/hardware legacy + hmp-payment-service/src/adapters/pinelabs/)
  • Two room-move services (RoomOpsService.roomMove + CheckInService.roomMove)
  • OnboardingService self-tagged DEPRECATED — uses xPrisma anti-pattern but still wired

2. No silent fallbacks. In production, missing config is a boot failure.​

@Optional() injection of security-critical services is a footgun. EINVOICE_MOCK=true defaulted on prod compose is a footgun. bookingEngineApiKey: 'hmp-default-...' shared seed is a footgun.

Rule: every required prod config (encryption key, NIC creds, Razorpay creds, INTERNAL_SERVICE_TOKEN, JWT public key) must have a startup validator that throws when NODE_ENV === 'production' and the config is missing or matches a known development/test value. Add the validator inside the module's onModuleInit and unit-test it.

3. Schema-first: typed columns, not JSON blobs.​

Where the audit found values stuffed into a JSON settings field (e.g. Property.settings.businessDate, Property.settings.tapeChartColors, Property.settings.bookingEngineApiKey), promote them to typed columns with constraints, indexes, and migration history. Use JSON only for genuinely free-form per-tenant overrides where querying is never required.

Apply specifically to: currentBusinessDate Date? on Property; irn/signedQRCode/ackDate on Folio; idempotencyKey on Reservation; latitude/longitude/googleMapsUrl on Property; frroCity on Property; inclusive Boolean on TaxConfiguration; visa columns on Guest.

4. One implementation, not two.​

For every audit-flagged duplicate path, designate the canonical owner and migrate consumers to it before deleting the rival. Concretely:

  • C-Form: frontdesk/cform.* is canonical. Delete compliance/cform.* (and verify dashboard does not call it).
  • Hardware: hmp-payment-service/src/adapters/pinelabs/ is canonical. Strip @hmp/hardware from hmp-frontdesk-service/package.json and any other dep lists.
  • Room-move: consolidate into one service. The CheckInService variant has the pricing safeguard; that is the keeper.
  • Onboarding: rebuild OnboardingService around PropertyServiceClient (no xPrisma).

5. Idempotency at every boundary.​

Every external write (vendor API call, internal POST that creates an entity, every webhook delivery, every Kafka publish-consume pair) carries an idempotency key, and the receiver enforces dedup using either a unique constraint or a processed_events table.

Concrete obligations:

  • Reservation gets idempotencyKey String? @unique (or @@unique([propertyId, idempotencyKey])).
  • Razorpay Orders create body sets payment_capture: 1 and the request carries an idempotency key in the header (already partially done at razorpay-gateway.adapter.ts:87,131,217 — extend to all paths).
  • Every Kafka consumer must dedup on a domain key; the EventStore table is the source of truth.

6. Observability is part of the feature, not optional polish.​

Every new integration logs a structured event (correlation ID + tenantId + propertyId + operation) at INFO on success and WARN/ERROR on failure. Every Kafka publish-consume pair appears as an Outbox row. Every external vendor call is wrapped in @hmp/observability HTTP histogram.

Concrete: a Pino log line at every state-changing call; a Prometheus histogram for every external HTTP call; an alert rule for any failed-job DLQ depth > 0.

7. Tests before the fix (TDD discipline).​

For every bug-class fix (rate recalc, rollback filter, B2B IRN), write the failing test first. Submit the test in a separate commit before the fix commit so reviewers can see the bug reproduced. No "fix + test in one commit" shortcuts.

For every new feature, the order is: contract test → unit test → integration test → flow test → e2e test. Skipping any layer requires a written justification in the PR description.

8. Cross-service correctness via ServiceClient + receiver-side assertPropertyTenant.​

The xPrisma cast pattern is dead. Every cross-service read uses the typed client in @hmp/service-clients. Every receiving controller's first line of defence is await this.<x>Client.assertPropertyTenant(propertyId, tenantId). Gateway 5-min cache is not a substitute.

Reference: hmp-docs/docs/architecture/cross-service-communication.md.

9. Compose env hygiene; no _MOCK=true outside dev-all.yml.​

QA, staging, prod compose files MUST NOT carry mock flags. Required prod creds are placeholders fed from vault/SSM/ExternalSecrets, never inline in YAML.

CI gate (AP-CC-1 builds the gate): grep -E '(EINVOICE_MOCK|HARDWARE_MOCK|EMAIL_MOCK)\s*:\s*"true"' docker-compose.{qa-aws,staging-aws,prod-aws}.yml must return zero rows.

10. Decide and commit on every "build OR descope" question.​

Mobile frontdesk, kiosk self-check-in, mobile reservations, mobile manager NA widget — each has a "build OR descope" gate in this plan. The decision must be documented in hmp-docs/docs/platform-registry/phase1-mobile-scope-decision-2026-04-28.md before the dependent action items start. A "deferred to Phase 2" decision is fine; a silent omission is not.

11. Update CLAUDE.md and the platform-registry in the same PR as the fix.​

Every audited service's root CLAUDE.md is the working source of truth. Stale CLAUDE.md is a defect. After every fix:

  • Add or update the relevant module/endpoint/schema row in the service's root CLAUDE.md.
  • If a per-folder CLAUDE.md exists in the affected directory, update it too.
  • Add the corresponding registry entry under hmp-docs/docs/platform-registry/_data/ (use cases, drift, gates, compliance — whichever applies).
  • If the fix establishes a new architectural pattern, add an entry in hmp-docs/docs/architecture/.

A PR that ships code without the CLAUDE.md / registry update is incomplete and will be rejected.

12. Migration safety.​

Every Prisma schema change goes through migrate dev → migrate deploy, with a rollback SQL committed alongside. No prisma db push --force-reset against shared databases. Backfill scripts run separately under feature-flag, idempotent, and resumable.

For every new column on a non-empty table:

  1. Add column nullable + default in migration A.
  2. Backfill in migration B (separate deploy).
  3. Flip to NOT NULL in migration C (separate deploy). Migration files live in the per-service prisma/migrations/ directory; backfill scripts live in prisma/migrations-data/.

Part 2 — Mandatory Deliverables Per Fix​

Every AP-* item that ships must include all of the following. PRs missing any one of these are not "Definition of Done."

#DeliverableOwnerWhere it lives
ACode change implementing the gold-standard fix (root cause, not workaround)Engservice repo
BFailing-then-passing tests (one or more layers per principle 7)Engservice repo test/ or e2e/
CRoot CLAUDE.md of every affected repo updated (endpoints, modules, gotchas, schema row)Engeach affected repo
DPer-folder CLAUDE.md updated where one existsEngeach affected repo
ERegistry entry under hmp-docs/docs/platform-registry/_data/Enghmp-docs
FIf schema change: migration file + backfill SQL + rollback SQLEngservice repo prisma/migrations/
GIf new external vendor call or compliance-impacting: runbook in hmp-docs/docs/operations/ or compliance/Enghmp-docs
HIf feature flag introduced: documented in service root CLAUDE.md + flag defaultEngservice repo
IPR description referencing this plan, AP-ID, gold-standard principles satisfied, what was deletedEngPR body
JIf a new event topic, consumer, or Kafka producer is introduced: entry in hmp-shared-packages/packages/messaging/src/events/event-types.tsEnghmp-shared-packages
KOne CI run green on the feature branch before opening PREngCI

Branch + commit conventions​

  • Branch: audit/AP-TXX-NNN-<short-slug> (e.g. audit/AP-T08-001-pdf-irn-real-lookup).
  • Commit format: subject line audit(AP-TXX-NNN): <imperative summary>; body explains why and lists what was deleted.
  • One AP-ID per PR. Do not bundle.
  • Co-Authored-By trailer is required when an agent contributed.

Definition of Done (DoD) for any AP-*​

  1. All twelve gold-standard principles satisfied for this fix.
  2. All eleven deliverables above committed.
  3. Failing test exists in commit history; passing test in latest commit.
  4. CI green.
  5. CLAUDE.md + registry updated in same PR.
  6. PR reviewed by at least one engineer who did not author it.
  7. The corresponding row in phase1-action-plan.md (or this document's tracker) flipped to "DONE" with the merge SHA.

Part 3 — Fifteen Architectural Tracks​

Every action item belongs to exactly one track. Track-level architectural decisions are stated once; per-item rows then reference them.


Vision: every Indian-statutory artifact (GST e-invoice, Form C / FRRO, DPDPA erasure, GSTR aggregates) must be backed by real vendor integration, not mocks; and by a written compliance runbook with a CI guard against regression.

Architectural decisions:

  • EINVOICE_MOCK becomes development-only. Prod boot fails when NODE_ENV=production && EINVOICE_MOCK=true.
  • EinvoiceJob.irn/ackNumber/ackDate/signedQRCode are denormalised onto Folio after success; PDF reads from Folio, not from a JOIN.
  • B2B path (corporate stays) is first-class. EInvoiceService always calls GuestServiceClient.getCompany + getGuestAddresses; nullable result yields B2C/URP, not silently-null company.
  • GUEST_ERASED event is consumed by every service that holds guest PII (frontdesk, pos, housekeeping, analytics, crm, payment, notification, banquet, spa). Each consumer scrubs its own tables with idempotency.
  • Foreign-guest visa is a first-class GuestVisa model (FK → Guest), not a polymorphic GuestIdentity{idType:OTHER}.
  • Production refuses to boot if ENCRYPTION_KEY is unset.
  • A 90-day retention timer is scheduled at checkout commit; cron writes GdprErasureLog with legalBasis='RETENTION_EXPIRY'.

P0 items:

AP-IDGold-standard fixAcceptance criteriaCLAUDE.md targetRegistry target
AP-T08-001Replace irn = 'Pending' literal with a real lookup. PDF service reads from the new Folio.irn/Folio.signedQRCode columns (added by AP-T08-006). When folio has no IRN yet, the PDF clearly states "DRAFT — IRN PENDING" with a watermark, not a literal "Pending" embedded in the QR placeholder.A folio with a generated EInvoiceJob produces a PDF whose IRN string equals the job's IRN; QR code base64 round-trips through a NIC-format validator.hmp-frontdesk-service/CLAUDE.md (folio + invoice tables)_data/use-cases/uc-frontdesk-einvoice-irn.yaml
AP-T08-002Add NIC_CLIENT_ID/SECRET/USERNAME/PASSWORD/GSTIN/EINVOICE_ENV env placeholders. Prod-startup validator rejects boot when `NODE_ENV=production && (EINVOICE_MOCK!=='false'any NIC var missing)`. Add CI grep gate.Boot of hmp-frontdesk-service with NODE_ENV=production and EINVOICE_MOCK=true exits non-zero with a clear error.
AP-T08-003Wire GuestServiceClient.getCompany + getGuestAddresses into EInvoiceService.generateIrnForFolio. null result legitimately produces B2C; non-null result populates the B2B section of the IRN payload with PoS calculation.A reservation tied to a Company produces an IRN payload with Buyer.GSTIN populated; a walk-in produces Buyer.URP. Verified by unit test + flow test.hmp-frontdesk-service/CLAUDE.md einvoice + cross-service section_data/use-cases/uc-frontdesk-einvoice-b2b.yaml
AP-T08-004New GuestRetentionTimer model in guest-service ({guestId, expiresAt, basis}). Checkout publishes GUEST_RETENTION_SCHEDULED. Daily cron in guest-service scans timers ≤ now, calls gdpr.service.eraseGuest(guestId), writes GdprErasureLog row, publishes GUEST_ERASED.A reservation checked out today produces a timer row with expiresAt = today+90d; flipping the clock 91 days produces a GdprErasureLog row.hmp-guest-service/CLAUDE.md gdpr module, hmp-frontdesk-service/CLAUDE.md checkout_data/compliance/dpdpa-retention.yaml
AP-T08-005New GuestErasedConsumer per service (frontdesk, pos, housekeeping, analytics, crm, payment, notification, banquet, spa). Each consumer scrubs its denormalised PII (Folio.guestName, Order.guestName, Task.assignedToName, mv_*, CommunicationLog.metadata, Payment.metadata, etc.) idempotently and emits GUEST_ERASED_ACK.Erasing a guest produces ACK events from every service holding their PII; manual SQL audit confirms no plaintext name/passport/visa remains.each service's root CLAUDE.md (consumers section)_data/compliance/guest-erased-consumers.yaml
AP-T06-001IndianFrroClient interface in @hmp/integrations defining submitFormC, getStatus, markSubmitted. Initial impl is ManualUploadFrroClient (today's behaviour: returns submission URL, accepts manual ack). Future impl FrroPortalClient slots in via env. Runbook in hmp-docs/docs/operations/frro-form-c.md walks staff through the manual flow.A C-Form generated for a non-Indian guest emits a FORM_C_GENERATED event with the manual upload URL; a "mark submitted" call records the ack number against the row.hmp-frontdesk-service/CLAUDE.md cform module_data/compliance/form-c-frro.yaml
AP-T06-002Drop the idType=OTHER visa hack. New GuestVisa model in guest-service ({id, guestId, visaNumber, visaType, issuedDate, expiryDate, issuedAt, frroCity}). DTO splits cleanly; CForm generation reads from GuestVisa, not GuestIdentity. Migration converts existing rows.Foreigners with passport+visa produce two distinct rows (passport identity + visa); the C-Form PDF carries both correctly.hmp-guest-service/CLAUDE.md Guest table + new GuestVisa_data/use-cases/uc-guest-visa-record.yaml
AP-T06-009Boot validator on every service that uses @hmp/security field encryption: throws if NODE_ENV=production && !ENCRYPTION_KEY. Add to all 13 backend services in their app.module.ts onModuleInit.Prod-mode boot without ENCRYPTION_KEY exits with code 1 within 200ms.each service's root CLAUDE.md (security section)_data/gates/prod-encryption-key-required.yaml

P1 items in this track: AP-T08-006 (Folio IRN columns — depends on the migration discipline of principle 12), AP-T08-007 (credit-note IRN through queue), AP-T08-009 (NIC webhook URL registration).


Track 2 — Payment Platform Hardening​

Vision: every tenant has a per-tenant MerchantAccount with encrypted credentials provisioned through the admin onboarding wizard; Razorpay calls always pass an idempotency key and explicit payment_capture: 1; missed webhooks self-heal via a reconciliation job; hardware terminals run through one canonical adapter.

Architectural decisions:

  • The MerchantAccount table (with encrypted creds at rest) is the only source of payment credentials. Compose-injected RAZORPAY_* env vars are forbidden in QA/staging/prod (only used by local dev seed when scaffolding the first MerchantAccount row).
  • Admin onboarding wizard step "Payment Provider" calls POST /api/merchant-accounts with sandbox creds; tenant later edits to live via the same endpoint.
  • A new WebhookReconcileService runs every 15 minutes per tenant: fetches Razorpay /payments?from=lastCursor and reconciles against Payment rows in IN_PROGRESS state.
  • Pinelabs adapter under hmp-payment-service is canonical; @hmp/hardware is removed from hmp-frontdesk-service/package.json.
  • All four compose files (dev-all, qa-aws, staging-aws, prod-aws) gain a CI-checked invariant: HARDWARE_MOCK=true only in dev-all.

P0 items:

AP-IDGold-standard fixAcceptance criteriaCLAUDE.md targetRegistry target
AP-T12-001Add a Razorpay sandbox MerchantAccount seed row for tenant 1 in seeds/seed-keynova-payment.sql, encrypted via PAYMENT_KMS_KEY_VERSION=v1. Document the seed-key rotation runbook.GET /api/merchant-accounts on QA returns one row with mode=SANDBOX after seed-master.sh.hmp-payment-service/CLAUDE.md MerchantAccount section, hmp-infrastructure/CLAUDE.md seeds_data/use-cases/uc-payment-merchant-account.yaml
AP-T12-002Build admin-portal "Payment Provider" wizard step. Calls POST /api/merchant-accounts (auth as SUPER_ADMIN with x-impersonated-tenant-id when admin acts on tenant). Stores creds encrypted; never logs them. After save, performs validateCredentials and surfaces failure.Tenant onboarding completes only after validateCredentials succeeds for at least one MerchantAccount; verified by Playwright e2e.hmp-admin-portal/CLAUDE.md onboarding wizard, hmp-payment-service/CLAUDE.md merchant-account flow_data/use-cases/uc-tenant-payment-onboarding.yaml
AP-CC-3RS256 migration for /housekeeping, /kitchen, /events WebSocket gateways. Each gateway loads JWT_PUBLIC_KEY; HS256 path remains for dev, fail-closed for prod. Mobile/kiosk clients auto-handle algorithm negotiation.Setting NODE_ENV=production + missing JWT_PUBLIC_KEY → boot fails. HS256-signed token to /kitchen in prod is rejected.hmp-housekeeping-service/CLAUDE.md, hmp-pos-service/CLAUDE.md, hmp-api-gateway/CLAUDE.md_data/gates/ws-rs256-prod.yaml
AP-CC-4Strip EINVOICE_MOCK=true and HARDWARE_MOCK=true from staging-aws.yml + prod-aws.yml. Remove inline credentials. Use ${RAZORPAY_KEY_ID} placeholders sourced from external secrets. CI grep gate enforces.grep -E '(EINVOICE_MOCK|HARDWARE_MOCK)\s*:\s*"true"' docker-compose.{prod-aws,staging-aws}.yml returns 0 rows.hmp-infrastructure/CLAUDE.md deploy section_data/gates/no-prod-mocks.yaml
AP-CC-5Provision SendGrid creds across QA + prod via External Secrets. Templates already exist in notification-service. Add e2e test covering invoice email delivery via SendGrid sandbox.Sending an invoice on QA produces a SendGrid message ID logged at INFO level.hmp-notification-service/CLAUDE.md channels section_data/use-cases/uc-notification-email-invoice.yaml

P1 items: AP-T12-003 (payment_capture: 1), AP-T12-004 (WebhookReconcileService), AP-T12-005 (override prod compose HARDWARE_MOCK). All inherit the architectural decisions above.


Track 3 — Data Integrity & Tenant Isolation​

Vision: every cross-service write is idempotent; every cross-service read uses ServiceClient with receiver-side assertPropertyTenant; every JSON-blob "settings" config is migrated to a typed column; every API key is per-property and DB-backed.

Architectural decisions:

  • Reservation.idempotencyKey is String? @unique; absence allows legacy rows. Booking-engine, dashboard quick-create, and channel inbound all stamp keys.
  • ApiKey is a first-class table ({id, tenantId, propertyId, keyHash, label, lastUsedAt, expiresAt}). IApiKeyService is provided in reservation-service AppModule and looks up by SHA-256 of presented key.
  • Property.currentBusinessDate Date? replaces Property.settings.businessDate. Property-service exposes PATCH /properties/:id/business-date (internal-only); frontdesk's advanceBusinessDate calls this.
  • Folio.irn/signedQRCode/ackDate denormalised columns; PDF reads from folio.
  • TaxConfiguration.inclusive Boolean column.
  • CancellationPolicy.rules JSON has a documented JSON-schema validated at API boundary.
  • Guest schema gains nationality already present, plus the new GuestVisa model from Track 1.
  • CalendarService.getCalendar and createQuickReservation call assertPropertyTenant(propertyId, tenantId) first.
  • The xPrisma cast pattern is forbidden in new code; existing usages get a deprecation comment with target removal date.

P0 items:

AP-IDGold-standard fixAcceptance criteriaCLAUDE.mdRegistry
AP-T03-001Reservation.idempotencyKey String? @unique migration; BookingEngineController.createReservation and ReservationsController.create accept Idempotency-Key header; second submit returns the first reservation with HTTP 200 and a Idempotency-Replay: true header. Rejection on key collision with different body.Two consecutive POSTs with same key → one row, two responses with same id. Different body with same key → 409.hmp-reservation-service/CLAUDE.md reservation table + endpoints_data/use-cases/uc-booking-idempotency.yaml
AP-T03-002New ApiKey table; IApiKeyService impl in reservation-service; seed migration converts existing property.settings.bookingEngineApiKey to per-property hashed ApiKey rows. Rotation endpoint POST /api/properties/:id/api-keys/rotate emits API_KEY_ROTATED. Old shared seed key is revoked.Public booking-engine endpoints reject the old shared key after migration; per-property key hash is stored, never plaintext.hmp-reservation-service/CLAUDE.md booking-engine module, hmp-property-service/CLAUDE.md api-key section_data/use-cases/uc-property-api-key.yaml
AP-T13-001Add await this.propertyClient.assertPropertyTenant(propertyId, tenantId) to CalendarService.getCalendar AND createQuickReservation. tenantId comes from AsyncLocalStorage, not headers.Call with valid JWT for tenant A but propertyId belonging to tenant B → 403.hmp-frontdesk-service/CLAUDE.md calendar module_data/drift/calendar-tenant-assert.yaml
AP-T02-001Add latitude Float?, longitude Float?, googleMapsUrl String? to CreatePropertyDto + UpdatePropertyDto. Map in service. Add JSON-schema validation on googleMapsUrl (must match `^https://(www.maps.)?google.[a-z.]+/maps`).PUT /properties/:id round-trips all three fields. Invalid URL rejected with 400.hmp-property-service/CLAUDE.md property table
AP-T01-007Replace OnboardingService xPrisma anti-pattern with proper PropertyServiceClient + RoomTypeServiceClient calls. Saga commits each step or compensates. Delete the old service in the same PR.Onboarding a tenant from signup → property → room types → rooms → rate plans → tax → policy completes via the canonical client chain; no xPrisma reference remains in auth-service.hmp-auth-service/CLAUDE.md onboarding module_data/use-cases/uc-tenant-onboarding-saga.yaml

P1 items: AP-T08-006 (Folio IRN columns), AP-T09-004 (Property.currentBusinessDate column), AP-T02-005 (TaxConfiguration.inclusive), AP-T02-006 (CancellationPolicy.rules schema), AP-T06-002 (visa columns — listed in Track 1). All apply the typed-schema discipline.


Track 4 — Cross-Service Eventing​

Vision: event topics are a typed contract; every published event has at least one consumer or is removed; the analytics dispatcher subscribes to the full nine-topic spec; every consumer dedups; every Kafka producer-consumer pair appears in event-types.ts.

Architectural decisions:

  • The canonical event taxonomy lives in hmp-shared-packages/packages/messaging/src/events/event-types.ts. Producer-side and consumer-side import from this single source.
  • Rename folio.charge.posted → folio.posted to match spec, OR add an alias in event-types.ts and bump major. Decision: rename, single source of truth, even if it requires producer + consumer change in the same PR.
  • kot.completed is a new topic, emitted by KitchenService.markKotComplete; consumer in analytics builds mv_kot_throughput.
  • kot:created over WebSocket and KOT_CREATED over Kafka are two different beasts: first is real-time UI push, second is durable for analytics. OrdersService.generateKOT emits both.
  • RESERVATION_MODIFIED is emitted on every mutation (room-move included).
  • night.audit.completed consumer in analytics rebuilds the daily-flash MV.
  • The dispatcher.service.ts registers each (topic, handler, deduper) triple via a single subscribeAndDedup(...) helper that wraps kafkaService.subscribe and the processed_events table.

P0 items:

AP-IDGold-standard fixAcceptance criteriaCLAUDE.mdRegistry
AP-T07-002Wire kitchenGateway.emitKotCreated(outletId, kot) after KOT row insert in OrdersService.generateKOT. Also publish KOT_CREATED to Kafka. Add kot.completed Kafka publish on status flip to READY/SERVED.Playwright e2e: order entry → KDS card visible in <2s on dashboard /pos/kitchen; flip to READY → status column shifts.hmp-pos-service/CLAUDE.md orders + kitchen_data/use-cases/uc-pos-kot-realtime.yaml
AP-T07-004Extend FOLIO_CHARGE_POSTED payload schema with cgstAmount, sgstAmount, igstAmount, customerState, placeOfSupply. Bump payload version. Consumer writes distinct CGST/SGST/IGST rows on FolioTransaction.A POS order with state-split tax breakdown produces a folio with three tax transaction rows whose sum equals the order's tax.hmp-pos-service/CLAUDE.md, hmp-frontdesk-service/CLAUDE.md consumers_data/use-cases/uc-folio-tax-breakdown.yaml
AP-T08-005(also Track 1) GuestErasedConsumer in 7 services. Listed under Track 1.Same.Same.Same.
AP-T09-002Subscribe analytics EventDispatcherService to night.audit.completed. Consumer rebuilds mv_daily_flash for the audit's business date.A manual NA run on QA refreshes the daily-flash MV; verified by curl /api/reports/daily-flash.hmp-analytics-service/CLAUDE.md consumers_data/use-cases/uc-analytics-night-audit.yaml
AP-T10-001Compute and persist MvAdrRevpar.adr and revpar on every upsert in reservation.consumer.ts. Backfill historical rows via scripts/backfill-mv.ts --target=mv_adr_revpar.After backfill, /api/reports/revenue/daily and /api/analytics/dashboard/metrics agree on ADR + RevPAR for any seeded date.hmp-analytics-service/CLAUDE.md MV table_data/use-cases/uc-analytics-adr-revpar.yaml

P1 items: AP-T07-006 (transactionCode by orderType), AP-T10-006 (subscribe missing 4 of 9 topics + rename folio.charge.posted), AP-T05-007 (RESERVATION_MODIFIED on room-move). All apply the canonical-taxonomy + dedup pattern.


Track 5 — Frontend Real-Data + Brand Coherence​

Vision: zero hardcoded business data outside *.fixture.ts test files; UI never recomputes business numbers the server can compute; one theme system per app, source-of-truth in @hmp/design-tokens; one @hmp/api-contracts typed client.

Architectural decisions:

  • Dashboard /reservations/:id FolioTable reads res.folios[].transactions[] directly. @hmp/ui-kit ships FolioTable@2.1.0 with the corrected math; the local ui-kit-fixes/FolioTable shim is removed.
  • Dashboard /reports/[category]/page.tsx removes the 40-entry hardcoded fallback list. The catalog endpoint is the only source.
  • Admin portal /analytics/page.tsx removes all platformKpis/Top tenants/Feature adoption hardcoded blocks; new GET /analytics/admin/platform-overview aggregates across tenants.
  • Mobile dashboardApi.getMetrics URL fixed to /analytics/dashboard/metrics; X-Property-ID header injected via shared interceptor.
  • Tape-chart FilterChipsRow either wires to getCalendar({roomTypeId, floor, source, vipOnly}) or is deleted. Decision: wire it; calendar gains optional filter params.
  • Booking-engine theme strategy: keep direction-a/b as the architecture (six tested presets); document in branding spec; rename "KeyNova default" to one of these. Drop the unused Midnight Sovereign / Alabaster Court / Ink & Brass references from product docs. (The dashboard's current default is the KeyNova theme; Ink & Brass was the previous default and is deprecated. Different repo, different design system from the booking engine.)
  • AiSuggestionToast either reads from a /ai/suggestions endpoint or is feature-flagged OFF. Decision: feature-flag OFF until Phase 2.

P0 items:

AP-IDGold-standard fixAcceptance criteriaCLAUDE.mdRegistry
AP-T03-003Update branding doc to declare direction-a/b as the official theme set with six presets. Remove product references to "Midnight Sovereign / Alabaster Court / Ink & Brass" for the booking engine. (Dashboard's current default is the KeyNova theme; Ink & Brass was deprecated.)One paragraph in hmp-docs/docs/branding/booking-engine-themes.md lists the six canonical presets; release notes call it out. Dashboard root CLAUDE.md updated to reflect KeyNova as current theme.hmp-booking-engine/CLAUDE.md themes, hmp-web-dashboard/CLAUDE.md theme_data/branding/booking-engine-themes.yaml, _data/branding/dashboard-theme.yaml
AP-T10-002DashboardController.getMetrics queries MvAdrRevpar via the analytics service for ADR/RevPAR/today-revenue; remove the live ReservationServiceClient aggregation. Single source of truth./dashboard/metrics and /reports/revenue/daily agree on the same date for ADR/RevPAR.hmp-analytics-service/CLAUDE.md dashboard module_data/use-cases/uc-dashboard-metrics-mv.yaml
AP-T10-004Fix dashboardApi.getMetrics URL → /analytics/dashboard/metrics. Inject X-Property-ID via shared mobile axios interceptor.Mobile manager-app dashboard tab loads metrics without 404.hmp-mobile-apps/CLAUDE.md api-client_data/use-cases/uc-mobile-dashboard.yaml
AP-T10-005New analytics controller for /gst/gstr3b aggregating EventStore einvoice payloads by HSN/PoS/B2B-vs-B2C. Scheduled monthly snapshot stored in MvGstr3bSnapshot.GET /reports/gstr3b?month=2026-04 returns a JSON summary of total taxable + tax + B2B/B2C split that matches a hand-computed value from the underlying EventStore.hmp-analytics-service/CLAUDE.md reports + new gst module_data/use-cases/uc-gstr3b-prep.yaml
AP-T10-007Replace (dashboard)/reports/[category]/page.tsx:23-79 hardcoded list with backend catalog only. 404 on unknown category.Visiting an unlisted report category renders a 404 page; visiting a real category renders real reports.hmp-web-dashboard/CLAUDE.md reports module_data/drift/dashboard-reports-catalog.yaml
AP-T10-008New /analytics/admin/platform-overview endpoint (cross-tenant aggregate). Admin portal calls it.Admin portal /analytics page renders real numbers; demo fixtures deleted.hmp-admin-portal/CLAUDE.md, hmp-analytics-service/CLAUDE.md_data/use-cases/uc-admin-platform-overview.yaml
AP-T13-002getCalendar controller accepts optional roomTypeId, floor, source, vipOnly. FilterChipsRow writes to URL state and refetches. Cosmetic chips deleted; only working filters render.Selecting "VIP only" chip refetches calendar with vipOnly=true and renders only those rows.hmp-frontdesk-service/CLAUDE.md calendar, hmp-web-dashboard/CLAUDE.md tape chart_data/use-cases/uc-tape-chart-filters.yaml

P1 items: AP-T04-004 (FolioTable real data), AP-T05-004 (CTA buttons on detail page), AP-T13-005 (in-place dialog instead of redirect), AP-T13-003 (multi-room visual continuity).


Track 6 — Configuration Management & Deploy Hygiene​

Vision: secrets never in compose YAML; required prod creds enforced at boot; Kubernetes CronJobs for scheduled work; one CI gate prevents drift.

Architectural decisions:

  • All secrets injected via Kubernetes ExternalSecrets (AWS SSM Parameter Store backend) for QA/staging/prod. Compose files use ${VAR} placeholders that fail-loud if unset.
  • Boot validators in every service for required prod env: NIC_*, RAZORPAY_*, RESAVENUE_* (channel-service), SENDGRID_API_KEY, AWS_S3_*, ENCRYPTION_KEY, JWT_PUBLIC_KEY, INTERNAL_SERVICE_TOKEN.
  • Night-audit scheduler promoted from in-pod cron to k8s CronJob with miss-detection alert (Prometheus rule).
  • A new tokenctl helm post-install hook mints INTERNAL_SERVICE_TOKEN automatically on deploy and writes to ExternalSecrets, eliminating the recurring "503 after deploy" gotcha.

P0 items:

AP-IDGold-standard fixAcceptance criteriaCLAUDE.mdRegistry
AP-T02-002Add MinIO sidecar to dev-all.yml; add real S3 buckets to QA/prod via Terraform (hmp-infrastructure/terraform/modules/storage). Add AWS_S3_BUCKET, AWS_REGION, AWS_S3_ENDPOINT (for MinIO), AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY env. Extend AssetType enum: PROPERTY_IMAGE, ROOM_TYPE_IMAGE, GUEST_ID_PHOTO, GUEST_SIGNATURE.POST /api/assets/upload-url returns 200 with presigned URL on local + QA.hmp-property-service/CLAUDE.md asset module, hmp-infrastructure/CLAUDE.md terraform storage_data/use-cases/uc-property-image-upload.yaml
AP-T02-003Drag-drop image upload widget in dashboard (dashboard)/settings/property/page.tsx and room-type page. Calls POST /assets/upload-url → S3 PUT → POST /assets/process for variant generation; persists URLs into Property.images[] via PATCH.Hotelier uploads a 5MB JPG; system generates 3 variants (thumb/medium/full); URLs persist.hmp-web-dashboard/CLAUDE.md settings/property_data/use-cases/uc-property-image-upload.yaml
AP-T02-004 / AP-CC-2Re-run seed-master.sh against QA RDS for tenant 1 + Property 40b29005-.... If property no longer exists, re-create via the seed. Update doc to reference the canonical property UUID. Add a "post-deploy seed verification" step to deploy-qa.sh.After redeploy, GET /api/properties/40b29005-... returns 200 with rooms, rate plans, taxes. Smoke-test 13/0/0.hmp-infrastructure/CLAUDE.md seeds + deploy_data/operations/qa-redeploy-runbook.yaml
AP-T11-006Set CHANNEL_MODE=simulator in qa-aws.yml until live ResAvenue creds land. Document in deploy runbook.docker-compose -f qa-aws.yml exec hmp-channel-service printenv CHANNEL_MODE returns simulator.hmp-channel-service/CLAUDE.md, hmp-infrastructure/CLAUDE.md_data/gates/channel-mode-set.yaml
AP-T08-002(also Track 1) NIC creds + EINVOICE_MOCK=false gate.Same.Same.Same.

P1 items: AP-T09-005 (NA scheduler → k8s CronJob), AP-T12-005 (override prod compose HARDWARE_MOCK=false).


Track 7 — Gateway Routing & Two-File Rule​

Vision: services.config.ts is the single declarative source. proxy.controller.ts decorators are auto-generated or CI-validated against the config. No more silent 404s.

Architectural decisions:

  • New CI script scripts/validate-gateway-routing.ts reads services.config.ts and parses proxy.controller.ts AST for @All('<prefix>') decorators; fails the build if any prefix exists in one but not the other.
  • Long-term (Phase 2): a code-generator emits proxy.controller.ts decorators from services.config.ts. This plan does not require the generator; it requires the validator.
  • Dead routes (/ai/query resolving to nothing) are deleted from both files in the same PR.

P0 items:

AP-IDGold-standard fixAcceptance criteriaCLAUDE.mdRegistry
AP-T11-001Add @All('intermediary') + @All('intermediary/*') + @All('channel-stats') + @All('channel-stats/*') + @All('channel-setup') + @All('channel-setup/*') + @All('webhook-management') + @All('webhook-management/*') decorators to proxy.controller.ts.Curl /api/intermediary/connections reaches channel-service. CI validator passes.hmp-api-gateway/CLAUDE.md routing, hmp-channel-service/CLAUDE.md_data/drift/gateway-decorators.yaml
AP-T07-003Drop /pos/ prefix from hmp-mobile-apps/packages/api-client/src/index.ts to match gateway routing. (Dashboard already correct — no /pos/ prefix.)Mobile F&B order list call GET /api/orders returns 200.hmp-mobile-apps/CLAUDE.md api-client_data/drift/mobile-api-prefix.yaml
AP-CC-1New CI script scripts/validate-gateway-routing.ts + workflow that fails on prefix drift. Run on every PR.A PR that adds a prefix to services.config.ts without a matching @All decorator (or vice versa) fails CI.hmp-api-gateway/CLAUDE.md routing-rules_data/gates/gateway-two-file-rule.yaml

P2 items in this track: AP-T09-009 (add bare @All('night-audit')), AP-T10-013 (delete dead /ai/query).


Track 8 — Mobile Surface Decisions & Build​

Vision: every Phase 1 in-scope mobile flow has a typed API contract, real-time via shared package, and Detox/Maestro e2e in CI. Out-of-scope screens are deleted or marked WIP/disabled, not left as silent stubs.

Architectural decisions:

  • A formal Phase 1 Mobile Scope Decision document (hmp-docs/docs/platform-registry/phase1-mobile-scope-decision-2026-04-28.md) lists each potential mobile surface (frontdesk, reservations, F&B, manager NA widget, kiosk self-checkin) as IN-SCOPE or DEFERRED.
  • IN-SCOPE surfaces: each gets a screen, a Detox/Maestro spec, and a CLAUDE.md entry.
  • DEFERRED surfaces: their stub files are deleted, not left as <PlaceholderScreen>. The decision is permanent until a future Phase.
  • The "frontdesk-app" referenced in the audit prompt does NOT exist; the decision must explicitly say "no frontdesk app for Phase 1" OR scaffold it under apps/staff/ with the check-in screen.

P0 items:

AP-IDGold-standard fixAcceptance criteriaCLAUDE.mdRegistry
AP-T04-002Decision document. PM + Eng lead author phase1-mobile-scope-decision-2026-04-28.md. Lists each surface IN-SCOPE/DEFERRED with rationale. Approved before any mobile-related action item starts.Document committed, signed off in PR description by PM + Eng lead.hmp-mobile-apps/CLAUDE.md scope section_data/decisions/phase1-mobile-scope.yaml
AP-T04-001 / AP-CC-2(Track 6) Re-seed QA.Same.Same.Same.
AP-T05-003Nginx WS routes /ws/housekeeping/ → housekeeping-service:3006 and /ws/kitchen/ → pos-service:3007. Mobile + KDS clients use wss://<host>/ws/<namespace>/. Document the upgrade-headers + read-timeout values.Curl wss://qa-host/ws/housekeeping/socket.io/?EIO=4 returns 200 from outside the cluster.hmp-infrastructure/CLAUDE.md nginx, hmp-housekeeping-service/CLAUDE.md, hmp-pos-service/CLAUDE.md_data/operations/nginx-ws-routing.yaml
AP-T06-004Per scope decision: build apps/staff/app/(tabs)/checkin.tsx with real /check-in/:reservationId call OR delete the placeholder + remove the tab.If built: Detox spec covers Indian + International happy paths. If deferred: apps/staff/app/(tabs)/checkin.tsx and the tab no longer exist.hmp-mobile-apps/CLAUDE.md apps/staff_data/decisions/phase1-mobile-scope.yaml
AP-T07-003(Track 7) Drop /pos/ prefix.Same.Same.Same.
AP-T10-004(Track 5) Mobile dashboardApi URL fix.Same.Same.Same.

P1/P2: AP-T05-008 (Detox e2e for HK realtime), AP-T11-005 (Channel-manager flow test, multiple).


Track 9 — KDS / Kiosk Rebuild​

Vision: the KDS Electron app and the kiosk Electron app share @hmp/realtime-kit (WS auth + reconnect + station-account JWT) and @hmp/api-contracts (typed REST). Both apps run RS256 JWTs minted at install time per device.

Architectural decisions:

  • New shared package @hmp/realtime-kit providing createSocketClient({namespace, token, propertyId}) with infinite reconnect, structured error logs, and namespace-correctness assertions.
  • KDS app subscribes to /kitchen namespace (NOT /events); listens to kot:created, kot:statusChanged, order:itemReady, order:updated.
  • Long-lived "station-account" JWT (RS256, 30-day expiry, scope station:kitchen or station:kiosk) provisioned on install; rotation via admin portal.
  • KitchenStation is a first-class model ({id, propertyId, code, displayName}); MenuItem.kitchenStationId FK; OrdersService.generateKOT groups items per station and broadcasts on outlet:${id}:station:${code} rooms.
  • Kiosk self-check-in either talks to /api/guest-portal/self-checkin/* for real OR the kiosk Electron app is descoped and removed from Phase 1.

P0 items:

AP-IDGold-standard fixAcceptance criteriaCLAUDE.mdRegistry
AP-T07-001Rewrite hmp-kds-kiosk WebSocket client to use @hmp/realtime-kit. Target /kitchen namespace; pass auth.token; subscribe to canonical events (colon-delimited). Remove VITE_USE_DEMO=true default.KDS receives a real KOT in <2s after dashboard /pos order entry, on QA.hmp-kds-kiosk/CLAUDE.md (rebuild section)_data/use-cases/uc-kds-realtime.yaml
AP-T06-003Decision via the Mobile Scope doc (Track 8): build kiosk self-check-in (connect useCheckInFlow to /guest-portal/self-checkin) OR delete the kiosk-check-in flow from hmp-kds-kiosk.If built: Playwright/Spectron e2e covers a real check-in. If deferred: useCheckInFlow.ts mock is gone.hmp-kds-kiosk/CLAUDE.md_data/decisions/phase1-mobile-scope.yaml
AP-T07-002(Track 4) emitKotCreated wiring.Same.Same.Same.

P1/P2: AP-T07-005 (KitchenStation model), AP-T07-007 (reconnect Infinity), AP-T07-008 (e2e).


Track 10 — Tenant Onboarding & Self-Service​

Vision: a tenant signs up on the public web, receives a verified email, lands in the dashboard with a usable subscription and the OnboardingWizard reachable as ADMIN, completes property/rooms/rate-plans/tax/policy, and is ready to take a booking — all without operator intervention.

Architectural decisions:

  • ResetPasswordRequestDto accepts {email} only when the email belongs to a single tenant; ambiguous email returns the same generic If the email exists... message but logs at WARN.
  • Notification dispatcher subscribes to TENANT_CREATED and renders welcome-tenant + verify-email templates.
  • OnboardingController allows ADMIN scoped to own tenantId (not just SUPER_ADMIN).
  • Admin portal (auth)/signup/page.tsx and (auth)/forgot-password/page.tsx are the public entry points.
  • GET /billing/plans is @Public() (anon-cacheable) so prospective tenants see pricing.
  • OnboardingService is rebuilt around ServiceClient (no xPrisma).

P0 items:

AP-IDGold-standard fixAcceptance criteriaCLAUDE.mdRegistry
AP-T01-001Make ResetPasswordRequestDto.tenantId optional. Service resolves user by email + tenantId? ?? lookup-by-email. Non-unique email → log + generic 200 response. Unit test covers all 3 cases.Dashboard /forgot-password POST {email} → 200 with password.reset.requested event.hmp-auth-service/CLAUDE.md auth module_data/use-cases/uc-forgot-password.yaml
AP-T01-002Notification dispatcher subscribes to TENANT_CREATED (handler renders welcome-tenant.hbs) and a new USER_REGISTERED for verify-email. SendGrid sandbox required (Track 6).Signup → email arrives in MailHog (local) / SendGrid sandbox (QA) within 30s. Flow test covers it.hmp-notification-service/CLAUDE.md dispatcher + templates_data/use-cases/uc-tenant-welcome-email.yaml
AP-T01-003Replace @Roles('SUPER_ADMIN') with @Roles('SUPER_ADMIN','ADMIN') on every OnboardingController route, AND add a tenantId guard so ADMIN can only act on their own tenantId.Tenant created via signup, logging in as that admin, can call /onboarding/:tenantId/property for their own tenant.hmp-auth-service/CLAUDE.md onboarding module_data/use-cases/uc-onboarding-admin-role.yaml
AP-T01-004Add (auth)/signup/page.tsx (calls POST /tenants/signup) and (auth)/forgot-password/page.tsx to admin portal. Real captcha. Email-only forgot. After signup, redirect to /onboarding.Signup → email arrives → click link → land in /onboarding Step 1 with valid session. Playwright e2e covers it.hmp-admin-portal/CLAUDE.md auth pages_data/use-cases/uc-tenant-self-onboarding.yaml

P1 items: AP-T01-005 (@Public() GET /billing/plans), AP-T01-007 (OnboardingService rebuild — also Track 3).


Track 11 — HK / Frontdesk Operations​

Vision: every reservation mutation (date change, room change, status flip) goes through one canonical service that recomputes rate, posts folio side-effects in the same transaction (or via the outbox), and emits RESERVATION_MODIFIED.

Architectural decisions:

  • ReservationsService.update() becomes the single canonical mutation entry. It recomputes per-night rates × new nights when dates change. If reservation is CHECKED_IN and new dates extend beyond original, it posts an EXTENSION_NIGHT folio CHARGE in the same transaction.
  • RoomOpsService.roomMove and CheckInService.roomMove consolidate into one — keep the variant with the pricing safeguard. Paid-upgrade rate-difference posted to folio in the same transaction.
  • A new RoomBlock model ({id, roomId, propertyId, startsAt, endsAt, reason}) replaces "set Room.status=OUT_OF_ORDER indefinitely". Cron auto-reverts on endsAt.
  • Tape-chart context menu gains mark-no-show action; extend/shorten use the existing in-place dialog (no /reservations/:id redirect).
  • Multi-room reservation visual continuity: a thin connector line between sibling RoomReservation bars sharing reservationId.

P0 items:

AP-IDGold-standard fixAcceptance criteriaCLAUDE.mdRegistry
AP-T05-001Fix rate recalc in ReservationsService.update() — when departureDate changes, recompute Σ ratePerNight × nights for every roomReservation. Tax recalc rides off the corrected subtotal. Failing test first (reproduces 3→5 night under-bill); then fix.Extending 3→5 nights produces totalAmount += 2×nightlyRate; flow test asserts.hmp-reservation-service/CLAUDE.md reservations module_data/use-cases/uc-reservation-extend-rate.yaml
AP-T05-002When reservation is CHECKED_IN and update extends nights, post folio CHARGE rows with transactionCode='EXTENSION_NIGHT' for each added night, in the same prisma.$transaction. Emit FOLIO_CHARGE_POSTED per night. Transactional outbox handles retry if Kafka is down.Folio balance reflects extra nights immediately on extend; flow test covers.hmp-reservation-service/CLAUDE.md, hmp-frontdesk-service/CLAUDE.md consumers_data/use-cases/uc-reservation-extend-folio.yaml
AP-T09-001Extend rollback transactionCode filter in night-audit.service.ts:910-916 from ['ROOM'] to ['ROOM','OVERSTAY_NIGHT','NO_SHOW']. Failing test reproduces orphan; then fix.After NA + rollback, folio has 0 leftover charges of any of the three codes.hmp-frontdesk-service/CLAUDE.md night-audit module_data/use-cases/uc-night-audit-rollback.yaml

P1 items: AP-T05-005 (consolidate two room-move impls), AP-T05-006 (paid-upgrade folio post inside tx), AP-T05-004 (CTA buttons on detail page), AP-T13-003 (multi-room continuity), AP-T13-004 (no-show action), AP-T13-005 (in-place dialog), AP-T13-006 (date-range RoomBlock).


Track 12 — Property Onboarding (typed schema sweep)​

Vision: every property field a hotelier sets is a typed column with a validator; pictures live in a relational PropertyImage table; geocoding runs automatically.

Architectural decisions:

  • PropertyImage model ({id, propertyId, url, variantUrls Json, caption?, sortOrder, isPrimary}); Property.images Json is migrated and dropped.
  • New Amenity model ({id, tenantId, code, label, category, icon}) per tenant; RoomType amenityIds[] references it; UI uses dropdown.
  • Geocoding runs on address change via @hmp/integrations/google-maps adapter; populates latitude/longitude/googleMapsUrl.
  • TaxConfiguration.inclusive Boolean @default(false) column.
  • CancellationPolicy.rules JSON validated against an embedded JSON-schema document at API boundary.
  • RatePlanRate seeded for at least the next 90 days for each rate plan in seeds.

P0 items: AP-T02-001 (lat/lng — Track 3), AP-T02-002 (S3 — Track 6), AP-T02-003 (image widget — Track 6).

P1 items: AP-T02-005 (inclusive Boolean), AP-T02-006 (cancellation rules schema). P2: AP-T02-007 (geocoding stub — promote earlier if hotelier UX needs it), AP-T02-008 (amenity master).


Track 13 — WebSocket RS256 + Resilience​

(Largely covered by Track 2 AP-CC-3 and Track 8 AP-T05-003. Items here are additional resilience polish.)

Architectural decisions:

  • Shared @hmp/realtime-kit package (Track 9) provides client-side reconnect-Infinity, token refresh on reconnect, structured error events.
  • Server-side WebSocket gateways verify tenantId and propertyId on every joinX event; failure disconnects.
  • Token-refresh propagation: when access-token rotates, the client re-auths over the same socket via a new refreshToken event before disconnect.

P0 items: AP-CC-3 (Track 2), AP-T05-003 (Track 8). P1 items: AP-T07-007 (kiosk reconnect Infinity).


Track 14 — Data Quality / QA Parity​

Vision: local Docker, AWS QA, AWS staging, AWS prod all converge on the same schema, the same seed contract, the same secret-injection mechanism, and the same INTERNAL_SERVICE_TOKEN automation.

Architectural decisions:

  • seed-master.sh is idempotent and self-verifying: every seed phase ends with a row-count assertion; failures abort.
  • deploy-qa.sh ends with a prisma migrate status check + a smoke test of the 13 happy-path flow tests.
  • INTERNAL_SERVICE_TOKEN minted by tokenctl on every deploy; ExternalSecrets propagates it; expiry monitor alerts 7 days before.
  • Local seed creates admin@keynovahotels.com AND admin@hmphotel.com (both work) to remove cred drift.
  • A nightly backfill-mv.ts k8s CronJob self-heals MV staleness.
  • A weekly prisma db pull against QA RDS produces a drift report committed to hmp-docs/docs/platform-registry/drift-snapshots/.

P0 items: AP-T04-001 / AP-T02-004 / AP-CC-2 (re-seed QA — Track 6). P1/P2 items: AP-T09-008 (sync admin creds), AP-T10-003 (QA empty-MV root cause), AP-T10-012 (nightly backfill cron).


Track 15 — Test Coverage & Quality Gates​

Vision: every business flow has a flow test; every UI has Playwright/Detox; every Kafka producer has a contract test; CI rejects PRs that drop coverage on touched files.

Architectural decisions:

  • flow-tests/ directory under hmp-infrastructure/testing/flow-tests/ is the single home for cross-service business-flow tests.
  • Per-service unit tests stay in <service>/test/; per-frontend Playwright stays in <frontend>/e2e/.
  • Mobile e2e (Detox / Maestro) lives under hmp-mobile-apps/apps/<app>/e2e/.
  • A new flow-test-coverage.yaml registry file lists every business flow and its test files; CI enforces that every flow has at least one entry.
  • Codecov per-PR delta cannot drop below -2%.

P0 items: none specific (covered transversely by every other track's test obligation). P1 items: AP-T11-005 (channel-manager flow test). P2 items: AP-T04-010 (reservation-browse), AP-T07-008 (KDS realtime e2e), AP-T13-010 (tape-chart drag/drop e2e), AP-T05-008 (mobile HK realtime Detox), AP-T09 flow-test bundle.


Part 4 — Sequenced Roll-out (refined from the audit's Section F)​

Wave 1 and Wave 2 close every P0. Wave 3 closes every P1. Wave 4 is post-launch.

Wave 1 — independent P0s (≈1 week wall-clock; up to 8 parallel agents)​

Each row below is one PR/branch. Items with no (blocks: …) are leaf-independent and can ship any time.

OrderAP-IDTrackOwner roleBlocks
W1.01AP-CC-17Platform EngAP-T11-001 may proceed in parallel; CI gate ensures no future drift
W1.02AP-T11-0017Platform EngAP-T11-002 (Wave 2)
W1.03AP-T07-0037Mobile Engnothing
W1.04AP-T13-0013Frontdesk Engnothing
W1.05AP-T03-0013Reservation EngAP-T03-002 (Wave 2)
W1.06AP-T05-00111Reservation EngAP-T05-002
W1.07AP-T05-00211Reservation Engnothing
W1.08AP-T09-00111Frontdesk Engnothing
W1.09AP-T07-0024POS EngAP-T07-001 (Wave 2)
W1.10AP-T08-0011Frontdesk EngAP-T08-006 (P1) follows
W1.11AP-T10-0014Analytics EngAP-T10-002
W1.12AP-T10-0025Analytics Engnothing
W1.13AP-T10-0045Mobile Engnothing
W1.14AP-T01-00110Auth EngAP-T01-004 (Wave 2)
W1.15AP-T01-00210Notification EngAP-T01-004 (Wave 2) — needs SendGrid (AP-CC-5 Wave 2)
W1.16AP-T01-00310Auth Engnothing
W1.17AP-T02-0013Property Engnothing
W1.18AP-T13-00411Dashboard Engnothing
W1.19AP-T05-0038Infra Engmobile WS reachability on QA
W1.20AP-CC-46Infra EngAP-T08-002 (Wave 2)
W1.21AP-T04-0028PM + Eng leadevery mobile-related Wave 2 item
W1.22AP-T04-001 / AP-CC-26Infra Engunblocks T04, T10, T11, T12 data

Wave 2 — dependent P0s (≈2 weeks wall-clock)​

OrderAP-IDTrackDepends on
W2.01AP-T01-00410AP-T01-001, AP-T01-002, AP-CC-5
W2.02AP-CC-5 (SendGrid)6AP-CC-4
W2.03AP-T02-0026nothing else; unblocks T02-003, T06-005
W2.04AP-T02-0036AP-T02-002
W2.05AP-T03-0023AP-T03-001
W2.06AP-T06-0021nothing else; needs migration discipline
W2.07AP-T06-003 / AP-T06-0048AP-T04-002 (decision)
W2.08AP-T07-0019AP-T07-002
W2.09AP-T08-0021AP-T08-001, AP-CC-4
W2.10AP-T08-0031nothing
W2.11AP-T08-0041nothing
W2.12AP-T08-0051, 4nothing
W2.13AP-T09-0024nothing
W2.14AP-T10-00314AP-CC-2 (re-seed)
W2.15AP-T10-0055AP-T10-003
W2.16AP-T11-00211AP-T11-001
W2.17AP-T11-00311AP-T11-002
W2.18AP-T12-0012AP-CC-2
W2.19AP-T12-0022AP-T12-001
W2.20AP-T13-0025AP-T13-001
W2.21AP-CC-32, 13nothing
W2.22AP-T01-0073, 10the OnboardingService rebuild
W2.23AP-T06-0011AP-T06-002
W2.24AP-T06-0091nothing
W2.25AP-T10-0075AP-T10-005
W2.26AP-T10-0085nothing

After Wave 2, every P0 in the audit's Section E is closed.

Wave 3 — P1 (≈3 weeks)​

The 41 P1 items in the audit's Section E. Group by track owner (most are Frontdesk + Reservation + Dashboard + Analytics).

Wave 4 — P2 (post-launch backlog)​

The 39 P2 items, all track-tagged.


Part 5 — Agent Dispatch Pattern​

Every fix runs as one agent (for small S-effort items) or one focused multi-step agent (for M/L items). The dispatch prompt MUST contain:

You are implementing AP-<id> per the gold-standard fix plan at
hmp-docs/docs/platform-registry/property-1-gold-standard-fix-plan-2026-04-28.md.

You MUST read Part 1 (gold-standard charter), Part 2 (mandatory deliverables),
and the relevant Track section before writing any code.

Hard rules:
- Fix the root cause, delete the workaround.
- No silent fallbacks. Production-mode missing config = boot failure.
- Schema-first: typed columns, not JSON blobs.
- One implementation, not two. Delete duplicates.
- Idempotency at every boundary.
- Tests before fix (TDD). Failing test commit, then fix commit.
- Cross-service via ServiceClient + receiver-side assertPropertyTenant.
- Update CLAUDE.md and platform-registry in the same PR.
- Migration: nullable → backfill → NOT NULL, three deploys.

Acceptance criteria for this AP-id: <copy from the per-item row>.
Files to touch: <copy from the per-item row>.
CLAUDE.md target: <copy>.
Registry target: <copy>.

Branch: audit/AP-<id>-<slug>
Commit format: audit(AP-<id>): <imperative summary>

Begin.

For multi-item bundles (e.g. all of Track 4's eventing P0s), use a parent agent that dispatches per-item children, with a track-level review at the end.


Part 6 — Ledger​

This section is the live status tracker. Every PR that closes an AP-id flips the row.

AP-IDTrackWaveStatusOwnerBranchMerge SHACLAUDE.md updated?Registry entry?
AP-T01-00110W1DONEagentmasterhmp-auth-serviceyesyes
AP-T01-00210W1DONEagentmaster77e32bb (notification-service)yesyes
AP-T01-00310W1DONEagentmasterhmp-auth-serviceyesyes
AP-T01-00410W2DONEagentmasterhmp-auth-serviceyesyes
AP-T01-00510W4DONEagentmasterhmp-auth-serviceyesyes
AP-T01-0073, 10W2DONEagentmasterhmp-auth-serviceyesyes
AP-T02-0013W1DONEagentmasterhmp-property-serviceyesyes
AP-T02-0026W2DONEagentmasterhmp-infrastructureyesyes
AP-T02-0036W2DONEagentmasterhmp-web-dashboardyesyes
AP-T02-004 / AP-CC-26, 14W1OPEN (deferred to Phase 2)
AP-T02-0053W4DONEagentmasterhmp-property-serviceyesyes
AP-T02-0063W4DONEagentmasterhmp-property-serviceyesyes
AP-T03-0013W1DONEagentmasterhmp-reservation-serviceyesyes
AP-T03-0023W2DONEagentmasterhmp-reservation-serviceyesyes
AP-T03-0035W1DONEagentmasterhmp-docsyesyes
AP-T03-0043W4DONEagentmasterhmp-reservation-service + hmp-web-dashboard + hmp-infrastructure (seeds)yesn/a (re-audit finding, not in original plan)
AP-T04-00114W1(covered by AP-CC-2)
AP-T04-0028W1DONEagentmasterhmp-docs (decision doc)yesyes
AP-T04-0034P1DONEagentmasterc7a8acb (reservation-service)yesyes
AP-T04-0044P1DONEagentmaster5142488 (web-dashboard)yesyes
AP-T05-00111W1DONEagentmasterhmp-reservation-serviceyesyes
AP-T05-00211W1DONEagentmasterhmp-reservation-serviceyesyes
AP-T05-0038W1DONEagentmasterhmp-infrastructureyesyes
AP-T05-00711W4DONEagentmasterhmp-reservation-serviceyesyes
AP-T06-0011W2DONEagentmasterhmp-shared-packagesyesyes
AP-T06-0021W2DONEagentmasterhmp-guest-serviceyesyes
AP-T06-0038, 9W2OPEN (decision-gated — mobile scope deferred Phase 2)
AP-T06-0048W2OPEN (decision-gated — mobile scope deferred Phase 2)
AP-T06-0091W2DONEagentmaster12 services + infrayesyes
AP-T07-0019W2DONEagentmasterhmp-pos-serviceyesyes
AP-T07-0024W1DONEagentmasterhmp-pos-serviceyesyes
AP-T07-0037, 8W1DONEagentmasterhmp-mobile-appsyesyes
AP-T07-0044W4DONEagentmasterhmp-pos-serviceyesyes
AP-T07-0064W4DONEagentmasterhmp-pos-serviceyesyes
AP-T08-0011W1DONEagentmasterhmp-frontdesk-serviceyesyes
AP-T08-0021, 6W2DONEagentmasterhmp-frontdesk-serviceyesyes
AP-T08-0031W2DONEagentmasterhmp-frontdesk-serviceyesyes
AP-T08-0041W2DONEagentmasterhmp-guest-serviceyesyes
AP-T08-0051, 4W2DONEagentmaster8 servicesyesyes
AP-T08-0061W1DONEagentmasterhmp-frontdesk-serviceyesyes
AP-T09-00111W1DONEagentmasterhmp-frontdesk-serviceyesyes
AP-T09-0024W2DONEagentmasterhmp-analytics-serviceyesyes
AP-T09-00411W4DONEagentmasterhmp-property-serviceyesyes
AP-T09-00511W4DONEagentmasterhmp-infrastructureyesyes
AP-T10-0014W1DONEagentmasterhmp-analytics-serviceyesyes
AP-T10-0025W1DONEagentmasterhmp-analytics-serviceyesyes
AP-T10-00314W2OPEN (deferred to Phase 2)
AP-T10-0045W1DONEagentmasterhmp-mobile-appsyesyes
AP-T10-0055W2DONEagentmasterhmp-analytics-serviceyesyes
AP-T10-0065W4DONEagentmasterhmp-analytics-serviceyesyes
AP-T10-0075W2DONEagentmasterhmp-analytics-serviceyesyes
AP-T10-0085W2DONEagentmasterhmp-analytics-service + admin-portalyesyes
AP-T10-0125W4DONEagentmasterhmp-analytics-serviceyesyes
AP-T11-0017W1DONEagentmasterhmp-api-gatewayyesyes
AP-T11-0026, 11W2DONEagentmasterhmp-channel-serviceyesyes
AP-T11-00311W2DONEagentmasterhmp-channel-serviceyesyes
AP-T11-00611W2DONEagentmasterhmp-channel-serviceyesyes
AP-T12-0012W2DONEagentmasterhmp-infrastructureyesyes
AP-T12-0022W2DONEagentmasterhmp-admin-portalyesyes
AP-T12-0032W4DONEagentmasterhmp-payment-serviceyesyes
AP-T12-0042W4DONEagentmasterhmp-payment-serviceyesyes
AP-T12-0052W4DONEagentmasterhmp-infrastructureyesyes
AP-T13-0013W1DONEagentmasterhmp-frontdesk-serviceyesyes
AP-T13-0025W2DONEagentmasterhmp-web-dashboardyesyes
AP-T13-00411W1DONEagentmasterhmp-web-dashboardyesyes
AP-CC-17W1DONEagentmasterhmp-api-gatewayyesyes
AP-CC-32, 13W2DONEagentmastermultiple servicesyesyes
AP-CC-46W1DONE (gap patched re-audit 2026-04-29)agent6681da7 (infra)hmp-infrastructureyesyes
AP-CC-51, 6, 10W2DONEagentmasterhmp-notification-serviceyesyes

Summary as of 2026-04-29 (re-audit): 47/47 P0 items DONE (3 deferred to Phase 2: AP-T02-004/AP-CC-2, AP-T06-003, AP-T06-004). 35/41 P1 items DONE. Audit verdict: CONDITIONAL GO — NIC IRP e-invoice credentials must be provisioned before production launch.

Re-audit 2026-04-29 code-verified spot-checks (8/8 pass; 1 gap found and closed):

  • AP-CC-4 gap: EINVOICE_MOCK: "true" was left in staging-aws.yml + prod-aws.yml. Patched commit 6681da7. CI grep gate now passes.
  • AP-T08-001 IRN PDF reads from folio.irn — VERIFIED (invoice-pdf.service.ts)
  • AP-T13-001 assertPropertyTenant in CalendarService.getCalendar — VERIFIED (line 104)
  • AP-T05-001 rate recalc on extend — VERIFIED (reservation-service lines 1429-1476)
  • AP-T07-002 emitKotCreated wired in generateKOT — VERIFIED (WebSocket + Kafka both emitted)
  • AP-T11-001 gateway @All decorators for channel/intermediary routes — VERIFIED (all 5 prefixes)
  • AP-T10-001 ADR/RevPAR written in ReservationConsumer.upsert — VERIFIED
  • AP-T01-001 tenantId is @IsOptional() on ResetPasswordRequestDto — VERIFIED

Re-audit 2026-04-29 (Sunita Joshi tax=0 follow-up): AP-T03-004 OTA tax double-charge + folio preview tax derivation

  • Reported: front-desk staff observed "Full Payment Received" on a 29 Apr 2026 OTA reservation (Sunita Joshi, MAKEMYTRIP) but the folio preview tax line showed ₹0 despite a non-zero taxAmount field on the reservation row.
  • Root causes (4 stacked bugs):
    1. ChannelBookingReceivedConsumer.handle() passed event.totalAmount (the gross OTA amount, GST-inclusive) as rateAmount, then reservations.service.create() added 12 / 18% GST on top — silent double-charge ⇒ artificial outstanding balance at front desk.
    2. No reconciliation between OTA-asserted total and system-computed total — drift went undetected.
    3. KeyNova reservation seed wrote totalAmount = base × nights (excluding tax) and paidAmount = totalAmount, which violated the API's INCLUSIVE convention (reservations.service.ts:1116-1118 writes totalAmount = base + tax).
    4. Dashboard check-in folio preview derived tax as totalAmount − rate × nights, which silently produced ₹0 for any reservation row using the seed (legacy) convention.
  • Fix:
    • RoomReservationDto.isTaxInclusive flag + reverse-derivation in reservations.service.create().
    • CreateReservationDto.expectedTotalAmount reconciliation guard with errorCode: 'OTA_TOTAL_MISMATCH' (±₹2 tolerance).
    • ChannelBookingReceivedConsumer sets both flags; OTA totals now flow through correctly.
    • Dashboard folio-preview.ts (extracted, unit-tested) reads reservation.taxAmount explicitly.
    • Seed seed-keynova-reservations.sql appended with idempotent normalisation block (per-night × nights vs taxAmount detection).
  • Tests: 5 new unit tests in reservations.service.spec.ts, 3 in channel-booking-received.consumer.spec.ts, 7 in folio-preview.test.ts.
  • Branch: audit/AP-T03-004-ota-tax-and-folio-preview (TBD).

(Remaining open P1 items: AP-T09-003 night audit PDF, AP-T13-003 tape chart multi-room continuity, AP-T13-005 in-place dialog, AP-T13-006 date-range block UI. P1 ledger continues in phase1-action-plan.md.)


Part 7 — Documentation Update Protocol​

When you close any AP-id, perform these doc updates in the same PR as the code change:

Per-service root CLAUDE.md​

  • Update the relevant table row (endpoints, schema, modules, gotchas, consumers).
  • Add a new numbered Gotcha if the fix uncovers a non-obvious behaviour.
  • Update the "Audit Ground Truth" date at the bottom.

Per-folder CLAUDE.md​

  • If a CLAUDE.md exists in the directly affected directory, update it to reflect the new state.

Platform registry (hmp-docs/docs/platform-registry/_data/)​

  • Use cases: _data/use-cases/uc-<flow>-<feature>.yaml — owner, status, tests, compliance.
  • Drift: _data/drift/<finding>.yaml — flip status: open → closed with merge SHA.
  • Gates: _data/gates/<gate>.yaml — for new CI gates.
  • Compliance: _data/compliance/<area>.yaml — for compliance items.
  • Decisions: _data/decisions/<decision>.yaml — for scope decisions like Phase 1 mobile.

Architecture docs (hmp-docs/docs/architecture/)​

  • New <pattern>.md if the fix establishes a reusable pattern (e.g. realtime-kit-package.md, idempotency-keys.md).
  • Update cross-service-communication.md if ServiceClient surface changes.

Operations runbooks (hmp-docs/docs/operations/)​

  • Required for: vendor onboarding (NIC IRP, ResAvenue, Razorpay, SendGrid), seed/deploy procedures, incident response.

CHANGELOG / release notes​

  • Add a one-line entry under the upcoming release section in the affected service's CHANGELOG.md.

Audit ledger (this document, Part 6)​

  • Flip the row to DONE with the merge SHA, owner, branch, "yes" for both doc columns.

End of Grand Plan.