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,183 @@
|
||||
# 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`.
|
||||
Reference in New Issue
Block a user