Skip to main content

BF-009 — Housekeeping Lifecycle

Business flow (cross-EPIC user journey). Wave 2 of the RTM Scale-Out (BF-002…BF-013) following the BF-001 template. Reconciled 2026-05-25.

Narrates the daily heartbeat of an Indian SME hotel's housekeeping floor: a guest checks out → the room flips DIRTY → the next available cleaner gets a push on their phone → photo evidence on completion → supervisor signs off → the room becomes a bed for the next arrival in time for the 14:00 standard Indian check-in window.

Each step names (a) the user story that owns the requirement, (b) the primary code path that runs it, (c) the closest automated test that exercises it, and (d) pre/post-conditions and notes. Where the spec contains no matching catalog story, the step is tagged [SME-PROPOSED] as a NEW story for the SME's intake.

Single-EPIC scope: EP-006 Housekeeping Management (with documented read-only seams into EP-002 Property — room ownership, room status — and EP-005A Front Desk — checkout event, folio post on minibar; see "Cross-EPIC seams" at the bottom for the explicit boundaries).

Personas​

PersonaRoleSource
Front Desk AgentPosts checkout, releases the room to housekeepingEP-005A; emits RESERVATION_CHECKED_OUT
Housekeeping SupervisorTriages the dirty-rooms queue, assigns cleaners, runs inspectionEP-006 (US-006-001 board, US-006-007 inspection)
Room Attendant ("Cleaner")Mobile-app user; receives tasks, uploads before/after photos, marks doneEP-006 (US-006-004 mobile queue, US-006-005 task status, US-006-006 photos)
Maintenance TechnicianEngineering staff that takes over a MaintenanceRequest when the cleaner finds a faultEP-006 (US-006-008) + EP-017 (asset-service Work Orders)
Lost & Found CustodianLogs items left behind, runs the 90-day disposal clockEP-006 (US-006-012)
Front Office ManagerReads the discrepancy report and shift KPIs at handoverEP-006 (US-006-014, US-006-015)

Entry points​

EntryURL / interfaceBacked by
Front-desk checkout buttonhttps://dashboard.<env>.keynova.ai/front-desk/checkouthmp-frontdesk-service (cross-EPIC trigger — fires RESERVATION_CHECKED_OUT consumed in Step 1)
HK supervisor boardhttps://dashboard.<env>.keynova.ai/housekeeping (Rooms tab + Tasks tab)hmp-housekeeping-service rooms/tasks controllers + WebSocket /housekeeping namespace
Cleaner mobile appExpo / React Native install on Android — primary device in Indian SME hotels (cleaners share a property phone, not personal)hmp-mobile-apps/apps/housekeeping/ + WebSocket /ws/housekeeping/socket.io/
Maintenance requestdashboard /housekeeping/maintenance OR cleaner mobile maintenance.tsxhmp-housekeeping-service/src/maintenance/maintenance.controller.ts
Lost & Found registerdashboard /housekeeping/lost-foundhmp-housekeeping-service/src/lost-found/lost-found.controller.ts

The 10-step journey​

Step 1: Front desk posts checkout → room flips OCCUPIED → DIRTY → CHECKOUT_CLEANING task auto-created​

  • Story: US-006-016 — Auto-Task Generation on Checkout (catalog "Not Started", EPIC body claims same — but catalog is stale, the consumer is shipped; see EP-006 RTM D-006 numbering drift).
  • Code: hmp-frontdesk-service/src/checkout/checkout.service.ts publishes RESERVATION_CHECKED_OUT to Kafka on a successful checkout. Consumer in hmp-housekeeping-service/src/events/events.service.ts:163 subscribes, calls TasksService.create({ taskType: 'CHECKOUT_CLEANING', priority: 'NORMAL', roomId, propertyId, notes: 'Auto-created on checkout' }). Dedup (W1.B.9 / AP-T05-001) via housekeepingTask.findFirst({ where: { roomId, reservationId, taskType: 'CHECKOUT_CLEANING', createdAt: { gte: startOfDay } } }) — same checkout never spawns a second task. As a side-effect of TasksService.create(), Room.housekeepingStatus is set to DIRTY.
  • Test: hmp-housekeeping-service/src/events/events.service.spec.ts :: 26 specs incl. 3 W1.B.9 dedup tests covering "dedup within same UTC day" + "re-checkout same room next day creates fresh task" + "two CHECKOUT_CLEANING events for different reservations both create" — PASS. Flow: hmp-infrastructure/testing/flow-tests/src/flows/housekeeping-lifecycle.flow.spec.ts — boots the fixture chain and exercises a DAILY_CLEANING create variant (CHECKOUT_CLEANING auto-path is covered indirectly via flow-5-fd-hk-updates.spec.ts 11/12 PASS).
  • Pre-conditions: Reservation in CHECKED_IN state. Front desk POSTs /api/checkout (frontdesk-service). The destination room's housekeepingStatus is INSPECTED or CLEAN (occupied side).
  • Post-conditions: HousekeepingTask row with status=PENDING, taskType=CHECKOUT_CLEANING, priority=NORMAL, propertyId, roomId, reservationId populated. Room.housekeepingStatus=DIRTY. HOUSEKEEPING_TASK_CREATED Kafka event emitted. WebSocket task:created fanned out to all /housekeeping subscribers on the property.
  • Notes: Dedup uses same UTC day boundary — across the IST midnight handover (00:00 IST = 18:30 UTC) the dedup window resets ~5.5 h before the local day ends. Indian shift practice (06:00–14:00 / 14:00–22:00 / 22:00–06:00) lives entirely on one side of this boundary so it's typically fine, but late-night double-checkouts of the same room could in theory de-dup unexpectedly if they straddle the UTC date line. [SME-PROPOSED] Add AC: "Dedup window SHOULD use property timezone from Property.timezone and the property's business date, NOT UTC. Indian properties: Asia/Kolkata." See D-009 below.

