# Contributing Keep changes focused, operable and consistent with the project's Perl-first, PostgreSQL-native, SSR-first design. Use **Good Phorum** or **GP Forum** in prose. Preserve identifiers such as `GPForum::*`, `gpforum` and `GPFORUM_*`. Follow the [name guide](docs/BRANDING.md) and [writing guide](docs/WRITING.md). Project attribution is **GPForum Contributors**; individual credits are in [CONTRIBUTORS.md](CONTRIBUTORS.md). ## Development loop 1. Select **Perl 5.40 or newer** from your distribution, Homebrew, perlbrew/plenv/asdf or a custom build. Set `GPFORUM_PERL=/absolute/path/to/perl` or activate it on `PATH`; confirm with `make perl-runtime`. Install App::cpm and Carton for that interpreter with `cpanm -M https://cpan.metacpan.org/ App::cpm Carton`. 2. Install build tools and PostgreSQL dependencies: a C toolchain, `libpq-dev`, `libssl-dev` and `zlib1g-dev` on Debian/Ubuntu; or `brew install perl cpanminus postgresql@18` on macOS. A selected custom Perl can use Homebrew's PostgreSQL client tools. See the [runtime runbook](docs/ops/perl-runtime.md). 3. Run `make install-deps-postgres` (`script/bootstrap-deps --postgres`). It installs from `cpanfile.snapshot` over HTTPS MetaCPAN with exclusive snapshot resolution, including the PostgreSQL driver. After changing `cpanfile`, refresh with `script/bootstrap-deps --update --postgres` and commit the snapshot. Omitting `--postgres` can remove the driver from the lock. 4. Run `script/system-preflight` to check the host. 5. Make one focused change. Update the relevant guide and add or amend an ADR when a design decision changes. Use the [documentation index](docs/README.md) and [ADR index](docs/adr/README.md). 6. Run the quality gate before opening a pull request: ```sh make check ``` When a change touches what a new contributor runs first — the README quick start, `cpanfile` or `cpanfile.snapshot`, `script/bootstrap-deps`, or the `check` target — also run: ```sh make fresh-checkout ``` Commit first: this check clones **HEAD**, so it excludes uncommitted changes. It reinstalls dependencies and runs the quick start and `make check` in a clean checkout, catching missing files, undeclared modules and omitted steps. With `GPFORUM_DATABASE_DSN` set, it also migrates a throwaway database and runs the integration tier. `t/166-deploy-contract.t` checks the README's command sequence. Run profiling for performance-sensitive changes: ```sh script/profile-nytprof -- -Ilib -It/lib t/17-notifications.t ``` ## Architectural rules - Give services, stores, command handlers and projections narrow responsibilities. - PostgreSQL is authoritative. Search, inboxes, feeds, and counters are rebuildable projections. - cpm, Carton, Perl::Critic, tests, coverage, and profiling are mandatory project surfaces. - Any deviation from Perl-first, PostgreSQL-native or SSR-first needs an ADR. Shared caching is optional; PostgreSQL remains the source of truth. - Durable event writes should use `GPForum::Infrastructure::EventRecorder`. - Raw SSR HTML must pass through `GPForum::Web::RenderPolicy`. - Browser security headers belong in `GPForum::Security::*`, not controllers. ## Migrations - Follow the [migration layout and rules](migrations/README.md). The schema starts from the fresh-database baseline, 2000 to 2025 (ADR 0162); a new migration goes in `migrations/` with the next number after the highest. Keep existing filenames and SQL bytes intact. - An applied migration never changes: the runner compares checksums and refuses to run when a file differs from what the database recorded. - An index on a table that already exists is built with `CREATE INDEX CONCURRENTLY`, so writes to the table go on while it builds. PostgreSQL refuses that inside a transaction block, and the runner sends a file whole, so start the file with `-- gpforum:no-transaction`: its statements then run one at a time. Each must be safe to repeat; begin with `DROP INDEX CONCURRENTLY IF EXISTS` for the index, because a failed concurrent build leaves an invalid one behind. A partitioned parent cannot be built concurrently: say so in a `-- gpforum:blocking-index ` line. `t/209-migration-indexes.t` enforces this from migration 049 on. ## Pull requests Every pull request should include: - a short user or operator reason; - tests for the affected behavior; - documentation and ADR updates when contracts change; - migration and rollback notes when database shape changes; - profiling notes when hot paths change. For isolated PostgreSQL integration tests, install the postgres dependency feature and PostgreSQL client/server programs, then run `make integration` (`JOBS=8` by default). Each test file owns its temporary socket-only cluster; an explicit database DSN still selects an existing server. Generate a service and its SQL contract from the repository root: ```sh script/gpforum-carton exec perl -Ilib local/bin/mojo generate gpforum-service Forum::Name ``` Review the generated migration, add authorization and behavior, then run its integration test and `make check`. The generator refuses existing files.