- 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>
184 lines
8.4 KiB
Markdown
184 lines
8.4 KiB
Markdown
# 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/<timestamp>/` (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`.
|