Step 2: HK queue picks up the DIRTY room​

  • Story: US-006-001 — Real-Time Room Status Board.
  • Code: hmp-housekeeping-service/src/rooms/rooms.controller.ts:18-44 — @Get('/rooms/status') returns the board grouped by floor + status, scoped to tenantId via getRequestContext(). rooms.service.ts::getRoomStatusBoard() joins the local Room read-model (mirror from property-service) with the housekeepingStatus column and active task counts. Dashboard: hmp-web-dashboard/src/app/(dashboard)/housekeeping/page.tsx. Real-time push: hmp-housekeeping-service/src/gateway/housekeeping.gateway.ts emits task:created, task:updated, task:assigned, task:inspected, room:status-changed on the /housekeeping namespace.
  • Test: rooms.service.spec.ts :: 13 specs (board grouping, status update + Kafka, inspect-success/fail, discrepancy classification) — PASS. housekeeping.gateway.spec.ts :: 24 WebSocket emission tests — PASS. E2E: hmp-web-dashboard/e2e/tests/housekeeping-board.spec.ts.
  • Pre-conditions: Step 1 completed; supervisor's dashboard tab is open OR their /housekeeping WebSocket is connected.
  • Post-conditions: The DIRTY room appears in the "Dirty Rooms" column within < 2 s of the checkout (qa-PMS-7 routing fix gated on the WebSocket emit, not the HTTP poll). Supervisor sees badge counts update on the floor card.
  • Notes: 7-status board UI joins HK-side enum (4 values: CLEAN/DIRTY/INSPECTED/PICKUP) with property-side RoomStatus/frontOfficeStatus (3 more: OUT_OF_ORDER/OUT_OF_SERVICE/DO_NOT_DISTURB) — see EP-006 D-002. Two enums, two services, one composite view.

Step 3: Supervisor assigns the task to a cleaner​

  • Story: US-006-003 — Assign Task to Staff.
  • Code: hmp-housekeeping-service/src/tasks/tasks.controller.ts:144-158 — @Patch(':id/assign') (role-gated SUPER_ADMIN/ADMIN/MANAGER/HOUSEKEEPING; @Audit; x-property-id from JWT). tasks.service.ts::assign() validates assignee, auto-transitions PENDING → IN_PROGRESS (per the exported TASK_STATUS_TRANSITIONS machine), publishes HOUSEKEEPING_TASK_ASSIGNED, emits WebSocket task:assigned. Workload guard — SafeguardService.HK_STAFF_OVERLOADED enforced in assign(): blocks if assignee has >= maxTasksPerStaff active tasks (per src/tasks/CLAUDE.md and staff.service.ts).
  • Test: tasks.service.spec.ts :: 15 specs incl. assignment + state transition + safeguard cases — PASS. staff.service.spec.ts :: 10 specs — PASS. Flow: housekeeping-lifecycle.flow.spec.ts :: "assigns the task to a housekeeper (auto-transitions PENDING → IN_PROGRESS)" — exercises the auto-transition + read-back. E2E: hmp-web-dashboard/e2e/tests/housekeeping-assign.spec.ts.
  • Pre-conditions: Step 2 completed; task is PENDING; supervisor knows which cleaner is on shift on that floor (Indian shift practice: 1 cleaner per 14 rooms is the standard ratio).
  • Post-conditions: HousekeepingTask.status=IN_PROGRESS, assignedTo populated, assignedAt timestamped. HOUSEKEEPING_TASK_ASSIGNED Kafka event. WebSocket task:assigned push to the cleaner's app and the supervisor's board.
  • Notes: Auto-assign endpoint POST /tasks/auto-assign is NOT shipped (EP-006 D-005). Supervisors do this manually today, one tap per task. For a 60-room property at 11:00 IST checkout-peak that's 60 taps. [SME-PROPOSED] see D-005 in EP-006 RTM for round-robin + nearest-floor proposal that requires a new StaffShift clock-in table.

