Contributing to Countersign
Thank you for considering a contribution. Countersign is used where records must be trustworthy, so changes are held to one rule above all: the behaviour of the software is specified, and every specified behaviour is tested. This page explains how to propose a change and what it must come with.
Contents
- Ways to contribute
- Before you start
- Setting up
- The rule: requirement, tagged test, green trace
- Compatibility: old audit trails must still verify
- Coding conventions
- Commits and pull requests
- Sign-off
- Security issues
- Licence
Ways to contribute
| Contribution | How |
|---|---|
| A bug report | An issue with the version, the storage and sign-in method, what you did, what you expected and what happened. The exact message helps: messages are specific. For anything touching integrity, include the output of npm run cli -- verify. Never attach real records. |
| A question or an idea | An issue or discussion. For larger features, describe the need before writing code. |
| A fix or a feature | A pull request, as described below. |
| Documentation | A pull request. Documents must match the code: names of variables, defaults and messages are quoted exactly, and example output is output that was really produced. |
| An example configuration or mapping | A pull request adding it under examples/, validated with config validate and tried on a scratch store. Synthetic data only. |
| A storage adapter or sign-in method | See Developer setup; talk to the maintainers first about whether it belongs in the main repository. |
Before you start
- Search the existing issues.
- For anything beyond a small fix, open an issue first and agree on the approach. It saves work on both sides, especially where the engine, the audit entry format or the security model are concerned.
- Security problems are not reported in public issues; see SECURITY.md.
Setting up
docs/developer-setup.md takes you from a clone to a running application and the tests.
In short: Node.js 22.18 or newer, npm install, npm run dev, npm test.
Before you push, run what continuous integration runs:
npm run lint
npm run typecheck
npm test # with COUNTERSIGN_TEST_DATABASE_URL set if you touched storage
node scripts/trace.ts --check
npm run build
After a change to the user interface also run the walkthrough (npm run dev without COUNTERSIGN_* variables in one
terminal, npm run shots in another) and look at the screenshots it produces.
The rule: requirement, tagged test, green trace
Countersign ships a validation pack (docs/validation/) that organisations use to qualify their installation. Its core is a chain: user requirement → functional requirement → test. That chain must never be broken by a change.
A change in behaviour comes with all three of these:
A requirement in
docs/validation/requirements.json. Either a new functional requirement or an amended one:{ "id": "FR-WF-12", "group": "Workflow", "text": "A Confirmed version cannot be edited; it must be stepped back first.", "satisfies": ["UR-03"], "risk": "high", "verification": "automated test" }Ids are
FR-<AREA>-<number>; use the next free number of the area and never reuse or renumber one.satisfiesnames the user requirement(s) it serves; if none fits, the change needs a new user requirement, which is a larger discussion. State the requirement as a testable sentence about what the system does.A test whose name starts with the requirement's id in square brackets:
test("[FR-WF-12] a Confirmed version is locked until it is stepped back", () => { … });in
lib/engine.test.ts,lib/migrate.test.ts,lib/adopt/adopt.test.tsorserver/server.test.ts. Requirements about the user interface are verified by a tagged step inscripts/guide-shots.mjs. One requirement may have several tests; one test may carry one tag. Test the refusal as well as the acceptance.A regenerated traceability matrix:
npm run trace # rewrites docs/validation/traceability-matrix.md node scripts/trace.ts --check # must print nothing and exit with 0The check fails when a requirement has no test, a test carries an unknown id, a requirement refers to an unknown user requirement, a user requirement is met by nothing, or the matrix file is stale. It runs as part of
npm testand in CI. Commit the regenerated matrix; never edit it by hand.
What counts as a change in behaviour: anything a user, an operator or an integrating system can observe. A new or changed rule, validation, permission, message that documents rely on, endpoint, status code, CLI option, setting or default, export format, or anything about the audit entry.
What does not need a new requirement: refactoring with unchanged behaviour, performance work, documentation, tests for behaviour that is already specified, build and tooling changes. A bug fix needs a test that fails without the fix; if the bug shows that a behaviour was never specified, add the requirement.
Also update:
- the documents under
docs/that describe what you changed; docs/validation/functional-specification.mdand the risk assessment if the change adds a function or a hazard;CHANGELOG.md, under "Unreleased".
Compatibility: old audit trails must still verify
An installation's whole state is its audit trail, verified by re-running every stored command. Therefore:
- A command that was accepted by a released version must be accepted, with the same result, by every later
version. Do not tighten the validation of existing field types, change what an existing command writes into its
entry, or change
stable,hashEntryor the entry's fields. Add new commands, types or optional properties instead. - If a change cannot avoid altering the outcome of old entries, it is a breaking change: it needs a prior discussion, a migration path, a major version, and a prominent changelog entry.
- The tests replay the built-in demo histories; a change that makes them fail is such a change.
- Changes to
db/postgres.sqlmust be idempotent and must work on a database created by the previous version.
Coding conventions
The full list with reasons is in Developer setup. The essentials:
lib/has no DOM and no Node APIs;server/has no DOM.- Relative imports in
lib/,server/andscripts/end in.ts. - No enums, parameter properties, namespaces or decorators: Node runs these files by stripping types.
- Every state change goes through
actinlib/engine.ts, which stays pure. - Refusals are returned as messages a person can act on, not thrown.
- Settings are read in
server/settings.tsonly. - Synthetic data only in tests, examples and screenshots. No secrets in the repository.
- Keep dependencies few. A new runtime dependency needs a reason in the pull request; the engine has none and must stay that way.
- Comments say why, briefly; each file starts with what it is for.
Commits and pull requests
Commits
- One logical change per commit; the tree builds and the tests pass at each commit.
- The subject line is an imperative sentence of up to about 72 characters ("Refuse edits of a Confirmed version"), without a trailing full stop. The body explains what and why, and names the requirement ids touched.
- Do not mix reformatting with changes in behaviour.
Pull requests
Branch from
main; keep the pull request focused. Several small ones are reviewed faster than one large one.Describe: the problem, the change, how you tested it, and what a reviewer should look at closely.
Complete this checklist in the description:
- [ ] Behaviour changed? Requirement added or amended in docs/validation/requirements.json - [ ] Tests added, named with the requirement id; they fail without the change - [ ] npm run trace run; node scripts/trace.ts --check is clean; the matrix is committed - [ ] npm run lint, npm run typecheck, npm test and npm run build pass - [ ] Storage touched? Tests run with COUNTERSIGN_TEST_DATABASE_URL - [ ] UI touched? npm run shots run, screenshots reviewed - [ ] Old trails still replay (no change to the outcome of existing commands), or the break is declared - [ ] Documents and CHANGELOG.md updated - [ ] No real data, no secretsA maintainer reviews every pull request; changes to the engine, the hash, sign-in or storage need particular care and may take longer. In the spirit of the software, nobody merges their own change without a second person's review.
Be ready to revise. Review comments are about the change, not about you; see the Code of Conduct.
Sign-off
Signing off your commits in the style of the Developer Certificate of Origin is
welcome and optional. It states that you wrote the change or otherwise have the right to submit it under the
project's licence. Add the line with git commit -s:
Signed-off-by: Your Name <your.name@example.com>
Whether or not you sign off, by submitting a contribution you confirm that you have the right to submit it under the project's licence.
Security issues
Do not open a public issue or pull request for a vulnerability. Follow SECURITY.md.
Licence
Countersign is licensed under the Apache License, Version 2.0 (see LICENSE and NOTICE). Contributions are
accepted under the same licence, as its section 5 provides.