Skip to content

Commit 3bcc82d

Browse files
ci: add self-contained Nix flake + nix flake check
Package st2 with rustPlatform.buildRustPackage (cargoLock.lockFile — no git deps, so no per-dep hashes to maintain). `nix flake check` builds the binary, runs the hermetic unit tests (`--lib --bins`), installs shell completions, and smoke-tests `st2 --help` + the completions. The integration tests in tests/*.rs assume a real environment the Nix sandbox lacks (/bin/bash+jq, /usr/bin/git on a hardcoded PATH, live pty/convoy/systemd), so they run on native CI rather than here. fmt/clippy are intentionally not gated — a repo-wide format/lint gate would fight the maintainer's own commits; the devShell ships rustfmt + clippy. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 441f881 commit 3bcc82d

4 files changed

Lines changed: 243 additions & 0 deletions

File tree

.github/workflows/nix.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
name: Nix
2+
on:
3+
pull_request:
4+
push:
5+
branches: [main]
6+
7+
jobs:
8+
check:
9+
runs-on: ubuntu-latest
10+
timeout-minutes: 30
11+
steps:
12+
- uses: actions/checkout@v4
13+
- uses: DeterminateSystems/determinate-nix-action@v3
14+
# `nix flake check` rather than `nix build`: it builds the package *and*
15+
# evaluates every `checks.*`, so fmt, clippy, the hermetic test suite, and
16+
# the `--help` smoke test all gate the PR from one entrypoint.
17+
- run: nix flake check --print-build-logs

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
/target
22

3+
# Nix build symlink.
4+
/result
5+
36
# Per-agent runtime overlay (materialized per clone; machine-specific, never product).
47
.st2/
58
.claude/

flake.lock

Lines changed: 61 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

flake.nix

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
{
2+
description = "st2 - harness-agnostic runner: reconcile a catalog+inbox folder of agent specs, keep their ptys running, deliver messages by moving files";
3+
4+
inputs = {
5+
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
6+
flake-utils.url = "github:numtide/flake-utils";
7+
};
8+
9+
outputs =
10+
{
11+
self,
12+
nixpkgs,
13+
flake-utils,
14+
}:
15+
flake-utils.lib.eachDefaultSystem (
16+
system:
17+
let
18+
pkgs = import nixpkgs { inherit system; };
19+
20+
# Cargo.toml is the single source of truth for the version, so a release
21+
# bump needs no matching edit here.
22+
version = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.version;
23+
24+
# NixStamp for the shared build-versioning contract: the flake rev is a
25+
# pure input, so baking it lets a hermetic build know its own identity
26+
# without an impure `.git` read. Same env var + JSON shape as the rest of
27+
# the fleet (TS `@overeng/utils/node/cli-version`; the otel-scrape Rust
28+
# reader) — `src/version.rs` reads it via `option_env!("CLI_BUILD_STAMP")`.
29+
# `self.shortRev`/`lastModified` are absent only for a dirty tree, where
30+
# `dirtyShortRev` and the working-tree mtime stand in and `dirty` is true.
31+
buildStamp = builtins.toJSON {
32+
type = "nix";
33+
inherit version;
34+
rev = self.shortRev or self.dirtyShortRev or "unknown";
35+
commitTs = self.lastModified or 0;
36+
dirty = !(self ? rev);
37+
};
38+
39+
completionShells = [
40+
"bash"
41+
"zsh"
42+
"fish"
43+
];
44+
45+
st2 = pkgs.rustPlatform.buildRustPackage {
46+
pname = "st2";
47+
inherit version;
48+
src = self;
49+
50+
# No git or crates.io-yanked deps in the lockfile, so the lockfile
51+
# alone pins every input reproducibly — no per-dep outputHashes, and
52+
# nothing here to hand-patch when a dep bumps.
53+
cargoLock.lockFile = ./Cargo.lock;
54+
55+
# This NixStamp is the binary's authoritative build identity; it wins
56+
# over the LocalStamp `build.rs` bakes from git (which is empty here
57+
# anyway — a flake source carries no `.git`). Reaches rustc as a plain
58+
# env var, captured at compile time by `option_env!` (see
59+
# src/version.rs). A derivation env var change rebuilds the crate.
60+
CLI_BUILD_STAMP = buildStamp;
61+
62+
# `git` is present for the tests below (they init throwaway repos);
63+
# `installShellFiles` provides `installShellCompletion`.
64+
nativeBuildInputs = [
65+
pkgs.git
66+
pkgs.installShellFiles
67+
];
68+
69+
# Completions are generated by the binary we just built (never
70+
# committed), so they cannot drift from the actual command tree —
71+
# `checks.completions` gates that.
72+
postInstall = ''
73+
${pkgs.lib.concatMapStringsSep "\n" (shell: ''
74+
$out/bin/st2 completions ${shell} > completions-${shell}
75+
'') completionShells}
76+
77+
installShellCompletion --cmd st2 \
78+
--bash completions-bash \
79+
--zsh completions-zsh \
80+
--fish completions-fish
81+
'';
82+
83+
# Run only the hermetic **unit** tests (`--lib --bins`). The integration
84+
# tests in `tests/*.rs` each assume a real environment the Nix build
85+
# sandbox deliberately lacks — `/bin/bash` + `jq` (the shipped Codex
86+
# hooks), `/usr/bin/git` on a hardcoded `PATH` (materialize's
87+
# git-worktree safety check), and a live `pty` / `convoy` / systemd
88+
# `--user` manager (the survival + render-neutrality gates). Chasing
89+
# those with per-test skips is unbounded as the suite grows, so they run
90+
# on native CI (real runner) while the flake proves the package here:
91+
# it builds, its ~150 pure unit tests pass, and `--help`/completions
92+
# smoke-test the wired binary below.
93+
cargoTestFlags = [
94+
"--lib"
95+
"--bins"
96+
];
97+
98+
# A few unit tests write under $HOME; the sandbox HOME is not writable.
99+
preCheck = "export HOME=$(mktemp -d)";
100+
101+
meta = {
102+
description = "Harness-agnostic runner over a unified catalog+inbox folder of agent specs";
103+
homepage = "https://github.com/compoundingtech/st2";
104+
license = pkgs.lib.licenses.mit;
105+
mainProgram = "st2";
106+
};
107+
};
108+
in
109+
{
110+
packages.st2 = st2;
111+
packages.default = st2;
112+
113+
# `nix flake check` is the whole CI: it builds the package — which runs
114+
# the hermetic portion of the in-tree `cargo test` suite via doCheck —
115+
# and evaluates the `--help` + completions smoke tests below.
116+
#
117+
# `cargo fmt --check` / `clippy -D warnings` are intentionally NOT gated:
118+
# this is a packaging PR on an actively-developed, hand-crafted tree, and a
119+
# repo-wide formatting/lint gate here would fight the maintainer's own
120+
# commits on every rebase. The devShell ships rustfmt + clippy for whoever
121+
# wants them.
122+
checks.st2 = st2;
123+
124+
# Smoke test that the built binary actually runs and its command tree is
125+
# wired, independent of the in-tree `cargo test`.
126+
checks.help = pkgs.runCommand "st2-help-${version}" { } ''
127+
export HOME=$(mktemp -d)
128+
${st2}/bin/st2 --help > /dev/null
129+
${st2}/bin/st2 ls --help > /dev/null
130+
touch $out
131+
'';
132+
133+
# Guards the completions contract: every shell we install still generates
134+
# a non-empty script, and fish in particular still binds to `st2` (the
135+
# name the installed st2.fish file claims). Written to files first —
136+
# clap_complete streams to stdout and panics on a `grep -q` early
137+
# pipe-close (BrokenPipe), which the real `> file` usage never hits.
138+
checks.completions = pkgs.runCommand "st2-completions-${version}" { } ''
139+
${pkgs.lib.concatMapStringsSep "\n" (shell: ''
140+
${st2}/bin/st2 completions ${shell} > ${shell}.out
141+
test -s ${shell}.out || { echo "empty ${shell} completions" >&2; exit 1; }
142+
'') completionShells}
143+
144+
grep -q 'complete -c st2' fish.out \
145+
|| { echo "fish completions do not bind to \`st2\`" >&2; exit 1; }
146+
147+
touch $out
148+
'';
149+
150+
devShells.default = pkgs.mkShell {
151+
packages = [
152+
pkgs.cargo
153+
pkgs.rustc
154+
pkgs.clippy
155+
pkgs.rustfmt
156+
pkgs.rust-analyzer
157+
pkgs.git
158+
];
159+
};
160+
}
161+
);
162+
}

0 commit comments

Comments
 (0)