# Contributing to proxy-monster Thanks for your interest in contributing. This page covers how to build and test, and the conventions a change is expected to follow. Start by reading [AGENTS.md](./AGENTS.md) — the project entry point: intent, module layout, the stack, and the settled decisions. ## Build, test, run The toolchain and the tasks are both pinned in `mise.toml`, so `mise install` once and then, from anywhere in the repo. JDK 24 is the floor — the Gradle build compiles with `--release 24` and targets JVM 24 — and `mise` supplies it: ```sh mise run dev # the whole local stack: datastores, control plane, both proxies, web console mise run lint # prettier over ts/js/md/json/yaml/css + eslint over the web sources mise run verify # the whole gate: lint, JVM tests, Go tests, web unit tests + build ``` Run `mise run verify` before opening a pull request — it is the gate documented in [AGENTS.md](./AGENTS.md#build-test-run). It runs `verify-static`, `verify-jvm` and `verify-go`, which CI runs as parallel jobs: `PM_TEST_SHARD=i/n` runs one slice of the JVM test classes, and `PM_GO_PACKAGES` narrows the Go tests. `mise tasks ls` lists the per-component tasks for running one piece at a time; install, run locally, and deploy is [INSTALL.md](./INSTALL.md). Prettier owns formatting (`.ts`/`.tsx`/`.js`/`.mjs`, `.md`, `.json`, `.yml`/`.yaml`, `.css`); eslint owns the TypeScript rule set. `mise run lint` checks both and `mise run format` rewrites what prettier owns, so a formatting-only diff never has to be argued about in review. MySQL is the primary, fully-enforced target and the correctness bar; verify it first. PostgreSQL support is experimental. The Go modules live under a root `go.work`, so Go commands take root-relative package paths and never need a `cd` — `go test ./goproxy/...` from the repo root. Bare `go test ./...` does not work there: the root is a workspace, not a module. `mise run verify` enumerates the modules for you. Every task that runs the Go tests passes `-race`, so a hand-run `go test` without it is a weaker check than the gate: a data race reads as a pass. ## Tests are DB-backed Enforcement is exercised against real databases, not mocks. Write MySQL and PostgreSQL tests with Testcontainers through the shared helpers (`SharedPostgres`/`SharedMySql` on the JVM, each module's `dbtest` in Go). They start each container once per test JVM or `go test` run, and tests isolate by database or schema; never start a container per test. The control-plane tests run in parallel forks (`-Ppm.testForks=n`), each with its own pair of containers; under `mise run verify` the forks share one server per engine instead. They are required, not optional: without a Docker environment they fail rather than skip, so Docker must be running for `mise run verify`. `PM_REQUIRE_DB_TESTS=true` turns a missing-Docker skip into a hard failure. CI sets it so a security test can never pass by skipping. Locally, leaving it unset lets the DB-backed tests skip on a machine without Docker. The catalog and wire tests use a hard gate that ignores it and fails on missing Docker either way. A few tests are `@Disabled` in source, so even a fully passing run reports a handful of skips; zero skips is not the signal to look for. ## Conventions - English for all docs, code comments, and identifiers. US spelling: canceled, color, catalog, behavior, favor. - Comments and docs describe the current state in present tense. No task, phase, or workstream codes in comments or docs — they go stale. Commit messages may keep such references. - A comment states what the code cannot: a constraint from outside the file, a non-obvious failure mode, why this and not the obvious alternative. It never narrates how the code came to be — no review findings, no what-was-tried, no "previously X", no account of a debugging session. **This repository is public.** A comment recounting how something was arrived at tends to carry what was around at the time: host names, directory layouts, internal tooling, network topology, who asked for what. Commit messages and pull requests are where that history belongs; they are equally public, so the same rule about naming internal systems applies there too. - Localization is non-negotiable. Every user-facing string is localized (currently English and Korean). The server returns a stable dot-namespaced code and params, never English prose; the web client resolves it through a `next-intl` namespace. Every message key must exist in every supported locale under `web/messages//` (see [docs/l10n.md](./docs/l10n.md)). - Fail-closed, always. When the analyzer cannot prove a statement safe, route it through the deny-by-default Cedar gate rather than a hardcoded error in code. Coverage gaps are security gaps. - Kotlin uses standard conventions and Gradle (Kotlin DSL); Go uses the standard toolchain. - Commit subjects and pull request titles follow [Conventional Commits](https://www.conventionalcommits.org): `type(scope)!: subject`. The type is one of `feat`, `fix`, `perf`, `refactor`, `build`, `docs`, `test`, `ci`, `chore`, `style`, `revert` — the component goes in the scope, so `fix(web): wrap long Cedar lines`, not `web: ...`. A `!` marks a breaking change. CI checks this on every pull request. Design docs follow the house style of [docs/authz-model.md](./docs/authz-model.md): lead with a short decision, justify after, stay grounded in real files, show worked examples, and name the failure modes. The design-doc index is [docs/README.md](./docs/README.md). ## Releases Four release trains, each with its own version and changelog (`release-please-config.json`). Merging a release PR tags and publishes; a push to `main` only opens or updates the PR. | Train | Tag | Covers | | --- | --- | --- | | Server | `server-v0.1.0` | `goproxy`, `control-plane`, `auditmon`, `engine`, `auth`, `proto`, `web` | | Client | `pmon-v0.1.0` | `pmon`, the client CLI | | CI | `pm-ci-v0.1.0` | `sql-action`, the `pm-ci` binary its GitHub Action runs | | Library | `mysqlwire/v0.1.0` | `mysqlwire`, published for `go get` | The paths a commit touches decide its train, so a `pmon/` change never bumps the server. Subjects follow [Conventional Commits](https://www.conventionalcommits.org): `feat:`/`fix:`/`perf:` appear in the changelog, `chore:`/`test:`/`ci:` do not, and `!` or a `BREAKING CHANGE:` footer drives the major bump. A `pmon` release also ships the desktop app; its Windows signing and update feeds are in [pmon/tray/README.md](./pmon/tray/README.md#releases). Last, bump [ridi-oss/homebrew-tap](https://github.com/ridi-oss/homebrew-tap): `Formula/pmon.rb` takes the version and the four CLI archives' SHA-256 from the release's `checksums.txt`, and `Casks/proxy-monster-desktop.rb` the version and the universal zip's. The tap's `main` takes changes only through a pull request. `mysqlwire`'s tag carries a slash because Go requires that prefix for a module outside the repository root. Inside the workspace `go.work` still resolves the in-repo modules locally, so an unreleased `mysqlwire` edit reaches `goproxy` and `pmon` immediately; only a standalone build (`GOWORK=off`) uses the pinned version. ## License proxy-monster is licensed under the [Apache License 2.0](./LICENSE). Section 5 of that license already places every contribution intentionally submitted for inclusion in the work under the same terms, so opening a pull request licenses your contribution under Apache-2.0 and there is no separate CLA to sign. Keep [NOTICE](./NOTICE) to attribution that redistributors are obliged to carry forward. Documentation belongs in the docs, not in a file every downstream distribution has to reproduce. ## Code of conduct This project follows the [Contributor Covenant](./CODE_OF_CONDUCT.md). By taking part you agree to uphold it; report unacceptable behavior through the private channel named there. ## Security issues Do not file security vulnerabilities as public issues or pull requests. Report them privately as described in [SECURITY.md](./SECURITY.md).