Add project guidance, security/governance/hygiene rules, and product wiki
- 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>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
45b5b34680
commit
035711f08c
@@ -0,0 +1,70 @@
|
||||
# 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_available` by 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](01-overview.md) and [SECURITY.md](../../SECURITY.md)).
|
||||
|
||||
The authoritative schema is in
|
||||
[`backend/migrations/001_init.sql`](../../backend/migrations/001_init.sql).
|
||||
Reference in New Issue
Block a user