# Contributing to OpenBooks OpenBooks welcomes accounting expertise, code, tests, documentation, design, translations, deployment improvements, and reproducible bug reports. The project is an accounting system before it is a collection of screens. Changes that affect money, posting, permissions, audit evidence, or tenant data must preserve the controls described below. ## Before starting - Search existing issues and pull requests. - Open an issue before a large feature, schema redesign, or behavior change so the accounting treatment and migration path can be agreed first. - Never include customer records, production exports, credentials, private infrastructure details, or generated build/test output in a contribution. - Report vulnerabilities privately as described in [SECURITY.md](SECURITY.md). ## Development setup Requirements: - Node.js 24 or newer - npm - PostgreSQL 16 - Redis 7 for queue-backed functionality ```bash npm ci cp .env.example .env # Configure a disposable development database in .env. npx tsx scripts/bootstrap.ts npm run dev -w web ``` The application listens on `http://localhost:4780` in development. For a packaged deployment, use the root `compose.yaml` through: ```bash ./scripts/compose-up.sh ``` Do not run integration tests, migration experiments, simulations, or destructive maintenance commands against production or shared data. ## Financial integrity requirements Every accounting change must preserve: - strict organization and legal-entity isolation; - balanced, deterministic, idempotent posting; - exact decimal and currency handling without floating-point financial math; - closed-period immutability and controlled correction evidence; - explicit lifecycle transitions, approvals, permissions, and concurrency behavior; - complete audit evidence for material configuration and transaction changes; - effective-dated financial configuration where changing a rule could reinterpret history; - domain- and API-level enforcement rather than UI-only controls; - backward-compatible migrations and preserved tenant data; and - one authoritative source for each policy or feature setting. Organization feature gates belong on **Company Settings → Features**. A module-specific page may explain effective state and link there, but it must not persist a second gate. Child capabilities must fail closed when their parent feature is disabled. Disabling a feature must preserve its data and audit history. ## Code and product conventions - Use TypeScript and keep strict type checking clean. - Use the money helpers and decimal strings for financial values. Do not convert financial amounts through JavaScript `number`. - Scope tenant-owned records by `org_id`; add and test row-level security for new tenant tables. - Add permissions and server-side enforcement for every mutation. - Make retries safe. External callbacks, imports, posting, and workers require stable idempotency behavior. - Posted corrections require explicit lineage and audit evidence. - Add generated and handwritten migrations as required. Never edit an applied migration to reinterpret existing databases. - Keep user-visible copy in all seven locale catalogs: `en`, `fr`, `es`, `de`, `pt-BR`, `zh`, and `ja`. - Keep optional organization features in the centralized registry at `web/lib/features.ts`. - Add or update in-product documentation for material user workflows. ## Tests Run the release gate before requesting review: ```bash npm run verify:release ``` That command type-checks workspaces, runs the test suite, and creates a production build. Database-backed tests execute when `OPENBOOKS_DB_URL` points to a bootstrapped scratch database; they skip when no database is configured. CI includes a canary that fails if database tests silently skip. For browser smoke tests: ```bash npx playwright install chromium npx playwright test ``` New accounting behavior should include: - nominal examples; - boundary and rounding cases; - invalid-state rejection; - idempotent retry behavior; - concurrency behavior where applicable; - tenant-isolation checks; and - proof that every generated journal entry balances. ### What a test in this repository must be Tests are product code: someone reads them to learn what the system promises, and they fail only when that promise breaks. Every test must: - **Exercise behaviour through a real interface.** Call the function, the HTTP route, or the database path a caller uses. Assert on what that caller observes: return values, response status and body, rows written, journal lines posted. - **Use an independent expected value.** Compute it by hand, take it from a published source (a statute, a tax table, a file-format specification), or derive it from the inputs. Never compute it with the code under test or a copy of its formula. Asserting what a mock was told to return proves nothing. - **Fail for one reason, and say so.** One behaviour per test. The name states the behaviour ("posting into a closed period is refused with the period named"), not the ticket that prompted it. - **Mock only what you don't own.** External services, the clock and randomness are fine to double. Doubling our own modules turns the test into a description of today's call graph. - **Be deterministic.** Pin the clock, and seed anything random. A test that passes only before a calendar date, or only in one time zone, is broken. These are not tests, and review refuses them: - **Source-text assertions.** A regex, `includes` or `indexOf` over a `.ts`, `.tsx`, `.sql` or `.yml` file ("the guard call exists", "the view uses `table({`"). They break on reformatting and pass while the behaviour regresses. `check:test-source-pins` refuses new ones. The rare file that pins a genuine external contract (a CI or release workflow's trigger policy, a published migration's bytes) declares it with a `// source-pin-contract: ` header. - **Inventory and fingerprint pins.** Asserting that a catalog has exactly N keys, or that some content hashes to a fixed digest, so every legitimate addition fails until someone "re-pins". Assert the rule instead: every English message key exists in every locale with the same placeholders. - **A regression test with no behavioural gap behind it.** When fixing a bug, first look for an existing test of that behaviour and extend it. Add a new test only when nothing covers the behaviour, and make it fail on the behaviour before the fix, not on the exact bug example or the fixed line. - **Trivial or duplicate tests.** "The function exists", constants compared with themselves, or a second test of a case another test already covers. ## Pull requests Keep pull requests focused. Describe: - the user or accounting problem; - the accounting treatment and affected invariants; - schema and migration impact; - permission, audit, and feature-gate impact; - rollback or operational considerations; and - the exact checks performed. By contributing, you agree that your contribution is licensed under the repository's GNU Affero General Public License v3.0 or later.