Contributing
Before You Start
Check the ADR index for existing architectural decisions. Many questions about “why does it work this way?” have an ADR that explains the context, tradeoffs, and alternatives considered.
If you are proposing a significant change — a new context, a new protocol, a change to an existing ADR’s decision — write an ADR first. The ADR is the primary artifact; code is its implementation. See Writing ADRs below.
:::note ADR First For architectural decisions, the ADR is the primary artifact. Code is the implementation of the ADR decision, not a substitute for it. A PR that changes an established decision without an ADR will not be merged. :::
Branch and PR Conventions
- Branch from
main:feature/<short-description>,fix/<issue>,adr/<number>-<slug>. - PR title follows Conventional Commits:
feat(files): add copy-on-write for version snapshots. - Required for merge:
- Tests pass (unit + integration; E2E for critical flows).
- CI green (lint, buf lint/breaking, import-boundary check).
- Updated docs if observable behavior changes.
- Updated ADR if an existing decision is revised.
gen/committed if.protochanged.
Adding a New Service/Module
- Create
internal/<context>/with the standard four-layer structure:internal/<context>/ ├── domain/ ├── app/ ├── repo/ └── grpc/ - Add proto definition in
proto/bitvault/<context>/v1/<context>.proto. - Run
task genand commit the generatedgen/go/,gen/ts/, andgen/openapi/changes. - Wire the new module into
bitvaultdincmd/bitvaultd/main.go. - Add SQL migration in
migrations/if the context owns new tables. - Add the new service to
deploy/helm/bitvault/values.yaml(disabled by default until ready). - Instrument with OTel (
internal/platform/observability) before writing domain logic.
Adding a Storage Provider
BitVault’s storage layer is defined by a provider interface (ADR-0005). New providers:
- Implement the interface in
internal/storage/provider/<name>/. - Pass the conformance test suite in
internal/storage/conformance/. All operations — PUT, GET, HEAD, DELETE, presign, multipart, refcount — must pass. No exemptions. - Add configuration mapping in
internal/storage/provider/factory.go. - Add
PROVIDER=<name>documentation indeploy/compose/and Helm values. - Add the provider to the ADR-0005 adapter list.
The conformance suite is the acceptance criterion. A provider that passes it is guaranteed interchangeable with MinIO, S3, and the other adapters.
Writing ADRs
Use the template in docs/adr/ (see existing ADRs for the format). Required
sections:
| Section | What to write |
|---|---|
| Status | Accepted, Proposed, or Superseded by ADR-XXXX |
| Context | The problem, constraints, and forces at play |
| Decision | What was decided, in one clear sentence |
| Consequences | Positive consequences AND costs/negative consequences (both are required) |
| Alternatives considered | What else was evaluated and why it was not chosen |
An ADR without honestly stated negative consequences will be sent back. The costs are the part future contributors need most.
Testing Requirements
| Layer | Requirement |
|---|---|
| Domain logic | Unit tests with no external dependencies |
| Repositories | Integration tests against a real Postgres instance (no mocks) |
| Storage operations | Integration tests against real MinIO (no mocks) |
| Critical flows | E2E tests: upload commit, sync conflict, cross-tenant isolation |
| Invariants | Chaos tests for dual-write defense and orphan GC |
Mocks are not acceptable for Postgres or storage tests. The integration tests
run against the lite tier (task up:lite) and are part of task test.
E2E and chaos tests run against a full stack and are separated into task test:e2e
and task test:chaos. These run in CI on the main branch and on release
branches; they are optional on feature branches but required before merge for
changes to the commit protocol, sync, or GC.