docs(ci): establish docs/ci/adr namespace (ci-001 pipeline, ci-002 flake)
ci / gate (push) Successful in 2m33s

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.
This commit is contained in:
claude@clouddev1
2026-06-12 22:38:34 +00:00
parent 89b9392c25
commit da8bfebc36
4 changed files with 221 additions and 7 deletions
+135
View File
@@ -0,0 +1,135 @@
# 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.lockFile` → `importCargoLock`, 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).