Step 4: Mobile app push the assignment to the cleaner's phone​

  • Story: US-006-004 — Mobile Task Queue.
  • Code: hmp-mobile-apps/apps/housekeeping/app/(tabs)/my-tasks.tsx renders the assigned-to-me queue, sorted by priority + scheduledFor. Realtime: hmp-mobile-apps/packages/shared-realtime/src/notifications-store.ts + useNotifications.ts listens on wss://<host>/ws/housekeeping/socket.io/ per AP-T05-003 Nginx pattern; auth via auth.token from store on every reconnect. Foreground FCM suppression: fcm-foreground-suppressor.ts (so the user doesn't get a duplicate toast + push). The WebSocket emit is the in-app notification; the Kafka event HOUSEKEEPING_TASK_ASSIGNED is consumed by hmp-notification-service which fires the out-of-band FCM for backgrounded apps.
  • Test: notifications-store.test.ts + useNotifications.test.ts + deep-link-handler.test.ts (shared-realtime package) — PASS. No mobile E2E — no Detox / Maestro test of the swipe-start / mark-complete UI loop (EP-006 D-010 SME-PROPOSED Detox coverage).
  • Pre-conditions: Step 3 completed; cleaner's phone is logged in (RN auth store has a valid JWT); WebSocket is connected OR FCM token is registered.
  • Post-conditions: Cleaner sees the task at the top of their queue with a vibration / toast / push notification. Tap-through navigates to the per-room detail screen (app/room/[id].tsx).
  • Notes: Indian SME hotel reality — the housekeeping phone is a single shared Android device (typically a low-end model, 2–4 GB RAM, Android 9–12) handed off at every shift change. Personal mobile-data plans are not provided. The hotel WiFi covers public + corridors but basement service areas + back-of-house staircases are dead zones in most Tier-2 / Tier-3 properties. The offline-sync surface (Step 7) carries the weight of this. [SME-PROPOSED] Add AC: "On every shift change the supervisor SHALL force-logout the previous shift's account on the shared phone via a one-tap Sign out of shift button; the next shift signs in fresh. This prevents the next-shift cleaner from seeing the previous shift's task history (audit + privacy)."

Step 5: Cleaner accepts and starts the task (status → IN_PROGRESS via auto-assign, otherwise via explicit start)​

  • Story: US-006-005 — Update Task Status (In-Progress / Complete).
  • Code: Auto-transitioned to IN_PROGRESS by assign() in Step 3 (no separate user action needed). An explicit start path also exists: hmp-housekeeping-service/src/tasks/tasks.controller.ts:160-174 — @Patch(':id/status') body { status: 'IN_PROGRESS' } validated by UpdateTaskStatusDto via @IsIn. State machine TASK_STATUS_TRANSITIONS exported at tasks.service.ts:39-45 enforces PENDING→IN_PROGRESS (and rejects IN_PROGRESS→IN_PROGRESS — see the flow-spec comment about the guard).
  • Test: tasks.service.spec.ts :: 15 specs incl. state-machine transitions — PASS. Flow: housekeeping-lifecycle.flow.spec.ts :: "transitions task to COMPLETED" via client.patch('/api/tasks/:id/status', { status: 'COMPLETED' }) — passes after the IN_PROGRESS auto-transition from Step 3 lands.
  • Pre-conditions: Step 4 completed; task is in IN_PROGRESS (auto-set by assign) OR was PENDING and the cleaner POSTs the explicit start.
  • Post-conditions: HousekeepingTask.status=IN_PROGRESS, startedAt timestamped (if not already from assign). HOUSEKEEPING_TASK_UPDATED Kafka event. WebSocket task:updated push.
  • Notes: The redundant explicit-start path is held for properties that auto-assign tasks at shift-start (US-006-003 AC-2, not shipped — see D-005) and need the cleaner to consciously "claim" the task. With manual assign (today) the auto-transition is the canonical path.

Step 6: Cleaning + photo capture (before / after)​

  • Story: US-006-006 — Photo Upload for Task Completion (catalog: Partial; verified: Partial — presigned URL endpoint shipped, BEFORE/AFTER discriminator + sharp compression NOT).
  • Code: Mobile camera capture → POST /api/housekeeping/photos/presigned-url → S3 PUT → POST /api/housekeeping/photos records the HousekeepingPhoto row. hmp-housekeeping-service/src/photos/photos.controller.ts exposes presigned-URL + listing. photos.service.ts generates the URL via the shared StorageService. HK_INSPECTION_PHOTO_REQUIRED safeguard — when requireInspectionPhoto is true and no photos attached, COMPLETED is blocked (tasks.service.ts).
  • Test: photos.service.spec.ts :: 9 specs — PASS.
  • Pre-conditions: Step 5 completed; cleaner is in the room; the per-property requireInspectionPhoto flag is set per regulatory + brand-standard policy.
  • Post-conditions: HousekeepingPhoto rows linked to the task via referenceId; photoUrl is an S3 URL (not base64-in-DB per the pre-AP-T16-001 anti-pattern).
  • Notes: No photoType: BEFORE | AFTER enum on HousekeepingPhoto (EP-006 D-010). The mobile UI today shows an unordered photo grid — supervisor cannot reliably split BEFORE vs AFTER for the comparison view the EPIC body AC asks for. [SME-PROPOSED] see D-010 SME refinements: add photoType, enforce S3 content-length-range ≤ 1 MB, mobile-side Sharp / expo-image-manipulator 1080px / 70% JPEG pre-compression. 2G/3G basement fallback: upload SHALL be background-queued with WiFi-only resume; task completion SHALL NOT be blocked on photo upload (status flips to COMPLETED immediately; HousekeepingPhoto.uploadStatus tracks the upload separately). Indian SME reality: every basement service corridor in Tier-2/3 properties is a Faraday cage for 4G — the cleaner has to walk back to the lobby to get signal, and the task list has to keep working through that.

