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

Adding a New Service/Module

  1. Create internal/<context>/ with the standard four-layer structure:
    internal/<context>/
    ├── domain/
    ├── app/
    ├── repo/
    └── grpc/
    
  2. Add proto definition in proto/bitvault/<context>/v1/<context>.proto.
  3. Run task gen and commit the generated gen/go/, gen/ts/, and gen/openapi/ changes.
  4. Wire the new module into bitvaultd in cmd/bitvaultd/main.go.
  5. Add SQL migration in migrations/ if the context owns new tables.
  6. Add the new service to deploy/helm/bitvault/values.yaml (disabled by default until ready).
  7. 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:

  1. Implement the interface in internal/storage/provider/<name>/.
  2. Pass the conformance test suite in internal/storage/conformance/. All operations — PUT, GET, HEAD, DELETE, presign, multipart, refcount — must pass. No exemptions.
  3. Add configuration mapping in internal/storage/provider/factory.go.
  4. Add PROVIDER=<name> documentation in deploy/compose/ and Helm values.
  5. 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.