Imported from .github/copilot-instructions.md in the go-guess repository. --- # Copilot instructions ## Build, test, and lint Run repository-wide commands from the root: ```bash make dev # run PostgreSQL, RabbitMQ, API, and frontend with development fixtures make test # complete suite, including disposable infrastructure integration tests make test-integration # Go tests; starts PostgreSQL and RabbitMQ Testcontainers make test-env # isolated seeded UI stack at http://localhost:15173 make test-env-down # remove the test stack and its PostgreSQL volume make lint # go vet and ESLint make build # compile the API and production frontend ``` Focused tests: ```bash cd backend && go test ./internal/service -run '^TestCandidatesRequireSixtyPercent$' cd backend && go test -tags=integration ./internal/integration -run '^TestCompleteInterviewWorkflow$' cd frontend && npm test -- src/app/App.test.tsx -t "filters jobs by title" ``` The required toolchain is Go 1.27.1 and React 19.3 with TypeScript/Vite. Use `cd frontend && npm run format:check` for frontend formatting and `gofmt` for Go. ## Architecture - `backend/cmd/api` wires configuration, PostgreSQL, security, services, handlers, and the Chi router. Requests flow through `internal/web/{router,handler,model,mapper}` to `internal/service/{model,services}` and then `internal/database/{repository,mapper,entity}`. Never expose GORM entities from handlers. - `backend/cmd/seed` applies embedded environment-aware fixtures. - `backend/internal/eventbus` publishes note and shared-document events to the durable `go-guess.interview-events` RabbitMQ fanout exchange. Each API instance consumes through its own exclusive queue and distributes interview-filtered events to authenticated interviewer SSE streams or token-authorized participant streams. - `frontend/src/app` owns application routing and providers, `src/features` groups feature pages and components, `src/components` contains reusable UI, `src/api/client.ts` is the typed HTTP boundary, `src/types` contains client domain types, and `src/styles/global.css` contains application-wide styles. Keep feature-specific components inside their feature directory. Vite proxies `/api` to the Go service in development. - Multi-tenant URLs use `.`, where the app domain is `guess-dev.urpi.be` in local development and the Kubernetes development profile, and `guess.urpi.be` in production. The frontend derives the tenant from the hostname, calls same-origin `/api` through the HTTPS proxy, and sends `X-Tenant-Id` on every request type. Generated participant links must use the validated matching tenant origin. Keep this contract documented in `README.md` and `frontend/README.md`. - Protected interviewer routes require a bearer JWT only. Go Loose is used for browser login, not for API-key authorization. Goose files in `backend/migrations` are schema-only and run in every environment. `internal/database/seed/common.sql` contains shared reference data; `development.sql` and `test.sql` contain idempotent environment-specific fixtures. Every entity change requires a migration plus corresponding fixture and integration test updates. Never put demo users or test records in schema migrations. Authentication uses bcrypt passwords and HMAC-signed bearer JWTs. Domain routes are protected except `/api/health`, `/api/openapi.json`, `/api/auth/login`, `/api/auth/callback`, `/api/auth/logout`, `/api/auth/session`, and opaque-token participant interview routes. Any tenant host starts browser login with `github.com/danyel/go-loose/client`: `/api/auth/login` redirects to `https://./connect/authorize` and Go Loose returns to `/api/auth/callback`. `GO_LOOSE_AUTH_DOMAIN` is `auth-dev.urpi.be` locally and in the development profile, and `auth.urpi.be` in production; `GO_LOOSE_APP_DOMAIN` is the matching application domain. Both default to the development domains. `GET /api/auth/session` answers `404` when no Go Loose login is configured for the request host, and the frontend uses that to fall back to the password form instead of redirecting. Go Loose client credentials come from uncommitted environment variables. The tenant comes from `X-Tenant-Id`, or from the request host and `X-Forwarded-Host` when the header is absent; missing tenant context uses the public schema. Never expose password hashes, CV/photo bytes, or interviewer reference answers in public JSON. Questions are permanent reusable records linked to jobs through `job_posting_questions`; detach links rather than deleting questions. Deprecated questions remain searchable but cannot be newly attached. Question values are `open`, `multiple_choice`, `radio`, and `code_review`. Choice types require at least two options. Open/code-review types require `referenceAnswer`; code review also requires `codeSnippet`. Jobs move through `draft`, `published`, and `deprecated`. Publishing requires a question, only published jobs can create invitations, and candidate matching uses labels plus both skill groups with a 60% threshold. Invitation URLs use `/participant/:invitationId`, where the identifier is an opaque token. Participant questions are acceptance-gated and answers are saved before final submission; interviewer review uses `GET /api/jobs/{id}/invitations/{invitationId}`. Only completed assessment invitations can receive a `passed` or `failed` outcome, and only passed candidates can be scheduled for an interview. Scheduled interviews include the creator plus selected co-interviewers. Attendees manage their invitation through `/api/invitations`, see accepted interviews in `/api/schedule`, and can write notes only after the interview has started. Notes are private to authenticated attendees. The opaque `/participant/meeting/:candidateToken` view exposes only candidate-safe schedule and shared-document data. ## Repository conventions - Prefix every Go interface name with `I`. - Keep web, service, and persistence models separate and convert them in adjacent `mapper` packages. - Participant creation uses multipart fields `firstName`, `lastName`, `birthday`, `email`, `contactInfo`, `photo`, and `cv`. - Return explicit errors; map expected failures to HTTP status codes and log unexpected failures instead of silently falling back. - Update the directly related local documentation whenever behavior, configuration, architecture, commands, or public API surfaces change. - Register backend routes through the typed OpenAPI-aware router helper. The runtime `/api/openapi.json` document is the API source of truth; do not add a manually maintained route inventory or require a generation command. - Commit every repository change with a meaningful conventional prefix such as `feat:`, `fix:`, `refactor:`, `test:`, `docs:`, or `chore:`.