Skip to main content

EP-018: Multi-Property & Chain — Phase 1 Hardening

Phase 1 hardening sprint, 2026-04. Branch in hmp-web-dashboard. Not yet pushed.

The multi-property epic gained one item in Phase 1: a Playwright E2E test suite that exercises the property switch flow and verifies tenant + property scope is enforced end-to-end. The work also surfaced one architectural finding about the API gateway proxy that's worth recording even though it's not a bug.


C3 — Multi-Property Isolation E2E Tests​

Branch: feat/multi-property-isolation-tests (in hmp-web-dashboard)

What was added​

e2e/tests/19-multi-property-isolation.spec.ts — 5 Playwright tests, all passing on the first run. The suite uses two seeded properties (A and B) under the same tenant, with two test users: one with access to A only, one with access to both.

#TestWhat It Verifies
1Login to property A, switch to property B, verify reservations list refreshesProperty switch updates active scope; cached state cleared
2User with access to A only cannot fetch property B reservations via APITenant + role enforcement on direct API calls
3Reservation created on property A is not visible on property BTenant scope on writes
4HK task on property A floor 3 is not visible to property B usersProperty scope on real-time channels (WebSocket rooms)
5Folio close on property A doesn't trigger night audit on property BEvent topics are property-scoped

Files touched​

  • hmp-web-dashboard/e2e/tests/19-multi-property-isolation.spec.ts (new)
  • hmp-web-dashboard/e2e/fixtures/multi-property-fixture.ts (new — seeds 2 properties + 2 users)
  • hmp-web-dashboard/e2e/playwright.config.ts (added new test pattern)

Result​

No bugs were found. The isolation boundary works as designed at every layer the tests exercise. The suite is now in CI as a regression net.


Architectural Finding (not a bug — documenting for future authors)​

While reviewing the auth flow, the C3 work surfaced this from proxy.service.ts:407-408 in hmp-api-gateway:

// Override any client-supplied X-Property-Id with the JWT claim
headers['x-property-id'] = req.user.propertyId;
headers['x-tenant-id'] = req.user.tenantId;

This means a user cannot spoof a different propertyId by setting the header in their own request — the gateway always overwrites it with whatever's in their JWT. To switch properties, the user must hit the auth endpoint that issues a new JWT bound to the new propertyId.

The implication for downstream service authors: treat x-property-id as authoritative for the request. Don't add redundant validation against request body propertyId fields — the header is already trusted. Documenting in architecture/multi-property-isolation.md so future endpoints don't reinvent the check.


Open Gaps​

These were known before C3 and remain open:

  • Cross-property reads within a tenant (chain manager viewing all 5 properties) are not yet covered by isolation tests. EP-018 has stories for chain rollups (US-018-005, US-018-006); tests will follow when those land.
  • Audit log scoping. auth-service's auditLog table scopes by tenantId only, not property. Acceptable for now since auditLog is admin-only, but worth noting for SOC 2 prep.

Summary​

StoryStatus beforeStatus afterNotes
US-018-001 (Property Switch)BuiltBuiltC3 verified end-to-end
US-018-002 (Tenant Scope)BuiltBuiltC3 verified at API + WebSocket layers
US-018-007 (Chain Rollup)Not StartedNot StartedOut of Phase 1 scope

No story-level status promotions. The value added is regression coverage, not new features.