feat(nix): add a dev shell and a checks output (task 0039)
All checks were successful
CI / test (pull_request) Successful in 54s
CI / test (push) Successful in 53s

`nix develop` now yields the toolchain the repository actually needs — Node,
`git`, `tea`, and `curl` — giving a declarative answer to "what do I need to
work on this", which the repository previously specified nowhere.

The shell takes its Node from the package's `passthru` rather than naming
`pkgs.nodejs` a second time, so development and the shipped artifact cannot
drift onto different majors and cannot be set independently. `package.nix`
declares that `passthru` as an interface rather than leaving the shell to read
an incidental build attribute; it does not enter the derivation, so the store
path is unchanged.

The checks output aliases the package, so `nix flake check` builds it and
thereby runs both its verification phases instead of being a silent no-op. No
per-stage checks: the only stage adding coverage is the full typecheck, which
spans `test/` and `bench/` and would drag the benchmark harness into the
derivation's inputs, undoing the source filtering. It stays in CI.

The shell deliberately omits `gitea-axi` itself. The benchmark's arm resolves
that binary by name off PATH and must get the locally built `dist/main.js`, so
supplying the packaged one would silently substitute the wrong artifact.
This commit was merged in pull request #48.
This commit is contained in:
2026-07-19 23:36:40 -04:00
parent 82cdad4ce0
commit 6a9d4d4770
4 changed files with 102 additions and 12 deletions

View File

@@ -16,8 +16,38 @@ The full typecheck stays in continuous integration, where it already runs.
## Acceptance criteria
- [ ] `nix develop` yields a shell with Node, `git`, and `tea` available.
- [ ] The build, the fast tier, and the benchmark harness's runner all work from inside that shell.
- [ ] The shell's Node and the package's Node come from one reference — changing it moves both, and they cannot be set independently.
- [ ] `nix flake check` builds the package and runs its tests, and fails when the package fails.
- [ ] No per-stage check derivations are added.
- [x] `nix develop` yields a shell with Node, `git`, and `tea` available.
- [x] The build, the fast tier, and the benchmark harness's runner all work from inside that shell.
- [x] The shell's Node and the package's Node come from one reference — changing it moves both, and they cannot be set independently.
- [x] `nix flake check` builds the package and runs its tests, and fails when the package fails.
- [x] No per-stage check derivations are added.
## Implementation Notes
### The single Node reference is a `passthru`, not a second `pkgs.nodejs`
The shell takes `self.packages.${system}.gitea-axi.nodejs` rather than naming `pkgs.nodejs` again.
Naming it twice would satisfy the criterion's letter while leaving two places to edit, which is the drift the criterion exists to prevent; reading it back off the derivation means there is genuinely one reference.
The first cut relied on `buildNpmPackage` incidentally surfacing its `nodejs` argument as a derivation attribute — which works, but only as a side effect of `inherit src nodejs`, with nothing marking it load-bearing.
Review caught that: moving or dropping that `inherit` would have broken the shell silently at a distance.
`package.nix` now declares `passthru = { inherit nodejs; };`, making it an interface with a comment saying what depends on it.
`passthru` does not enter the derivation, so the store path is unchanged and the change costs no rebuild — verified: the drv hash before and after is identical.
### `curl` was a missing part of the toolchain
The benchmark's `raw-api` arm shells out to `curl` (`ARM_BINARY` in `bench/guard.ts`), so the shell carries it alongside Node, `git`, and `tea`.
### The shell deliberately does not supply `gitea-axi`
The criterion asks that the benchmark harness's runner work from inside the shell, and it does.
A *live* arm run is a further step the shell cannot take: `provisionArmBin` resolves each arm's binary by name off `PATH`, and the `gitea-axi` arm's binary must be the locally built `dist/main.js` so a run measures the working tree rather than whatever the flake last packaged.
Putting the packaged binary on `PATH` would satisfy the lookup with the wrong artifact — a silently misleading benchmark, worse than a missing one.
Exposing the *built* one was considered and rejected as well: `tsc` does not set an executable bit on `dist/main.js` (npm sets it at install time from the manifest's `bin` entry, which is what the packaging tier's bit assertion guards), so a `shellHook` would have had to `chmod +x` the build output on every shell entry — mutating build artifacts to work around a lookup that is the benchmark's own concern.
Both the flake and the CLAUDE.md gotcha now state the boundary rather than implying the shell covers it.
### CLAUDE.md's toolchain gotcha was stale by construction
It read "the repository has no dev shell yet (task 0039 adds one)".
This slice is that task, so the entry was rewritten to point at `nix develop --command`, keeping `nix shell nixpkgs#nodejs -c ...` only as the one-off outside the repository.