Step 7: Cleaner marks the task COMPLETED​

  • Story: US-006-005 (continued) — Update Task Status.
  • Code: PATCH /api/tasks/:id/status body { status: 'COMPLETED' }. tasks.service.ts::updateStatus() runs the state-machine check (IN_PROGRESS → COMPLETED allowed; COMPLETED → COMPLETED rejected), validates the photo safeguard, sets completedAt, computes cleanTimeMinutes = Math.round((completedAt - startedAt) / 60000), publishes HOUSEKEEPING_TASK_COMPLETED carrying assignedTo + cleanTimeMinutes + completedAt + targetHousekeepingStatus (the FU-55 producer contract, locked by a regression test in tasks.service.spec.ts because mv_hk_staff_productivity in analytics-service silently drops the row if assignedTo is absent). The completed task does not auto-flip Room.housekeepingStatus; the room stays DIRTY until supervisor inspection (Step 9).
  • Test: tasks.service.spec.ts :: 15 specs incl. FU-55 producer-contract regression "publishes assignedTo + cleanTimeMinutes + completedAt" — PASS. Flow: housekeeping-lifecycle.flow.spec.ts :: "transitions task to COMPLETED" + "reads back the COMPLETED task by id" with polling — PASS in the harness.
  • Pre-conditions: Step 5+6 completed; task IN_PROGRESS; photos uploaded if requireInspectionPhoto.
  • Post-conditions: HousekeepingTask.status=COMPLETED, completedAt set, cleanTimeMinutes computed. HOUSEKEEPING_TASK_COMPLETED Kafka event (consumed by analytics-service for mv_hk_staff_productivity). WebSocket task:updated push. Room status unchanged (still DIRTY) — awaits inspection.
  • Notes: Indian SME hotel productivity benchmark: 25–30 min per checkout-clean for a standard double room. cleanTimeMinutes over 45 min is a soft flag for the supervisor's KPI board (US-006-015).

