- CLAUDE.md + AGENTS.md: canonical guiding principles for AI agents and developers (tenant isolation, no RLS, stack, security, conventions, environment gotchas) - SECURITY.md: required security controls (isolation, secrets, auth, input handling, least privilege) with VAPT status - GOVERNANCE.md: how changes are validated before merge — deterministic gates + AI review, risk tiers, human accountability - CONTRIBUTING.md: code hygiene, definition of done, file hygiene - docs/wiki/: product reference (overview, domain model, features, roles & access, API reference, glossary) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3.0 KiB
Domain model
Entities
| Entity | Scope | Purpose |
|---|---|---|
| Tenant | — | A travel agency. The owner of all tenant-scoped data. |
| User | per-tenant | Agency staff who sign in (admin or agent). |
| Airport | global | Reference data (IATA code, city, country, timezone). |
| Airline | global | Reference data (IATA code, name). |
| Flight | per-tenant | Sellable inventory: a route on a date at a fare, with seats. |
| Booking | per-tenant | A confirmed sale against a flight. |
| Passenger | per-tenant | A traveller on a booking. |
"Per-tenant" entities carry a tenant_id and cascade-delete with the tenant.
"Global" entities are shared by all tenants and have no tenant.
Relationships
Tenant 1───* User
Tenant 1───* Flight ───* Booking ───* Passenger
│ │
Airline, Airport User (the agent who sold it)
(global refs)
- A Flight references a global Airline and two global Airports (origin, destination), but belongs to one tenant — so the same physical route exists as a separate, independently-priced Flight row per tenant.
- A Booking references a Flight, the User who made it, and has many Passengers.
Key fields & rules
Tenant — slug (unique), name, status ∈ {active, suspended}. A
suspended tenant's users cannot sign in, and it is hidden from the public tenant
list.
User — email, password_hash (bcrypt), full_name, role ∈
{admin, agent}. Email is unique per tenant, not globally — the same
person can hold accounts at several agencies.
Flight — flight_number, departure_time/arrival_time, cabin_class ∈
{economy, premium_economy, business, first}, base_price, currency,
seats_total, seats_available, status ∈ {scheduled, delayed,
cancelled}. Constraints guarantee seats_available ≤ seats_total and
arrival_time > departure_time.
Booking — booking_reference (the PNR, unique per tenant),
passenger_count, total_amount, currency, status ∈ {confirmed,
cancelled}, contact email/phone.
Passenger — name, optional date of birth, passport number, seat.
Invariants the system must uphold
- No overselling. A booking is created in a transaction that row-locks the
flight (
SELECT … FOR UPDATE) before reading availability, so two concurrent sales of the last seat cannot both succeed — the second gets a 409. - Seat accounting round-trips. Confirming a booking decrements
seats_availableby the passenger count; cancelling returns them (guarded so a double-cancel can't credit twice). - PNR uniqueness is per tenant, generated from an alphabet that omits easily-confused characters (no I/O/0/1).
- Tenant isolation on every read and write (see Overview and SECURITY.md).
The authoritative schema is in
backend/migrations/001_init.sql.