# Contributing There are two distinct kinds of contribution to this repository, and they follow different rules: 1. **Code and documentation contributions** — changes to `src/govtool/`, `tests/`, `docs/`, `demo/`, CI workflows. These follow ordinary open-source review: maintainer approval and green CI. 2. **Constitutional amendments** — changes to anything under `constitution/` or `citizens/registry.yaml`. These are governed by the constitution itself. **No maintainer can merge them.** They merge only through the vote gate. CI enforces the boundary: a PR that touches `constitution/` triggers the vote gate workflow, and the branch protection rules require the gate check to pass before merge. --- ## Contributing code ### Setup ```bash python -m venv .venv && source .venv/bin/activate pip install -e ".[dev]" pytest ``` All tests must pass. New behavior requires new tests. The test suite is the contract: if your change isn't covered, it isn't done. ### Ground rules - **Determinism.** Anything that feeds a hash or a signature must go through `govtool.canonical`. Never serialize governance data with ad-hoc `json.dumps`/`yaml.dump` calls — byte-stability is a correctness property here, not a style preference. - **Append-only ledger.** No code path may rewrite or delete ledger entries. Corrections are new entries that reference the entry they correct. - **No silent policy.** Quorums, thresholds, windows, and eligibility rules live in the constitution YAML, never as constants in Python. The tooling *reads* policy; it does not *embody* policy. If you find a hard-coded threshold, that's a bug — file it. - **Errors are verdicts.** Gate failures must produce machine-readable verdicts (see `govtool.gate`), not bare exceptions, so CI can report exactly which rule blocked a merge. - **Stdlib first.** Runtime dependencies are deliberately minimal (`PyYAML`, `cryptography`). New runtime dependencies need a strong justification in the PR description. ### Style - Python 3.10+, type hints on public functions. - Exceptions derive from `govtool.errors.GovtoolError`. - Keep modules single-purpose; the module map in `src/govtool/` is part of the documentation. ### Pull request checklist - [ ] `pytest` passes locally - [ ] New behavior has tests (including at least one adversarial/negative case) - [ ] No constitutional policy hard-coded in Python - [ ] Docs updated if behavior or CLI surface changed - [ ] PR description explains *why*, not just *what* --- ## Contributing an amendment You must be a citizen (an entry in `citizens/registry.yaml` with status `active` and a registered public key). The funding pool's backers are the initial citizen set; the registry itself is constitutional text, so adding citizens is itself an amendment. ### 1. Draft Branch from `main`. Edit the YAML under `constitution/`. Keep one logical change per proposal — the classifier labels the *whole diff* at the strictest tier it touches, so bundling a typo fix with a kernel change buys the typo a supermajority requirement. Run locally before opening the PR: ```bash govtool classify --base main # see which tier your diff lands in govtool gate check --dry-run # schema + invariant checks, no vote ``` The dry run catches the two most common rejections early: schema violations and invariant violations (e.g. wording that would let a simple majority modify the amendment procedure itself — Article 6 blocks that regardless of vote outcome). ### 2. Propose Open the PR, then register the proposal: ```bash govtool propose create --pr --title "..." --key ``` This appends a signed `proposal` entry to the ledger containing the diff digest. If the PR diff changes after proposal (new commits), the digest no longer matches and the gate fails — re-register after any change so voters always sign over exactly the text that will merge. ### 3. Vote The voting window opens at proposal registration. Citizens vote: ```bash govtool vote cast --proposal --choice yes|no|abstain --key ``` Ballots are Ed25519-signed over the canonical ballot payload (which includes the proposal's diff digest) and appended to the ledger. Duplicate ballots from the same citizen: the latest valid ballot within the window counts, and the supersession is visible in the ledger forever. ### 4. Gate and ratification When the window closes, CI runs the full gate. If it passes, the ratification workflow merges, bumps the version, tags the release, and writes the ratification record. If it fails, the verdict in the PR check output tells you exactly which rule failed (quorum, threshold, signature, eligibility, digest mismatch, invariant). Failed proposals stay in the ledger — failure is data. ### Disputing a classification If you believe the classifier mislabeled your diff (e.g. flagged a userland change as kernel-major), do **not** work around it by restructuring the diff to dodge the classifier — that pattern is itself tested for. Instead open an issue with the diff and the expected label. Classifier fixes are code changes (ordinary review), but every dispute becomes a regression test case in `tests/test_classifier.py`. --- ## Reporting exploits If you find a wording or tooling exploit — a way for a faction to drain the commons, entrench power, or suppress votes *within the rules* — that is the most valuable contribution this project can receive. Open an issue tagged `exploit` with a concrete scenario. Every confirmed exploit becomes a permanent regression test before (or alongside) the fix. There is no embarrassment in being exploited on paper; that is the entire point of doing it on paper first.