Files
nilesh.pandClaude Opus 4.8 035711f08c 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>
2026-10-10 13:31:04 +05:30

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

  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.

.\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.