Step 8: Supervisor inspection queue​

  • Story: US-006-007 — Supervisor Inspection with Checklist Scoring (catalog: Partial; verified: Partial — only binary pass / fail today, no structured checklist).
  • Code: hmp-housekeeping-service/src/tasks/inspection.service.spec.ts :: 6 specs reference inspection.service.ts (CITED-BUT-NOT-FOUND in this pass; CLAUDE.md doesn't enumerate). Dashboard: hmp-web-dashboard/src/app/(dashboard)/housekeeping/ inspection tab — supervisor sees a queue of COMPLETED tasks awaiting inspection on their assigned floors. The board uses the same task:updated WebSocket signal Step 7 emitted.
  • Test: inspection.service.spec.ts :: 6 specs — PASS.
  • Pre-conditions: Step 7 completed; supervisor on shift; task COMPLETED.
  • Post-conditions: Task is visible in the supervisor's "Awaiting inspection" queue. No state change yet.
  • Notes: Indian SME hotel reality — supervisor inspects 15–25 rooms per shift, ~3–5 min per inspection. Without a structured checklist (D-011), inspection is "supervisor walks in, eyeballs the room, taps Pass" — there's no audit trail of WHICH items were checked. This is fine for owner-managed Tier-3 properties; it's a gap for brand-standard Tier-1 hotels claiming HACCP / DLF / IHCL audits.

Step 9: Supervisor passes (or fails) the inspection​

  • Story: US-006-007 (continued).
  • Code: hmp-housekeeping-service/src/rooms/rooms.controller.ts — @Post('/rooms/:id/inspection') (binary pass; rooms.service.ts::inspectRoom() finds the latest COMPLETED task for the room, marks inspectedBy/inspectedAt, publishes ROOM_INSPECTED, and flips Room.housekeepingStatus to INSPECTED on pass). @Post('/rooms/:id/request-inspection') queues an INSPECTION task type if the supervisor wants a structured re-inspection record. The EPIC-body fail-path ("Failed inspection triggers re-clean task with specific failed items") is NOT shipped — auto re-clean on fail does not happen today (EP-006 D-011).
  • Test: rooms.service.spec.ts :: inspectRoom specs within the 13-spec suite — PASS. Flow: flow-5-fd-hk-updates.spec.ts covers the happy-path pass (11/12 PASS).
  • Pre-conditions: Step 8 completed; supervisor reviews the room (in person or via the BEFORE/AFTER photos, gated on D-010 landing).
  • Post-conditions: On pass: Room.housekeepingStatus=INSPECTED, inspectedBy/inspectedAt set; ROOM_INSPECTED Kafka event. On fail (manual today): supervisor manually creates a follow-up CLEANING task — no atomic state-revert + re-task flow.
  • Notes: [SME-PROPOSED] see EP-006 D-011 for the structured InspectionTemplate + per-item scoring proposal — 4 / 5 / 6-section templates by roomType (Standard / Deluxe / Suite) with weighted-score pass thresholds (default 85%). Failed inspection SHALL atomically (a) mark current task INSPECTED-FAIL, (b) auto-create a new CLEANING task with priority=HIGH and notes auto-populated with failed item labels, (c) revert housekeepingStatus to DIRTY, (d) publish HOUSEKEEPING_INSPECTION_FAILED carrying the original housekeeper UUID for analytics.

Step 10: Room flips to VACANT_CLEAN → bed for next arrival​

  • Story: US-006-001 (room status board) + cross-EPIC into EP-005A (front office status). Also covered indirectly by US-006-014 (Discrepancy Report, FD vs HK).
  • Code: After Room.housekeepingStatus=INSPECTED (Step 9), the front-office side of the room status (frontOfficeStatus on the property-service Room table) is what determines "saleable" for the next reservation. If the reservation was already checked out (Step 1 emitted RESERVATION_CHECKED_OUT which the property-service Room mirror consumed to set frontOfficeStatus=VACANT), then the composite status (VACANT, INSPECTED) is what the front-desk arrivals tab shows as "Ready for arrival". The board UI joins both enums and shows the room with the green "READY" tag. The next arrival's assignRoom step (in EP-005A / EP-003) is now unblocked.
  • Test: rooms.service.spec.ts discrepancy classification specs cover the (FRONT_OFFICE=VACANT, HK=INSPECTED) happy state. housekeeping-lifecycle.flow.spec.ts ends at the COMPLETED read-back — the explicit room status flip in the optional final step is documented in the flow's preamble.
  • Pre-conditions: Step 9 completed (housekeepingStatus=INSPECTED); Step 1's checkout has propagated frontOfficeStatus=VACANT to the property-service Room mirror.
  • Post-conditions: Room is sellable. Next reservation's assignRoom can pick it. Discrepancy report (US-006-014) shows no discrepancy.
  • Notes: Indian SME hotel target SLA — for a 14:00 IST standard arrival window the room MUST be (VACANT, INSPECTED) by 13:30 IST at the latest. The 30-min buffer accounts for room-walk + key-card encode at the front desk. If checkout was at 11:00 IST (standard checkout time), the housekeeping window is 2.5 hours for the full Step 1 → Step 10 loop. The double-cycle expectation (06:00 IST stayover clean + 17:00 IST turndown — [SME-PROPOSED] US-006-NEW-A in the EP-006 RTM) is layered on top of this checkout cycle.

Auxiliary paths (run in parallel with steps 1-10)​

Step 11: Discrepancy path — FD says VACANT, HK says DIRTY (or vice versa)​

  • Story: US-006-014 — Discrepancy Report (FD vs HK). Catalog: Partial; verified: Partial.
  • Code: hmp-housekeeping-service/src/rooms/rooms.controller.ts — @Get('/rooms/discrepancy') calls rooms.service.ts::getDiscrepancies() which queries the local Room read-model for OR: [{ frontOfficeStatus: 'OCCUPIED', housekeepingStatus: 'CLEAN' }, { frontOfficeStatus: 'VACANT', housekeepingStatus: 'DIRTY' }] and classifies as POSSIBLE_SKIP (occupied + clean — guest may have left without checking out) or NEEDS_ATTENTION (vacant + dirty — checked out but cleaning slipped). reporting-feeds-hk.controller.ts exposes the analytics live-proxy at GET /reporting-feeds/housekeeping/discrepancies.
  • Test: rooms.service.spec.ts discrepancy classification specs (within 13-spec suite) — PASS. reporting-feeds-hk.controller.spec.ts :: 16 specs — PASS.
  • Pre-conditions: A room appears in the OR-classified set (Step 1 checkout flipped FO to VACANT but the auto-task hasn't fired yet — race condition; OR housekeeping marked CLEAN before front office processed the late checkout).
  • Post-conditions: Room is on the discrepancy list. No resolution endpoint today — supervisor manually reconciles by issuing the corrective PATCH (either /api/rooms/:id/status to HK side or /api/properties/.../rooms/:id/status to property-service for FO side).
  • Notes: EP-006 D-007 — the report is read-only; no POST /rooms/:id/discrepancy/resolve endpoint, no DiscrepancyResolution audit log, no duration tracking, no daily summary email at night-audit time. [SME-PROPOSED] see EP-006 RTM D-007 SME refinements for the full resolution-with-logging surface.

Step 12: Lost & Found path — cleaner finds an item​

  • Story: US-006-012 — Lost & Found Register. Catalog: Built; verified: Built-Untested (16 service specs but no flow / smoke / E2E despite lost-and-found.flow.spec.ts file existing — exists with TBD pass count).
  • Code: Cleaner taps "Log lost & found" on the mobile per-room screen → POST /api/housekeeping/lost-found body { propertyId, itemDescription, foundLocation, foundDate, foundBy, category, roomNumber, notes, estimatedValue }. hmp-housekeeping-service/src/lost-found/lost-found.controller.ts + lost-found.service.ts with full CRUD + filters. Claim path: PATCH /:id/claim body { claimedBy, verificationRef, contactPhone, notes } flips status to CLAIMED. Disposal: PATCH /:id/dispose. Reporting-feed: /reporting-feeds/housekeeping/lost-and-found for analytics-service. Note: a legacy InventoryController also mounts at /api/lost-found per CLAUDE.md gotcha 1 — two paths, dashboard should target the newer /api/housekeeping/lost-found.
  • Test: lost-found.service.spec.ts :: 16 specs — PASS. Flow: lost-and-found.flow.spec.ts :: probes both routes, exercises Scenario 1 (log) / Scenario 2 (list) / Scenario 3 (claim).
  • Pre-conditions: Cleaner is in a checked-out room (Step 1-10 path) and finds an item the guest left behind.
  • Post-conditions: LostAndFound row with status=FOUND (or PENDING), categorized (PERSONAL / CLOTHING / ELECTRONICS / JEWELRY / DOCUMENT / OTHER), 90-day disposal clock starts ticking.
  • Notes: EP-006 D-009 — roomId is NOT validated against the requesting tenant in lost-found.create(). Forged roomId in the POST body would auto-link the item to the wrong tenant's last-checked-out guest, leaking tenant-B's guest into a tenant-A lost-found list. [SME-PROPOSED] see D-009 SME refinements. Auto-link to last guest (EPIC AC-1) requires a guest-service cross-service read; not verified in source. High-value approval (JEWELRY/ELECTRONICS Manager approval before logging) is not implemented — no role-gated branch. 90-day auto-disposal cron is NOT shipped — manual review only. Indian regulatory context: items with monetary value over ₹10,000 SHOULD be entered in the hotel's Goods Register and (if unclaimed beyond 90 days) reported to the local police station per the Hotel Industry Best Practices guidelines.

Step 13: Maintenance request blocks cleaning (cleaner finds a fault mid-clean)​

  • Story: US-006-008 — Maintenance Request Creation. Catalog: Built; verified: Built.
  • Code: Cleaner taps "Report maintenance" on the per-room mobile screen → POST /api/maintenance body { propertyId, location, category: 'PLUMBING'|'ELECTRICAL'|'HVAC'|'CARPENTRY'|'PAINTING'|'GAS'|'OTHER', description, priority: 'LOW'|'NORMAL'|'HIGH'|'URGENT', reportedBy }. hmp-housekeeping-service/src/maintenance/maintenance.controller.ts + maintenance.service.ts runs the full lifecycle (OPEN/IN_PROGRESS/WAITING_PARTS/COMPLETED/CANCELLED) + Kafka events. Escalation to engineering Work Order: hmp-housekeeping-service/src/tasks/escalation.service.ts — POST /tasks/:id/escalate creates a Work Order in asset-service (port 3016) and atomically writes back HousekeepingTask.workOrderId (idempotent; 400 if already linked).
  • Test: maintenance.service.spec.ts :: 6 specs — PASS. escalation.service.spec.ts :: 6 specs (double-escalation safety, atomic WO write-back) — PASS. Flow: maintenance-requests.flow.spec.ts :: 4 scenarios (create / list / patch IN_PROGRESS / resolve COMPLETED).
  • Pre-conditions: Cleaner is in a room (Step 5-7) and finds a leaking faucet / broken AC / blown fuse.
  • Post-conditions: MaintenanceRequest row with status=OPEN. If URGENT + (ELECTRICAL|GAS|PLUMBING), [SME-PROPOSED] EP-006 D-008 wants atomic frontOfficeStatus=OUT_OF_ORDER flip — today this requires a SEPARATE second write by the dashboard, and if the supervisor forgets, the room can be sold with live exposed wiring. MAINTENANCE_REQUEST_CREATED Kafka event published.
  • Notes: Two paths to engineering: (a) free-standing maintenance via POST /maintenance (this step); (b) escalate an in-progress cleaning task via POST /tasks/:id/escalate. Path (b) is the typical "I started cleaning, found a problem, now Engineering owns the room" flow. Engineering ownership is in hmp-asset-service port 3016 (cross-EPIC into EP-017) — HK just holds the workOrderId foreign key. Double-escalation guarded (BadRequestException if workOrderId already set) but race condition (two near-simultaneous clicks) is acknowledged as v1 limitation (slated for Phase 5 unique-constraint hardening on asset-service).

Failure modes worth testing​

#ScenarioExpected behaviourCoverage todayOwning story / drift
F1Cleaner phone goes offline mid-clean (basement Faraday cage)Mutations queue locally via sync.service.ts batch endpoint; on reconnect, last-write-wins merge via mutationId idempotencysync.service.spec.ts (10 specs) — PASSUS-006-004
F2Photo upload fails (2G/3G drop, S3 5xx)Task SHALL still complete; HousekeepingPhoto.uploadStatus=PENDING_RETRY; background queue retries on WiFi[NOT IMPLEMENTED] — today the upload is blocking; failure surfaces as error toastUS-006-006 / EP-006 D-010 [SME-PROPOSED]
F3Supervisor backlog — 30+ COMPLETED tasks awaiting inspection at shift endTasks remain in queue; handed over to next shift's supervisor via the (proposed) shift-handover report[NOT IMPLEMENTED] — no shift-handover surface todayEP-006 D-004 [SME-PROPOSED] US-006-NEW-SHIFT-HANDOVER
F4Inspection skipped (supervisor never opens the task → room stuck COMPLETED but never INSPECTED)Night-audit job at 02:00 IST (night-audit-hk-reset.consumer.ts) flags rooms with COMPLETED task >12h old; emails Front Office Managernight-audit-hk-reset.consumer.spec.ts (16 specs) — PASSUS-006-007
F5Discrepancy unresolved at end-of-daySurfaces on the /rooms/discrepancy list + analytics live-proxy; daily summary email at night-audit timeRead-only today — no resolution endpoint, no auto-email; surfaces in report onlyUS-006-014 / EP-006 D-007 [SME-PROPOSED]
F6Maintenance request blocks cleaning — cleaner can't complete because the AC is broken and the room is not "clean to brand standard"Cleaner POSTs /tasks/:id/escalate → creates Work Order in asset-service → HK task auto-cancels (or stays IN_PROGRESS pending WO completion)escalation.service.spec.ts (6 specs covers atomic WO link); the auto-cancel of the HK task is NOT shipped — task stays IN_PROGRESS forever until WO closesUS-006-008
F7Lost item without owner — guest checked out, item logged, but no claim within 90 daysAuto-disposal cron flags + emails Front Office Manager; item moved to status=DISPOSED after manager approval[NOT IMPLEMENTED] — no cron, manual review onlyUS-006-012 / EP-006 D-009 [SME-PROPOSED]
F8Cross-tenant forgery — attacker POSTs /tasks with a roomId from another tenant403 ROOM_NOT_OWNED_BY_TENANT from assertRoomOwnership()task-cross-tenant-denial.spec.ts (12 specs) — ORPHANED for create-path #3 per EP-006 D-009 / D-006; create-time tenant validation gap is openEP-006 D-009 [SME-PROPOSED] + Task #11 security gap

Cross-EPIC seams (this BF is single-EPIC, but reads + writes cross 2 service boundaries)​

SeamDirectionSurfaceRisk
EP-005A → EP-006frontdesk-service emits RESERVATION_CHECKED_OUT, housekeeping-service consumesStep 1 auto-taskDedup window is UTC-based, not property-timezone (D-009 below)
EP-006 → EP-002 (property-service)housekeeping reads local Room mirror for frontOfficeStatus + room ownershipStep 2 board, Step 11 discrepancyRead-model freshness bounded by property-service Kafka sync cadence; under sync lag, supervisor sees stale FO state
EP-006 → EP-017 (asset-service)HK escalates a task to a Work OrderStep 13 maintenance escalationEngineering owns the WO; HK only carries the workOrderId FK; double-escalation race acknowledged
EP-006 → EP-005B (frontdesk folio)Minibar consumption (US-006-011) posts a charge to the folioNOT in this BF flow but adjacentIndia GST slab math happens at folio-post time, not minibar-consume time
EP-006 → EP-012 (analytics-service)HOUSEKEEPING_TASK_COMPLETED → mv_hk_staff_productivityStep 7 KPIFU-55 contract — assignedTo MUST be on the event or the MV silently drops the row

Indian SME hotel reality applied throughout​

  • 3-shift handover: morning 06:00-14:00 / evening 14:00-22:00 / night 22:00-06:00. HousekeepingTask.scheduledFor is a free DateTime today with no shift discriminator (EP-006 D-004); supervisors can't filter the queue by "tasks left over from previous shift" or "tasks I created for next shift". [SME-PROPOSED] US-006-NEW-SHIFT-HANDOVER + a shift enum on the task.
  • Double-cycle 06:00 + 17:00 IST: 06:00 daily-clean for stayovers + 17:00 turndown service (chocolates / robes / lights). Today both are manual or bulk operations at shift-change. [SME-PROPOSED] US-006-NEW-A (Turndown) + US-006-NEW-C (Stayover deep clean) in EP-006 RTM.
  • 7-stayover deep-clean cadence: per-property deepCleanConfig.stayoverInterval=7. Cron at 06:00 IST per property timezone creates a DEEP_CLEANING task for each occupied room whose night-count is a multiple of 7. Long-stay (≥7 nights) checkouts SHALL upgrade auto-CHECKOUT_CLEANING to DEEP_CLEANING. [SME-PROPOSED] EP-006 US-006-NEW-C.
  • 2G/3G fallback in basements: photo upload SHALL background-queue; task completion SHALL NOT block. [SME-PROPOSED] EP-006 D-010.
  • Shared housekeeping phone: one shared Android handed off at shift change. [SME-PROPOSED] Step 4 — one-tap "Sign out of shift" + audit log.
  • ₹10,000+ Goods Register: Lost-and-found items above this threshold SHALL be logged in the hotel's Goods Register; unclaimed beyond 90 days SHOULD be reported to local police. [SME-PROPOSED] US-006-012 surface.
  • 14:00 IST standard check-in / 11:00 IST standard check-out: 3-hour housekeeping window per room turn. Step 10 SLA target is (VACANT, INSPECTED) by 13:30 IST.

Drift summary​

D-NNN refs follow the EP-006 RTM numbering. BF-009 surfaces a few BF-only drifts not owned by a single story; these get fresh BF-009 D-NNN ids to keep traceability.

IDTypeSeverityDescriptionSurfaced by step
EP-006 D-001Catalog-vs-EPIC-bodyP0Catalog and EPIC-body disagree on numbering from US-006-005 onward; 4 catalog-only stories have no EPIC counterpart. BF-009 follows EPIC body.All steps
EP-006 D-002Status-enum-driftP1EPIC body says room board has 7 statuses; Prisma HousekeepingStatus enum has 4. Board UI joins HK + property-side enums.Step 2
EP-006 D-003Story-vs-code-enum-driftP0Three copies of task-type enum disagree (EPIC body / Prisma+service / dashboard filter).Step 1 auto-CHECKOUT_CLEANING
EP-006 D-004Story-incompleteP1No shift-handover model on HousekeepingTask (scheduledFor is a bare DateTime).Step 3, F3
EP-006 D-005Story-incomplete + integration-gapP1Auto-assign endpoint POST /tasks/auto-assign not shipped; prerequisite StaffShift/clock-in model also missing.Step 3
EP-006 D-006Catalog-vs-codeP0Catalog row for auto-reorder claims "Not Started" but code, schema, 18-spec suite are shipped (inverse of usual drift direction).(US-006-013 minibar/linen — adjacent to this BF, not in the 10-step path)
EP-006 D-007Story-incomplete + cross-service-handoffP0Discrepancy report is read-only; no resolution endpoint, no audit log, no duration tracking.Step 11 / F5
EP-006 D-008Cross-service-handoffP1URGENT + (ELECTRICAL/GAS/PLUMBING) maintenance does NOT auto-flip room to OUT_OF_ORDER; requires a second dashboard write that supervisors can forget.Step 13 / F6
EP-006 D-009Security + missing-validationP0roomId not validated for tenant on lost-found.create(), maintenance.create(), task.create(). Forged roomId leaks data across tenants. Task #11 security gap.Step 12, Step 13, Step 1, F8
EP-006 D-010Story-incomplete + missing-ACP1HousekeepingPhoto has no photoType: BEFORE | AFTER discriminator; no Sharp compression; no 2G/3G background-queue; mobile E2E missing.Step 6, F2
EP-006 D-011Story-incomplete + India-hospitality-criticalP1Inspection is binary pass/fail; no checklist, no per-item scoring, no auto re-clean on fail.Step 8, Step 9
BF-009 D-001Cross-service-handoff timingP0Step 1 dedup window is UTC-based (startOfDay = 00:00 UTC = 05:30 IST). Late-night double-checkouts straddling the UTC date line could dedup unexpectedly. Indian properties run on Asia/Kolkata.Step 1
BF-009 D-002Test-vs-flow-gapP1housekeeping-lifecycle.flow.spec.ts exercises DAILY_CLEANING happy path but does NOT exercise the auto-CHECKOUT_CLEANING path triggered by RESERVATION_CHECKED_OUT. The flow asserts cleanliness state but not the cross-service event chain.Step 1
BF-009 D-003Test-vs-flow-gapP1housekeeping-lifecycle.flow.spec.ts ends at COMPLETED read-back; does NOT exercise Step 8-10 (inspection → INSPECTED → room sellable). The supervisor side of the loop has no flow coverage.Steps 8-10
BF-009 D-004Cross-EPIC-handoff missing-testP1Step 10 cross-service join (frontOfficeStatus=VACANT + housekeepingStatus=INSPECTED) is the "room sellable" invariant for the next reservation — no flow asserts the next reservation's assignRoom is unblocked after the cycle.Step 10

Cross-EPIC narrative summary​

StepSpansWhy cross-EPIC
1EP-005A → EP-006Front desk emits checkout event; HK consumer auto-creates task.
2-9EP-006 (internal)Single-EPIC steps inside housekeeping-service.
4EP-006 → EP-013 (notification)WebSocket + FCM push to cleaner phone via notification-service.
6EP-006 → EP-002 (storage/S3)Photo presigned URL flow via shared StorageService.
7EP-006 → EP-012 (analytics)HOUSEKEEPING_TASK_COMPLETED populates mv_hk_staff_productivity (FU-55 contract).
10EP-006 → EP-002 (property) + EP-005A (FO state)Composite (VACANT, INSPECTED) status that unblocks next-arrival room-assign.
11EP-006 ↔ EP-002Discrepancy joins HK + property-side enums; resolution writes to both.
12EP-006 → EP-004 (guest)Auto-link to last-checked-out guest requires guest-service cross-service read.
13EP-006 → EP-017 (asset)Maintenance escalation creates a Work Order in asset-service.

The dominant cross-EPIC seam is Step 1 (checkout → auto-task); without that handoff the entire flow doesn't start. The dominant in-EPIC weakness is Steps 8-9 (inspection) — binary pass/fail with no audit trail, the gap most likely to bite Indian SME hotels claiming brand-standard inspection regimes.

See also​

  • ./BF-001-tenant-property-onboarding.md — template
  • ../reconciliation/PLAYBOOK.md — RTM reconciliation pattern
  • ../reconciliation/EP-006-rtm.md — per-EPIC RTM (16 stories, 12 D-NNN findings)
  • hmp-infrastructure/testing/flow-tests/src/flows/housekeeping-lifecycle.flow.spec.ts — flow test (5 specs, daily-clean variant)
  • hmp-infrastructure/testing/flow-tests/src/flows/maintenance-requests.flow.spec.ts — flow test (4 scenarios)
  • hmp-infrastructure/testing/flow-tests/src/flows/lost-and-found.flow.spec.ts — flow test (3 scenarios)
  • hmp-housekeeping-service/CLAUDE.md + src/tasks/CLAUDE.md + src/rooms/CLAUDE.md + src/maintenance/CLAUDE.md + src/sync/CLAUDE.md + src/photos/CLAUDE.md — code ground-truth