Code conventions
Build & gates
./mvnw clean verify # everything: Java tests (Arconia/Testcontainers PostgreSQL),
# Spotless, Checkstyle, CycloneDX SBOM, UI gates, packaged jar
(cd src/main/frontend && pnpm test:stories) # Storybook story tests in real chromium (axe-as-error)
(cd src/main/frontend && pnpm e2e) # Playwright vs the real jar + mock OIDC IdP (compose.e2e.yaml)
reqstool status local -p docs/reqstool # must end "N/N complete · PASS" (run after the two above)
openspec validate --all --strict
./mvnw -Pnative -DskipTests native:compile # GraalVM native binary (release path; CI does this)
- Always
clean before trusting the reqstool gate: the annotation processor
writes per-source-set files that incremental compilation truncates.
- The UI is built INSIDE
./mvnw verify (frontend-maven-plugin, pinned
node/pnpm); cd src/main/frontend && pnpm … is only for UI development loops and e2e.
./mvnw -q spotless:apply before committing Java.
Java
- Java 25, Spring Boot 4. Constructor injection, no Lombok, records for DTOs.
- Persistence via
JdbcClient + Flyway (no JPA). Single V1__init.sql until
the owner says otherwise — fold schema changes into it (Testcontainers
recreate the schema every run).
- Enumerated values are native PostgreSQL enum types, never
TEXT ... CHECK (col IN (...)): CREATE TYPE <singular table>_<column> AS ENUM (...) (the column
name alone collides — three tables carry a state). Write through an explicit
cast (:state::snapshot_state); reads are unchanged, the driver returns a
String. The trade is deliberate and permanent-ish: a new value cannot be
used in the transaction that adds it (adding one and backfilling rows with it
takes two migrations), and no value can ever be dropped — ALTER TYPE ... DROP VALUE is "not implemented", so removing one means a replacement type and a
rewrite of every dependent column.
- JGit for all git operations — never subprocess git in production code.
Tests may run the git binary only via
AbstractGatewayTest.git(...)
(isolated from host config).
- Container-backed tests use Arconia Dev Services where one exists — one
container serves both
bootRun and the tests. This project uses postgresql
(automatic) and lgtm (opt-in via the observability profile). Check the
arconia BOM for what else is published before assuming a gap; floci (an AWS
emulator) is there and is not obvious from a Maven Central name search. A raw Testcontainers container is a fallback
for dependencies Arconia does not cover; justify it in the change's design and
consider proposing the dev service upstream. See CLAUDE.md, "Process".
- Formatting is Spotless (palantir-java-format) + Checkstyle — non-negotiable
gates, auto-fix with
spotless:apply.
- REST errors:
ResponseStatusException / ProblemDetail. New endpoints get
@Tag, @Operation, @ApiResponse, and @Schema on their DTOs (the
Scalar reference at /docs renders them).
TypeScript / UI (src/main/frontend/)
- Strict TS, oxlint, vitest (jsdom
unit project), Storybook story tests
with axe-as-error (browser storybook project, pnpm test:stories),
Playwright e2e. pnpm verify runs the jsdom UI gate; the story and e2e
suites run outside mvnw verify — as their own commands locally and their
own CI jobs (#103).
- API types are GENERATED:
src/main/frontend/openapi.json → src/api/types.gen.ts
(pnpm gen:api-types — it pins TypeScript 5.9.3 via dlx, because
openapi-typescript cannot run against the workspace's TypeScript 7).
Regenerate after backend API changes (snapshot comes from
OpenApiDocsTests → target/openapi.json). Never hand-edit.
OpenApiContractTests fails the build when the committed openapi.json is not
the document the gateway serves, so regenerate it in the same change — it is
the baseline the breaking-change gate diffs against, not just an input to
codegen. info.version is a placeholder there by design (the served document
carries the real one).
- MSW handlers are typed from the generated types; mocks never appear in the
acceptance path (Playwright runs against the real gateway).
Traceability (reqstool)
- SSOT:
docs/reqstool/requirements.yml + software_verification_cases.yml
(GW_*, SVC_GW_*). Follow the reqstool plugin skills' conventions.
- Java:
@Requirements({"GW_XXXX"}) on the implementing method,
@SVCs({"SVC_GW_XXXX"}) on the verifying test.
- TypeScript: JSDoc
@Requirements GW_XXXX on components, @SVCs SVC_GW_XXXX
above Playwright test(...) calls — test titles must be snake_case
identifiers (junit-name matching).
- Never weaken or delete an existing SVC test to make a change pass.
Git
- Conventional Commits, DCO sign-off (
git commit -s), branches
<type>/<kebab-description>, PR title = conventional commit (squash merge).
1---2name: code-conventions3description: Skills Gateway build commands, quality gates, Java and TypeScript conventions, and reqstool traceability annotations. Load before writing or reviewing any code in this repo.4---56# Code conventions78## Build & gates910```bash11./mvnw clean verify # everything: Java tests (Arconia/Testcontainers PostgreSQL),12 # Spotless, Checkstyle, CycloneDX SBOM, UI gates, packaged jar13(cd src/main/frontend && pnpm test:stories) # Storybook story tests in real chromium (axe-as-error)14(cd src/main/frontend && pnpm e2e) # Playwright vs the real jar + mock OIDC IdP (compose.e2e.yaml)15reqstool status local -p docs/reqstool # must end "N/N complete · PASS" (run after the two above)16openspec validate --all --strict17./mvnw -Pnative -DskipTests native:compile # GraalVM native binary (release path; CI does this)18```1920- Always `clean` before trusting the reqstool gate: the annotation processor21 writes per-source-set files that incremental compilation truncates.22- The UI is built INSIDE `./mvnw verify` (frontend-maven-plugin, pinned23 node/pnpm); `cd src/main/frontend && pnpm …` is only for UI development loops and e2e.24- `./mvnw -q spotless:apply` before committing Java.2526## Java2728- Java 25, Spring Boot 4. Constructor injection, no Lombok, records for DTOs.29- Persistence via `JdbcClient` + Flyway (no JPA). Single `V1__init.sql` until30 the owner says otherwise — fold schema changes into it (Testcontainers31 recreate the schema every run).32- Enumerated values are native PostgreSQL enum types, never `TEXT ... CHECK (col33 IN (...))`: `CREATE TYPE <singular table>_<column> AS ENUM (...)` (the column34 name alone collides — three tables carry a `state`). Write through an explicit35 cast (`:state::snapshot_state`); reads are unchanged, the driver returns a36 `String`. The trade is deliberate and permanent-ish: a new value cannot be37 *used* in the transaction that adds it (adding one and backfilling rows with it38 takes two migrations), and no value can ever be dropped — `ALTER TYPE ... DROP39 VALUE` is "not implemented", so removing one means a replacement type and a40 rewrite of every dependent column.41- JGit for all git operations — never subprocess git in production code.42 Tests may run the git binary only via `AbstractGatewayTest.git(...)`43 (isolated from host config).44- Container-backed tests use **Arconia Dev Services** where one exists — one45 container serves both `bootRun` and the tests. This project uses `postgresql`46 (automatic) and `lgtm` (opt-in via the `observability` profile). Check the47 arconia BOM for what else is published before assuming a gap; `floci` (an AWS48 emulator) is there and is not obvious from a Maven Central name search. A raw Testcontainers container is a fallback49 for dependencies Arconia does not cover; justify it in the change's design and50 consider proposing the dev service upstream. See CLAUDE.md, "Process".51- Formatting is Spotless (palantir-java-format) + Checkstyle — non-negotiable52 gates, auto-fix with `spotless:apply`.53- REST errors: `ResponseStatusException` / `ProblemDetail`. New endpoints get54 `@Tag`, `@Operation`, `@ApiResponse`, and `@Schema` on their DTOs (the55 Scalar reference at `/docs` renders them).5657## TypeScript / UI (`src/main/frontend/`)5859- Strict TS, oxlint, vitest (jsdom `unit` project), Storybook story tests60 with axe-as-error (browser `storybook` project, `pnpm test:stories`),61 Playwright e2e. `pnpm verify` runs the jsdom UI gate; the story and e2e62 suites run outside `mvnw verify` — as their own commands locally and their63 own CI jobs (#103).64- API types are GENERATED: `src/main/frontend/openapi.json` → `src/api/types.gen.ts`65 (`pnpm gen:api-types` — it pins TypeScript 5.9.3 via `dlx`, because66 `openapi-typescript` cannot run against the workspace's TypeScript 7).67 Regenerate after backend API changes (snapshot comes from68 `OpenApiDocsTests` → `target/openapi.json`). Never hand-edit.69 `OpenApiContractTests` fails the build when the committed `openapi.json` is not70 the document the gateway serves, so regenerate it in the same change — it is71 the baseline the breaking-change gate diffs against, not just an input to72 codegen. `info.version` is a placeholder there by design (the served document73 carries the real one).74- MSW handlers are typed from the generated types; mocks never appear in the75 acceptance path (Playwright runs against the real gateway).7677## Traceability (reqstool)7879- SSOT: `docs/reqstool/requirements.yml` + `software_verification_cases.yml`80 (`GW_*`, `SVC_GW_*`). Follow the reqstool plugin skills' conventions.81- Java: `@Requirements({"GW_XXXX"})` on the implementing method,82 `@SVCs({"SVC_GW_XXXX"})` on the verifying test.83- TypeScript: JSDoc `@Requirements GW_XXXX` on components, `@SVCs SVC_GW_XXXX`84 above Playwright `test(...)` calls — test titles must be snake_case85 identifiers (junit-name matching).86- Never weaken or delete an existing SVC test to make a change pass.8788## Git8990- Conventional Commits, DCO sign-off (`git commit -s`), branches91 `<type>/<kebab-description>`, PR title = conventional commit (squash merge).