Files
rdbms-playground/docs/ci/adr/20260612-adr-ci-002.md
T
claude@clouddev1 da8bfebc36
ci / gate (push) Successful in 2m33s
docs(ci): establish docs/ci/adr namespace (ci-001 pipeline, ci-002 flake)
Records the CI/release pipeline as ADR-ci-001 and relocates the nix-flake
ADR from main's ADR-0049 to ADR-ci-002 (content unchanged, history note
added). Both live in docs/ci/adr/ with a README index — a dated,
ci-segmented namespace disjoint from main's integer ADR sequence, the
same split the website subproject uses to avoid cross-branch number
collisions. Drops the ADR-0049 entry from docs/adr/README.

ci-001 covers the runner model, the baked nix CI image, the clippy+test
gate, the static-musl release on tag, trigger hygiene, auth, and scope.
2026-06-12 22:38:34 +00:00

6.9 KiB

ADR-ci-002: Nix flake for a reproducible dev + build environment

Status

Accepted (2026-06-12). Implemented the same day on the ci branch: flake.nix, flake.lock, rust-toolchain.toml, .envrc. Verified end-to-end before acceptance — nix develop provides the pinned toolchain; nix build .#default produces a working binary; cargo clippy --all-targets -- -D warnings is clean and cargo test is 2424 passed / 0 failed / 1 ignored (the ignored item is the intentional ```ignore doctest at src/friendly/mod.rs:21), all run through the flake. This ADR is the dev/build-environment foundation; the CI pipeline that consumes it (runner model, image, gate, release) is ADR-ci-001.

History. Created as ADR-0049 in main's integer ADR namespace (docs/adr/); moved here to ADR-ci-002 on 2026-06-12 to keep the CI/dev-env decisions out of main's sequence and end the cross-branch number collision (main independently reaches for the next integer too — the same problem the website subproject hit). Content is otherwise unchanged. See ADR-0000 "Numbering discipline".

Context

The project is near feature-complete and CI is finally being set up (requirements.md TT5, CI in the deferred list). CI must not depend on whatever Rust/toolchain happens to be installed on the build machine — that is neither reproducible nor honest about what the build needs.

The sibling project datamage already solved this with a Nix flake (its ADR 0046): the flake is the single, version-pinned declaration of the toolchain, and both the dev shell and CI go through it so they cannot drift. We adopt the same pattern here. Ours is dramatically simpler than datamage's — this is a pure-Rust TUI with no Tauri / WebKitGTK / Node / WASM surface — so the flake carries almost no system dependencies.

Two build facts drove the (tiny) dependency set, confirmed from Cargo.lock:

  • libsqlite3-sys is built with bundled → SQLite is compiled from vendored C, which needs a C compiler. nixpkgs' stdenv provides one automatically; nothing is declared for it.
  • arboard's clipboard backend is x11rb — a pure-Rust socket XCB client that links no C X11 libraries. So no X11/pkg-config system inputs are needed to build or test. A live X server is only required at runtime to actually copy; headless sessions fall back to OSC 52.

Decision

Adopt a Nix flake at the repository root as the canonical declaration of the dev and build environment.

  • flake.nix exposes two outputs (user-chosen 2026-06-12 over a dev-shell-only variant):
    • devShells.default — the pinned Rust toolchain (from rust-toolchain.toml via rust-overlay) plus cargo-sweep for the target/ build-hygiene discipline (CLAUDE.md / the datamage ADR 0050 equivalent).
    • packages.default (= packages.rdbms-playground) — a rustPlatform.buildRustPackage that produces the binary reproducibly from the pinned toolchain and the committed Cargo.lock (cargoLock.lockFileimportCargoLock, which fetches each dependency by its lockfile checksum: offline, deterministic, no cargoHash to churn). nix build yields the artifact CI's gate/release can consume.
  • rust-toolchain.toml pins an exact stable release (1.95.0), not the floating stable channel, so nix flake update cannot surprise-bump Rust into new clippy lints that would fail the -D warnings gate (same reasoning as datamage ADR 0046). Components: rustfmt + clippy. No coverage/WASM tooling and no cross-compilation targets yet — those are added when the release matrix needs them, not before.
  • flake.lock pins every input (nixpkgs nixos-26.05, rust-overlay, flake-utils) to a commit, making the env bit-reproducible.
  • .envrc contains use flake for direnv auto-activation, kept for parity with datamage even though direnv is not installed on the current dev VM (entry is via nix develop).
  • packages.default sets doCheck = false. The test suite is not run during nix build — the Nix build sandbox has no HOME and no X server, which fights the project-directory / clipboard paths the tests touch. Tests run as their own CI stage via nix develop -c cargo test, keeping "build the artifact" and "run the suite" cleanly separate.
  • The package version is read from Cargo.toml via builtins.fromTOML, so it never drifts from the crate metadata.

Consequences

  • One toolchain definition. Dev and CI share the exact pinned toolchain; they cannot drift. New contributors run nix develop (or get auto-activation via direnv) and have the same Rust as CI.
  • D2 (static binary) is unaffected and still pending. The nix build artifact links the Nix-store glibc dynamically — it is a reproducible build/test artifact, not the single static release binary D2 calls for. Release binaries will target a static toolchain (e.g. x86_64-unknown-linux-musl) in the forthcoming CI release work; that is a release-step concern, not a dev-shell one.
  • fmt is deliberately not gated yet. The tree is not clean under stock rustfmt (~100 files would change; no rustfmt.toml is committed and the code was shaped by something other than default rustfmt). Reformatting churns blame across every file and would conflict with the in-flight website branch and ongoing main work, so — user decision 2026-06-12 — the fmt gate is left out for now and revisited on main. The CI gate is clippy + test.
  • Engine-name posture (CLAUDE.md) is respected. The flake's comments may name SQLite/rusqlite where technically necessary (build-input rationale); no user-facing string is affected.

Alternatives considered

  • Dev-shell only (no build package). Matches datamage exactly; CI would cargo build inside nix develop -c. Rejected (user choice): a nix build package gives a reproducible release artifact straight from the pinned toolchain, which the release job wants.
  • A standard rust:1.95 image in CI, flake for dev only. Simpler in CI (no nix-in-CI caching to solve), but it is a second place that defines the toolchain — exactly the drift this ADR exists to prevent. Rejected for the unified-env goal; the nix-in-CI caching cost is solved in the CI pipeline work instead.
  • rustup on the build machine. The status quo CI would replace — non-reproducible, machine-dependent, the thing we are eliminating.

Relationship to other decisions

  • Mirrors datamage ADR 0046 (nix flake dev env) and its build hygiene companion. This is the rdbms-playground analogue, scoped to a pure-Rust project.
  • Feeds ADR-ci-001 (the CI + release pipeline), which consumes this flake for requirements.md TT5 (CI runs the tiers) and the D1/D2/D3 distribution items (the release uses a static musl target built through this flake).