{"source":"companies/Staywire/02_DEFINE/USER_STORIES.md","epics":{"E1":"Platform foundation & multi-tenancy","E2":"Public API core","E3":"MCP server & agent surface","E4":"Guest booking surface","E5":"Payments (Stripe Connect)","E6":"Admin","E7":"Housekeeping day-sheet & roles","E8":"CSGH cutover & agent-bookability demo"},"summary":{"total_stories":49,"tested":26,"untested":14,"not_built":9,"gaps_referenced":9,"test_titles_scanned":237},"code_coverage":[{"package":"@staywire/core","functions_pct":93.1,"lines_pct":92.16},{"package":"@staywire/db","functions_pct":72.72,"lines_pct":92.04},{"package":"@staywire/booking","functions_pct":46.31,"lines_pct":55.52},{"package":"@staywire/emails","functions_pct":93.33,"lines_pct":100},{"package":"@staywire/api","functions_pct":97.01,"lines_pct":90},{"package":"@staywire/admin","functions_pct":46.31,"lines_pct":53.3},{"package":"@staywire/book","functions_pct":60,"lines_pct":56.66}],"stories":[{"id":"US-001","title":"Tenant isolation on every row","epic":"E1","epic_title":"Platform foundation & multi-tenancy","status":"tested","tests":[{"file":"packages/db/test/rls.test.ts","title":"cross-tenant isolation (US-001 AC3)"}]},{"id":"US-002","title":"Double-booking impossibility at the database level","epic":"E1","epic_title":"Platform foundation & multi-tenancy","status":"tested","tests":[{"file":"packages/core/test/bookings.int.test.ts","title":"DOUBLE-BOOKING RACE (US-002 AC3): two concurrent creates → exactly one 201, one 409 room_unavailable"},{"file":"packages/core/test/bookings.int.test.ts","title":"half-open ranges: back-to-back stays share the boundary day (US-002 AC5)"}]},{"id":"US-003","title":"Data-model seams for the OTA fast-follow","epic":"E1","epic_title":"Platform foundation & multi-tenancy","status":"untested","tests":[]},{"id":"US-004","title":"Event log as the audit trail","epic":"E1","epic_title":"Platform foundation & multi-tenancy","status":"tested","tests":[{"file":"packages/db/test/rls.test.ts","title":"event_log is append-only by grants (US-004 AC2)"}]},{"id":"US-005","title":"Rates resolved per-night at quote time","epic":"E1","epic_title":"Platform foundation & multi-tenancy","status":"tested","tests":[{"file":"packages/core/test/pricing.test.ts","title":"resolveNightlyRates — per-night resolution at quote time (US-005)"},{"file":"packages/core/test/pricing.test.ts","title":"prices each night at its own rate across a seasonal boundary (US-005 AC2 exact values)"}]},{"id":"US-006","title":"Admin and housekeeping roles","epic":"E1","epic_title":"Platform foundation & multi-tenancy","status":"untested","tests":[]},{"id":"US-007","title":"Scoped API keys (test + live)","epic":"E2","epic_title":"Public API core","status":"tested","tests":[{"file":"apps/api/test/api.int.test.ts","title":"auth (US-007)"}]},{"id":"US-008","title":"Availability search endpoint","epic":"E2","epic_title":"Public API core","status":"tested","tests":[{"file":"apps/api/test/api.int.test.ts","title":"blocked room surfaces reason blocked (bookings ∪ blocks — US-008 AC2)"},{"file":"packages/core/test/availability.int.test.ts","title":"getAvailability (US-008)"}]},{"id":"US-009","title":"Quote endpoint with expiry and idempotency","epic":"E2","epic_title":"Public API core","status":"tested","tests":[{"file":"apps/api/test/api.int.test.ts","title":"quote → booking → fetch, with Idempotency-Key replay semantics (US-009/US-010)"},{"file":"packages/core/test/pricing.test.ts","title":"computeTaxLines — itemized, integer cents (US-009 AC5)"},{"file":"packages/core/test/quotes.int.test.ts","title":"createQuote (US-009)"}]},{"id":"US-010","title":"Booking creation consuming a quote","epic":"E2","epic_title":"Public API core","status":"tested","tests":[{"file":"apps/api/test/api.int.test.ts","title":"quote → booking → fetch, with Idempotency-Key replay semantics (US-009/US-010)"},{"file":"packages/core/test/bookings.int.test.ts","title":"createBooking (US-010)"},{"file":"packages/core/test/bookings.int.test.ts","title":"replays with the same Idempotency-Key return the SAME booking (US-010 AC2)"}]},{"id":"US-011","title":"Bookings read, modify, cancel over the API","epic":"E2","epic_title":"Public API core","status":"tested","tests":[{"file":"packages/core/test/bookings-admin.int.test.ts","title":"listBookings — filters + cursor pagination (US-011/US-049)"},{"file":"packages/core/test/bookings-admin.int.test.ts","title":"cancelBooking — policy snapshot + refund (US-011/US-025/US-032)"},{"file":"packages/core/test/policies.test.ts","title":"computeRefundCents — policy SNAPSHOT refund math (US-011 AC3)"}]},{"id":"US-012","title":"Rooms, blocks, and policies over the API","epic":"E2","epic_title":"Public API core","status":"tested","tests":[{"file":"apps/api/test/admin.int.test.ts","title":"rooms & blocks CRUD over REST (US-012/US-037/GAP-11)"},{"file":"packages/core/test/rooms-blocks.int.test.ts","title":"rooms CRUD (US-012 · US-037)"},{"file":"packages/core/test/rooms-blocks.int.test.ts","title":"blocks CRUD (US-012 · US-035 · GAP-11)"}]},{"id":"US-013","title":"Deterministic errors and structured policy semantics","epic":"E2","epic_title":"Public API core","status":"tested","tests":[{"file":"packages/core/test/settings-guests-daysheet.int.test.ts","title":"policies + settings (US-013 · US-038)"}]},{"id":"US-014","title":"Published OpenAPI spec, versioning, and rate limits","epic":"E2","epic_title":"Public API core","status":"tested","tests":[{"file":"apps/api/test/admin.int.test.ts","title":"openapi.json includes the new admin-lane paths (US-014)"},{"file":"apps/api/test/api.int.test.ts","title":"serves /openapi.json publicly with all M1 paths (US-014)"}]},{"id":"US-015","title":"Webhook registration, delivery, and retry","epic":"E2","epic_title":"Public API core","status":"tested","tests":[{"file":"apps/api/test/admin.int.test.ts","title":"webhook endpoint management surface (US-015)"},{"file":"packages/core/test/notifications.int.test.ts","title":"post-commit webhook fan-out (US-015)"},{"file":"packages/core/test/webhooks.test.ts","title":"event fan-out (US-015 catalog)"}]},{"id":"US-016","title":"Hosted MCP server with the five booking tools","epic":"E3","epic_title":"MCP server & agent surface","status":"tested","tests":[{"file":"apps/api/test/api.int.test.ts","title":"MCP surface (US-016/US-017) — real client over Streamable HTTP"},{"file":"apps/api/test/api.int.test.ts","title":"exposes exactly the six tools with schemas + descriptions (US-016 + GAP-14)"},{"file":"apps/api/test/api.int.test.ts","title":"REST/MCP parity: identical error codes for the same invalid request (US-016 AC2)"}]},{"id":"US-017","title":"MCP authentication scoped like API keys","epic":"E3","epic_title":"MCP server & agent surface","status":"tested","tests":[{"file":"apps/api/test/api.int.test.ts","title":"MCP surface (US-016/US-017) — real client over Streamable HTTP"},{"file":"packages/core/test/bookings.int.test.ts","title":"bookings created by an MCP actor record source: agent (US-017)"}]},{"id":"US-018","title":"Always-free sandbox with test-mode property mirror","epic":"E3","epic_title":"MCP server & agent surface","status":"untested","tests":[]},{"id":"US-019","title":"Realistic seeded sandbox inventory","epic":"E3","epic_title":"MCP server & agent surface","status":"untested","tests":[]},{"id":"US-020","title":"llms.txt and schema.org markup per property","epic":"E3","epic_title":"MCP server & agent surface","status":"untested","tests":[]},{"id":"US-021","title":"Date-range availability search with calendar","epic":"E4","epic_title":"Guest booking surface","status":"untested","tests":[]},{"id":"US-022","title":"Room list with photos and itemized total","epic":"E4","epic_title":"Guest booking surface","status":"untested","tests":[]},{"id":"US-023","title":"Three-click checkout","epic":"E4","epic_title":"Guest booking surface","status":"untested","tests":[]},{"id":"US-024","title":"Confirmation email with reference and cancellation link","epic":"E4","epic_title":"Guest booking surface","status":"tested","tests":[{"file":"packages/core/test/notifications.int.test.ts","title":"confirmation email on booking.created (US-024)"}]},{"id":"US-025","title":"Self-serve cancellation honoring policy","epic":"E4","epic_title":"Guest booking surface","status":"tested","tests":[{"file":"packages/core/test/bookings-admin.int.test.ts","title":"cancelBooking — policy snapshot + refund (US-011/US-025/US-032)"},{"file":"packages/core/test/notifications.int.test.ts","title":"cancellation notice on cancel (US-025)"}]},{"id":"US-026","title":"Native embeddable components with token inheritance","epic":"E4","epic_title":"Guest booking surface","status":"tested","tests":[{"file":"apps/api/test/admin.int.test.ts","title":"secret-vs-publishable boundary (§4.2 · US-026 AC3)"}]},{"id":"US-027","title":"Hosted fallback booking page","epic":"E4","epic_title":"Guest booking surface","status":"untested","tests":[]},{"id":"US-028","title":"Mobile-first forms with no sub-16px inputs","epic":"E4","epic_title":"Guest booking surface","status":"untested","tests":[]},{"id":"US-029","title":"Stripe Connect onboarding","epic":"E5","epic_title":"Payments (Stripe Connect)","status":"not_built","tests":[]},{"id":"US-030","title":"Per-property payment mode: full, deposit, or card-on-file","epic":"E5","epic_title":"Payments (Stripe Connect)","status":"not_built","tests":[]},{"id":"US-031","title":"Off-session balance and card-on-file charging","epic":"E5","epic_title":"Payments (Stripe Connect)","status":"not_built","tests":[]},{"id":"US-032","title":"Refunds via Stripe","epic":"E5","epic_title":"Payments (Stripe Connect)","status":"tested","tests":[{"file":"packages/core/test/bookings-admin.int.test.ts","title":"cancelBooking — policy snapshot + refund (US-011/US-025/US-032)"}]},{"id":"US-033","title":"Pricing tiers wired through Stripe fees","epic":"E5","epic_title":"Payments (Stripe Connect)","status":"not_built","tests":[]},{"id":"US-034","title":"Bookings calendar (month/week)","epic":"E6","epic_title":"Admin","status":"untested","tests":[]},{"id":"US-035","title":"Manual bookings and date blocks","epic":"E6","epic_title":"Admin","status":"tested","tests":[{"file":"apps/api/test/admin.int.test.ts","title":"manual booking → detail drawer flow (US-035/US-036)"},{"file":"packages/core/test/rooms-blocks.int.test.ts","title":"blocks CRUD (US-012 · US-035 · GAP-11)"}]},{"id":"US-036","title":"Booking detail: modify, cancel, resend","epic":"E6","epic_title":"Admin","status":"tested","tests":[{"file":"apps/api/test/admin.int.test.ts","title":"manual booking → detail drawer flow (US-035/US-036)"}]},{"id":"US-037","title":"Rooms and rates management","epic":"E6","epic_title":"Admin","status":"tested","tests":[{"file":"apps/api/test/admin.int.test.ts","title":"rooms & blocks CRUD over REST (US-012/US-037/GAP-11)"},{"file":"packages/core/test/rooms-blocks.int.test.ts","title":"rooms CRUD (US-012 · US-037)"},{"file":"packages/core/test/rooms-blocks.int.test.ts","title":"rejects overlapping seasonal ranges with a clear message (US-037 AC2)"},{"file":"packages/core/test/rooms-blocks.int.test.ts","title":"rate changes affect FUTURE pricing only — snapshots stay (US-037 AC3)"}]},{"id":"US-038","title":"Policies, taxes, and property settings","epic":"E6","epic_title":"Admin","status":"tested","tests":[{"file":"packages/core/test/policies.test.ts","title":"POLICY_TEMPLATES (US-038 — exactly three)"},{"file":"packages/core/test/settings-guests-daysheet.int.test.ts","title":"policies + settings (US-013 · US-038)"},{"file":"packages/core/test/settings-guests-daysheet.int.test.ts","title":"policy change applies to FUTURE quotes only (US-038 AC5)"}]},{"id":"US-039","title":"Owner notifications: email and webhook","epic":"E6","epic_title":"Admin","status":"untested","tests":[]},{"id":"US-040","title":"Bookings CSV export","epic":"E6","epic_title":"Admin","status":"untested","tests":[]},{"id":"US-049","title":"Guest lookup","epic":"E6","epic_title":"Admin","status":"tested","tests":[{"file":"apps/admin/test/lib.test.ts","title":"search box → API filter mapping (US-049)"},{"file":"apps/api/test/admin.int.test.ts","title":"guest search + detail with booking history (US-049)"},{"file":"packages/core/test/bookings-admin.int.test.ts","title":"listBookings — filters + cursor pagination (US-011/US-049)"},{"file":"packages/core/test/settings-guests-daysheet.int.test.ts","title":"guest lookup (US-049)"}]},{"id":"US-041","title":"Read-only day-sheet","epic":"E7","epic_title":"Housekeeping day-sheet & roles","status":"tested","tests":[{"file":"packages/core/test/settings-guests-daysheet.int.test.ts","title":"day-sheet (US-041/042/043)"}]},{"id":"US-042","title":"Late check-out/check-in flags","epic":"E7","epic_title":"Housekeeping day-sheet & roles","status":"untested","tests":[]},{"id":"US-043","title":"Role-boundary enforcement for housekeeping","epic":"E7","epic_title":"Housekeeping day-sheet & roles","status":"tested","tests":[{"file":"apps/admin/test/proxy-policy.test.ts","title":"housekeeping role boundary (US-043)"},{"file":"apps/api/test/admin.int.test.ts","title":"day-sheet projects the day and leaks no money/contact fields (US-043)"},{"file":"packages/core/test/settings-guests-daysheet.int.test.ts","title":"PHYSICALLY excludes rates, totals, payment refs, and guest contact (US-043 AC2)"}]},{"id":"US-044","title":"M1: Taylor books a room end-to-end in sandbox","epic":"E8","epic_title":"CSGH cutover & agent-bookability demo","status":"not_built","tests":[]},{"id":"US-045","title":"CSGH data migration out of WebRezPro","epic":"E8","epic_title":"CSGH cutover & agent-bookability demo","status":"not_built","tests":[]},{"id":"US-046","title":"Replace WebRezPro link-outs with embedded booking","epic":"E8","epic_title":"CSGH cutover & agent-bookability demo","status":"not_built","tests":[]},{"id":"US-047","title":"Taylor on production MCP","epic":"E8","epic_title":"CSGH cutover & agent-bookability demo","status":"not_built","tests":[]},{"id":"US-048","title":"M4: first live booking and cutover verification","epic":"E8","epic_title":"CSGH cutover & agent-bookability demo","status":"not_built","tests":[]}],"gaps":[{"id":"GAP-3","tests":[{"file":"packages/core/test/bookings-admin.int.test.ts","title":"refunds endpoint — GAP-3"}]},{"id":"GAP-4","tests":[{"file":"packages/core/test/bookings-admin.int.test.ts","title":"no_show — GAP-4"}]},{"id":"GAP-5","tests":[{"file":"packages/core/test/bookings-admin.int.test.ts","title":"direct (quote-less) creation — GAP-5"}]},{"id":"GAP-7","tests":[{"file":"packages/core/test/bookings-admin.int.test.ts","title":"resend_confirmation — GAP-7"}]},{"id":"GAP-10","tests":[{"file":"packages/core/test/bookings-admin.int.test.ts","title":"patchBooking — GAP-4b widened scope, GAP-10 no auto-charge"}]},{"id":"GAP-11","tests":[{"file":"apps/api/test/admin.int.test.ts","title":"rooms & blocks CRUD over REST (US-012/US-037/GAP-11)"},{"file":"packages/core/test/rooms-blocks.int.test.ts","title":"blocks CRUD (US-012 · US-035 · GAP-11)"},{"file":"packages/core/test/rooms-blocks.int.test.ts","title":"PATCH moves a block (GAP-11); list filters by overlap window"}]},{"id":"GAP-13","tests":[{"file":"packages/core/test/quotes.int.test.ts","title":"422 exceeds_capacity with sleeps in details (GAP-13)"}]},{"id":"GAP-14","tests":[{"file":"apps/api/test/api.int.test.ts","title":"exposes exactly the six tools with schemas + descriptions (US-016 + GAP-14)"},{"file":"apps/api/test/api.int.test.ts","title":"get_booking: reference lookup with speakable summary; unknown reference → not_found (GAP-14)"}]},{"id":"GAP-15","tests":[{"file":"apps/api/test/admin.int.test.ts","title":"publishable key GET /v1/rooms returns the content subset only (GAP-15)"}]}]}