- 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>
8.4 KiB
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 · GOVERNANCE.md · CONTRIBUTING.md. Product reference: docs/wiki/. Testing: TESTING.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
-
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 ontenant_idas 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.
-
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. -
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.
-
Scope discipline. Do what was asked; don't widen, narrow, or sneak in unrelated changes. AI-generated PRs that drift in scope are rejected.
-
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.
.\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) ande2e/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.ps1re-seeds automatically vianpm run db:check; if the UI shows no flights, runnpm 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.
.envand*.envare git-ignored (only.env.exampleis tracked). Credentials, tokens and DB passwords live in.env, read at runtime. Thepre-commithook blocks committed secrets; don't defeat it. - Never print or store a live credential. The e2e API-capture redacts the
Authorizationheader (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 usesGITEA_TOKENfrom.env. Never embed the token in.git/configor 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.hooksPathis 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.ps1emitsjunit.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.ps1andrun-tests.ps1refreshPATHfrom the registry themselves, because a terminal opened before Go/Node were installed won't see them — if a fresh shell can't findgo/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
Continuearound native calls). npx playwright show-report≠ the evidence report. That opens Playwright's own debug report frome2e/playwright-report/. The rich report (screenshots + API traffic) ise2e/evidence-report/, opened withnpm run report.psqlis atC:\Program Files\PostgreSQL\17\bin; the PostgreSQL service ispostgresql-x64-17.