# TravelHub — project guide for AI agents and developers Guiding principles and working conventions for this repository. Read this before generating or changing code here. It is the canonical guide; other tools' entry points (`AGENTS.md`) point to it. TravelHub is a **multi-tenant flight-booking platform**: an Angular SPA, a Go API, and PostgreSQL. Several travel agencies ("tenants") share one deployment and one database, and **no tenant may ever see another's data**. That promise is the point of the product and the first thing to protect in any change. **Required reading** (rules are enforced, not advisory): [SECURITY.md](SECURITY.md) · [GOVERNANCE.md](GOVERNANCE.md) · [CONTRIBUTING.md](CONTRIBUTING.md). Product reference: [docs/wiki/](docs/wiki/README.md). Testing: [TESTING.md](TESTING.md), [qa/README.md](qa/README.md). --- ## Stack (do not swap without being asked) - **Frontend:** Angular 22 (standalone, zoneless), TypeScript, SCSS. - **Backend:** Go 1.26, module `github.com/travelswitch/travelhub/backend`, `pgx/v5`, standard-library HTTP (`net/http`), `log/slog`. - **Database:** PostgreSQL 17. - **Tests:** Go (integration, real DB), Angular (Vitest), Playwright (e2e). New work for this team defaults to this stack and to multi-tenancy; design tenant isolation in from the start, never retrofit it. --- ## Guiding principles 1. **Tenant isolation is enforced in application code, and it is sacred.** - The tenant id comes from the **authenticated identity**, never from the URL, query string, or request body. A client cannot name its own tenant. - **Every** query touching a tenant-owned table (`users`, `flights`, `bookings`, `passengers`) filters on `tenant_id` as its first parameter. - A lookup that misses within the tenant returns `ErrNotFound` → **HTTP 404**, never 403 — so probing for another tenant's record is indistinguishable from asking for one that does not exist. - **Do not introduce PostgreSQL row-level security (RLS).** It is a deliberate choice for this project: per-request session state (`SET LOCAL`) fights connection pooling and a missed reset leaks across tenants rather than failing closed. Enforce isolation in the repository layer with the convention above. If stronger isolation is wanted at scale, prefer schema- or database-per-tenant, not RLS. 2. **Security is not optional.** See the security rules below and `security/VAPT-REPORT.md`. Never weaken a security control to make something work; flag it instead. 3. **A change isn't done until its tests pass and prove it.** Assert on behaviour a user can observe, not on implementation. Never loosen an assertion to get green. 4. **Scope discipline.** Do what was asked; don't widen, narrow, or sneak in unrelated changes. AI-generated PRs that drift in scope are rejected. 5. **The human is accountable for what merges.** AI generates and reviews; people own the merge decision on anything security- or tenant-sensitive. --- ## Layout ``` backend/ cmd/api/ server entrypoint, graceful shutdown internal/ httpapi/ the route tree (shared by server and tests) handlers/ HTTP layer — takes tenant id from the identity middleware/ basic auth + tenant resolution, CORS, recoverer repository/ ALL SQL; every tenant query filters tenant_id = $1 auth/ identity carried in request context config/ .env loading, DSN assembly database/ pgx pool models/ API types (camelCase JSON) testsupport/ per-test tenant harness for the Go suite migrations/ 001 schema · 002 seed · 003 demo bookings (idempotent) frontend/ Angular SPA (features/, core/, layout/, shared/) e2e/ Playwright: pages/ fixtures/ tests/ reporters/ CLAUDE.md conventions for generating Playwright tests qa/ scenario cards, the generation prompt, the QA runbook scripts/ db.mjs, report + hook helpers app.ps1 start/stop/restart/rebuild/status/logs the app run-tests.ps1 run any test layer, log, emit JUnit + evidence ``` --- ## Running, testing, data Everything has an npm script or a ps1; prefer them over ad-hoc commands. ```powershell .\app.ps1 start # DB up, API (:8080) + Angular (:4200) .\app.ps1 rebuild -Clean # build Go binary + Angular bundle .\run-tests.ps1 -Layer all # backend + web + ui, logged, JUnit + evidence .\run-tests.ps1 -Layer unit # fast: backend + web, no browser npm run db:migrate | db:reset # schema + seed (reset re-seeds fresh) npm run report # open the evidence report ``` - **Test output** lands in `.testlogs//` (logs, `summary.json`, `junit.xml`) and `e2e/evidence-report/` (screenshots + API request/response). All are git-ignored run artifacts. - **Demo data is time-relative** — seeded flights depart within 21 days of seed time and go stale after ~3 weeks. `run-tests.ps1` re-seeds automatically via `npm run db:check`; if the UI shows no flights, run `npm run db:reset`. --- ## Conventions **Go.** `gofmt` + `go vet` clean. Errors wrapped with `%w` and context. SQL only in `repository/`, always parameterised (`$1…$n`) — never string-concatenate values into SQL. Whitelist anything that must reach the SQL text (e.g. sort columns). Return `ErrNotFound`/`ErrNoSeats`; handlers map them to status codes. **Angular.** Standalone components, signals, lazy-loaded routes. Services in `core/`. The auth interceptor stamps credentials + `X-Tenant`; handlers never trust a client-sent tenant. Keep SCSS theme-aware and responsive (no horizontal scroll at 390px — grid tracks use `minmax(0, 1fr)`, wide tables scroll in their own container). **SQL/migrations.** Idempotent and re-runnable. Multi-tenant tables carry `tenant_id` with `ON DELETE CASCADE`; global reference data (airports, airlines) does not. Unique constraints are per-tenant (e.g. PNR unique within a tenant). **Tests.** See `e2e/CLAUDE.md` for Playwright specifics and `qa/README.md` for the scenario-card workflow. Go tests get a fresh tenant each via `testsupport`. --- ## Security rules (hard) - **Never commit secrets.** `.env` and `*.env` are git-ignored (only `.env.example` is tracked). Credentials, tokens and DB passwords live in `.env`, read at runtime. The `pre-commit` hook blocks committed secrets; don't defeat it. - **Never print or store a live credential.** The e2e API-capture redacts the `Authorization` header (it's the Basic-auth secret); keep it redacted. - Passwords are bcrypt; hash in Go, and keep the real and the timing-dummy hash at the **same cost** (a cost mismatch created a username-timing oracle — see the VAPT). - For production, the app must connect to PostgreSQL as a **least-privilege role**, not a superuser, and over TLS. The open findings are tracked in `security/VAPT-REPORT.md`. --- ## Git & CI - Branch from `main`; don't commit or push unless asked. On push, if the remote has moved, **rebase** onto it (keep history linear) rather than force-push. - The Gitea remote is `origin`; auth uses `GITEA_TOKEN` from `.env`. Never embed the token in `.git/config` or in output — pass it inline for one push, redacted. - Install hooks once per clone: `.\scripts\install-hooks.ps1` (pre-commit = secret scan, pre-push = unit tests). `core.hooksPath` is local and does not travel with a push. - Commit/PR attribution lines are set by the session; follow whatever the current reminder specifies. - CI contract: `run-tests.ps1` emits `junit.xml` + the evidence report for any pipeline to publish. Azure DevOps integration is planned but not yet wired. --- ## This machine (known gotchas) - **Windows + PowerShell 5.1.** No `&&`/`||`/ternary. `app.ps1` and `run-tests.ps1` refresh `PATH` from the registry themselves, because a terminal opened before Go/Node were installed won't see them — if a fresh shell can't find `go`/`npm`, that's why (or restart VS Code). - **Native stderr isn't failure.** Treat a tool's exit code as truth; a warning on stderr must not throw (use `Continue` around native calls). - **`npx playwright show-report` ≠ the evidence report.** That opens Playwright's own debug report from `e2e/playwright-report/`. The rich report (screenshots + API traffic) is `e2e/evidence-report/`, opened with `npm run report`. - `psql` is at `C:\Program Files\PostgreSQL\17\bin`; the PostgreSQL service is `postgresql-x64-17`.