Skip to content
GantryCD

Contributing

The mechanics of working in the repository. The golden rule: always use make, never raw go — the targets set build tags, env, and tooling correctly.

Build and test

make check          # fmt + lint + the fast tests
make fmt | lint     # individually

make test_unit          # unit
make test_integration   # needs a database (internal/testutil/dbtest)
make test_acceptance    # end-to-end
make test_web           # vitest
make test_all

Test targets accept ARGS for passthrough — go test flags for the backend, vitest flags for the web. The build tags are unit_tests, integration_tests, and acceptance_tests.

Build artifacts and images are covered in Building And Release Artifacts (and summarized in Install).

Schema vs. migrations

This trips people up, so internalize the split:

DirectoryHoldsRule
schema/The declarative table definitions (Atlas).All table/column/index changes go here.
migrations/Data-only migrations — seed inserts, backfills.Never put schema DDL here. Every migration must be idempotent (ON CONFLICT, IS DISTINCT FROM).
AUTO_APPROVE=1 make atlas_schema_apply     # apply the declarative schema
make atlas_migrate_set_baseline            # bootstrap, then…
make atlas_migrate_apply                   # run data migrations

To change a table, edit schema/ and re-apply — you don’t hand-write DDL migrations.

The cross-stack explore index (gantrycd_explore) has the same split in schema-explore/ + migrations-explore/, provisioned the same way via make atlas_explore_schema_apply / atlas_explore_migrate_set_baseline / atlas_explore_migrate_apply (a separate Atlas env, so its destructive diff never sees the core tables). It is recreatable from runs, so it has no data migrations today — only the baseline anchor.

Conventions that matter

These are enforced socially and, where possible, by lint and tests. The full set is in CLAUDE.md and the Reference section; the load-bearing ones:

  • No backwards compatibility. GantryCD favors clean breaking changes over compatibility shims. Don’t annotate changes as deprecated or write “remove after one release” — just make the change.
  • Respect the layers. Handlers parse and authorize; services hold logic; repositories do SQL; jobs go through services/repos. See Backend internals.
  • Respect the communication boundaries. Web → Backend, Runner → Backend, and Backend → its dependencies. Nothing else.
  • No trivial wrappers. Inline a one-line errors.As rather than adding an IsNotFoundError helper; the deliberately-removed ones should stay removed.
  • No SQL string interpolation of user input. Bind values as parameters — this is absolute in internal/authz and everywhere else.
  • Typed errors and typed values. Return pkg/domain errors and check with errors.As; internal APIs pass typed structs, not []byte — JSON marshaling lives only at the HTTP and S3 boundaries.
  • Context logging. contextutil.GetLogger(ctx), snake_case attribute keys.

Where the deep docs live

The Reference section holds the detailed implementation docs — authz, the deployment state machine, the runner protocol, configuration, error handling, rate limiting, and more. When you change behavior, that’s the page you update in the same change.