Everything an evaluator needs to sign off.
This page exists so your technical advisor, integration partner, or in-house IT lead can verify the platform on their own terms — architecture, data model, tenancy isolation, real-time fan-out, observability, self-hosting, security. Open standards. Open-source dependencies. Predictable interfaces.
Why this page exists
Most hospitality platforms ask buyers to trust a glossy demo. We expose the architecture, the API surface, the deployment topology, and the runtime so independent evaluators can verify the claims before signing.
What we don't expose
Proprietary algorithms (pricing models, anomaly detection, AI concierge prompts) and customer data live outside this reference. Everything below is the contract surface — interfaces, schemas, deployment shape.
For consultants
If you're advising a property on whether to adopt this platform, the sections below give you enough to write a defensible recommendation: data isolation, lock-in story, ops burden, and integration surface.
git clone https://github.com/your-org/guest-journey
cd guest-journey
./dev-start.sh # brings up Postgres + every service in one command
open http://localhost:8000
Architecture
Four clients sit on top of Next.js BFFs that fan out to 22 Rust microservices. Each service owns its own data; cross-service reads only happen over the public API. Real-time updates flow over a shared event bus exposed as Server-Sent Events. Postgres-per-property gives tenants hard data isolation.
Service boundaries
Each Rust service owns its tables. Cross-service reads happen over the API. No shared schema, no shared transactions — services are deployable, restartable, and scalable independently.
BFF pattern
The Next.js dashboard ships its own server-side BFF that handles auth-cookie translation, request fan-out, and SSR. Mobile apps talk to the same gateway, so there's a single network boundary to harden.
Stateless services
No service holds session state in memory. Auth tokens are short-lived JWTs; refresh tokens are persisted. Every pod is interchangeable; rolling updates are zero-downtime.
Stack
No proprietary runtimes, no closed-source databases. Everything is community-supported, well-instrumented, and easy to fork.
Backend
- Rust 1.95 · Tokio · Axum 0.7
- SQLx 0.7 · compile-time-checked queries
- tower-http · CORS · CompressionLayer · ServeDir
- tokio::sync::broadcast for SSE pub/sub
- governor rate limiting
- OpenTelemetry · Prometheus exporter
- JWT via jsonwebtoken · bcrypt for passwords
- utoipa OpenAPI docs from Rust types
Frontend
- Next.js 14 · App Router · standalone build
- React 18 · Zustand state · TanStack Query
- Theme system: 7 palettes × light/dark
- Playwright e2e — 49+ specs
- Vitest unit tests
- OTel browser exporter
Mobile
- Flutter 3.35 · Dart 3.9 · Material 3
- Provider state · Dio HTTP
- image_picker · mobile_scanner (QR)
- qr_flutter for the rotating digital key
- Custom SSE client with exponential backoff
- Web · iOS · Android targets from one codebase
Operations
- Docker Compose dev stack · K8s manifests
- Argo Rollouts for canary deploys
- Velero backup · per-tenant pg_dump
- Falco runtime rules · ZAP DAST
- k6 load · 5 scenarios
- Trivy + cosign image supply chain
- GitHub Actions CI with security gates
Service catalog
22 Rust microservices, each with a single bounded context. Ports below match the production deployment (one systemd unit per service).
| Service | Port | Owns | Key endpoints |
|---|---|---|---|
| auth | 8201 | Identity, JWT, RBAC, MFA, password hashing, MFA recovery | POST /auth/login · GET /auth/me |
| reservations | 8202 | Reservations, room types, rate codes, availability, channel manager | GET /api/v1/properties/:id/reservations |
| guest-services | 8204 | Transfers, lost & found, incidents, shifts, supply requests, team chat, inspections, uploads | POST /api/v1/uploads/photo |
| guest-experience | 8205 | Concierge, room service, activities, spa, POS, dining | GET /api/v1/properties/:id/concierge |
| messaging | 8206 | SMS (Twilio), email (SendGrid), push (Firebase), templates | POST /messages/send |
| housekeeping | 8207 | Cleaning tasks, room status, schedule, supplies | GET /api/v1/properties/:id/housekeeping/rooms |
| loyalty | 8208 | Points, tiers, redemptions | GET /loyalty/members/:id |
| personalization | 8209 | Guest preferences, segmentation, recommendations | GET /personalization/profile/:id |
| property-config | 8210 | Per-property config: room types, rate plans, taxes, policies | GET /property-config/:id |
| local-content | 8211 | Local guides, activities, dining, things to do | GET /local/:property_id/activities |
| iot | 8212 | Door sensors, energy, smart-room signals | POST /iot/event |
| transport | 8213 | Airport transfers, EV charging spots, dispatch | POST /transport/transfer |
| business-services | 8214 | Meeting rooms, business centre bookings | GET /business/rooms |
| group | 8215 | Group bookings, MICE, contracts | POST /groups |
| lock | 8216 | Digital keys, rotating QR, lock event audit | POST /lock/issue-key |
| concierge-ai | 8217 | "Aria" conversational AI, intent → ticket routing | POST /concierge-ai/chat |
| maintenance | 8218 | Work orders, contractors, schedule, photo evidence | GET /maintenance/work-orders |
| revenue | 8219 | Pricing rules, demand forecast, AI rate recommendations, BAR overrides | GET /revenue/calendar |
| payments | 8106 | Stripe + PayPal, folio, night audit, financial reports | POST /payments/pre-authorize |
| analytics | 8008 | OLAP queries, cohort analysis, revenue mgmt KPIs | GET /analytics/kpi |
| shared-lib | — | Common types, error model, auth middleware, audit logger | library only |
API reference
REST + JSON over HTTPS. Every mutating endpoint requires a JWT (cookie or Authorization: Bearer). Every read endpoint returns a consistent envelope.
Response envelope
{
"success": true,
"data": { ... }, // shape varies per endpoint
"meta": { "page": 1, "total": 42 } // optional
}
Auth
POST /auth/login
// Body
{
"email": "[email protected]",
"password": "…",
"property_id": "00000000-0000-…"
}
// 200 → sets HttpOnly access_token + refresh_token cookies
Common patterns
| Pattern | Example |
|---|---|
| Property-scoped lists | GET /api/v1/properties/:property_id/reservations |
| By-id reads | GET /api/v1/properties/:property_id/reservations/:reservation_id |
| Status transitions | PUT /…/:id/status · body { "status": "checked_in" } |
| Bulk operations | POST /…/bulk with an array body |
| File upload | POST /api/v1/uploads/photo · multipart, 10MB cap, image/pdf only |
OpenAPI
Each service exposes its own OpenAPI 3.1 doc generated from its Rust types via utoipa. The merged spec for the BFF is published at /api/openapi.json; the per-service docs live at :port/swagger-ui/.
Real-time (SSE)
Live updates use Server-Sent Events because they're a one-way fan-out, work through standard proxies, and need no protocol upgrade. Behind each stream is a topic-based tokio::sync::broadcast channel — slow subscribers are dropped, never block fast ones.
Subscribing
GET /api/v1/properties/:id/messaging/staff/channels/:cid/stream
// Server keeps the connection open; events look like:
event: created
data: {"id":"…","content":"Towels for villa 12","sender_name":"Aisha"}
event: updated
data: {"id":"…","status":"acknowledged"}
: keep-alive // every 15s, ignore
Client (Flutter)
// staff_app/lib/services/sse_client.dart
SseClient.instance
.subscribe(
'/api/v1/properties/$propertyId/transfers/stream',
auth: authService,
)
.listen((evt) {
// evt.event ∈ {'created','updated','deleted'}
// evt.json carries the entity
});
Topics shipping today
chat::<property_id>::<channel_id>transfers::<property_id>supply_requests::<property_id>incidents::<property_id>room_service::<property_id>
Data & tenancy
Database-per-property tenancy. Each property has its own Postgres instance behind a control-plane registry. Cross-property queries go through analytics-service, which has read-replica access to all of them.
How a request resolves
- Client sends a request with a JWT.
- Service decodes the JWT and extracts
property_id. shared_lib::tenant_poollooks up the connection string in the control-plane DB.- The request executes against that property's pool.
Migrations
Each service ships its own migrations/ directory. On boot, sqlx::migrate!() runs against the connected pool. guest-services-service additionally falls back to in-memory mode when no DATABASE_URL is set, so the demo runs offline.
Backups
Velero schedules pg_dump per tenant nightly. Restore drills are part of the launch readiness checklist (see Phase 23 in LAUNCH_READINESS_PLAN.md).
Self-hosting
Two paths: Docker Compose for evaluation and dev, Kubernetes (Helm + Argo Rollouts) for production.
1 · Docker Compose (5 minutes)
git clone https://github.com/your-org/guest-journey
cd guest-journey
cp env.example .env
docker compose -f docker-compose.dev.yml up -d
./dev-start.sh # builds + runs every service
Hits localhost:8000 for the dashboard, localhost:8080 / :8090 for the Flutter web builds.
2 · Kubernetes
helm repo add guest-journey https://charts.your-org.example
helm install gj guest-journey/platform \
--set postgres.host=… \
--set redis.host=… \
--set objectStorage.endpoint=…
Manifests are in k8s/; the chart wraps them with sensible defaults and Argo Rollouts annotations for canary deploys.
Required env
| Variable | Purpose |
|---|---|
DATABASE_URL | Default tenant pool |
CONTROL_DATABASE_URL | Control-plane (tenants registry) |
REDIS_URL | Cache + rate-limit backing store |
JWT_SECRET | Token signing (rotate per env) |
STRIPE_SECRET_KEY | Payments |
SENDGRID_API_KEY | Transactional email |
TWILIO_AUTH_TOKEN | SMS |
STAFF_UPLOADS_DIR | Path for uploaded photos (or S3 endpoint) |
Observability
OpenTelemetry traces, Prometheus metrics, structured logs. Every service exposes /health, /metrics, and labelled spans on every handler.
What's wired
- Prometheus scrape configs and Grafana dashboards in
infrastructure/grafana/ - OTLP traces over gRPC; sampling configurable per service
- SLO templates and burn-rate alerts in Alertmanager
- Falco runtime rules under
compliance/runtime/ - k6 load profiles for 5 critical journeys
Default SLOs
| Surface | Latency | Availability |
|---|---|---|
| Dashboard reads (p95) | < 250 ms | 99.9% |
| Mutations (p95) | < 500 ms | 99.9% |
| SSE push (p95) | < 150 ms | 99.5% |
| Photo upload (p95) | < 3 s | 99.0% |
Testing & quality
Tests are first-class; the launch checklist gates promotions on coverage and end-to-end pass rate.
Rust
397 tests passing across shared-lib · guest-services-service · payments-service · reservations-service · maintenance-service · guest-experience-service. Run with cargo test --workspace.
Frontend
Vitest unit tests for hooks and the API client. Playwright e2e covers auth, reservations, dashboard rendering, and 9 critical journeys. Run with npm test / npx playwright test.
Mobile
flutter analyze is clean on both apps. Widget tests scaffolded for new modules.
Security
Snyk SAST · cargo-audit · OWASP Dependency-Check · gitleaks · ggshield · Trivy + cosign in the build pipeline; ZAP DAST profiles in compliance/dast/.
Contributing
Issues, PRs, and discussions welcome. The repo is an Apache-2.0 monorepo. The codebase is opinionated but the conventions are documented.
- CONTRIBUTING.md — development setup, commit style, review rules
- ARCHITECTURE-TENANCY.md — multi-tenant data model
- SECURITY.md — disclosure process
- GitHub repo — stars welcome